I’m wondering if this is the correct way to extend and use classes with Symfonies autowiring.
For example, I have a BaseClass that instantiates and auto wires the entity manager.
class BaseClass { protected $entityManager; public function __construct(EntityManagerInterface $entityManager) { $this->entityManager = $entityManager; } protected function someMethodIWantToUse(Entity $something) { // Do something there $this->entityManager->persist($something); $this->entityManager->flush(); } }
Then I have a subclass that extends the BaseClass and needs access that method. So I let it autowire again and pass it to the parent constructor.
class SubClass extends BaseClass { private $handler; public function __construct(EntityManagerInterface $em, SomeHandler $handler) { parent::__construct($em); $this->handler = $handler; } public function SubClassMethod() { // Get some data or do something $entity = SomeEntityIGot(); $this->someMethodIWantToUse($entity); } }
Now I’m wondering if this is actually the correct way to do this or there’s something I’m missing and the parent class should be able to autowire the entitymanager by itself?
Advertisement
Answer
To summarize the comments, yes your way is correct. Depending on your use case there are alternatives.
This are the ways you can go about it:
1. Extending Class and using Constructor Injection (what you do)
class BaseClass { protected $some; public function __construct(SomeInterface $some) { $this->some = $some; } } class SubClass extends BaseClass { private $other; public function __construct(SomeInterface $some, OtherInterface $other) { parent::__construct($some); $this->other = $other; } }
class BaseClass { protected $some; public function __construct(SomeInterface $some) { $this->some = $some; } } class SubClass extends BaseClass { private $other; public function setOther(OtherInterface $other) { $this->other = $other; } }
Now setOther
won’t automatically be called, you have to “manually” call it by either specifying a calls
property in your services.yaml
file, as described here: https://symfony.com/doc/current/service_container/calls.html. This would then look something like this:
// services.yaml AppSubClass: calls: - [setOther, ['@other']]
Or
// services.yaml app.sub_class: class: AppSubClass calls: - [setOther, ['@other']]
assuming, an implementation of OtherInterface
is available as @other
in the service container.
A more elegant solution if you’re using autowiring, simply add a @required
annotation to the function as described here: https://symfony.com/doc/current/service_container/autowiring.html#autowiring-calls, which would look like this:
/** * @required */ public function setOther(OtherInterface $other) { $this->other = $other; }
class BaseClass { protected $some; public function __construct(SomeInterface $some) { $this->some = $some; } } class SubClass extends BaseClass { public $other; }
As with the Setter Injection, you’ll need to tell Symfony to populate this property, by specifying it in your services.yaml
file like this:
// services.yaml AppSubClass: properties: other: '@other'
Or
// services.yaml app.sub_class: class: AppSubClass properties: other: '@other'
assuming, an implementation of OtherInterface
is available as @other
in the service container.
Conclusion:
Since there are different ways to solve this, it’s up to you to determine the correct way for your use case. I personally go with either option 1 (Constructor Injection) or option 2 (Setter Injection) using the annotation. Both of them allow you to use typehints and thus allowing your IDE to help you write clean code.
In 90% of cases, I’d go with option 1, as then it’s clear for every one reading your code, what services are available with one glance at the __constructor
function.
One use case for Setter Injection would be a base class offering all the setXXX
functions but then sub classes not needing all of them. You could have a constructor in each sub class, requesting the needed services and then calling the setXXX
methods of the base class.
Note: this is kind of an edge case and you probably won’t run into this.
You can find a list of advantages and disadvantages of each method directly in the Symfony documentation about the Service Container -> Types of Injection