Usa la classe Action invece di Controller in Symfony
Aderisco all'approccio Action Class che utilizza invece di Controller . La spiegazione è molto semplice: molto spesso il Controller include molte azioni, quando si segue il principio di Dependency Injection dobbiamo passare tutte le dipendenze richieste a un costruttore e questo crea una situazione in cui il Controller ha un numero enorme di dipendenze, ma in un certo momento di tempo (es. richiesta) usiamo solo alcune dipendenze. È difficile mantenere e testare quel codice di spaghetti.
Per chiarire, ho già lavorato con questo approccio in Zend Framework 2, ma lì si chiama Middleware . Ho trovato qualcosa di simile in API-Platform, dove usano anche la classe Action invece di Controller, ma il problema è che non so come cucinarlo.
UPD: Come posso ottenere la prossima Action Class e sostituire il controller standard e quale configurazione dovrei aggiungere nel normale progetto Symfony?
<?php
declare(strict_types=1);
namespace App\Action\Product;
use App\Entity\Product;
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SoftDeleteAction
{
/**
* @var EntityManager
*/
private $entityManager; /** * @param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; } /** * @Route( * name="app_product_delete", * path="products/{id}/delete" * ) * * @Method("DELETE") * * @param Product $product
*
* @return Response
*/
public function __invoke(Request $request, $id): Response
{
$product = $this->entityManager->find(Product::class, $id); $product->delete();
$this->entityManager->flush();
return new Response('', 204);
}
}
Risposte
La domanda è un po 'vaga per stackoverflow anche se è anche un po' interessante. Quindi ecco alcuni dettagli di configurazione.
Inizia con un progetto scheletro S4 fuori dagli schemi:
symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack
Aggiungi SoftDeleteAction
namespace App\Action\Product;
class SoftDeleteAction
{
private $entityManager; public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request, int $id) : Response
{
return new Response('Product ' . $id);
}
}
E definisci il percorso:
# config/routes.yaml
app_product_delete:
path: /products/{id}/delete
controller: App\Action\Product\SoftDeleteAction
A questo punto il cablaggio è quasi completo. Se vai all'URL ottieni:
The controller for URI "/products/42/delete" is not callable:
Il motivo è che i servizi sono privati per impostazione predefinita. Normalmente estenderesti da AbstractController che si occupa di rendere pubblico il servizio, ma in questo caso l'approccio più rapido è semplicemente etichettare l'azione come controller:
# config/services.yaml
App\Action\Product\SoftDeleteAction:
tags: ['controller.service_arguments']
A questo punto dovresti avere un'azione cablata funzionante.
Ovviamente ci sono molte varianti e qualche dettaglio in più. Ti consigliamo di limitare il percorso a POST o CANCELLA falsa.
Potresti anche considerare di aggiungere un ControllerServiceArgumentsInterface vuoto e quindi utilizzare la funzionalità istanza dei servizi per applicare il tag del controller in modo da non dover più definire manualmente i servizi del controller.
Ma questo dovrebbe essere sufficiente per iniziare.
L'approccio che stavo cercando di implementare è chiamato pattern ADR (Action-Domain-Responder) e Symfony lo ha già supportato a partire dalla versione 3.3. Puoi fare riferimento ad esso come controller invocabili .
Dai documenti ufficiali:
I controller possono anche definire una singola azione utilizzando il metodo __invoke (), che è una pratica comune quando si segue il modello ADR (Action-Domain-Responder):
// src/Controller/Hello.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/hello/{name}", name="hello")
*/
class Hello
{
public function __invoke($name = 'World') { return new Response(sprintf('Hello %s!', $name));
}
}