Search code examples
phplaravelphpstan

phpstan: class implements generic interface but does not specify its types error


Background

I am building a class for a laravel system. It is for casting the Ramsey\Uuid\Uuid type in laravel models. I also use phpstan and I seem to be having problems with the generics/templating.

  • in the DB, uuid is stored as binary
  • when loading or setting, Ramsey\Uuid\UuidInterface is enforced

The problem

The laravel vendor package has the following interface:

/**
 * @template TGet
 * @template TSet
 */
interface CastsAttributes
{
    /**
     * Transform the attribute from the underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  mixed  $value
     * @param  array  $attributes
     * @return TGet|null
     */
    public function get($model, string $key, $value, array $attributes);

    /**
     * Transform the attribute to its underlying model values.
     *
     * @param  \Illuminate\Database\Eloquent\Model  $model
     * @param  string  $key
     * @param  TSet|null  $value
     * @param  array  $attributes
     * @return mixed
     */
    public function set($model, string $key, $value, array $attributes);
}

My class Uuid implements this

// ...
use Ramsey\Uuid\Uuid as RamseyUuid;
use Ramsey\Uuid\UuidInterface;
// ...

class Uuid implements CastsAttributes // ...
{
    /**
     * Cast the given value.
     *
     * @param Model $model
     * @param string $key
     * @param string $value
     * @param array<mixed> $attributes
     * @return UuidInterface
     */
    public function get($model, string $key, $value, array $attributes)
    {
        $uuid = RamseyUuid::fromBytes($value);
        return $uuid;
    }

    /**
     * Prepare the given value for storage.
     *
     * @param Model $model
     * @param string $key
     * @param UuidInterface $value
     * @param array<mixed> $attributes
     * @return string
     * @throws Exception
     */
    public function set($model, string $key, $value, array $attributes)
    {
        if (!$value instanceof (UuidInterface::class)) {
            throw new Exception('The uuid property is not an instance of Ramsey - UuidInterface');
        }

        return $value->getBytes();
    }

When I run phpstan (above level 6), I get the following error:

  • Class Uuid implements generic interface Illuminate\Contracts\Database\Eloquent\CastsAttributes but does not specify its types: TGet, TSet

I have read the phpstan documentation (generics section, generics by example) several times and I'm simply not sure how it relates to the issue I'm facing. I've also used the playground and it also didn't help me understand the issue.

Question

How do you specify the types of an interface in phpstan


Solution

  • Following a response from the author of phpstan (thanks!) the part I was missing was using the @implements tag.

    /**
     * @implements CastsAttributes<Uuid, UuidInterface>
     */
    class Uuid implements CastsAttributes
    

    This solves the issue up to phpstan max level.