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

Psalm Plugin Laravel Package

php-standard-library/psalm-plugin

Psalm plugin for PHP Standard Library (PSL) that improves type inference for PSL Type\specifications. Enables more precise array/shape types (e.g., Type\shape coercions) so Psalm reports correct, specific types during static analysis.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Install the plugin in your Laravel project:
    composer require --dev php-standard-library/psalm-plugin
    
  2. Enable the plugin for your Psalm config:
    php vendor/bin/psalm-plugin enable php-standard-library/psalm-plugin
    
  3. Update psalm-plugin config (psalm-plugin.json):
    {
      "plugins": ["php-standard-library/psalm-plugin"]
    }
    

First Use Case: Type-Safe Shapes

Replace generic array coercion with PSL shapes:

use Psl\Type;

$shape = Type\shape([
  'name' => Type\string(),
  'age'  => Type\int(),
]);

$validated = $shape->coerce($_GET['user']);
// Psalm now infers: array{name: string, age: int} (not generic array)

Where to Look First

  • Psalm config: Ensure psalm-plugin is enabled and the plugin is listed.
  • PSL docs: PHP Standard Library for shape/validation syntax.
  • Psalm error output: Focus on psalm:suppress for false positives until the plugin’s type inference matures.

Implementation Patterns

Workflow: PSL + Psalm in Laravel

  1. Define shapes in a Types trait/class:
    trait UserShape
    {
        public function shape(): Type\TypeInterface
        {
            return Type\shape([
                'id'    => Type\int(),
                'email' => Type\email(),
                'roles' => Type\optional(Type\list_of(Type\string())),
            ]);
        }
    }
    
  2. Use in FormRequests:
    public function rules(): array
    {
        $shape = (new UserShape())->shape();
        $validated = $shape->coerce($this->all());
    
        // Psalm now knows $validated is array{id: int, email: string, roles?: list<string>}
        return [
            'email' => ['required', 'email'],
            // ... other rules
        ];
    }
    
  3. Service layer validation:
    public function createUser(array $data): User
    {
        $shape = (new UserShape())->shape();
        $validated = $shape->coerce($data); // Psalm infers exact shape
    
        return User::create([
            'email' => $validated['email'],
            // ... other fields
        ]);
    }
    

Integration Tips

  • Psalm config tweaks: Add to psalm.xml:
    <file-list>
        <dir>app/Shapes</dir> <!-- PSL shape definitions -->
    </file-list>
    
  • CI pipeline: Run Psalm with PSL plugin in GitHub Actions:
    - name: Psalm + PSL Plugin
      run: vendor/bin/psalm --init --plugins
    
  • IDE support: Configure PHPStorm to use Psalm’s type inference for PSL shapes.

Common Patterns

Pattern PSL + Psalm Example Benefit
Input validation Type\shape()->coerce($request->all()) Eliminates runtime Validator for simple cases.
API contracts Type\shape() for DTOs in app/Http/Resources Self-documenting API responses.
Eloquent casting protected $casts = ['metadata' => 'array']; // + PSL shape Type-safe JSON attributes.
Event payloads Type\shape() for Illuminate\Events\Dispatchable Guarantee event consistency.

Gotchas and Tips

Pitfalls

  1. False positives with coerce():

    • Psalm may still infer loose types if PSL’s runtime behavior diverges. Suppress with:
      /** @psalm-suppress MixedAssignment */
      $validated = $shape->coerce($data);
      
    • Fix: Use @psalm-trace to debug inferred types:
      /** @psalm-trace $validated */
      
  2. Plugin compatibility:

    • Psalm 5.x: Requires plugin ^2.1. Downgrade if issues arise:
      composer require php-standard-library/psalm-plugin:^2.0
      
    • PSL 2.x: Some TList types may not be fully supported. Prefer list<T> or array<T>.
  3. Nested shapes:

    • Deeply nested Type\shape() may confuse Psalm. Flatten or use @var annotations:
      /** @var array{user: array{id: int, name: string}} */
      $data = $shape->coerce($input);
      

Debugging

  • Check plugin status:

    php vendor/bin/psalm-plugin list
    

    Ensure php-standard-library/psalm-plugin shows as "enabled."

  • Verbose Psalm output:

    php vendor/bin/psalm --no-cache --output-format=json | jq '.errors[] | select(.message | test("PSL"))'
    

Extension Points

  1. Custom return types: Extend the plugin by adding return type providers (see PSL docs). Example:

    // In a custom plugin class
    public function getReturnTypeProviders(): array
    {
        return [
            new ReturnTypeProvider(
                'Psl\MyNamespace\my_function',
                fn (TypeElement $type): ?TypeElement => Type\string()
            ),
        ];
    }
    
  2. Psalm config overrides: Disable plugin for specific files:

    <file-list exclude="tests/legacy-code.php">
        <!-- ... -->
    </file-list>
    
  3. Performance:

    • Cache PSL shapes in a static property to avoid re-parsing:
      private static ?Type\TypeInterface $userShape;
      
      public static function shape(): Type\TypeInterface
      {
          return self::$userShape ??= Type\shape([...]);
      }
      

Pro Tips

  • Leverage nullish(): Use Type\nullish() for optional fields with runtime null checks:

    $shape = Type\shape([
        'metadata' => Type\nullish(Type\array_of(Type\string())),
    ]);
    

    Psalm will infer array<string>|null.

  • Pipe operator support: Chain PSL functions with -> (PHP 8.1+):

    $result = Str\lowercase($_GET['name'])
        ->trim()
        ->coerce(Type\string());
    

    The plugin improves type inference for each step.

  • Test with psalm:fix: Run php vendor/bin/psalm --fix to auto-correct simple type issues after enabling the plugin.

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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport