Symfony to framework, w którym kontroler powinien przyjąć Request i zwrócić Response, nie jest wymagane dziedziczenie z klas abstrakcyjnych. Kontrolerem może być prosta klasa PHP.
Poniżej przykład prostego w pełni funkcjonalnego kontrolera:
namespace AppBundle\Controller; use Symfony\Component\HttpFoundation\Response; class HelloController { public function indexAction($name) { return new Response('<html><body>Hello '.$name.'!</body></html>'); } }
Co jeśli jednak potrzebujemy czegoś więcej? Symfony dostarcza klase abstrakcyjną Controller, która zawiera dostęp do kontenera oraz zestaw skrótów do przydatnych metod
namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { // ... }
Kontroler wg najlepszych praktyk symfony powinien stosować się do zasady 5-10-20, która mówi o tym, że kontroler nie powinien zawierać więcej niż 5 metod, 10 akcji, a każda akcja powinna mieć mniej niż 20 linii. Jest to istotne z punktu widzenia czytelności kodu. Taki podział, nie jest możliwy bez użycia serwisów.
Załóżmy, że potrzebujemy użyć dwóch serwisów pierwszy do logowania, a drugi to EntityManger z Doctrine możemy to zróbić na dwa sposoby.
Pobranie serwisów przez kontener
namespace AppBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller; class HelloController extends Controller { public function helloAction() { $this->get('entity_manager')->getRepository('AppBundle::Hello')->findAll(); $this->get('logger')->log(Logger:INFO, 'S**t happens', []); $this->getDoctrine()->getManager()->getRepository('AppBundle::Hello')->findAll(); } public function whatsupAction() { $this->get('entity_manager')->getRepository('AppBundle::Hello')->findAll(); $this->get('logger')->log(Logger:INFO, 'S**t happens', []); } }
W powyższym kodzie widać, że często musimy pobierać serwisy ponieważ nie da ich się przypisać w konstruktorze powtarzamy kod. Można jednak zrobić to inaczej.
Wstrzykiwanie serwisów przez Dependency Injeciton
namespace AppBundle\Controller; class HelloController { private $em; private $logger; public function __construct(EntityManagerInterface $em, LoggerInterface $logger) { $this->em = $em; $this->logger = $logger; } public function helloAction() { $this->em->getRepository('AppBundle::Hello')->findAll(); $this->logger->log(Logger:INFO, 'S**t happens', []); } public function whatsupAction() { $this->em->getRepository('AppBundle::Hello')->findAll(); $this->em->log(Logger:INFO, 'S**t happens', []); } }
Co nam to daje?
Po pierwsze, po konstruktorze klasy jesteśmy w stanie domyśleć się co robi klasa, w tym przypadku klasa na pewno będzie pobierała coś z bazy i logowała operacje.
Po drugie nasz kod jest zgodny z SOLID.
Po trzecie możemy łatwo napisać unit testy, gdzie tak naprawdę testujemy serwisy.
Po czwarte możemy zmodyfikować zachowanie kontrolera wymieniając komponenty w definicji serwisu bez konieczności zmiany kodu kontrolera.
Jakie są minusy?
Więcej pracy, nie mamy dostępu do użytecznych metod z abstrakcyjnej klasy Controller
Konieczność definiowania serwisów dla każdego kontrolera.
Przydatne linki:
- http://symfony.com/doc/current/cookbook/controller/service.html
- http://symfony.com/doc/current/book/controller.html
- http://symfony.com/doc/current/book/service_container.html
- https://knpuniversity.com/screencast/question-answer-day/controllers-services
- http://richardmiller.co.uk/2011/04/15/symfony2-controller-as-service/
Zostaw komentarz