v1.1.0
PHPStan Laravel 1.1.0
PHPStan Laravel 1.1.0 improves array-shape and model-property inference, fixes several schema helper types, and understands Laravel's conventionally static utility macros.
No configuration changes are required when upgrading.
Precise Arr::only() shapes
Arr::only() now preserves and narrows array shapes instead of returning a generic array:
/** @var array{id: int, name: string, email: string} $row */
Arr::only($row, ['id', 'name']);
// array{id: int, name: string}
Arr::only($row, 'id');
// array{id: int}
Arr::only($row, ['id', 'missing']);
// array{id: int}Unknown keys preserve the original shape with optional entries, since Arr::only() can remove values but cannot add them:
Arr::only($row, $keys);
// array{id?: int, name?: string, email?: string}Generic maps retain their value type while the requested keys are narrowed:
/** @var array<string, int> $counts */
Arr::only($counts, ['open', 'closed']);
// array<'open'|'closed', int>The inference follows Laravel's runtime behavior, including optional and integer-like keys. Arr::only() uses top-level keys and does not support dot notation.
See the Arr::only() guide for more examples.
The model-property guide now also documents the existing shape inference for Model::only(), including columns, casts, accessors, missing attributes, and dotted keys:
$user->only(['name', 'blocked']);
// array{name: string, blocked: bool}
$user->only(['name', 'missing']);
// array{name: string, missing: null}Static utility macros
Laravel utility classes expose a conventionally static API even when a macro is registered with a normal closure. These calls are now understood without suppressing method.staticCall:
Str::macro('initials', function (string $name): string {
return collect(explode(' ', $name))
->map(fn (string $part): string => $part[0])
->join('');
});
Str::initials('Taylor Otwell'); // stringStatic-facing macros are enabled by default for:
Illuminate\Support\ArrIlluminate\Support\StrIlluminate\Support\NumberIlluminate\Support\BenchmarkIlluminate\Validation\Rule
The exception applies only to dynamically discovered macros. Native instance methods still receive PHPStan's normal static-call checks, and macro closure parameters and return types remain available to analysis.
Projects can add their own static-facing macro classes with staticMacroClasses. Entries apply to subclasses, so Eloquent's static forwarding can be enabled for every model when that matches the project's conventions:
parameters:
laravel:
staticMacroClasses:
- Illuminate\Database\Eloquent\ModelUse staticMacroClasses! to replace the defaults instead of extending them:
parameters:
laravel:
staticMacroClasses!: []See the new macros guide for discovery rules, static and instance macros, facades, and configuration.
Fixes #2.
Correct schema helper types
Migration-based model-property inference now matches Laravel's schema helpers more closely:
Schema::table('users', function (Blueprint $table): void {
$table->rememberToken();
$table->year('birth_year');
$table->year('graduation_year')->nullable();
$table->timestampTz('published_at');
$table->timeTz('opens_at');
$table->softDeletesDatetime('deleted_at');
$table->softDeletesTz('deleted_at_tz');
});The resulting model properties are inferred as:
$user->remember_token; // string|null
$user->birth_year; // int
$user->graduation_year; // int|null
$user->published_at; // string
$user->opens_at; // string
$user->deleted_at; // string|null
$user->deleted_at_tz; // string|nullThis release also correctly recognizes spatialIndex() as an index operation rather than a column declaration.
Fixes #4.
Collection variance guidance
New troubleshooting and FAQ documentation explains PHPStan errors where the expected and returned collection types appear identical, followed by a TValue is not covariant tip.
Laravel collections are mutable, so their value type remains invariant. Use call-site covariance when a method exposes a read-only projection of a wider type:
/** @return Collection<int, covariant string|null> */
function names(): Collection
{
return User::query()->pluck('name');
}This widens the public return contract without making every mutable Collection globally covariant. Unsafe writes through the projected type remain rejected by PHPStan.
See the collection troubleshooting guide for details.
Addresses #1.
Documentation
- Added a complete guide to macro discovery, call forms, facades, and
staticMacroClasses. - Added detailed
Arr::only()andModel::only()inference examples. - Added collection variance troubleshooting and FAQ entries.
- Added a PHP compatibility badge to the README.
Upgrade
composer require --dev calebdw/phpstan-laravel:^1.1This release has no intended breaking configuration changes. More precise inferred types may reveal previously hidden type mismatches or cause obsolete baseline entries to become unmatched.
Thanks to Sander Muller for the detailed reports and reproductions behind #1, #2, and #4.
Full changelog: v1.0.1...v1.1.0