Managing large numbers of parameters

Clean Code by Robert C. Martin suggests that you have as few parameters as possible with three being the general ceiling, and four as an extreme, possibly indicating you need to refactor.

How many parameters is too many is subjective. I personally think the above is a tad extreme and use up to three or four, sometimes five, in functions/methods but I have some constructors that require more.

What I find is, when you work with a larger application, your classes require a handful of parameters in addition to what I will call core/application classes. I pretty much avoid globals and static functions altogether and inject everything but in the higher level classes I sometime end up with three or four injections in the class that are quite specific in addition to a handful of other objects (up to six or seven) for things like:

  • Logging
  • Database abstraction
  • Event manager
  • Errors
  • Utility classes for date, strings, JSON, scalar, etc
  • Config

These tend to be either wrapper classes for core PHP functionality — which would be globally available anyway — or they need to be available anyway in the application — such as logging and broadcasting and listening to events. So, I think they are legitimate dependencies and not a sign the classes are doing too much.

As far as I can see there are several options that don’t require using global variables or having classes create their own objects:

  1. Store all the core objects in a registry and inject that
  2. Aggregate the core objects into groups and inject them as they are needed (I could probably group all these into: Database, Files, Utilities and Application)
  3. Inject all objects individually and accept the fact that some higher level classes will have 8–12 injections in the constructor
  4. Use a builder pattern and build up the objects piece by piece

Option 1 to me is only a step up from globals. I personally hate option four, which leaves me with 2 or 3.

What are your thoughts? What would you do?

1 Like

The Symfony base controller class has always suffered from this sort of issue. It provides various helper functions such as generating routes or rendering templates which in turn requires potential access to 12 or so dependencies. Their solution was to just inject the registry and live with it. In fact if a given method needed additional services then it would just pull them directly from the registry as well. Service locator at it finest.

However, Symfony recently released a new base controller class which still has the registry injected but limits access to specific services. It looks like:

 abstract class AbstractController implements ServiceSubscriberInterface
{
    public static function getSubscribedServices()
    {
        return array(
            'router' => '?'.RouterInterface::class,
            'request_stack' => '?'.RequestStack::class,
            'http_kernel' => '?'.HttpKernelInterface::class,
            'serializer' => '?'.SerializerInterface::class,
            'session' => '?'.SessionInterface::class,
            'security.authorization_checker' => '?'.AuthorizationCheckerInterface::class,
            'templating' => '?'.EngineInterface::class,
            'twig' => '?'.Environment::class,
            'doctrine' => '?'.ManagerRegistry::class,
            'form.factory' => '?'.FormFactoryInterface::class,
            'security.token_storage' => '?'.TokenStorageInterface::class,
            'security.csrf.token_manager' => '?'.CsrfTokenManagerInterface::class,
        );

Any other services that a particular method might need has to be specifically injected.

Kind of an extreme solution perhaps but it does solve this particular problem rather nicely.

Thanks. :slight_smile:

So does it basically create a custom registry that only has what object it needs?

Is it still technically a service locator if the objects are created before they are injected?

Objects are not created until they are actually requested.

Kind of hard to explain the implementation and I’m not quite up to speed on it but a ServiceLocator object is created using a list of factories. The factories are actually closures that reach directly into the master service container.

The ServiceLocator then gets injected and when the client object pulls out a service they actually end up going back up to the master container and getting the service from there. So you end up with limited access to the master container as well as creating objects on demand.

This might help: https://symfony.com/doc/master/service_container/service_locators.html

There is quite a bit of code required to actually implement all this so it’s the sort of thing that you would only do if you really had the need.

The PHP League container is worth checking out. The only thing it doesn’t have which the Laravel and Symfony containers do is tagging. The league container is very similiar to the laravel container in functionality. Where as the Symfony one uses compilers and yaml/xml files to configure.

Thanks for that. So, is the difference between service locator and registry when the objects are created. I’m finding I have arrived at a lof of the techniques through intuition, although I don’t the (somewhat nebulous) terms.

Someone recommend that container on another thread. I tried it out last week and really liked it — it was the first DI container I’d ever used and was much simpler and easier than I expected.

Correct me if I’m wrong though, that deosn’t actually reduce the number of parameters but rather means you only have to specify the dependencies once. Correct?

One thing I don’t get with DI containers, specifically the League one, is how you create objects where some of the parameters are not known until runtime (e.g. from user input).

class Foo {
    public function __constuct(Bar $bar, $userInput) { /* ... */ }
}

And you would create like this:

$foo = new Foo(new Bar(), $_POST['foo-bar']);

Can containers handle this or are you expected to redesign your classes? I looked through League’s Container and couldn’t find anything that supports this.

Typically you want your container wiring to be static and not directly dependent on request parameters. You actually should be able to do what you want using a factory enclosure: http://container.thephpleague.com/2.x/factory-closures/

I don’t use the league container myself so I’m not positive if it would work or not.

A different approach would be to make a FooFactory class and inject the request into it.

class FooFactory {
    public function create($request) {
        return new Foo(new Bar(), $request->get('foo-bar'));

And then with Symfony you could define a foo service like:

Foo:
    factory: 'FooFactory::create'
1 Like

That’s great, thanks!

This is an interesting problem and my opinion is that for a class to be cleanest, most readable and reusable these rules should be followed:

  1. All mandatory dependencies should be injected in the constructor.
  2. There should be no injected containers, factories, etc. that are used to pull dependencies from (no service locators).
  3. The dependencies should be the minimum of what is required, not groups containing needed and unneeded stuff. For example, when I need a user ID I require $user_id and not the whole User object. When I need the root upload directory path I require that path and not the whole Config, etc.

I’ve found that following these principles allows to write most readable classes without superfluous dependencies. However, this can sometimes result in too many dependencies so ideally I should be able to refactor the class into smaller classes and be happy, or if that’s impossible then simply leave all those many constructor arguments since they appear to be necessary in order to have a well constructed class.

Of course, there are always cases where I feel I need to break the rules for some higher purpose, or at least I believe so. My practice is that the further down the application structure I go the stricter I am about following the proper DI principle. So this would generally be the Model (or Service) classes that perform the business part of my application - I try to make them as small as possible with few and specific dependencies, which makes them easy to read and port.

As I go higher up in the application layers I allow myself more leeway in ā€œabusingā€ rules - that’s because the higher layers are more framework specific and less likely in need to be reused elsewhere. An example is controllers - I still keep them as independent classes and inject needed dependencies but trying to limit them I usually require a controller utility object, which is nothing more than a collection of methods that deal with request/response/session, etc. stuff like redirecting, url creation, http exceptions, etc. by invoking framework-specific code. I would call it a simple service locator or maybe a service wrapper? In this way I limit the number of dependencies but still keep the possibility of porting my controllers to another framework, provided I port them along with the utility class. I could require the whole Application object and do anything in the controller but then I get tied to a specific framework.

But because I generally consider controllers to be less in need to be reusable I don’t worry so much about a large number of dependencies. However, I still find it cleaner to limit them and while in the past I had all kinds of actions in a single controller like list, add, edit, delete, etc., now I usually have a separate controller for list and a separate one for add, edit and delete.

In short, my general belief is that if a class really needs many dependencies then it should require them in the constructor and we should live with it.

A somewhat related issue I have found is passing a database object. To me, a database object is almost like a global variable because if a class has access to a database object then it can do anything in the database because it can execute any kind of SQL and there’s no way to limit that. So if an object has access to the database object then it literally has access to the whole application and it’s not immediately obvious what the object is going to do to the database - it could be anything. Of course, I don’t abuse my classes with improper SQL but conceptually I feel that they have access to too much stuff than they should.

1 Like

Thanks, that is exactly my opinion.

I tend to class the lower-level stuff as the ā€œlibraryā€ where dependencies are few and specific — and classes really can be reused.

On top of that I have the ā€œframeworkā€ which is an opinionated version of how the site runs (MVC, router, etc). This could be used on other sites but how it operates is not that configurable.

On top of that is really where code would never get used outside of the current site so injecting is more about testability rather than portability.

A somewhat related issue I have found is passing a database object. To me, a database object is almost like a global variable because if a class has access to a database object then it can do anything in the database because it can execute any kind of SQL and there’s no way to limit that. So if an object has access to the database object then it literally has access to the whole application and it’s not immediately obvious what the object is going to do to the database - it could be anything. Of course, I don’t abuse my classes with improper SQL but conceptually I feel that they have access to too much stuff than they should.

I’ve thought this too and I don’t really think there is a better way to do it without making your database abstraction unnecessarily complicated.

1 Like

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.