Symfony-hexagonal-skill symfony-doctrine-persistence
Symfony Doctrine persistence — repository adapters, entity mapping, migrations, transactions, database patterns. Triggers on: doctrine, repository, persistence, database, mapping, migration, ORM, entity manager, DBAL, transaction
install
source · Clone the upstream repo
git clone https://github.com/aligundogdu/symfony-hexagonal-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/aligundogdu/symfony-hexagonal-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/symfony-doctrine-persistence" ~/.claude/skills/aligundogdu-symfony-hexagonal-skill-symfony-doctrine-persistence && rm -rf "$T"
manifest:
skills/symfony-doctrine-persistence/SKILL.mdsource content
Symfony Doctrine Persistence
You are an expert in Doctrine ORM within Symfony hexagonal architecture.
When to Activate
- User needs a repository implementation
- User asks about entity mapping
- User needs migration management
- User discusses transactions or database access
Core Rules
- Mapping NEVER in Domain: No
or annotations on domain entities#[ORM\Entity] - Mapping in Infrastructure: Use XML or separate mapping files in
Infrastructure/{Module}/Persistence/Mapping/ - Repository = Adapter: Implements domain port interface
- Event dispatch after persist: Repository dispatches domain events after flush
- NEVER use native/raw SQL: No
,$connection->executeQuery()
,$connection->executeStatement()
,NativeQuery
, or raw SQL strings anywhere in application code. Always use Doctrine QueryBuilder (ORM or DBAL), DQL, finder methods, or Criteria API. The only exception is Doctrine Migrations ($connection->prepare()
) which requires raw SQL by design.$this->addSql()
Repository Adapter Pattern
namespace App\Infrastructure\{Module}\Persistence; use App\Domain\{Module}\Entity\{Entity}; use App\Domain\{Module}\Port\{Entity}RepositoryInterface; use Doctrine\ORM\EntityManagerInterface; use Symfony\Component\Messenger\MessageBusInterface; final readonly class Doctrine{Entity}Repository implements {Entity}RepositoryInterface { public function __construct( private EntityManagerInterface $entityManager, private MessageBusInterface $eventBus, ) { } public function save({Entity} $entity): void { $this->entityManager->persist($entity); $this->entityManager->flush(); foreach ($entity->pullDomainEvents() as $event) { $this->eventBus->dispatch($event); } } public function findById({Entity}Id $id): ?{Entity} { return $this->entityManager->find({Entity}::class, $id->value); } public function remove({Entity} $entity): void { $this->entityManager->remove($entity); $this->entityManager->flush(); } }
Mapping Strategies
Option A: XML Mapping (Recommended for strict separation)
<!-- src/Infrastructure/User/Persistence/Mapping/User.orm.xml --> <doctrine-mapping> <entity name="App\Domain\User\Entity\User" table="users"> <id name="id" type="string" column="id" /> <embedded name="email" class="App\Domain\User\ValueObject\Email" /> <field name="name" type="string" /> <field name="createdAt" type="datetime_immutable" column="created_at" /> </entity> </doctrine-mapping>
Option B: PHP Attributes in Infrastructure
// Separate mapping class that maps to domain entity // Configure in doctrine.yaml with mapping paths
Migration Workflow
Always ask the user which strategy they prefer:
- Auto-diff:
(generates from mapping)php bin/console doctrine:migrations:diff - Manual: Write migrations by hand for full control
Why No Native SQL?
Native/raw SQL bypasses Doctrine's abstraction layers and creates several problems:
- Database portability lost — Raw SQL ties code to a specific database engine (MySQL, PostgreSQL, etc.)
- No type safety — QueryBuilder provides parameter binding with type inference, raw SQL doesn't
- Mapping bypass — Entities, value objects, and custom types are ignored when using raw SQL
- Harder to maintain — SQL strings scattered in code are harder to refactor than QueryBuilder chains
- Security risk — Raw SQL increases the surface for SQL injection if parameters are not properly bound
Detecting Native SQL (for code review)
Flag these patterns as CRITICAL violations:
// FORBIDDEN — native SQL patterns $connection->executeQuery('SELECT ...'); $connection->executeStatement('INSERT ...'); $connection->prepare('SELECT ...'); $connection->exec('DROP ...'); $entityManager->getConnection()->executeQuery(...); $entityManager->createNativeQuery(...); $rsm = new ResultSetMapping(); // ALLOWED — Doctrine abstractions $queryBuilder->select(...)->from(...)->where(...); // DBAL QueryBuilder $entityManager->createQueryBuilder()->select('u')...; // ORM QueryBuilder $entityManager->createQuery('SELECT u FROM User u'); // DQL $repository->findBy([...]); // Finder methods $repository->matching($criteria); // Criteria API
References
See
references/ for detailed guides:
— Repository patterns and transaction managementrepository-patterns.md
— XML mapping, embeddables, relationsmapping-patterns.md
— Migration strategies and best practicesmigration-workflow.md
— Why native SQL is forbidden and how to use QueryBuilder insteadno-native-sql.md