Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Modern PHP 8.3+ development with strict typing, PHPStan level 9, PSR standards, and Laravel/Symfony support.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/symfony-patterns.md
1# Symfony Patterns23## Dependency Injection45```php6<?php78declare(strict_types=1);910namespace App\Service;1112use App\Repository\UserRepositoryInterface;13use Psr\Log\LoggerInterface;14use Symfony\Component\Mailer\MailerInterface;1516final readonly class UserService17{18public function __construct(19private UserRepositoryInterface $userRepository,20private MailerInterface $mailer,21private LoggerInterface $logger,22) {}2324public function createUser(string $email, string $password): User25{26$user = new User($email, password_hash($password, PASSWORD_ARGON2ID));2728$this->userRepository->save($user);29$this->logger->info('User created', ['email' => $email]);3031return $user;32}33}34```3536## Service Configuration (services.yaml)3738```yaml39# config/services.yaml40services:41_defaults:42autowire: true43autoconfigure: true44bind:45string $projectDir: '%kernel.project_dir%'46bool $isDebug: '%kernel.debug%'4748App\:49resource: '../src/'50exclude:51- '../src/DependencyInjection/'52- '../src/Entity/'53- '../src/Kernel.php'5455# Interface binding56App\Repository\UserRepositoryInterface:57class: App\Repository\DoctrineUserRepository5859# Service with specific configuration60App\Service\PaymentService:61arguments:62$apiKey: '%env(PAYMENT_API_KEY)%'63$timeout: 306465# Tagged services66App\EventSubscriber\:67resource: '../src/EventSubscriber/'68tags: ['kernel.event_subscriber']69```7071## Controllers with Attributes7273```php74<?php7576declare(strict_types=1);7778namespace App\Controller;7980use App\DTO\CreateUserRequest;81use App\Entity\User;82use App\Service\UserService;83use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;84use Symfony\Component\HttpFoundation\JsonResponse;85use Symfony\Component\HttpFoundation\Response;86use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;87use Symfony\Component\Routing\Attribute\Route;88use Symfony\Component\Security\Http\Attribute\IsGranted;8990#[Route('/api/users', name: 'api_users_')]91final class UserController extends AbstractController92{93public function __construct(94private readonly UserService $userService,95) {}9697#[Route('', name: 'list', methods: ['GET'])]98#[IsGranted('ROLE_USER')]99public function list(): JsonResponse100{101$users = $this->userService->getAllUsers();102103return $this->json($users, Response::HTTP_OK, [], [104'groups' => ['user:read'],105]);106}107108#[Route('', name: 'create', methods: ['POST'])]109#[IsGranted('ROLE_ADMIN')]110public function create(111#[MapRequestPayload] CreateUserRequest $request112): JsonResponse {113$user = $this->userService->createUser(114$request->email,115$request->password116);117118return $this->json($user, Response::HTTP_CREATED, [], [119'groups' => ['user:read'],120]);121}122123#[Route('/{id}', name: 'show', methods: ['GET'])]124public function show(User $user): JsonResponse125{126$this->denyAccessUnlessGranted('view', $user);127128return $this->json($user, context: ['groups' => ['user:detail']]);129}130}131```132133## DTOs with Validation134135```php136<?php137138declare(strict_types=1);139140namespace App\DTO;141142use Symfony\Component\Validator\Constraints as Assert;143144final readonly class CreateUserRequest145{146public function __construct(147#[Assert\NotBlank]148#[Assert\Email]149public string $email,150151#[Assert\NotBlank]152#[Assert\Length(min: 8, max: 100)]153#[Assert\PasswordStrength(minScore: Assert\PasswordStrength::STRENGTH_MEDIUM)]154public string $password,155156#[Assert\NotBlank]157#[Assert\Length(min: 2, max: 100)]158public string $name,159160#[Assert\Choice(choices: ['admin', 'user', 'moderator'])]161public string $role = 'user',162) {}163}164165final readonly class UpdateUserRequest166{167public function __construct(168#[Assert\Email]169public ?string $email = null,170171#[Assert\Length(min: 2, max: 100)]172public ?string $name = null,173174#[Assert\Type('bool')]175public ?bool $isActive = null,176) {}177}178```179180## Event Subscribers181182```php183<?php184185declare(strict_types=1);186187namespace App\EventSubscriber;188189use App\Event\UserRegisteredEvent;190use Psr\Log\LoggerInterface;191use Symfony\Component\EventDispatcher\EventSubscriberInterface;192use Symfony\Component\Mailer\MailerInterface;193194final readonly class UserSubscriber implements EventSubscriberInterface195{196public function __construct(197private MailerInterface $mailer,198private LoggerInterface $logger,199) {}200201public static function getSubscribedEvents(): array202{203return [204UserRegisteredEvent::class => [205['sendWelcomeEmail', 10],206['logRegistration', 5],207],208];209}210211public function sendWelcomeEmail(UserRegisteredEvent $event): void212{213$user = $event->getUser();214// Send email logic215$this->logger->info('Welcome email sent', ['user_id' => $user->getId()]);216}217218public function logRegistration(UserRegisteredEvent $event): void219{220$this->logger->info('User registered', [221'user_id' => $event->getUser()->getId(),222'email' => $event->getUser()->getEmail(),223]);224}225}226```227228## Custom Events229230```php231<?php232233declare(strict_types=1);234235namespace App\Event;236237use App\Entity\User;238use Symfony\Contracts\EventDispatcher\Event;239240final class UserRegisteredEvent extends Event241{242public function __construct(243private readonly User $user,244private readonly \DateTimeImmutable $occurredAt = new \DateTimeImmutable(),245) {}246247public function getUser(): User248{249return $this->user;250}251252public function getOccurredAt(): \DateTimeImmutable253{254return $this->occurredAt;255}256}257258// Dispatching events259use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;260261final readonly class UserService262{263public function __construct(264private EventDispatcherInterface $eventDispatcher,265) {}266267public function registerUser(string $email, string $password): User268{269$user = new User($email, $password);270// ... save user271272$this->eventDispatcher->dispatch(new UserRegisteredEvent($user));273274return $user;275}276}277```278279## Console Commands280281```php282<?php283284declare(strict_types=1);285286namespace App\Command;287288use App\Service\UserService;289use Symfony\Component\Console\Attribute\AsCommand;290use Symfony\Component\Console\Command\Command;291use Symfony\Component\Console\Input\InputArgument;292use Symfony\Component\Console\Input\InputInterface;293use Symfony\Component\Console\Input\InputOption;294use Symfony\Component\Console\Output\OutputInterface;295use Symfony\Component\Console\Style\SymfonyStyle;296297#[AsCommand(298name: 'app:user:create',299description: 'Create a new user',300)]301final class CreateUserCommand extends Command302{303public function __construct(304private readonly UserService $userService,305) {306parent::__construct();307}308309protected function configure(): void310{311$this312->addArgument('email', InputArgument::REQUIRED, 'User email')313->addArgument('password', InputArgument::REQUIRED, 'User password')314->addOption('admin', 'a', InputOption::VALUE_NONE, 'Make user admin');315}316317protected function execute(InputInterface $input, OutputInterface $output): int318{319$io = new SymfonyStyle($input, $output);320321$email = $input->getArgument('email');322$password = $input->getArgument('password');323$isAdmin = $input->getOption('admin');324325$user = $this->userService->createUser($email, $password, $isAdmin);326327$io->success(sprintf('User created with ID: %d', $user->getId()));328329return Command::SUCCESS;330}331}332```333334## Voters (Authorization)335336```php337<?php338339declare(strict_types=1);340341namespace App\Security\Voter;342343use App\Entity\Post;344use App\Entity\User;345use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;346use Symfony\Component\Security\Core\Authorization\Voter\Voter;347348final class PostVoter extends Voter349{350public const VIEW = 'view';351public const EDIT = 'edit';352public const DELETE = 'delete';353354protected function supports(string $attribute, mixed $subject): bool355{356return in_array($attribute, [self::VIEW, self::EDIT, self::DELETE])357&& $subject instanceof Post;358}359360protected function voteOnAttribute(361string $attribute,362mixed $subject,363TokenInterface $token364): bool {365$user = $token->getUser();366367if (!$user instanceof User) {368return false;369}370371/** @var Post $post */372$post = $subject;373374return match ($attribute) {375self::VIEW => $this->canView($post, $user),376self::EDIT => $this->canEdit($post, $user),377self::DELETE => $this->canDelete($post, $user),378default => false,379};380}381382private function canView(Post $post, User $user): bool383{384return $post->isPublished() || $this->isOwner($post, $user);385}386387private function canEdit(Post $post, User $user): bool388{389return $this->isOwner($post, $user);390}391392private function canDelete(Post $post, User $user): bool393{394return $this->isOwner($post, $user) || $user->hasRole('ROLE_ADMIN');395}396397private function isOwner(Post $post, User $user): bool398{399return $post->getAuthor()->getId() === $user->getId();400}401}402```403404## Message Handler (Messenger)405406```php407<?php408409declare(strict_types=1);410411namespace App\Message;412413final readonly class SendWelcomeEmail414{415public function __construct(416public int $userId,417) {}418}419420namespace App\MessageHandler;421422use App\Message\SendWelcomeEmail;423use App\Repository\UserRepositoryInterface;424use Symfony\Component\Mailer\MailerInterface;425use Symfony\Component\Messenger\Attribute\AsMessageHandler;426427#[AsMessageHandler]428final readonly class SendWelcomeEmailHandler429{430public function __construct(431private UserRepositoryInterface $userRepository,432private MailerInterface $mailer,433) {}434435public function __invoke(SendWelcomeEmail $message): void436{437$user = $this->userRepository->find($message->userId);438439if (!$user) {440return;441}442443// Send email logic444}445}446447// Dispatching messages448use Symfony\Component\Messenger\MessageBusInterface;449450$this->messageBus->dispatch(new SendWelcomeEmail($user->getId()));451```452453## Quick Reference454455| Component | Purpose | File Location |456|-----------|---------|---------------|457| Controller | HTTP handlers | `src/Controller/` |458| Service | Business logic | `src/Service/` |459| Repository | Data access | `src/Repository/` |460| Event | Domain events | `src/Event/` |461| EventSubscriber | Event handlers | `src/EventSubscriber/` |462| Command | CLI commands | `src/Command/` |463| Voter | Authorization | `src/Security/Voter/` |464| Message | Async messages | `src/Message/` |465| MessageHandler | Message handlers | `src/MessageHandler/` |466| DTO | Data transfer | `src/DTO/` |467