/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
$ npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.
- You can call itInvoke it directly when you want it.
- Slash command
/symfony-doctrine-persistence
Context preview
The summary Claude sees to decide when to auto-load this skill.
Symfony Doctrine persistence — repository adapters, entity mapping, migrations, transactions, database patterns. Triggers on: doctrine, repository, persistence, database, mapping, migration, ORM, entity manager, DBAL, transaction
SKILL.md
symfony-doctrine-persistence.SKILL.mddescription: "Symfony Doctrine persistence — repository adapters, entity mapping, migrations, transactions, database patterns. Triggers on: doctrine, repository, persistence, database, mapping, migration, ORM, entity manager, DBAL, transaction"
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
1. **Mapping NEVER in Domain**: No `#[ORM\Entity]` or annotations on domain entities 2. **Mapping in Infrastructure**: Use XML or separate mapping files in `Infrastructure/{Module}/Persistence/Mapping/` 3. **Repository = Adapter**: Implements domain port interface 4. **Event dispatch after persist**: Repository dispatches domain events after flush 5. **NEVER use native/raw SQL**: No `$connection->executeQuery()`, `$connection->executeStatement()`, `NativeQuery`, `$connection->prepare()`, 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 (`$this->addSql()`) which requires raw SQL by design.
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: 1. **Auto-diff**: `php bin/console doctrine:migrations:diff` (generates from mapping) 2. **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 APIReferences
See `references/` for detailed guides:
- `repository-patterns.md` — Repository patterns and transaction management
- `mapping-patterns.md` — XML mapping, embeddables, relations
- `migration-workflow.md` — Migration strategies and best practices
- `no-native-sql.md` — Why native SQL is forbidden and how to use QueryBuilder instead
Read more
description: "Symfony Doctrine persistence — repository adapters, entity mapping, migrations, transactions, database patterns. Triggers on: doctrine, repository, persistence, database, mapping, migration, ORM, entity manager, DBAL, transaction"
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
1. **Mapping NEVER in Domain**: No `#[ORM\Entity]` or annotations on domain entities 2. **Mapping in Infrastructure**: Use XML or separate mapping files in `Infrastructure/{Module}/Persistence/Mapping/` 3. **Repository = Adapter**: Implements domain port interface 4. **Event dispatch after persist**: Repository dispatches domain events after flush 5. **NEVER use native/raw SQL**: No `$connection->executeQuery()`, `$connection->executeStatement()`, `NativeQuery`, `$connection->prepare()`, 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 (`$this->addSql()`) which requires raw SQL by design.
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: 1. **Auto-diff**: `php bin/console doctrine:migrations:diff` (generates from mapping) 2. **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 APIReferences
See `references/` for detailed guides:
- `repository-patterns.md` — Repository patterns and transaction management
- `mapping-patterns.md` — XML mapping, embeddables, relations
- `migration-workflow.md` — Migration strategies and best practices
- `no-native-sql.md` — Why native SQL is forbidden and how to use QueryBuilder instead
A Claude Code plugin that enforces hexagonal architecture (ports & adapters) in Symfony projects. Works with both new projects (full scaffolding) and existing projects (progressive, module-by-module refactoring).
Other skills on symfony-hexagonal-skill.
- /symfony-api-response
Symfony API response standardization — JSON payload format, exception handling, controllers, REST endpoints, error responses, debug mode. Triggers on: API, endpoint, controller, response, error handling, JSON, REST, API response, exception subscriber, HTTP
Open skill - /symfony-cqrs-handlers
Symfony CQRS command/query handlers — commands, queries, handlers, bus configuration, use cases. Triggers on: command, query, handler, CQRS, bus, use case, command handler, query handler, message bus
Open skill - /symfony-domain-modeling
Symfony domain modeling — entities, value objects, domain events, aggregates, domain exceptions. Triggers on: entity, value object, domain event, aggregate, domain exception, domain model, domain logic, business rule, invariant
Open skill - /symfony-hexagonal-architecture
Symfony hexagonal architecture setup — project structure, module scaffolding, layer responsibilities, dependency rules. Triggers on: architecture, module, layer, hexagonal, scaffold, project structure, directory structure, new project, new module
Open skill - /symfony-messenger-async
Symfony Messenger async processing — message queues, retry strategies, failure transport, Symfony Scheduler, idempotency patterns, background jobs. Triggers on: messenger, async, queue, retry, scheduler, background job, worker, transport, message queue, cron, scheduled task
Open skill - /symfony-ports-adapters
Symfony ports and adapters — port interfaces, adapter implementations, dependency injection, autowiring, repository interfaces. Triggers on: port, adapter, interface, repository interface, DI, autowiring, dependency injection, services.yaml, binding
Open skill

