skip to Main Content

I’m using Symfony 5.4 and PHP 7.4 with the default service configuration. I have an abstract service like this:

abstract class Parent
{
    protected FooInterface $fooDependency;
    public abstract function setFooDependency(FooInterface $fooDependency): void;
}

In my child classes, I want to inject different implementations of FooInterface for each child. The problem is that PHP rightly complains about the child method’s declaration not being compatible if the argument’s type is anything other than FooInterface:

class Child extends Parent
{
    public abstract function setFooDependency(FooService $fooDependency): void {}
}

I can solve this by not defining setFooDependency() in the abstract parent, but that kind of defeats the purpose of having the abstract class in the first place (I want to enforce a contract!)

Another workaround is to implement setFooDependency() in the parent class (instead of making it abstract), then in services.yaml manually specify the argument for each child class:

services:
    AppServiceChild:
        calls:
            - setFooDependency: [AppServiceFooService]

But I’m not fond of this, because anyone extending the abstract class has to magically know to go and add the service definition.

I’m hoping I’m missing an easy solution? What I want is to:

  • Enforce child classes having a setter method (or some other way to inject the dependency)
  • Allow each child class to inject a different implementation of FooInterface
  • Use autowiring so I don’t clutter my services file or confuse future devs

3

Answers


  1. Create a parent class (for example FooObject) for FooInterface and FooService.

    Make FooInterface and FooService … extends from this new class

    Define $fooDependency as FooObject

    Login or Signup to reply.
  2. What I want is to:

    • Enforce child classes having a setter method (or some other way to inject the dependency)
    • Allow each child class to inject a different implementation of FooInterface
    • Use autowiring so I don’t clutter my services file or confuse future devs

    There are various methods to achieve polymorphism in PHP, but it seems that you may be able to resolve this issue by defining an abstract method to retrieve the FooInterface instance instead of a setter method. This would enable you to inject a concrete FooInterface implementation directly into your constructor while maintaining immutability.

    interface FooInterface
    {
        public function getName(): string;
    }
    
    class ConcreteFoo implements FooInterface
    {
        public function getName(): string
        {
            return 'a string';
        }
    }
    
    abstract class Father
    {
        public function doSomething(): void
        {
            $name = $this->getFoo()->getName();
            
            // ...
        }
    
        abstract protected function getFoo(): FooInterface;
    }
    
    class Child extends Father
    {
        private ConcreteFoo $foo;
        
        public function __construct(ConcreteFoo $foo) 
        {
            $this->foo = $foo;
        }
        
        protected function getFoo(): ConcreteFoo
        {
            return $this->foo;
        }
    }
    

    By using this approach, your parent class will not depend on a property, but rather on a method that every child class must implement. This will allow for greater flexibility, maintainability, and the DI autowiring won’t be a problem.

    Login or Signup to reply.
  3. I think it’s easier using the constructor:

    abstract class Parent
    {
        protected FooInterface $fooDependency;
    
        publich function __construct(FooInterface $fooDependency) :void
        {
            $this->fooDependency = $fooDependency;
        }
    }
    

    Then you can define each implementation in services.yml without worring about calling a method:

    YourNamespaceChild:
      class: YourNamespaceChild
      arguments: [@your_foo_implementation_alias_for_this_child]
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search