So I've got a Symfony controller, and I'm injecting needed services into my methods via params.
One of the params (MySqlGroupDAO $groupDAO
) is used by all methods of this controller class.
Currently, I'm passing the 'common' param as my last param in each method like this:
/**
* @Route("/{id}", methods={"POST"})
* @IsGranted("EDIT_GROUP", subject="parentGroup")
*/
public function addGroup(Request $request, MySqlGroupDAO $groupDAO) {
$group = new Group();
//code to init group from request
$groupDAO->addGroup($group);
return new Response("Adding $groupName");
}
Doing it this way allows me to eliminate my __construct
method. However, I'm not sure this is the best way to go about it. Since it's common across all methods would it be better to re-add my constructor and do something like this?:
private $groupDAO;
public function __construct(
Config $config,
ValidatorInterface $validator,
TranslatorInterface $translator,
RequestStack $requestStack
) {
parent::__construct($config, $validator, $translator, $requestStack);
$this->groupDAO = new MySqlGroupDAO($config);
}
/**
* @Route("/{id}", methods={"POST"})
* @IsGranted("EDIT_GROUP", subject="parentGroup")
*/
public function addGroup(Request $request) {
$group = new Group();
//code to init group from request
$this->groupDAO->addGroup($group);
return new Response("Adding $groupName");
}
In doing so, I'm eliminating about half a dozen params across all my methods (in this particular class). But I'm adding back in my constructor, which requires me to add a class param, and inject several additional params in my constructor since it extends another class.
Is there an advantage to doing it one way vs the other?
Thanks.
If we use both constructor and setter injection, IOC container will use the setter injection. Changes: We can easily change the value by setter injection. It doesn't create a new bean instance always like constructor. So setter injection is flexible than constructor injection.
Constructor injection helps in creating immutable objects because a constructor's signature is the only possible way to create objects. Once we create a bean, we cannot alter its dependencies anymore.
Dependencies passed into the constructor should be useful to the class in a general way, with its use spanning multiple methods in the class. If a dependency is used in only one spot, method injection (covered below) might be a better choice.
Frameworks that apply the Constrained Construction anti-pattern can make using Constructor Injection difficult. The main disadvantage to Constructor Injection is that if the class you're building is called by your current application framework, you might need to customize that framework to support it.
Reasons to use DI in a controller's route methods:
parent::__construct
method. Using DI on the constructor means you have to adapt your code whenever this changes. Also notice that some Symfony bundles may assume that controllers have a particular signature and it might make things more complicated for you if it does not.Reasons to use DI in the contructor:
Alternative to using DI in the constructor:
You can also use setter injection and configure your service this way. This is functionally pretty similar to using DI in the constructor, but it bypasses the major drawback of generating a different signature from the parent and being more work to maintain if the parent constructor changes.
You can also make this easier to use for services you often inject. Make the services that need this DI implement an interface and configure them with _instanceof. Symfony does that with its ContainerAwareInterface
and even has a facilitator in the form of ContainerAwareTrait
to declare the setter and the property. In your case, if several services require the MySqlGroupDAO
service, you could define a MySqlGroupDAOAwareTrait
and MySqlGroupDAOAwareInterface
, add a MySqlGroupDAOAwareInterface
entry in your services.yaml
's _instanceof
section, and use the trait and implement the interface in services that need the DI.
Edit Nov. 2021:
This answer is still being read by new people, so I thought I'd complete it with something that was added with Symfony 5.2 (and it requires PHP 8): using PHP attributes for dependency injection.
This allows Setter Injection without touching services.yaml
, and it allows public property injection. Here's the doc's example for both of these:
use Symfony\Contracts\Service\Attribute\Required;
class SomeService
{
// Example of public property injection
#[Required]
public Bar $bar;
// Example of setter injection without editing services.yaml
#[Required]
public function setFoo(Foo $foo): void
{
// ...
}
}
Notice that the property and the setter method used above need to be public (which might or might not be okay with you).
Read more at https://symfony.com/blog/new-in-symfony-5-2-php-8-attributes.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With