| name | database-migrations |
| description | Create, manage, and apply database migrations using Doctrine ODM for MongoDB. Use when modifying entities, adding fields, managing database schema changes, creating repositories, or troubleshooting database issues. |
Database Migrations Skill
Context (Input)
- New entity needs database persistence
- Existing entity requires schema changes (add/modify/remove fields)
- MongoDB repository needs implementation
- Database schema validation fails
- Need to set up indexes for performance
Task (Function)
Create entities with XML mapping and repositories following hexagonal architecture and MongoDB best practices.
Success Criteria: make setup-test-db runs without errors, schema validates, all tests pass.
Core Principles
Domain-Driven Design
- Entities: Domain layer (
{Context}/Domain/Entity/)
- Repository Interfaces: Domain layer (
{Context}/Domain/Repository/)
- Repository Implementations: Infrastructure layer (
{Context}/Infrastructure/Repository/)
- XML Mappings: Infrastructure concern (
config/doctrine/)
See: implementing-ddd-architecture for DDD patterns.
MongoDB with Doctrine ODM
- Use XML mappings for all entity metadata (not annotations/attributes)
- Define indexes in XML for performance
- Use custom types (ULID, DomainUuid) for identifiers
- Schema updates applied via Doctrine commands
Quick Start
Creating a New Entity
Step 1: Create Entity (Domain Layer)
namespace App\Core\{Context}\Domain\Entity;
final class Customer
{
public function __construct(
private string $id,
private string $name,
private string $email,
private \DateTimeImmutable $createdAt
) {}
}
Step 2: Create XML Mapping
<document name="App\Core\Customer\Domain\Entity\Customer" collection="customers">
<field name="id" fieldName="id" id="true" type="ulid"/>
<field name="name" type="string"/>
<field name="email" type="string"/>
<field name="createdAt" fieldName="created_at" type="date_immutable"/>
<indexes>
<index><key name="email" order="asc"/><option name="unique" value="true"/></index>
</indexes>
</document>
Step 3: Configure API Platform
App\Core\Customer\Domain\Entity\Customer:
shortName: Customer
operations:
get_collection: ~
get: ~
post: ~
Step 4: Update Schema
make cache-clear
docker compose exec php bin/console doctrine:mongodb:schema:update
See: entity-creation-guide.md for complete workflow.
Modifying Existing Entities
- Update Entity Class (add/modify fields)
- Update XML Mapping (add field definitions)
- Clear Cache:
make cache-clear
- Update Schema:
docker compose exec php bin/console doctrine:mongodb:schema:update
See: entity-modification-guide.md
Creating Repositories
Step 1: Define Interface (Domain)
interface CustomerRepositoryInterface
{
public function save(Customer $customer): void;
public function findById(string $id): ?Customer;
}
Step 2: Implement (Infrastructure)
final class CustomerRepository implements CustomerRepositoryInterface
{
public function __construct(
private readonly DocumentManager $documentManager
) {}
public function save(Customer $customer): void
{
$this->documentManager->persist($customer);
$this->documentManager->flush();
}
}
Step 3: Register in services.yaml
App\Core\Customer\Domain\Repository\CustomerRepositoryInterface:
alias: App\Core\Customer\Infrastructure\Repository\CustomerRepository
See: repository-patterns.md
MongoDB-Specific Features
Custom Types
| Type | Usage | Purpose |
|---|
ulid | MongoDB _id field | Sortable, time-ordered identifiers |
domain_uuid | Domain identifiers | Standard UUID format (RFC 4122) |
<field name="ulid" id="true" type="ulid"/>
<field name="customerId" type="domain_uuid"/>
Indexes
<indexes>
<index><key name="email" order="asc"/><option name="unique" value="true"/></index>
<index><key name="createdAt" order="desc"/></index>
<index><key name="status"/><key name="type"/></index>
</indexes>
Embedded Documents
For Value Objects:
<embed-one field="address" target-document="App\Core\Customer\Domain\ValueObject\Address"/>
<embed-many field="tags" target-document="App\Core\Customer\Domain\ValueObject\Tag"/>
See: mongodb-specifics.md
Available Commands
make doctrine-migrations-migrate
make doctrine-migrations-generate
make setup-test-db
docker compose exec php bin/console doctrine:mongodb:schema:update
docker compose exec php bin/console doctrine:mongodb:schema:validate
Constraints (Parameters)
NEVER
- Use Doctrine annotations/attributes in Domain entities
- Modify existing migrations after they're applied
- Skip XML mapping validation
- Leave empty migration files in codebase
- Commit without testing schema changes
- Skip
make setup-test-db before integration tests
ALWAYS
- Create XML mappings for all entity metadata
- Keep Domain entities framework-agnostic
- Define indexes for frequently queried fields
- Test migrations on dev database before committing
- Run
make setup-test-db to verify schema
- Use Faker for unique test data (emails, names, etc.)
- Register resource directories in
api_platform.yaml
Format (Output)
Expected Schema Validation Output
$ docker compose exec php bin/console doctrine:mongodb:schema:validate
The MongoDB schema is valid.
Expected Test DB Setup Output
$ make setup-test-db
Database dropped and recreated successfully
Verification Checklist
After entity/migration changes:
Related Skills
Quick Commands
docker compose exec php bin/console doctrine:mongodb:schema:validate
docker compose exec php bin/console doctrine:mongodb:schema:update
make setup-test-db
make cache-clear
Reference Documentation
Detailed guides and examples:
Migration Best Practices
1. Clean Up Empty Migrations
MANDATORY: Delete empty migrations immediately.
public function up(Schema $schema): void { }
public function down(Schema $schema): void { }
2. Test Before Committing
- Apply migration on dev database
- Verify schema:
doctrine:mongodb:schema:validate
- Run all tests
- Test rollback if applicable
3. Production Safety
make doctrine-migrations-migrate
Testing with Database
Setup Test Database
make setup-test-db
Integration Test Pattern
final class CustomerRepositoryTest extends IntegrationTestCase
{
private CustomerRepositoryInterface $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->getContainer()->get(CustomerRepositoryInterface::class);
}
public function testSaveAndRetrieveCustomer(): void
{
$customer = new Customer();
$this->repository->save($customer);
$retrieved = $this->repository->findById($customer->getId());
$this->assertNotNull($retrieved);
}
}
Important: Always use Faker for unique test data.
Troubleshooting
Common Issues
Database Connection Errors:
docker compose ps mongodb
docker compose logs mongodb
Schema Sync Issues:
docker compose exec php bin/console doctrine:mongodb:schema:validate
docker compose exec php bin/console doctrine:mongodb:schema:update --force
Migration Conflicts:
docker compose exec php bin/console doctrine:migrations:status
docker compose exec php bin/console doctrine:migrations:migrate prev
See: reference/troubleshooting.md for comprehensive guide.