Symfony doctrine persistence
Skill aligundogdu/symfony-hexagonal-skill/skills/symfony-doctrine-persistence
Claude Code plugin that enforces hexagonal architecture (ports & adapters) in Symfony projects — with 10 auto-triggered skills, 2 review agents, and progressive refactoring support for existing codebases.
npx -y skills add aligundogdu/symfony-hexagonal-skill --skill symfony-doctrine-persistenceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
What its author says it does
Copied from the file, not written here
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
5.0 KB, as published. Nobody here has run it
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
#[ORM\Entity]or annotations on domain entities - 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,$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:
- Auto-diff:
php bin/console doctrine:migrations:diff(generates from mapping) - 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.md— Repository patterns and transaction managementmapping-patterns.md— XML mapping, embeddables, relationsmigration-workflow.md— Migration strategies and best practicesno-native-sql.md— Why native SQL is forbidden and how to use QueryBuilder instead