Symfony
01 / 02

Routing, Controllers & Services

Routing, Controllers & Services

Routes & Controllers

<?php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

class ArticleController extends AbstractController
{
    #[Route('/articles/{id}', name: 'article_show', methods: ['GET'])]
    public function show(int $id, ArticleRepository $repo): Response
    {
        $article = $repo->find($id) ?? throw $this->createNotFoundException();

        return $this->render('article/show.html.twig', [
            'article' => $article,
        ]);
    }

    #[Route('/articles', name: 'article_create', methods: ['POST'])]
    public function create(Request $request): Response
    {
        return $this->json(['status' => 'created'], Response::HTTP_CREATED);
    }
}

// Generate a URL from a route name elsewhere in code:
// $this->generateUrl('article_show', ['id' => $article->getId()]);

Services & Autowiring

<?php
namespace App\Service;

class NewsletterSender
{
    // Type-hinted constructor args — Symfony's DI container autowires them
    public function __construct(
        private MailerInterface $mailer,
        private LoggerInterface $logger,
    ) {}

    public function send(string $to): void
    {
        $this->mailer->send((new Email())->to($to)->subject('Weekly digest'));
        $this->logger->info('Newsletter sent', ['to' => $to]);
    }
}

// Any controller/service just type-hints it — no manual wiring needed:
class NewsletterController extends AbstractController
{
    public function trigger(NewsletterSender $sender): Response
    {
        $sender->send('user@example.com');
        return new Response('ok');
    }
}

// Scalar args (strings, ints) can't be autowired by type — bind them explicitly:
// services.yaml:
//   App\Service\ApiClient:
//     arguments:
//       $apiKey: '%env(API_KEY)%'

Forms & Validation

<?php
class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class)
            ->add('plainPassword', PasswordType::class);
    }
}

// Constraint attributes on the entity/DTO itself
class RegistrationDto
{
    #[Assert\NotBlank]
    #[Assert\Email]
    public string $email = '';

    #[Assert\Length(min: 8)]
    public string $plainPassword = '';
}

// In the controller
$form = $this->createForm(RegistrationFormType::class, $dto);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
    // $dto now holds validated, mapped data
}

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free