Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Psalm Plugin Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. 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).

  2. 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',
        ],
    ];
    
  3. 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',
        ],
    ];
    
  4. Run Psalm Execute Psalm with the plugin:

    ./vendor/bin/psalm --init  # Generate baseline
    ./vendor/bin/psalm --no-cache
    

First Use Case: Entity Typing

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.


Implementation Patterns

Workflow: Entity-Driven Development

  1. Define Entities Use annotations/attributes (e.g., @ORM\Entity) or PHP 8 attributes:

    #[ORM\Entity(repositoryClass: UserRepository::class)]
    class User { ... }
    
  2. 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`).
    
  3. Proxy Handling Avoid false positives for lazy-loaded properties:

    $user = $this->entityManager->find(User::class, 1);
    $user->getPosts(); // No "Possible null" error for proxies.
    
  4. 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>`.
    

Integration Tips

  • 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
    

Gotchas and Tips

Pitfalls

  1. Metadata Discovery Issues

    • Problem: Psalm may not detect entities if metadata is not properly loaded (e.g., missing orm.xml or annotations).
    • Fix: Ensure Doctrine’s metadata drivers are configured. For annotations, add:
      // psalm-doctrine-config.php
      'metadata_drivers' => [
          'annotation' => ['App\\Entities'],
      ],
      
  2. Proxy False Positives

    • Problem: Psalm might still warn about proxy-related null issues if the plugin isn’t configured to trust proxies.
    • Fix: Explicitly mark proxy classes as safe in psalm-doctrine-config.php:
      'proxy_namespace' => 'Proxy\\__CG\\\\App\\\\Entities',
      
  3. Dynamic Repository Methods

    • Problem: Custom repository methods with dynamic queries may still return mixed.
    • Fix: Use @psalm-suppress MixedReturnType sparingly or add return type hints:
      #[ReturnTypeWillChange]
      public function customFind(): array { ... }
      
  4. Laravel-Specific Quirks

    • Problem: Laravel’s service container might confuse Psalm’s type inference.
    • Fix: Use @var annotations for container-bound entities:
      /** @var UserRepository */
      $repo = app(UserRepository::class);
      

Debugging

  • 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.

Extension Points

  1. 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,
    ],
    
  2. 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) { ... }
    
  3. Ignore Specific Entities Exclude entities from analysis in psalm-doctrine-config.php:

    'ignored_entities' => [
        'App\\Entities\\LegacyUser',
    ],
    
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
datacore/hub-sdk
alengo/sulu-http-cache-bundle
develia/commons
cuci/prototurk-sdk
cuci/prototurk-sdk-symfony
develia/geo-bundle
dreamzy/livewire-charts
touchestate-sdk/php-sdk
22h/doctrine-garbage-collection-bundle
imbo/imbo-coding-standard
visualbuilder/filament-lottie
servicioslineaonce/starter-kit
atomcoder/laravel-reorderable
irajul/filament-shadcn-theme
agtp/agtp-php
agtp/mod-php
centraldesktop/protobuf-php
trappistes/laravel-custom-fields
splash/sonata-admin
splash/metadata