This document provides guidelines for AI agents working on the API Platform core codebase.
Unless explicitly asked otherwise:
- Focus on Test Writing: Your main responsibility is to write functional tests for new features or bug fixes.
- No Bug Fixing: Do not attempt to fix bugs; only write tests to expose them or verify fixes.
- Project Context: API Platform is a PHP framework supporting both Symfony and Laravel. The user will specify which framework (defaults to Symfony).
- Fixture Handling: Avoid altering existing fixtures to prevent unintended side effects on other tests. Create new entities/DTOs/models with unique names. Business logic is secondary; focus on framework testing.
- No Test Execution by Default: Do not run tests unless explicitly asked (to save context, as tests produce verbose output).
- No Git commit by default: Do not commit unless explicitly asked
This code is organized through components, when you need to run a component's specific test you need to link it using:
cd src/Laravel && composer link ../../Before running php-cs-fixer or phpstan it may be a good idea to remove the components deps:
find src -name vendor -exec rm -r {} \;Laravel's phpstan must run from src/Laravel directory.
- Full Behat suite takes ~10 minutes (30+ minutes without
--format=progress) - Full PHPUnit suite is also very slow
- Best Practice: Identify failing tests from GitHub CI, then run those specific tests locally
Debugging Workflow:
- Check GitHub CI to identify which specific test failed
- Run that exact test locally with verbose output for details
- Fix and verify only that test before pushing
Prerequisites:
- Clear cache when changing branches, dependencies, or
USE_SYMFONY_LISTENERS:rm -rf tests/Fixtures/app/var/cache/test - Optionally warm cache:
tests/Fixtures/app/console cache:warmup - For MongoDB tests: Set
APP_ENV=mongodband install MongoDB ODM:composer require --dev doctrine/mongodb-odm-bundle - MongoDB extension required: Ensure
mongodbPHP extension is installed
PHPUnit (Main Suite):
# ALWAYS use filtering - never run all tests unnecessarily!
vendor/bin/phpunit --filter testMethodName
# Filter by test class
vendor/bin/phpunit --filter MyTestClass
# Filter by path (recommended)
vendor/bin/phpunit tests/Functional/SomeTest.php
# Only if specifically needed (slow!)
vendor/bin/phpunitPHPUnit (Component-Specific):
First go to the component's directory then run dependency linking:
cd src/Metadata
composer link ../../Then you can run the component's test:
cd src/Metadata
./vendor/bin/phpunitOr from the main directory:
# Run tests for a specific component
composer {component-name} test
# Examples:
composer api-platform/doctrine-orm test
composer api-platform/graphql test
composer api-platform/metadata testBehat (Functional Tests):
# ⚠️ IMPORTANT: Always use --format=progress unless debugging!
# Without it, tests take 3x longer (30min vs 10min)
# Run specific scenario by line number (RECOMMENDED for debugging)
vendor/bin/behat features/main/crud.feature:120 -vvv
# Run specific feature file with progress (for CI-like run)
vendor/bin/behat features/main/crud.feature --format=progress
# Debug specific test (verbose, NO progress format)
vendor/bin/behat features/main/crud.feature:120 -vvv
# Filter by tags
vendor/bin/behat --tags=@pagination --format=progress
# With specific profile
vendor/bin/behat --profile=default --format=progress
vendor/bin/behat --profile=postgres --format=progress
vendor/bin/behat --profile=default --tags '~@!mysql' --format=progress
# Only run full suite if absolutely necessary (10+ minutes)
vendor/bin/behat --format=progressMongoDB Tests:
# Set MongoDB environment
export APP_ENV=mongodb
export MONGODB_URL=mongodb://localhost:27017
# Install MongoDB ODM
composer require --dev doctrine/mongodb-odm-bundle
# Clear cache (always required when changing APP_ENV)
rm -rf tests/Fixtures/app/var/cache/test
# Run PHPUnit tests (exclude ORM tests)
vendor/bin/phpunit --exclude-group=orm
# Run Behat tests with MongoDB profile
vendor/bin/behat --profile=mongodb-coverage --format=progressStatic Analysis:
# Clean up components dependencies (or else it'll try to load vendor directories and run endlessly)
find src -name vendor -exec rm -r {} \;
# Always run PHPStan to prevent trivial bugs
# CRITICAL: PHPStan requires MongoDB extension AND MongoDB ODM bundle
# Install MongoDB PHP extension first (e.g., pecl install mongodb)
# Then install MongoDB ODM:
composer require --dev doctrine/mongodb-odm-bundle
vendor/bin/phpstan analyse --no-interaction --no-progress
# PHPStan will fail without both:
# - mongodb PHP extension (for analyzing Document fixtures)
# - doctrine/mongodb-odm-bundle package (for ODM classes)Testing Event Listeners (vs Default Event System):
API Platform has two modes for handling Symfony events:
- Default Mode (Event System): Uses Symfony's event system with event subscribers
- Event Listeners Mode: Uses traditional Symfony event listeners (enabled with
USE_SYMFONY_LISTENERS=1)
When to test with event listeners:
- Set
USE_SYMFONY_LISTENERS=1environment variable - Always clear cache after switching modes:
rm -rf tests/Fixtures/app/var/cache/test - CI runs separate jobs for both modes to ensure compatibility
Special Note - Events vs Non-Events:
The event listeners mode (USE_SYMFONY_LISTENERS=1) changes how API Platform hooks into Symfony's lifecycle:
- Non-events (default): Uses event subscribers for better performance and flexibility
- Events (listeners): Uses traditional event listeners for backward compatibility
- Both modes must be tested to ensure feature compatibility
- When debugging event-related issues, test both modes
Setup:
cd src/Laravel
composer link ../../
composer run-script buildPHPUnit:
# Run Laravel tests
cd src/Laravel
vendor/bin/phpunit
# Or via composer script
composer run-script test
# Filter tests
vendor/bin/phpunit --filter testMethodNameStatic Analysis:
cd src/Laravel
./vendor/bin/phpstan analyse --no-interaction --no-progressAPI Platform is a powerful, extensible, open-source PHP framework for building API-first projects. It leverages Symfony and Laravel to provide a robust foundation for creating REST and GraphQL APIs.
- Language: PHP (with strict types:
declare(strict_types=1);) - Frameworks: Symfony, Laravel
- Code Quality: PSR-12 standard, enforced via
php-cs-fixerandphpstan - Testing: PHPUnit (functional and unit tests), Behat (legacy functional tests - do not add new Behat tests)
The codebase is organized into components supporting both Symfony and Laravel:
Core Components (in src/):
Doctrine/Common,Doctrine/Odm,Doctrine/Orm- Doctrine integrationsDocumentation- API documentation generationElasticsearch- Elasticsearch integrationGraphql- GraphQL supportHttpCache- HTTP cachingHydra- Hydra JSON-LD vocabularyJsonApi- JSON:API format supportHal- HAL format supportJsonSchema- JSON Schema generationJsonld- JSON-LD supportLaravel- Laravel-specific implementationMetadata- Resource metadata handlingOpenapi- OpenAPI specification generationParameterValidator- Query parameter validationRamseyUuid- UUID supportSerializer- Serialization layerState- State management (providers/processors)Symfony- Symfony-specific implementationValidator- Validation support
- Fixtures:
- API Resources:
tests/Fixtures/TestBundle/ApiResource/ - Entities:
tests/Fixtures/TestBundle/Entity/ - Documents (MongoDB):
tests/Fixtures/TestBundle/Document/(mirror of Entity/)
- API Resources:
- Tests:
- Functional:
tests/Functional/ - Unit: Component-specific
src/{Component}/Tests/ - Behat features:
features/(legacy - do not add new ones)
- Functional:
- Test App:
tests/Fixtures/app/(Symfony test application)
Fixture Examples:
-
ApiResource with Static Provider (No Entity) -
tests/Fixtures/TestBundle/ApiResource/Product.php#[Get(provider: [self::class, 'provide'])] class Product { #[ApiProperty(identifier: true)] public string $code; // Clever: Use a static method as provider to avoid entity persistence public static function provide() { $s = new self(); $s->code = 'test'; return $s; } }
-
Entity with QueryParameter Filters -
tests/Fixtures/TestBundle/Entity/Chicken.php#[ORM\Entity] #[GetCollection( parameters: [ 'chickenCoop' => new QueryParameter(filter: new IriFilter()), 'chickenCoopId' => new QueryParameter(filter: new ExactFilter(), property: 'chickenCoop'), 'name' => new QueryParameter(filter: new ExactFilter()), 'namePartial' => new QueryParameter( filter: new PartialSearchFilter(), property: 'name', ), 'autocomplete' => new QueryParameter( filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())), properties: ['name', 'ean'] ), 'q' => new QueryParameter( filter: new FreeTextQueryFilter(new PartialSearchFilter()), properties: ['name', 'ean'] ), ], )] class Chicken { #[ORM\Id] #[ORM\GeneratedValue] #[ORM\Column(type: 'integer')] private ?int $id = null; #[ORM\Column(type: 'string', length: 255)] private string $name; // ... getters/setters }
Best Practices:
- Use static provider method pattern when you don't need database persistence
- Use
Chicken.phpas reference for testing query parameters and filters on entities - Be smart: create new fixtures rather than modifying existing ones, adding a query parameter to chicken is perfectly fine as long as it doesn't alter the rest
- Base Directory:
src/Laravel/ - Tests:
src/Laravel/Tests/- Example tests:
EloquentTest.php,JsonLdTest.php
- Example tests:
- Fixtures/Models:
src/Laravel/workbench/app/- DTOs as ApiResource:
workbench/app/ApiResource/ - Eloquent Models:
workbench/app/Models/(declared as resources with attributes)
- DTOs as ApiResource:
- PHPUnit Binary:
src/Laravel/vendor/bin/phpunit
Critical: Adherence to established code style is mandatory.
- Standard: PSR-12 (enforced via
.php-cs-fixer.dist.php) - Strict Types: Always use
declare(strict_types=1);at the top of PHP files - Type Hints: Provide type hints for all arguments and return types
- Imports: Use
usestatements for all classes- Group by type: classes, functions, constants
- Sort alphabetically within groups
- Naming Conventions:
- Classes:
PascalCase - Methods:
camelCase - Variables:
camelCase - Constants:
UPPER_SNAKE_CASE
- Classes:
- Visibility: Always explicitly declare
public,protected, orprivate - Error Handling: Use exceptions, not error codes
@Symfony,@Symfony:risky- Comprehensive Symfony coding standards@DoctrineAnnotation- Consistent Doctrine annotation formattingstrict_param- Strict type declarations for parametersfully_qualified_strict_types- Fully qualified class names in type declarationsheader_comment- Correct license header formattingordered_imports- Alphabetically sorted imports by typeno_superfluous_phpdoc_tags- Remove redundant PHPDoc tagsphpdoc_order,phpdoc_trim_consecutive_blank_line_separation- Consistent PHPDoc formatting
These commands run in CI - understand them but don't execute unless asked:
# Code style linting
vendor/bin/php-cs-fixer fix --dry-run --diff
# Static analysis
vendor/bin/phpstan analyse
# Component dependency check
composer check-dependencies
# Container linting (Symfony)
tests/Fixtures/app/console lint:container- New Features & Deprecations: Target
mainbranch - Bug Fixes: Target current stable branch (e.g.,
4.x) - Legacy Code Removal: Target
mainbranch
- Always Add Tests: PHPUnit functional tests are preferred
- No New Behat Tests: Use PHPUnit instead (Behat is legacy)
- Tests Must Pass: Ensure green CI before submitting
- Don't Modify Existing Tests: Create new fixtures/resources instead
- Test Location:
- Symfony functional:
tests/Functional/ - Laravel functional:
src/Laravel/Tests/ - Component unit:
src/{Component}/Tests/
- Symfony functional:
- Never Break BC: Backward compatibility is sacred (see https://symfony.com/bc)
- Update CHANGELOG: Document changes in
CHANGELOG.md - Documentation PR: Required for new features (
api-platform/docsrepository) - Code Style: Ensure
php-cs-fixerandphpstanpass
Follow Conventional Commits:
- Types:
fix,feat,docs,spec,test,perf,ci,chore - Format:
type(scope): description - Examples:
fix(metadata): resource identifiers from propertiesfeat(validation): introduce a number constrainttest(graphql): add mutation validation tests
- Scope: Strongly recommended (component name)
- Note: Only first commit needs conventional format (others are squashed)
| Q | A
| ------------- | ---
| Branch? | main for features / 4.x for bug fixes
| Tickets | Closes #...
| License | MIT
| Doc PR | api-platform/docs#... (for features)Checklist:
- Tests added and passing
- No BC breaks
- CHANGELOG.md updated
- Documentation PR submitted (if feature)
- Conventional commit format used
- Code style passes (
php-cs-fixer,phpstan)
