Object-oriented – PHP OOP dependency injection – when is it o.k. to use the “new” statement

dependency-injectionobject-orientedPHP

I was told to avoid "new" statements in classes or functions but rather create the objects from classes in the root of the program (maybe with the use of a DI-container) and then inject the objects into the classes.

Anyway, I think there are many (the majority of) cases where this "rule" can not be true. Let's see this example:

<?php
$mailerobject = new PHPMailer();

class send_orderconfirmation {

    public function __construct(Mailer $mailer) {
        $this->mailer = $mailer;
    }

    public function send_orderconfirmation() {
        $email = "someaddress@somedomain.com";
        $prename = "Enrique";
        $name = "Iglesias";
        $subject = "Your blue pills order";

        $this->mailer->addAddress("$email", "$prename $name");
        $this->mailer->Subject = $subject;
    }
}

?>
  • First thing is, when do I create a send_orderconfirmation object? It makes no sense to create this in the root of the program, who knows if it is needed? It's rather logical that it should be created by a class that processed a new order and now wants to send a confirmation, doesn't it? But then the new-statement would have to be in this class.
  • Second, even more crucial: In the above example, as soon as I use some setters of the mailer object to fill it with data about the e-mail to send (subject, address, recipient name) the object is "polluted" with this data. So the next function that needs the mailerobject would have to clean the object first to make use of it. Also it is not possible to have two mailerobjects at the same time, e.g. if I want to fill two mailer objects concurrently with data.

What am I missing? Are there legitimate reasons to create objects in classes or do they always have to be injected?

Best Answer

Of course you need to create objects somewhere. In theory, applications are divided into two parts:

  • the business logic part - the fun parts, creating rules for behaviour by using classes together,
  • the factories - the necessary evil, a magical place where classes are newed to create objects and wired together.

Now, the business logic part is where the decoupling matters and loose coupling is an advantage. But why exactly?

For one (simple) reason: To be able to switch out dependencies (modules) if a new one having the same API proves to be better (faster, costs less resources, etc.).

Take a look at a database. You may use some ORM layer, for which you create adapters, so your business layer is not entirely dependent on the ORM directly but your abstraction.

You could obviously new the ORM layer directly in your code, whenever you wanted to use it, but what would happen if one day you decided there's a better alternative? You would have no choice than to find all the classes where the ORM layer objects are created and replace them and you would probably need to edit the classes as well, because the different ORM might have a slightly different API (the public (static) methods through which the ORM operates). That's a lot of work.

If you decided to inject dependencies instead, and even better if you already use an adapter, you are quite likely to new the ORM layer only once or very few times and reuse this newed object by injecting it into multiple classes, all of them need to access the ORM layer. Not only you don't have to manually replace the old new with the new one in multiple places, but most likely only in one, but by using adapter you probably will not need to touch the business layer at all.

The task of using the new ORM which would previously take a few hours could now be done in a few minutes (that is without the time needed for tests), which is amazing.

The only point of not newing classes directly in your business layer is to provide decoupling (the new keyword is delegated into the factory part of your application), but it makes no sense to try to decouple everything. Trying to do so will only turn your application into a factory hell.

The rule of thumb when deciding, whether a class should be directly created or rather delegate the creation (or the process of obtaining the instance) to a factory is to realize, whether decoupling will bring you some huge benefit. When you start thinking about a large application as sum of smaller modules (some of these modules may be: cache, database, domain, http), before newing any class directly, you should think, whether there's a possibility that a new version might come one day which will be better and which you would like to (potentially) use. If the answer is yes, inject the dependency and don't create it manually.

Other very important thing is, if all your application depends on dependency injection and programmers rely on it, a dependency should be injected when a class communicates with the outer world (database, web services). A developer using said class directly knows the class may communicate and alter outside data just by looking at its dependencies, which is amazing, because the class has an API which does not like about what it needs (by creating the needed objects directly), because it is asking for the dependencies from the outside.

If the dependency is external (pulled through a package manager such as Composer for PHP, NuGet for C# or Maven for Java), you should go through the hassle of hiding the real dependency behind your own abstraction and make your application depend on the abstraction, the interface you designed and are very well aware of how it works and what you can expect from it (the Adapter pattern once again).

On the other hand imagine a scenario of having a repository, which is supposed to provide a middle layer between the data access layer and your domain (business layer), transfering domain entities to pure data and vice versa. Domain is a pretty strict layer with (usually) well defined rules, where a class is an end product and is likely to be replaced only if business rules change. In a case like that there's usually only one way of constructing such entity and it is in most cases ok not to write a factory but new the objects directly.

If we look at your specific example, you're actually on the right track, where you inject a Mailer object. There's a great possibility a new Mailer will once come and will be better than the current one (but will have methods with the same names and taking the same arguments). As said before, you will now have to simply new a different class and inject that one instead.

You are then calling the addAddress method with 2 arguments, the address and name of the sender, but even if the addAddress took a data object like UserEmail, it would still be ok to new the UserEmail class directly in the send_orderconfirmation, because the UserEmail would very likely to be a simple messenger, groupping related data together so a method requires fewer arguments.

Injecting everything really is a non-sense and there are in fact cases when it's not only allowed but actually better to create an object directly in place. If someone is against that, you should ask them why and if they cannot provide a meaningul explanation, they're probably overengineering the application by throwing design patterns here and there just because they look cool.

Sadly, in programming there are too many edge cases which are impossible to describe in a book any shorter than a few thousand pages, and even then you would probably not cover them all. On top of that, thing you are actually trying to figure out is an architectural problem and solution to these rarely come from reading a few online tutorials (which is something you can do when you want to learn a new language) but rather from aged experience.

Not only for this specific case but for software development in general, you will most likely need to gain experience and realize what does and what does not work (either for you or your environment). Perhaps someone has already created a workaround how to skip the few years of programming and designing application to learn it, I myself have started programming little over 15 years ago and had to learn it the hard way, by gaining experience.