weirdan/doctrine-psalm-plugin
Psalm plugin for Doctrine ORM projects. Adds smarter type inference for EntityManager, repositories, proxies and collections, reducing false positives and improving static analysis of Doctrine entities and queries in PHP applications.
Install the Plugin Add the plugin to your project via Composer:
composer require --dev weirdan/doctrine-psalm-plugin
Ensure psalm is also installed (composer require --dev vimeo/psalm).
Configure Psalm
Update psalm.config.php to include the plugin:
return [
'plugins' => [
'DoctrinePsalmPlugin' => __DIR__ . '/vendor/weirdan/doctrine-psalm-plugin',
],
'additional_configs' => [
'src/' => __DIR__ . '/psalm-doctrine-config.php',
],
];
Create Doctrine-Specific Config
Generate a minimal psalm-doctrine-config.php:
return [
'entities' => [
'namespace' => 'App\\Entities',
'dir' => __DIR__ . '/../src/Entities',
],
'repositories' => [
'namespace' => 'App\\Repositories',
'dir' => __DIR__ . '/../src/Repositories',
],
];
Run Psalm Execute Psalm with the plugin:
./vendor/bin/psalm --init # Generate baseline
./vendor/bin/psalm --no-cache
Analyze a repository method returning an entity:
// Before: Psalm may warn about mixed return types
public function findByName(string $name): ?User { ... }
// After: Plugin infers correct return type
public function findByName(string $name): ?User { ... }
Psalm now understands User as a Doctrine entity and validates relationships.
Define Entities
Use annotations/attributes (e.g., @ORM\Entity) or PHP 8 attributes:
#[ORM\Entity(repositoryClass: UserRepository::class)]
class User { ... }
Leverage Repository Analysis
Psalm now understands repository methods like find(), findAll(), or custom queries:
$user = $this->entityManager->getRepository(User::class)->find(1);
// Psalm infers `$user` as `?User` (not `mixed`).
Proxy Handling Avoid false positives for lazy-loaded properties:
$user = $this->entityManager->find(User::class, 1);
$user->getPosts(); // No "Possible null" error for proxies.
Custom Query Validation Validate DQL/HQL queries with inferred return types:
$query = $this->entityManager->createQuery('SELECT u FROM User u WHERE u.active = :active');
$query->setParameter('active', true);
$users = $query->getResult(); // Psalm infers `array<User>`.
Symfony Dependency Injection:
Autowire EntityManagerInterface and let Psalm infer entity types:
public function __construct(private EntityManagerInterface $em) {}
Psalm will validate em->find(User::class, $id) as ?User.
Laravel Eloquent Hybrid Projects: If using both Doctrine and Eloquent, configure Psalm to ignore Eloquent models in the Doctrine plugin config to avoid conflicts.
CI Pipeline: Add Psalm to your CI (e.g., GitHub Actions) with the plugin:
- name: Run Psalm
run: ./vendor/bin/psalm --output-format=github
Metadata Discovery Issues
orm.xml or annotations).// psalm-doctrine-config.php
'metadata_drivers' => [
'annotation' => ['App\\Entities'],
],
Proxy False Positives
null issues if the plugin isn’t configured to trust proxies.psalm-doctrine-config.php:
'proxy_namespace' => 'Proxy\\__CG\\\\App\\\\Entities',
Dynamic Repository Methods
mixed.@psalm-suppress MixedReturnType sparingly or add return type hints:
#[ReturnTypeWillChange]
public function customFind(): array { ... }
Laravel-Specific Quirks
@var annotations for container-bound entities:
/** @var UserRepository */
$repo = app(UserRepository::class);
Enable Verbose Output:
Run Psalm with --verbose to debug plugin loading:
./vendor/bin/psalm --verbose
Check Plugin Logs:
Look for warnings in psalm.log (generated in project root) about missing entities or metadata.
Custom Metadata Drivers
Extend the plugin’s metadata handling by implementing DoctrineMetadataDriverInterface:
class CustomDriver implements DoctrineMetadataDriverInterface { ... }
Register it in psalm-doctrine-config.php:
'metadata_drivers' => [
'custom' => CustomDriver::class,
],
Override Type Inference
Use @psalm-param-type or @psalm-return-type to override plugin inferences:
/**
* @psalm-param int $id
* @psalm-return User|null
*/
public function findById(int $id) { ... }
Ignore Specific Entities
Exclude entities from analysis in psalm-doctrine-config.php:
'ignored_entities' => [
'App\\Entities\\LegacyUser',
],
How can I help you explore Laravel packages today?