I can see two issues in your code:
- 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
- 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();