Search by

esfahanahan / phpstan-rules

hosni

Custom PHPStan rules and extensions for EsfahanAhan.

Package info

github.com/esfahanahan/phpstan-rules

Homepage

Type:phpstan-extension

pkg:composer/esfahanahan/phpstan-rules

Statistics

Installs: 80

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.1.0 2026-10-17 09:58 UTC

This package is auto-updated.

Last update: 2026-10-17 10:00:17 UTC


README

Latest Version on Packagist Tests Total Downloads PHP Version License

Written by Hossein Hosni.

Usage docs: https://esfahanahan.github.io/phpstan-rules

Requirements

The nested eager-load rule inspects Eloquent with() call sites. The package does not depend on illuminate/*.

Installation

composer require --dev esfahanahan/phpstan-rules

The dev keyword in composer.json makes Composer offer to install this package into require-dev when composer require is run without --dev.

With phpstan/extension-installer, that is enough. The installer loads extension.neon.

Allow the plugin once:

{
    "config": {
        "allow-plugins": {
            "phpstan/extension-installer": true
        }
    }
}

Without the installer, include the extension yourself:

includes:
    - vendor/esfahanahan/phpstan-rules/extension.neon

extension.neon is the file to export. This package will hold rules and other PHPStan extensions (type, reflection, and similar). rules.neon only fits a package that is a ruleset and nothing else.

Rules

Nested eager-load with() must not drop a parent callback

Identifier: esfahanahan.nestedEagerLoadOverwritesConstraint

A later with() for a child relation replaces the parent callback registered by an earlier with().

User has many Posts, and Post has many Comments. Users 1 and 2 exist. User 1 has published post 10 and unpublished post 12. User 2 has published post 11. Splitting the constraints across two with() calls drops where('published', true):

$users = User::query()
    ->with([
        'posts' => static function (Relation $query): void {
            $query->where('published', true);
        },
    ])
    ->with([
        'posts.comments' => static function (Relation $query): void {
            $query->where('approved', true);
        },
    ])
    ->get();

Query log:

select * from `users`
bindings: []

select * from `posts` where `posts`.`user_id` in (?, ?)
bindings: [1, 2]

select * from `comments` where `comments`.`post_id` in (?, ?, ?) and `approved` = ?
bindings: [10, 11, 12, 1]

published is missing from the posts query, so unpublished post 12 is loaded.

Pass both relations in one with() array:

$users = User::query()
    ->with([
        'posts' => static function (Relation $query): void {
            $query->where('published', true);
        },
        'posts.comments' => static function (Relation $query): void {
            $query->where('approved', true);
        },
    ])
    ->get();

Query log:

select * from `users`
bindings: []

select * from `posts` where `posts`.`user_id` in (?, ?) and `published` = ?
bindings: [1, 2, 1]

select * from `comments` where `comments`.`post_id` in (?, ?) and `approved` = ?
bindings: [10, 11, 1]

Both constraints run. Comments load only for published posts 10 and 11. The usage doc shows the safe order when a child callback calls a model scope.

load() has the same result. Each call runs immediately, and posts.comments reloads posts with an empty callback, so the published posts are replaced:

$users->load([
    'posts' => static function (Relation $query): void {
        $query->where('published', true);
    },
]);

$users->load([
    'posts.comments' => static function (Relation $query): void {
        $query->where('approved', true);
    },
]);

Pass both relations in one load() array, or call loadMissing() for posts.comments when posts is already loaded. loadMissing() does not reload posts.

Laravel Boost

The package ships a skill at resources/boost/skills/eloquent-nested-eager-loads. Boost discovers that directory on a direct dependency. Add esfahanahan/phpstan-rules to boost.json packages, keep eloquent-nested-eager-loads in skills, then run Boost's update.

Credit

Written by Hossein Hosni.

Development

composer install
composer test
composer phpstan
composer format