0

I feel stupid asking such a simple question, but I am clearly missing something basic.

The CollectionOrdered class below is meant to "carry a payload" and the point of HasPayloadInterface is to encapsulate operations for all classes which carry a payload, not just CollectionOrdered.

I have a CollectionOrderedFactory which makes a CollectionOrdered object. I would have thought that the return type for the makeCollection method in the factory would be CollectionOrdered<PayloadType>, as I have written below.

BUT

Phpstan comes back with "makeCollection() should return CollectionOrdered<PayloadType> but instead returns CollectionOrdered<HasPayloadInterface>". I just don't understand where the problem lies..

Here's the code:

/**
 * Class HasPayloadInterface
 * @template PayloadType
 */
interface HasPayloadInterface
{
}

/**
 * Class CollectionOrdered
 * @template PayloadType of HasPayloadInterface
 * @implements HasPayloadInterface<PayloadType>
 */
class CollectionOrdered implements HasPayloadInterface
{
}

/**
 * Class CollectionOrderedFactory
 * @template PayloadType of HasPayloadInterface
 */
class CollectionOrderedFactory
{
    /**
     * makeCollection
     * @return CollectionOrdered<PayloadType>
     */
    public function makeCollection(): CollectionOrdered
    {
        return new CollectionOrdered();
    }
}

Thanks in advance for some guidance.

1 Answer 1

0

I can see two issues in your code:

  1. This part looks wrong to me because PayloadType refers to the generic interface HasPayloadInterface, but its type is not specified:
/**
 * Class CollectionOrdered
 * @template PayloadType of HasPayloadInterface
 * @implements HasPayloadInterface<PayloadType>
 */
class CollectionOrdered implements HasPayloadInterface

If we substitute PayloadType for its value, we'll obtain: @implements HasPayloadInterface<PayloadType of HasPayloadInterface>. In this expression it is apparent that the type parameter for the second reference to HasPayloadInterface is missing. If we add the missing type, we'll get something like this:

@implements HasPayloadInterface<PayloadType of HasPayloadInterface<PayloadType>>

But PayloadType is of HasPayloadInterface, so we end up with a cyclic reference.

I guess there might be a typo in your code. I would instead expect to see a separate type for the payload, e.g.:

/**
 * @template PayloadType of string
 * @implements HasPayloadInterface<PayloadType>
 */
class CollectionOrdered implements HasPayloadInterface
  1. There is no way to deduce the PayloadType to anything other than HasPayloadInterface in the following code:
/**
 * Class CollectionOrderedFactory
 * @template PayloadType of HasPayloadInterface
 */
class CollectionOrderedFactory

That is because there is no way for the user to specify the PayloadType (not even implicitly).

The error goes away when you allow the user to set the PayloadType in one way or another. For instance, in the following code, classes accept type information via constructor arguments:

<?php
/**
 * @template PayloadType of PayloadInterface
 * @implements HasPayloadInterface<PayloadType>
 */
class CollectionOrdered implements HasPayloadInterface
{
    /**
     * @param class-string<PayloadType> $payloadType
     */
    public function __construct(private string $payloadType) {}

    public function getPayload(): string
    {
        return $this->payloadType;
    }
}

/**
 * @template PayloadType of PayloadInterface
 */
class CollectionFactory
{
    /**
     * @param class-string<PayloadType> $payloadType
     */
    public function __construct(private string $payloadType) {}

    /**
     * @return CollectionOrdered<PayloadType>
     */
    public function makeCollection(): CollectionOrdered
    {
        return new CollectionOrdered($this->payloadType);
    }
}

/**
 * Class HasPayloadInterface
 * @template PayloadType
 */
interface HasPayloadInterface
{
}

interface PayloadInterface
{
    public function getPayload(): string;
}

class SimplePayload implements PayloadInterface
{
    public function __construct(private string $payload) {}

    public function getPayload(): string
    {
        return $this->payload;
    }
}

I admit that the code above is relatively useless, and you'll probably need another way of passing the type information into the classes, but it fixes the issue:

$ phpstan analyze --no-progress --no-ansi --level=9 -- test.php

 [OK] No errors

Here is how one could create a CollectionOrdered<SimplePayload>:

$factory = new CollectionFactory(SimplePayload::class);
$collection = $factory->makeCollection();
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.