|
| 1 | +# PHPStan Laravel |
| 2 | + |
| 3 | +Help PHPStan understand Laravel magic. |
| 4 | + |
| 5 | +## Usage |
| 6 | + |
| 7 | +### Installation |
| 8 | +``` bash |
| 9 | +composer require --dev recoded-dev/phpstan-laravel |
| 10 | +``` |
| 11 | + |
| 12 | +You might get asked whether you want to trust the `phpstan/extension-installer` package: |
| 13 | +``` |
| 14 | +Do you trust "phpstan/extension-installer" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] |
| 15 | +``` |
| 16 | +This is safe to trust and is used to automatically include phpstan-laravel in PHPStan. |
| 17 | +If you don't want this you can always add the following snippet to you phpstan.neon(.dist) file: |
| 18 | +```neon |
| 19 | +includes: |
| 20 | + - vendor/recoded-dev/phpstan-laravel/extension.neon |
| 21 | +``` |
| 22 | + |
| 23 | +### Examples |
| 24 | +What does phpstan-laravel add? |
| 25 | + |
| 26 | +#### Scopes |
| 27 | +It will help understand scopes on builders. |
| 28 | +```php |
| 29 | +<?php |
| 30 | + |
| 31 | +use Illuminate\Database\Eloquent\Builder; |
| 32 | +use Illuminate\Database\Eloquent\Model; |
| 33 | + |
| 34 | +class Media extends Model |
| 35 | +{ |
| 36 | + /** |
| 37 | + * Query only images. |
| 38 | + * |
| 39 | + * @param \Illuminate\Database\Eloquent\Builder<static> $builder |
| 40 | + * @param bool $exceptWebp |
| 41 | + * @return void |
| 42 | + */ |
| 43 | + public function scopeImages(Builder $builder, bool $exceptWebp): void |
| 44 | + { |
| 45 | + $builder->where('mime_type', 'LIKE', 'image/%'); |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +Media::query()->images()->count(); |
| 50 | +``` |
| 51 | + |
| 52 | +Will report: |
| 53 | +``` |
| 54 | +Method Media::images() invoked with 0 parameters, 1 required. |
| 55 | +``` |
| 56 | + |
| 57 | +#### Relational queries |
| 58 | +It will help understand what (custom) builder type is given in callbacks of relational queries: |
| 59 | + |
| 60 | +```php |
| 61 | +<?php |
| 62 | + |
| 63 | +use Illuminate\Database\Eloquent\Builder; |
| 64 | +use Illuminate\Database\Eloquent\Model; |
| 65 | +use Illuminate\Database\Eloquent\Relations\BelongsTo; |
| 66 | + |
| 67 | +class Post extends Model |
| 68 | +{ |
| 69 | + /** |
| 70 | + * Query only thumbnail. |
| 71 | + * |
| 72 | + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo<Post, Media> |
| 73 | + */ |
| 74 | + public function thumbnail(): BelongsTo |
| 75 | + { |
| 76 | + return $this->belongsTo(Media::class); |
| 77 | + } |
| 78 | +} |
| 79 | + |
| 80 | +/** |
| 81 | + * @extends \Illuminate\Database\Eloquent\Builder<Media> |
| 82 | + */ |
| 83 | +class MediaBuilder extends Builder |
| 84 | +{ |
| 85 | + // Add custom methods/"scopes" here. |
| 86 | +} |
| 87 | + |
| 88 | +class Media extends Model |
| 89 | +{ |
| 90 | + public function newEloquentBuilder($query): MediaBuilder |
| 91 | + { |
| 92 | + return new MediaBuilder($query); |
| 93 | + } |
| 94 | +} |
| 95 | + |
| 96 | +Post::query() |
| 97 | + ->whereHas('thumbnail', function ($builder) { |
| 98 | + \PHPStan\dumpType($builder); |
| 99 | + }) |
| 100 | + ->count(); |
| 101 | +``` |
| 102 | + |
| 103 | +Will report: |
| 104 | +``` |
| 105 | +Dumped type: MediaBuilder |
| 106 | +``` |
| 107 | + |
| 108 | +And allows you to call methods on your custom builder. |
| 109 | + |
| 110 | +#### Dependency resolving |
| 111 | +It will help understand what types are resolved from the container: |
| 112 | + |
| 113 | +```php |
| 114 | +<?php |
| 115 | + |
| 116 | +class MyService |
| 117 | +{ |
| 118 | + // |
| 119 | +} |
| 120 | + |
| 121 | +\PHPStan\dumpType(app()); |
| 122 | +\PHPStan\dumpType(app(MyService::class)); |
| 123 | +\PHPStan\dumpType(app('auth')); |
| 124 | +``` |
| 125 | + |
| 126 | +Will report: |
| 127 | +``` |
| 128 | +Dumped type: Illuminate\Contracts\Foundation\Application |
| 129 | +Dumped type: MyService |
| 130 | +Dumped type: Illuminate\Contracts\Auth\Factory |
| 131 | +``` |
| 132 | + |
| 133 | +And allows you to call methods on your custom builder. |
| 134 | + |
| 135 | +### Configuration |
| 136 | +All configuration options fall under `parameters` > `laravel` in the neon configuration. |
| 137 | +For example: |
| 138 | +```neon |
| 139 | +parameters: |
| 140 | + laravel: |
| 141 | + option: value |
| 142 | +``` |
| 143 | + |
| 144 | +#### Custom container bindings |
| 145 | +phpstan-laravel automatically understands it when you try to resolve class-strings from the container. |
| 146 | +But if you have bindings based on non-class-strings, it will need to know about that in order |
| 147 | +to provide you with the right type. |
| 148 | + |
| 149 | +You can set this up using: |
| 150 | +```neon |
| 151 | +parameters: |
| 152 | + laravel: |
| 153 | + customBindings: |
| 154 | + foo-bar: App\Service\FooBarService |
| 155 | +``` |
| 156 | + |
| 157 | +Now the following code will output `App\Service\FooBarService`: |
| 158 | +```php |
| 159 | +\PHPStan\dumpType(app('foo-bar')); |
| 160 | +``` |
| 161 | + |
| 162 | +#### Morph map |
| 163 | +phpstan-laravel checks whether you use the correct builder in the callback parameter |
| 164 | +of (morph) relation queries (whereHas, whereHasMorph). However when you use a morph |
| 165 | +map, phpstan-laravel will need to be informed about that. |
| 166 | + |
| 167 | +You can set this up using: |
| 168 | +```neon |
| 169 | +parameters: |
| 170 | + laravel: |
| 171 | + morphMap: |
| 172 | + media: App\Models\Media |
| 173 | +``` |
| 174 | + |
| 175 | +Now the following code will output `Illuminate\Database\Eloquent\Builder<App\Models\Media>`: |
| 176 | +```php |
| 177 | +$builder->whereHasMorph('model', ['media'], function ($builder) { |
| 178 | + \PHPStan\dumpType($builder); |
| 179 | +}); |
| 180 | +``` |
| 181 | +This will make it so that PHPStan understands what scopes exists and verify their signatures. |
| 182 | + |
| 183 | +## Why use this and instead of Larastan? |
| 184 | +We're not at all claiming this package is better than Larastan. Larastan is probably more feature-rich than |
| 185 | +this package. This package however does not boot your application, which Larastan does do. We think the |
| 186 | +best part about static analysis is that your code isn't run. |
| 187 | + |
| 188 | +## Contribution |
| 189 | +Development to this package requires tests and static analysis. |
| 190 | + |
| 191 | +To validate these locally run the following with dev dependencies installed: |
| 192 | +```bash |
| 193 | +vendor/bin/phpunit && vendor/bin/phpstan |
| 194 | +``` |
0 commit comments