docs(articles): Magento and Symfony examples side by side, request lifecycle as a Mermaid diagram
uk/articles/magento-to-symfony/core/routing.md
+6
−1
Роутинг і контролери
Magento · routes.xml + Controller/Question/View.php
<router id="standard">
<route id="faq" frontName="faq">
<module name="Vendor_Faq"/>
</route>
</router>
class View implements HttpGetActionInterface
{
public function __construct(
private RequestInterface $request,
private QuestionRepositoryInterface $questions,
private PageFactory $pageFactory,
) {}
public function execute(): ResultInterface
{
$id = (int) $this->request->getParam('id');
$question = $this->questions->getById($id);
$page = $this->pageFactory->create();
$page->getConfig()->getTitle()->set($question->getTitle());
return $page;
}
}
Symfony · src/Controller/FaqController.php
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\Routing\Attribute\Route;
final class FaqController extends AbstractController
{
#[Route('/faq', name: 'faq_index', methods: ['GET'])]
public function index(QuestionRepository $questions): Response
{
return $this->json($questions->findActive());
}
#[Route('/faq/{slug}', name: 'faq_show', methods: ['GET'])]
public function show(
#[MapEntity(mapping: ['slug' => 'slug'])]
Question $question,
): Response
{
return $this->json($question);
}
}
Magento · routes.xml + Controller/Question/View.php
<router id="standard">
<route id="faq" frontName="faq">
<module name="Vendor_Faq"/>
</route>
</router>
class View implements HttpGetActionInterface
{
public function __construct(
private RequestInterface $request,
private QuestionRepositoryInterface $questions,
private PageFactory $pageFactory,
) {}
public function execute(): ResultInterface
{
$id = (int) $this->request->getParam('id');
$question = $this->questions->getById($id);
$page = $this->pageFactory->create();
$page->getConfig()->getTitle()->set($question->getTitle());
return $page;
}
}
Symfony · src/Controller/FaqController.php
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\Routing\Attribute\Route;
final class FaqController extends AbstractController
{
#[Route('/faq', name: 'faq_index', methods: ['GET'])]
public function index(QuestionRepository $questions): Response
{
return $this->json($questions->findActive());
}
#[Route('/faq/{slug}', name: 'faq_show', methods: ['GET'])]
public function show(
#[MapEntity(mapping: ['slug' => 'slug'])]
Question $question,
): Response
{
return $this->json($question);
}
}
- Сутність підставляється за параметром маршруту; якщо її немає, буде 404 без жодного коду. Для
{id}атрибут#[MapEntity]не потрібен. - Сервіси можна просити прямо в аргументах дії, а не лише в конструкторі.
- URL генерують за іменем маршруту:
$this->generateUrl('faq_show', ['slug' => $q->getSlug()]). bin/console debug:router— усі маршрути,router:match /faq/returns— який маршрут спрацює.
JSON на вході: замість getParam()
Symfony · DTO з тіла запиту з валідацією
final class CreateQuestion
{
public function __construct(
#[Assert\NotBlank, Assert\Length(max: 255)]
public readonly string $title,
#[Assert\NotBlank]
public readonly string $answer,
) {}
}
#[Route('/api/questions', methods: ['POST'])]
public function create(
#[MapRequestPayload]
CreateQuestion $input,
QuestionService $questions,
): JsonResponse
{
$question = $questions->create($input);
return $this->json(['id' => $question->getId()], 201);
}
Невалідний JSON або порушені обмеження дають відповідь 422 зі списком помилок. Для query string є #[MapQueryString] і #[MapQueryParameter], для поточного користувача — #[CurrentUser].
Area → префікс і firewall
Symfony · config/routes.yaml
admin:
resource: ../src/Controller/Admin/
type: attribute
prefix: /admin
name_prefix: admin_
Доступ до /admin закривають у security.yaml (див. розділ про безпеку). Окремого DI-конфігу для area немає: якщо в адмінці потрібна інша реалізація сервісу, це два різні сервіси.