Skip to content

What it is

Symfony is a set of reusable PHP components and a full-stack framework, favoured for large, long-lived enterprise applications.

Controllers are classes with attribute-based routing. Services are autowired from the container, and Doctrine provides the ORM.

Installation

composer create-project symfony/skeleton my-app

Getting started

The smallest useful thing you can do with it, and what each part means.

Attribute routing and autowiring
<?php
#[Route('/books')]
final class BookController extends AbstractController
{
    public function __construct(
        private readonly BookRepository $books,   // autowired by type
    ) {}

    #[Route('/{id}', methods: ['GET'])]
    public function show(int $id): JsonResponse
    {
        $book = $this->books->find($id)
            ?? throw $this->createNotFoundException("Book $id not found");

        return $this->json($book, context: ['groups' => ['book:read']]);
    }

    #[Route('', methods: ['POST'])]
    public function create(
        #[MapRequestPayload] CreateBookDto $dto,   // decoded and validated
    ): JsonResponse {
        return $this->json($this->books->create($dto), Response::HTTP_CREATED);
    }
}
MapRequestPayload deserialises and validates the body in one step, so the controller receives a valid typed object or Symfony returns 422 before the method runs.

Advanced usage

Where the library earns its place over a simpler alternative.

Doctrine entities and Messenger
<?php
#[ORM\Entity(repositoryClass: BookRepository::class)]
#[ORM\Index(columns: ['year'])]
class Book
{
    #[ORM\Id, ORM\GeneratedValue, ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 200)]
    #[Assert\NotBlank, Assert\Length(max: 200)]
    private string $title;

    #[ORM\ManyToOne(inversedBy: 'books')]
    private ?Author $author = null;
}

// Asynchronous message handling
#[AsMessageHandler]
final class SendWelcomeEmailHandler
{
    public function __invoke(SendWelcomeEmail $message): void
    {
        $this->mailer->send(/* … */);
    }
}

$this->bus->dispatch(new SendWelcomeEmail($userId));
Validation constraints live on the entity alongside the mapping, so the same rules apply whether the object came from a form, an API payload or a fixture.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Cannot autowire service: argument references class but no such service exists
The class is outside the autoconfigured paths or is not concrete. Register it explicitly, or bind the interface to an implementation in services.yaml.
Doctrine schema is out of sync
Generate and run a migration with make:migration and doctrine:migrations:migrate rather than using schema:update in production.

Best practices

  • Prefer constructor injection with typed properties; autowiring resolves them.
  • Use MapRequestPayload with DTOs rather than reading the raw request in controllers.
  • Cache the container and routes in production (composer dump-env prod).
  • Choose Symfony for large, long-lived applications; Laravel is faster for smaller ones.

Background

Why it exists, and what it was reacting to.

Symfony's components underpin much of the PHP ecosystem — Laravel, Drupal and Composer itself all use them. The framework is more explicit and less convention-driven than Laravel, which suits large teams.