Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid long lists of arguments in constructor service Symfony

I've been using Symfony to develop my web applications but I keep running into one problem. I always end up with too much clutter in the constructor of my services since I want to be able to unit test my services properly.

Theoretical use case

Let's say I need a service which allows me to process an XML file and save it's contents into the database.

<?xml version="1.0" encoding="UTF-8" ?>
<users>
    <user>
        <id>1234</id>
        <username>Example User</username>
        <email>[email protected]</email>
        <usergroup>
            <id>567</id>
            <name>Example User Group</name>
        </usergroup>
        <permissions>
            <item>ALLOWED_TO_CREATE</item>
            <item>ALLOWED_TO_UPDATE</item>
            <item>ALLOWED_TO_DELETE</item>
            <item>ALLOWED_TO_view</item>
        </permissions>
    </user>
</users>

Already quite a few things come to mind which you need to inject into this service:

  • DomCrawler (to read the XML file)
  • UserRepository (to get existing Users)
  • UserGroupRepository (to get existing UserGroups)
  • PermissionsRepository (to get existing permissions)
  • EntityManager (to persist en flush new/updated objects)

The real XML file I'm working with contains much more data which requires me to inject many more repositories and other services which handle certain logic.

Solution 1: Use Doctrine service

Inject the Doctrine service directory into my service and get the repositories via $doctrine->getRepository(User::class)

Pros

  • Significatly reduces the amount of arguments

Cons

  • Unit testing becomes a lot harder since I don't know what the service accesses anymore which renders them useless

Solution 2: Use setter injection

Remove all services and repositories from the constructor and create setter methods and call then in the services.yml

services:
  AppBundle\Service\MyImportService:
    calls:
      - [setUserRepository, ['@app.user_repository']]

Pros

  • Arguably easier to read

Cons

  • More code
  • Services no longer mandatory, optional validation may be required

Question

What are solutions to make the argument lists in my services more maintainable in terms of readability and ability to unit test?

Is it even considered bad practise to have a long list of arguments?

like image 667
Programmy Avatar asked Mar 09 '23 13:03

Programmy


1 Answers

Having many arguments in your constructor is a code smell called Constructor Over-Injection. This code smell is often an indication of the class taking on too much responsibility, meaning it violates the Single Responsibility Principle (SRP). SRP violations cause maintenance problems, which is why you should keep a close eye on them.

Although refactoring to Property Injection might reduce the number of constructor arguments and therefore the amount of clutter in the constructor, it does not solve the underlying problem, which is that this class is becoming too complex. Property Injection is therefore not a solution to Constructor Over-Injection.

The solution to this code smell is to reduce the complexity of the class at hand. There are many ways to do this, but a very common approach is the Facade Services refactoring:

Facade Service [is] closely related to Parameter Objects, but the main difference is that a Parameter Object only moves the parameters to a common root, while a Facade Service hides the aggregate behavior behind a new abstraction. While the Facade Service may start its life as a result of a pure mechanistic refactoring, it often turns out that the extracted behavior represents a Domain Concept in its own right.

Pros:

  • Reduces the class' complexity and thereby solves the underlying problem
  • Prevents depending on the Doctrine service (which is an application of the Service Locator anti-pattern)
  • Introduces new concepts and improvements to the Domain language.
  • Prevents property injection (since property injection causes Temporal Coupling)

Cons:

  • Facade Services might not be the right refactoring. Alternatives are Domain Events and Decorators.

These are topics that Mark and I in our book Dependency Injection Principles, Practices, and Patterns. Section 6.1, for instance, specifically talks about refactoring from Constructor Over-Injection to Façade Services or Domain Events, while chapters 9 and 10 do a deep dive into using Decorators.

like image 152
Steven Avatar answered Mar 16 '23 00:03

Steven