Skip to content

v1.12.0

Latest

Choose a tag to compare

@calebdw calebdw released this 03 Nov 15:11
8b55dd0

This release adds custom pivot accessor inference, isolates config-file analysis from the calling code, and fixes several collection, validation, and array inference edge cases.

Requires PHPStan 2.3.5 or newer. PHP and Laravel requirements are unchanged.

Custom pivot accessors

BelongsToMany and MorphToMany results now respect the accessor name set with as(), rather than always describing the pivot property as pivot.

/** @return BelongsToMany<Podcast, $this, Subscription, 'subscription'> */
public function podcasts(): BelongsToMany
{
    return $this->belongsToMany(Podcast::class)
        ->using(Subscription::class)
        ->as('subscription');
}

Both $member->podcasts()->firstOrFail()->subscription and $member->podcasts->firstOrFail()->subscription are inferred as Subscription.

Accessor and pivot types are preserved through collection results, custom model collections, pagination, lazy results, and relation callbacks—including union and template model types. An unknown accessor or a union of accessor names does not promise a single pivot property.

More precise get() results

Many-to-many get() results now retain their pivot information too. This can expose collection-invariance errors in existing annotations. For example, a returned collection containing Podcast&object{subscription: Subscription} is not interchangeable with a declaration of Collection<int, Podcast>.

Where a declaration receives these pivot-aware results, include the pivot shape:

/** @return Collection<int, Podcast&object{subscription: Subscription}> */

This inference is enabled by default; there is no feature toggle. Laravel's runtime behavior is unchanged.

Config-file inference

Statically parsed config values are now analyzed in the config file's own scope, not the scope of whichever file calls config().

  • Respect local assignments, imports, and magic constants in the config file.
  • Ignore returns inside closures, functions, and classes when locating the config's return statement.
  • Decline ambiguous files rather than selecting the first of several returns.
  • Guard recursive config reads and treat unresolved values as unknown.
  • Preserve transitive config dependencies when serving cached inference results, so changes to referenced config files invalidate affected analysis.

Other inference fixes

  • FormRequest::safe() preserves alternative key lists instead of combining them into one required shape. Empty and optional key lists are supported as well.
  • Custom collection intersections resolve model types consistently in both the collection and its accompanying generic interfaces.
  • Arr::get() and Arr::pull() no longer treat string offsets as accessible arrays, respect overridden methods, and handle union inputs independently.
  • Arr::pull() correctly removes numeric-string keys and preserves both possible removal paths when an optional literal dotted key can shadow a nested path.

PHPStan attribute registration

Extension services and rules now use PHPStan's released attribute-based registration API. Conditional rules and services requiring multiple registrations retain their existing behavior.

No configuration migration is needed for applications: continue including extension.neon as before.

Full Changelog: v1.11.1...v1.12.0