Question

I have a Converter which use a Resolver to determine which Factory to use when converting a Resource to a Entity. To do so, I need to be sure that the Factory has a createFromResource method.

The problem: I am not able to create an interface handling the Factories because of the contravariance of arguments. The FactoryInterface cannot set the argument of createFromResource method because it will change in every Factory implementation and doesn't have a common parent.

How can I design those classes without breaking Liskov Substitution Principle? What I am doing wrong? Is that a bad design?

Here are my classes:

interface EntityInterface {}
class UserEntity implements EntityInterface {}
interface ResourceInterface {}
class UserResource implements ResourceInterface {}
class Converter
{
    private FactoryResolver $factoryResolver;

    public function convert(ResourceInterface $resource): EntityInterface
    {
        $factory = $this->factoryResolver->resolveFromResource($resource);

        // Need to ensure that $factory has createFromResource() method
        return $factory->createFromResource($resource);
    }
}
class FactoryResolver
{
    public function resolveFromResource(ResourceInterface $resource): FactoryInterface;
}
interface FactoryInterface
{
    // Cannot be implemented because of contravariance
    public function createFromResource(ResourceInterface $resource): EntityInterface;
}
class UserFactory implements FactoryInterface
{
    // This signature break contravariance
    public function createFromResource(UserResource $user): UserEntity;
}

PHP version: 7.4

Thanks a lot !

Was it helpful?

Solution

I think your error highlights an important point: Is there even a situation where you could treat UserFactory as if it was a FactoryInterface?

Since UserFactory.createFromResource requires a UserResource, it's less permissive than FactoryInterface.createFromResource. This means that casting UserResource to FactoryInterface is unsafe no matter how you look at it.

Covariance and contravariance only tell you what's guaranteed to be safe with respect to automatic type conversions. When implementing an inherited function, argument types can never be more restrictive than what the parent allows; otherwise, it would be unsafe to actually use it as an instance of the parent.

In summary, it appears that FactoryInterface doesn't express a common property of its children in this case, since you can't discard knowledge of the underlying implementation.

Licensed under: CC-BY-SA with attribution
scroll top