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
Create a parent class (for example FooObject) for FooInterface and FooService.
Make FooInterface and FooService … extends from this new class
Define $fooDependency as FooObject
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 concreteFooInterface
implementation directly into your constructor while maintaining immutability.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.
I think it’s easier using the constructor:
Then you can define each implementation in
services.yml
without worring about calling a method: