symfony-api-response
Symfony REST API response standard {result, error, extra, status}: thin controllers, #[MapRequestPayload] / #[MapQueryString]…
Doctrine persistence for Symfony hexagonal architecture (ORM 2.x/3.x, DBAL 3/4, DoctrineBundle 2/3): repository adapters, XML mapping in Infrastructure, embeddables, enums, custom DBAL types, native lazy objects, migrations, transactions, read models with DBAL QueryBuilder,
Claude Code
$ npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence --agent claude-code
Codex
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a codex -y
Cursor
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a cursor -y
Gemini CLI
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a gemini-cli -y
GitHub Copilot
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a github-copilot -y
OpenCode
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a opencode -y
Windsurf
$ npx skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistence -a windsurf -y
name: symfony-doctrine-persistence description: "Doctrine persistence for Symfony hexagonal architecture (ORM 2.x/3.x, DBAL 3/4, DoctrineBundle 2/3): repository adapters, XML mapping in Infrastructure, embeddables, enums, custom DBAL types, native lazy objects, migrations, transactions, read models with DBAL QueryBuilder, strict no-native-SQL rule. Use when: doctrine, repository, persistence, database, mapping, migration, ORM, entity manager, DBAL, transaction, query builder, pagination"
You are an expert in Doctrine ORM within Symfony hexagonal architecture.
| Topic | Doctrine ORM 2.x (Symfony 6.4) | Doctrine ORM 3.x (Symfony 7.4 / 8.x) | |---|---|---| | Mapping drivers | XML, attributes, annotations, YAML | XML and attributes only (annotations + YAML removed) | | XML mapping | XSD validation optional | XSD validation always on: invalid XML fails at boot | | DBAL | 3.x | 4.x (`Type::getName()` removed, register types by name) | | Lazy loading | proxy classes | PHP 8.4 + ORM 3.4+: native lazy objects (`enable_native_lazy_objects: true`, DoctrineBundle 2.16+; always on in DoctrineBundle 3.1) | | Pagination | `Tools\Pagination\Paginator` | ORM 3.7+: `OffsetPaginator` + `Window` (`Paginator` deprecated) | | Readonly properties / `private(set)` / hooks | readonly since 2.13 | readonly; asymmetric visibility + property hooks since 3.4 |
XML entity mapping is unaffected by Symfony 8 removing XML *service/route* configuration.
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, with `DispatchAfterCurrentBusStamp` 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.
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;
use Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp;
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) {
// released after the current command (and its transaction) succeeds
$this->eventBus->dispatch($event, [new DispatchAfterCurrentBusStamp()]);
}
}
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();
}
}File: `src/Infrastructure/User/Persistence/Mapping/Entity.User.orm.xml` (name = class relative to the mapping `prefix`, see `references/mapping-patterns.md`)
<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
https://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
<entity name="App\Domain\User\Entity\User" table="users">
<id name="id" type="user_id" column="id" />
<embedded name="email" class="App\Domain\User\ValueObject\Email" use-column-prefix="false" />
<field name="name" type="string" />
<field name="createdAt" type="datetime_immutable" column="created_at" />
</entity>
</doctrine-mapping>`#[ORM\Entity]` attributes must sit on the entity class itself, which lives in Domain. XML is the supported driver that keeps the mapping in Infrastructure. Legacy modules may keep attributes until they are refactored.
**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
Native/raw SQL bypasses Doctrine's abstraction layers and creates several problems:
Flag these patterns as CRITICAL violations:
// FORBIDDEN - native SQL patterns
$connection->executeQuery('SELECT ...');
$connection->exeA 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).
Symfony REST API response standard {result, error, extra, status}: thin controllers, #[MapRequestPayload] / #[MapQueryString]…
Symfony CQRS with Messenger: commands, queries, handlers, command.bus / query.bus / event.bus configuration, typed bus wrappers,…
Symfony DDD domain modeling in pure PHP: entities, aggregates, value objects, domain events, domain exceptions, ID generation…
Symfony hexagonal architecture (ports & adapters) setup for Symfony 6.4 LTS, 7.4 LTS and 8.x: project structure, module…
Symfony Messenger async processing and Symfony Scheduler instead of cron: transports, routing (#[AsMessage] 7.2+), retry with…
Symfony ports and adapters: port interfaces in Domain, adapter implementations in Infrastructure, dependency injection,…
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
Ask which skill or flow fits your situation. A router over the skills in this repo.