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
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.