PhpStorm: Is there a way to enforce a type on the return statement using an inline PHPDoc annotation?

Viewed 949

Consider the following code:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Car extends Model
{
    public static function getTheFirstCar(string $color): ?self
    {
        /** @var ?self */ // <-- Doesn't apply! Is there any alternative?
        return (new self())->newQuery()->firstWhere('color', '=', $color);
    }
}

The code is working correctly; nevertheless PhpStorm complains:

Return value is expected to be 'Car|null',
'\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model' returned

PhpStorm PHPDoc Type Annotation on return statement

Assigning the result of the expression into an annotated variable resolves the warning, but yet introduces a "redundant" variable!

/** @var ?self $redundant */
$redundant = (new self())->newQuery()->firstWhere('color', '=', $color);
return $redundant;

So, is there a way in PhpStorm to enforce an inline type-annotation for the value of the return statement expression explicitly as Car|null, without introducing a redundant variable or specifying all of the expected return types?

2 Answers

You can suppress this warning by adding the @noinspection PhpIncompatibleReturnTypeInspection annotation before your statement.
I personally would not do this, but it's the only answer to you your question about how to "enforce" the return type and suppress warning afaik.

    /** @noinspection PhpIncompatibleReturnTypeInspection */
    return (new self())->newQuery()->where('color', '=', $color)->first();

If you decide to respect the warning, then this is probably the reason and solution for it: newQuery() will create a new query on the models table (most likely: cars) without setting the appropriate model (Car).
Internally you're now running a bare query on cars. Therefore you will receive the appropriate record but is not gonna be an instance of Car, but an instance of Model instead. Therefore PhpStorm is expecting multiple additional return types here and printing this warning on your statement since it differs from the methods return type ?self.

The quick solution is to change newQuery() into newModelQuery(). This will create a new query and set the Model Car on the created query and return the appropriate instance or null

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Car extends Model
{
    public static function getTheFirstCar(string $color): ?self
    {
        return (new self())->newModelQuery()->firstWhere('color', '=', $color);
        // I'd use this statement though:
        // return self::where('color', '=', $color)->first();
    }
}

You need to add doc block to your class:

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
 * Class Car
 * @package App\Models
 *
 * @method self|Builder newQuery()
 * @method ?self firstWhere($column, $operator = null, $value = null, $boolean = 'and')
 */
class Car extends Model
{
    public static function getTheFirstCar(string $color): ?self
    {
        return (new self())->newQuery()->firstWhere('color', '=', $color);
    }
}
Related