Installation:
composer require --dev phpstan/phpstan-doctrine
Use phpstan/extension-installer for automatic configuration, or manually include:
includes:
- vendor/phpstan/phpstan-doctrine/extension.neon
First Use Case: Run PHPStan on a Doctrine-heavy project:
vendor/bin/phpstan analyse src
The extension will immediately validate:
findBy*, findOneBy*, countBy*).objectManagerLoader is configured).Repository Method Validation:
// Validated automatically
$user = $userRepository->findOneBy(['email' => 'test@example.com']);
PHPStan will infer $user as ?User (nullable) and validate field names (email).
QueryBuilder/DQL Type Inference:
$query = $entityManager->createQuery('SELECT u FROM App\Entity\User u');
$users = $query->getResult(); // array<User>
Requires objectManagerLoader in phpstan.neon:
parameters:
doctrine:
objectManagerLoader: tests/bootstrap.php
Custom Repository Base Class:
parameters:
doctrine:
ormRepositoryClass: App\Repository\CustomRepository
Extends validation to custom repository methods.
Dead Code Detection: Disable false positives for Doctrine-managed fields:
#[ORM\GeneratedValue]
private ?int $id = null; // Won't trigger "unused property" warnings
objectManagerLoader examples.Timestampable).Collection::first() return types when isEmpty() is checked:
if (!$collection->isEmpty()) {
$item = $collection->first(); // User (not User|false)
}
DQL Validation Limitations:
mixed (not statically analyzable).->select($dynamicField)) break type inference.parameters:
doctrine:
reportDynamicQueryBuilders: true
Custom Types:
parameters:
doctrine:
reportUnknownTypes: true
ReflectionDescriptor for types with proper typehints:
services:
- factory: PHPStan\Type\Doctrine\Descriptors\ReflectionDescriptor('App\Type\CustomType')
tags: [phpstan.doctrine.typeDescriptor]
Literal Strings:
literal-string for SQL parameters to prevent SQL injection:
parameters:
doctrine:
literalString: true
Connection::executeQuery():
// ❌ Risky
$sql = "SELECT * FROM users WHERE email = '$userInput'";
// ✅ Safe
$sql = "SELECT * FROM users WHERE email = ?";
Proxy Classes:
Proxy\__CG__\App\Entity\User) triggers warnings. Use interfaces or base classes instead.Final Classes/Constructors:
final entity classes/constructors may break proxy generation. Configure allowed cases:
parameters:
doctrine:
allowFinalEntityClasses: true
allowFinalConstructors: true
objectManagerLoader returns a valid EntityManager.TypedExpression for accurate type inference.levels:
- 5
parameters:
doctrine:
objectManagerLoader: null # Disable in CI
DoctrineExtension to add project-specific validations.vendor/phpstan/phpstan-doctrine/stubs/ for legacy Doctrine versions.parameters.doctrine to tweak behavior (e.g., allCollectionsSelectable for Collection::matching()).
```markdown
## Example Debugging Workflow
1. **Error**: `Call to undefined method App\Entity\UserRepository::customMethod()`.
- **Fix**: Add `ormRepositoryClass` to `phpstan.neon` pointing to the base class with `customMethod`.
2. **Error**: `Type error: Return type of App\Type\CustomType::convertToPHPValue() must be string|int, mixed given`.
- **Fix**: Implement a `DoctrineTypeDescriptor` or use `ReflectionDescriptor`.
3. **False Positive**: `Unused property $createdAt` (Gedmo field).
- **Fix**: Ensure `phpstan-doctrine` is included **after** `phpstan/extension-installer` in `includes`.
How can I help you explore Laravel packages today?