internal/skills/content/symfony/SKILL.md
Symfony 7+ framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Symfony projects, or when the user mentions Symfony. Provides Doctrine ORM, Twig templates, bundles, services, and security guidelines.
npx skillsauth add ar4mirez/samuel symfonyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Applies to: Symfony 7+, PHP 8.2+, Doctrine ORM, Twig, Messenger
composer.lock committedcomposer.json; run composer audit before adding dependenciesdeclare(strict_types=1);php-cs-fixer fix before committingvendor/bin/phpstan analyse src)PascalCase classes, camelCase methods, snake_case config keysAbstractController only when you need its shortcuts#[Route] attribute on both class and method#[MapRequestPayload] for automatic DTO deserialization and validationJsonResponse for APIs; never echo or die()#[ORM\Entity], #[ORM\Column], etc.)repositoryClass on entities#[ORM\HasLifecycleCallbacks] sparingly; prefer Doctrine event listeners$this->items = new ArrayCollection()DateTimeImmutable for all date columns#[ORM\Index]static for chainingreadonly promoted properties)EntityManagerInterface into controllers; inject repositories or services#[AsMessageHandler] for async operationsmyapp/
├── bin/
│ └── console # Symfony CLI
├── config/
│ ├── packages/ # Per-package YAML config
│ │ ├── doctrine.yaml
│ │ ├── security.yaml
│ │ ├── messenger.yaml
│ │ └── ...
│ ├── routes/ # Route imports
│ ├── routes.yaml
│ ├── services.yaml # Service definitions and autowiring
│ └── bundles.php # Registered bundles
├── migrations/ # Doctrine migrations (never edit after deploy)
├── public/
│ └── index.php # Single entry point
├── src/
│ ├── Controller/ # HTTP controllers (thin)
│ ├── Dto/ # Request/response DTOs
│ ├── Entity/ # Doctrine entities
│ ├── Repository/ # Doctrine repositories
│ ├── Service/ # Business logic
│ ├── EventSubscriber/ # Event subscribers
│ ├── Message/ # Messenger messages
│ ├── MessageHandler/ # Messenger handlers
│ ├── Command/ # Console commands
│ ├── Form/ # Form types (web apps)
│ ├── Security/ # Voters, authenticators
│ └── Kernel.php
├── templates/ # Twig templates
├── tests/
│ ├── Controller/ # Functional tests
│ ├── Service/ # Unit tests
│ └── bootstrap.php
├── translations/ # i18n files
├── var/ # Cache and logs (gitignored)
├── .env # Default env vars (committed)
├── .env.local # Local overrides (gitignored)
├── composer.json
├── phpunit.xml.dist
└── symfony.lock
src/Dto/ keeps request/response data separate from entitiessrc/Message/ and src/MessageHandler/ follow Messenger conventionsvar/ is ephemeral; never store persistent data thereconfig/packages/ files are loaded by environment (config/packages/test/)#[Route('/api/v1/users')]
class UserController extends AbstractController
{
public function __construct(
private readonly UserService $userService,
) {}
#[Route('', methods: ['GET'])]
public function index(Request $request): JsonResponse
{
$page = $request->query->getInt('page', 1);
$limit = $request->query->getInt('limit', 15);
return $this->json(
$this->userService->getPaginated($page, $limit),
Response::HTTP_OK,
[],
['groups' => 'user:read'],
);
}
#[Route('', methods: ['POST'])]
#[IsGranted('ROLE_ADMIN')]
public function create(#[MapRequestPayload] CreateUserDto $dto): JsonResponse
{
return $this->json(
$this->userService->create($dto),
Response::HTTP_CREATED,
[],
['groups' => 'user:read'],
);
}
}
/api/v1/...ParamConverter<?php
declare(strict_types=1);
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: 'users')]
#[ORM\HasLifecycleCallbacks]
class User
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
#[Assert\NotBlank]
#[Assert\Email]
private string $email;
#[ORM\Column(type: Types::DATETIME_IMMUTABLE)]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
// Getters and fluent setters (return static)
}
<?php
declare(strict_types=1);
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/** @extends ServiceEntityRepository<User> */
class UserRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findOneByEmail(string $email): ?User
{
return $this->createQueryBuilder('u')
->andWhere('u.email = :email')
->setParameter('email', strtolower($email))
->getQuery()
->getOneOrNullResult();
}
}
php bin/console make:migration # Generate from entity diff
php bin/console doctrine:migrations:migrate # Apply migrations
php bin/console doctrine:schema:validate # Check mapping vs DB
down() method{# templates/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}App{% endblock %}</title>
{% block stylesheets %}{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}{% endblock %}
</body>
</html>
{# templates/user/index.html.twig #}
{% extends 'base.html.twig' %}
{% block title %}Users{% endblock %}
{% block body %}
<h1>Users</h1>
{% for user in users %}
<p>{{ user.name|e }}</p>
{% else %}
<p>No users found.</p>
{% endfor %}
{% endblock %}
|raw on user data){% include %} for partials, {% embed %} for overridable partialsclass UserService
{
public function __construct(
private readonly UserRepository $userRepository,
private readonly EntityManagerInterface $entityManager,
private readonly UserPasswordHasherInterface $passwordHasher,
private readonly EventDispatcherInterface $eventDispatcher,
) {}
public function create(CreateUserDto $dto): User
{
$user = new User();
$user->setEmail($dto->email);
$user->setPassword($this->passwordHasher->hashPassword($user, $dto->password));
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->eventDispatcher->dispatch(new UserCreatedEvent($user));
return $user;
}
}
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
#[Autowire] attribute for non-standard parameters#[TaggedIterator] to inject all services with a specific tagclass UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('name', TextType::class)
->add('email', EmailType::class);
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults(['data_class' => User::class]);
}
}
data_class#[MapRequestPayload] over Symfony formsclass PostVoter extends Voter
{
public const EDIT = 'POST_EDIT';
public const DELETE = 'POST_DELETE';
protected function supports(string $attribute, mixed $subject): bool
{
return in_array($attribute, [self::EDIT, self::DELETE], true)
&& $subject instanceof Post;
}
protected function voteOnAttribute(
string $attribute,
mixed $subject,
TokenInterface $token,
): bool {
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return $subject->getAuthor() === $user || $user->getRole() === 'admin';
}
}
password_hashers: auto (Symfony picks bcrypt/argon2 automatically)stateless: true)lexik/jwt-authentication-bundle for APIs#[IsGranted] attribute on controller actionsis_granted('ROLE_...') for fine-grained checks)UserPasswordHasherInterface#[AsCommand(name: 'app:import-users', description: 'Import users from CSV')]
class ImportUsersCommand extends Command
{
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// Implementation here
$io->success('Import complete.');
return Command::SUCCESS;
}
}
#[AsCommand] attribute (not configure() for name/description)Command::SUCCESS, Command::FAILURE, or Command::INVALIDSymfonyStyle for consistent output formatting# Development
symfony serve # Local dev server
php bin/console cache:clear # Clear cache
php bin/console debug:router # List all routes
php bin/console debug:container # List all services
# Code Generation
php bin/console make:entity User
php bin/console make:controller UserController
php bin/console make:migration
php bin/console make:form UserType
php bin/console make:voter PostVoter
php bin/console make:command App:ImportUsers
php bin/console make:subscriber UserEventSubscriber
php bin/console make:message SendWelcomeEmail
# Database
php bin/console doctrine:database:create
php bin/console doctrine:migrations:migrate
php bin/console doctrine:schema:validate
php bin/console doctrine:fixtures:load
# Messenger
php bin/console messenger:consume async
php bin/console messenger:failed:show
php bin/console messenger:failed:retry
# Testing and Quality
php bin/phpunit
php bin/phpunit --coverage-html coverage
vendor/bin/phpstan analyse src
vendor/bin/php-cs-fixer fix
# Production
composer install --no-dev --optimize-autoloader
php bin/console cache:clear --env=prod
php bin/console cache:warmup --env=prod
readonly promoted propertiesDateTimeImmutable for all temporal dataphp bin/console doctrine:schema:validate in CIEntityManager inside loops (batch with $em->flush() once)$_GET, $_POST, $_SERVER (use Request object)%env()% syntax or #[Autowire])For detailed patterns and production guidance, see:
development
Zig language guardrails, patterns, and best practices for AI-assisted development. Use when working with Zig files (.zig), build.zig, or when the user mentions Zig. Provides comptime patterns, allocator conventions, C interop guidelines, and testing standards specific to this project's coding standards.
tools
WordPress framework guardrails, patterns, and best practices for AI-assisted development. Use when working with WordPress projects, or when the user mentions WordPress. Provides theme development, plugin architecture, REST API, blocks, and security guidelines.
tools
Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. Use when testing web apps, automating browser interactions, or debugging frontend issues.
tools
Suite of tools for creating elaborate, multi-component web applications using modern frontend technologies (React, Tailwind CSS, shadcn/ui). Use for complex projects requiring state management, routing, or shadcn/ui components - not for simple single-file HTML/JSX pages.