php-standard-library/psalm-plugin
Psalm plugin for PHP Standard Library (PSL) that improves type inference for PSL Type specifications (e.g., shape/optional), producing more precise array shapes and safer analysis. Install via Composer and enable with psalm-plugin.
composer require --dev php-standard-library/psalm-plugin
vendor/bin/psalm-plugin enable php-standard-library/psalm-plugin
psalm.config.php:
Ensure your config includes PSL types:
<?php
return [
'plugins' => [
'Psl\Psalm\Plugin',
],
'types' => [
__DIR__ . '/vendor/php-standard-library/psl/src/Type',
],
];
Use PSL’s Type\shape() to define strict input validation and let Psalm infer precise types:
use Psl\Type;
$spec = Type\shape([
'name' => Type\string(),
'age' => Type\int(),
]);
$data = $spec->coerce($_GET['user']);
// Psalm now knows `$data` is `array{name: string, age: int}` (not a generic array)
Request Validation:
Replace Laravel’s Validator with PSL shapes in controllers:
use Psl\Type;
$userShape = Type\shape([
'email' => Type\email_address(),
'password' => Type\string()->min_length(8),
]);
$validated = $userShape->coerce(request()->all());
// Psalm enforces types at dev time; runtime coercion handles edge cases.
Form Requests:
Extend Illuminate\Foundation\Http\FormRequest to integrate PSL:
public function rules(): array
{
$this->validatePsalmShape(); // Custom method to run PSL + Psalm checks.
return [];
}
protected function validatePsalmShape(): void
{
$shape = Type\shape([...]);
$shape->coerce($this->all());
}
Define API payloads as PSL shapes and enforce them in:
public function handle($request, Closure $next)
{
$shape = Type\shape(['token' => Type\string()->min_length(32)]);
$shape->coerce($request->all());
return $next($request);
}
public function processWebhook(array $payload): void
{
$webhookShape = Type\shape([
'event' => Type\literal('charge.succeeded'),
'data' => Type\shape([...]),
]);
$webhookShape->coerce($payload);
// Psalm guarantees `$payload` matches the shape.
}
Leverage PSL’s Iter functions with precise Psalm types:
use Psl\Iter;
$users = ['Alice', 'Bob'];
$firstName = Iter\first($users); // Psalm infers `string` (not `mixed` or `null`).
$count = Iter\count($users); // Psalm infers `positive-int`.
PSL’s Str functions now return type-accurate results:
use Psl\Str;
$email = 'user@example.com';
$localPart = Str\before($email, '@'); // Psalm infers `non-empty-string`.
Use PSL shapes in PHPUnit to validate test data:
public function testUserCreation()
{
$userShape = Type\shape([
'name' => Type\string(),
'email' => Type\email_address(),
]);
$userShape->coerce($this->userData); // Fails fast if data is invalid.
}
Psalm Version Mismatch:
composer.json:
"require-dev": {
"psalm/psalm": "^5.0",
"php-standard-library/psalm-plugin": "^2.1"
}
Overly Strict Types:
null inputs).@psalm-suppress sparingly or adjust shapes:
$shape = Type\shape(['name' => Type\optional(Type\string())]);
Performance in CI:
vendor/bin/psalm --init-cache
vendor/bin/psalm --cache-results
Enable Verbose Output:
vendor/bin/psalm --no-cache --output-format=json > psalm.json
Use jq to inspect errors:
jq '.errors[] | {file, message}' psalm.json
Isolate PSL Issues: Temporarily disable other Psalm plugins to test PSL-specific errors:
// psalm.config.php
return [
'plugins' => ['Psl\Psalm\Plugin'], // Only PSL plugin enabled.
];
Custom Return Type Providers: Extend the plugin by adding new return type logic for PSL functions:
// src/Psl/Psalm/Plugin/ReturnTypeProvider.php
public function getFunctionReturnType(
FunctionLikeInterface $functionLike,
ArrayType $argumentType,
string $callingCode
): ?Type {
if ($functionLike->getName() === 'Psl\Str\lowercase') {
return Type::string();
}
return null;
}
PSL Shape Validation in IDE:
Use psalm-plugin with PHPStorm’s Psalm integration for real-time feedback:
vendor/bin/psalm.Laravel Service Provider: Auto-load PSL shapes in a Laravel service provider:
public function boot(): void
{
Psalm::registerPlugin('Psl\Psalm\Plugin');
Psalm::addTypeLocation(__DIR__ . '/vendor/php-standard-library/psl/src/Type');
}
Prioritize High-Impact Areas: Start with PSL in:
Gradual Adoption:
Use @psalm-ignore for legacy code and migrate incrementally:
/** @psalm-ignore-next-line */
$oldCodeThatBreaksPsalm();
Combine with Laravel Valet:
Add Psalm to Valet’s after hook in ~/.valet/Laravel/valet.php:
$after[] = function () {
if (file_exists(__DIR__ . '/composer.json')) {
passthru('composer require --dev php-standard-library/psalm-plugin');
passthru('vendor/bin/psalm-plugin enable php-standard-library/psalm-plugin');
}
};
Document PSL Shapes: Use PHPDoc to annotate PSL shapes for better IDE support:
/**
* @psalm-type UserShape = array{
* id: positive-int,
* name: string,
* email: email-address,
* }
*/
$userShape = Type\shape([...]);
Leverage psalm-plugin CLI:
vendor/bin/psalm-plugin list
vendor/bin/psalm-plugin disable php-standard-library/psalm-plugin
How can I help you explore Laravel packages today?