This file provides guidance for AI agents working on the phpstan/phpstan-doctrine repository.
phpstan-doctrine is a PHPStan extension that provides static analysis capabilities for Doctrine ORM, DBAL, and ODM. It adds type inference, DQL validation, QueryBuilder analysis, and custom rules that detect Doctrine-specific bugs at analysis time without running the application.
Key features:
- DQL query validation (parse errors, unknown entities, unknown fields)
- QueryBuilder type inference and validation
- Magic repository method recognition (
findBy*,findOneBy*,countBy*) - Entity column/relation type checking against property types
- Return type inference for
EntityManager::getRepository(),find(),getReference() - Query result type inference for
getResult(),getSingleResult(), etc. - Doctrine ODM support
- Database driver-aware type resolution for expressions like
SUM(),AVG()
src/ # Extension source code (PSR-4: PHPStan\)
├── Classes/ # Forbidden class name extensions (proxy detection)
├── Doctrine/ # Core Doctrine integration (driver detection, metadata loading)
├── PhpDoc/ # PHPDoc type node resolver extensions
├── Reflection/ # Class reflection extensions (repository methods, selectable)
├── Rules/ # Custom PHPStan rules (entity validation, DQL checks)
│ ├── Doctrine/ORM/ # ORM-specific rules
│ └── Gedmo/ # Gedmo doctrine-extensions support
├── Stubs/ # Stub file loader
└── Type/ # Type inference extensions
└── Doctrine/
├── Collection/ # Collection type narrowing
├── DBAL/ # DBAL QueryBuilder and Result types
├── Descriptors/# Doctrine type → PHPStan type mappings (28 descriptors)
├── Query/ # DQL Query result type walker and inference
└── QueryBuilder/ # ORM QueryBuilder type tracking
tests/ # Test suite
├── DoctrineIntegration/# Integration tests (ORM, ODM, Persistence)
├── Platform/ # Database platform tests (MySQL, PostgreSQL, SQLite)
├── Reflection/ # Reflection extension tests
├── Rules/ # Rule tests (entity validation, dead code, properties)
└── Type/ # Type inference tests
stubs/ # PHPStan stub files for Doctrine classes
├── Collections/ # Collection and Selectable stubs
├── DBAL/ # DBAL types, cache, exception stubs
├── ORM/ # ORM Query, QueryBuilder, Mapping stubs
├── Persistence/ # Persistence layer stubs
└── runtime/Enum/ # PHP 8.1 enum polyfill stubs
compatibility/ # Compatibility layer for multiple Doctrine versions
├── patches/ # Composer patches for ORM v3 attribute support
├── AnnotationDriver.php # ORM v2 fallback
├── ArrayType.php # DBAL v3 fallback
└── orm-3-baseline.php # Dynamic baseline selection by ORM version
This repository supports PHP 7.4+. All source code in src/ and tests/ must be compatible with PHP 7.4. Do not use language features from PHP 8.0+ (named arguments, union types in signatures, match expressions, etc.) in the main source code.
Some test data files under tests/*/data-php-* and tests/*/data-attributes target specific PHP versions and are excluded from lint/analysis on older versions.
The extension supports multiple major versions of Doctrine libraries simultaneously:
- Doctrine ORM: 2.x and 3.x
- Doctrine DBAL: 3.x and 4.x
- Doctrine ODM: 2.4+
- Doctrine Persistence: 2.x and 3.x
This is achieved through:
- The
compatibility/directory providing fallback classes for missing APIs - Composer patches in
compatibility/patches/for ORM v3 attribute support - Version-specific PHPStan baselines (
phpstan-baseline-orm-2.neon,phpstan-baseline-orm-3.neon,phpstan-baseline-dbal-3.neon,phpstan-baseline-dbal-4.neon) - Dynamic baseline selection in
compatibility/orm-3-baseline.php method_exists()checks in source code for version-dependent API calls- CI matrix testing across version combinations
extension.neon— Main extension services: type descriptors, dynamic return type extensions, reflection extensions, PHPDoc resolvers. Loaded automatically via phpstan/extension-installer.rules.neon— Custom validation rules (DQL, entity columns, relations, mapping). Optionally included by users who provide anobjectManagerLoader.phpstan.neon— Self-analysis configuration (level 8, includes all baselines and strict rules).phpunit.xml— Test configuration. Theplatformtest group is excluded by default and runs separately in CI.
All commands are defined in the Makefile:
make check # Run all checks (lint, cs, tests, phpstan)
make tests # Run PHPUnit tests
make lint # Run parallel-lint on src/ and tests/
make cs # Run coding standard checks (requires make cs-install first)
make cs-fix # Auto-fix coding standard violations
make cs-install # Clone phpstan/build-cs repository
make phpstan # Run PHPStan self-analysis
make phpstan-generate-baseline # Regenerate the PHPStan baselinecomposer install
make tests # Runs: php vendor/bin/phpunitPlatform tests (requiring database containers) are in the platform group and excluded by default. They run in CI via .github/workflows/platform-test.yml with MySQL and PostgreSQL services.
The project uses the phpstan/build-cs coding standard (branch 2.x):
make cs-install # Clone the build-cs repo
make cs # Check coding standards
make cs-fix # Auto-fix violationsKey style rules:
- Tab indentation for PHP, XML, and NEON files
- Space indentation (2 spaces) for YAML files
- LF line endings, UTF-8 encoding
- Trailing whitespace trimmed, final newline required
The CI runs on every pull request and push to 2.0.x:
- Lint —
parallel-linton PHP 7.4–8.4 - Coding Standard — phpcs with build-cs rules
- Tests — PHPUnit on PHP 7.4–8.4, both lowest and highest dependencies, plus a special matrix entry with ORM 3.x + DBAL 4.x
- Static Analysis — PHPStan at level 8 on PHP 7.4–8.4, plus ORM 3.x + DBAL 4.x variant
- Mutation Testing — Infection on PHP 8.2–8.4, requires 100% MSI on changed lines
- Platform Tests — PHPUnit
platformgroup against real MySQL and PostgreSQL databases
Type descriptors (src/Type/Doctrine/Descriptors/) map Doctrine DBAL column types to PHPStan types. Each descriptor implements DoctrineTypeDescriptor with:
getType()— Returns the Doctrine type class namegetWritableToPropertyType()— The PHP type Doctrine writes to entity propertiesgetWritableToDatabaseType()— The PHP type that can be written to the database
Descriptors are registered as services tagged with phpstan.doctrine.typeDescriptor in extension.neon.
QueryResultTypeWalker (src/Type/Doctrine/Query/) walks the DQL AST to infer result types. It handles SELECT expressions, JOINs, aggregations, NEW expressions, and INDEX BY. It is driver-aware — result types for expressions like SUM() or AVG() depend on the database driver (MySQL vs PostgreSQL vs SQLite).
The extension tracks QueryBuilder method calls through QueryBuilderType, carrying DQL parts as PHPStan type metadata. When getQuery() is called, it reconstructs the DQL and runs it through the type walker.
Rules in src/Rules/Doctrine/ORM/ validate entity mappings at analysis time:
EntityColumnRule— Column types match property typesEntityRelationRule— Relation configurations are validEntityNotFinalRule— Entities aren'tfinal(breaks proxy generation)DqlRule/QueryBuilderDqlRule— DQL syntax and semantic validationEntityMappingExceptionRule— Catches mapping configuration errors
Stub files in stubs/ provide PHPStan with type information for Doctrine classes, adding generics (@template T of object) and narrowed return types that the original Doctrine source doesn't express. These are loaded via StubFilesExtensionLoader.
2.0.x— Development branch (default for PRs)main— Main/release branch