Skip to content

v1.1.0

Choose a tag to compare

@calebdw calebdw released this 24 Aug 17:44
· 17 commits to master since this release
163298c

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'); // string

Static-facing macros are enabled by default for:

  • Illuminate\Support\Arr
  • Illuminate\Support\Str
  • Illuminate\Support\Number
  • Illuminate\Support\Benchmark
  • Illuminate\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\Model

Use 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|null

This 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() and Model::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.1

This 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