when I run PHPstan at level 8, I get for example with this code:
/**
* @return Collection<int, Account>
*/
public function getCustomersAttribute(): Collection
{
return $this->account->children;
}
I get the following message:
Cannot access property $children on App\Account|null
I also get this message with many other classes, only with a different attribute.
Since I cannot determine any access to this accessor e.g.
$model->customers
I also don't know whether an empty collection is always returned or zero (not my project, but taken over from an ex-colleague). So no error would be displayed:
/**
* @return Collection<int, Account>|null
*/
public function getCustomersAttribute(): Collection|null
{
return $this->account?->children;
}
I suspect that PHPstan does not recognise that the account attribute is only referenced at runtime. Is there a PHPstan annotation for the Account class to tell PHPstan that the Account model can be null? Can anyone help me with this. I have about 100 of these messages and don't want to check for null everywhere since the code works.
I have tried in some places to check if the attribute is zero and if it is, I have returned zero, which has often led to errors. PHPstan should be able to recognise that the account model can also be null.
Because Laravel uses Eloquent (Eloquent does not define the model's properties anywhere in the class for PHP to recognize), then PHPStan is not able to know what Account
has as properties (remember Eloquent have them defined dynamically).
So, you have to define what the model has using PHPDoc blocks. Let me give you a super quick example of what you should expect to have inside Account
:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
*/
class Account extends Model
{
// ...
}
Adding @property int $id
will tell the IDE and PHPStan that Account
has a property, it is an integer
and is called id
, so if you try $account->
it should autocomplete id
and show it is an integer
.
Instead of you manually doing this, barryvdh/laravel-ide-helper
already created a package for these types of things. Install it using composer require --dev barryvdh/laravel-ide-helper
and then you have to have all your migrations run php artisan ide-helper:models -W
(you need to have the schema available, not data inside the table, it does not care about data).
Using -W
will automatically insert the generated PHPDoc blocks inside each model class, if you do not do use -W
, it will ask you if you want to generate a file called _ide_helper_models.php
and put the PHPDoc blocks there, you don't want to do that, I think PHPStan will not recognize that.
What you can also try is using php artisan ide-helper:models -M
, -M
will still create the _ide_helper_models.php
but add a @mixin
tag inside each model class, so it relates the model class with the definition inside the _ide_helper_models.php
file.
In the end, this example is what you should have somewhere (and related to each model):
/**
* App\Models\Post
*
* @property integer $id
* @property integer $author_id
* @property string $title
* @property string $text
* @property \Illuminate\Support\Carbon $created_at
* @property \Illuminate\Support\Carbon $updated_at
* @property-read \User $author
* @property-read \Illuminate\Database\Eloquent\Collection|\Comment[] $comments
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newModelQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post newQuery()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post query()
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post whereTitle($value)
* @method static \Illuminate\Database\Eloquent\Builder|\App\Models\Post forAuthors(\User ...$authors)
* …
*/