0

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 !

  • Why not just remove the Type Hints in the method signatures in the interface? PHP is still a dynamically-typed language. – Greg Burghardt Nov 20 at 23:33
  • @greg And also remove the type hint from UserFactory::createFromResource()? I need to be sure that its argument is a ResourceInterface. I could check it in the method body with $user instanceof UserResource and throw an exception if not but it also seems to break the Liskov Substitution Principle. – Benjamin Nov 21 at 14:35
  • Already answered in softwareengineering.stackexchange.com/q/389503 as far as the OOP Design issues are concerned. PHP may have some additional considerations (such as what Greg pointed out, PHP being a dynamically typed language), but the deeper issues of this use case with LSP is already fully debated in that earlier question. – rwong Nov 21 at 15:49
  • The key observation is that the method createFromResource is a subtle example of overloading: between the classes (UserResource and UserEntity) there is one method createFromResource; between the classes (AdminResource and AdminEntity) there is a different method also named createFromResource. There is nothing interchangeable between those methods; they merely share the same name. – rwong Nov 21 at 15:57
0

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.

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.