Skip to content

Commit a73bb1d

Browse files
refactor(dev): Fix couple errors in DI chapter examples
Couple code example errors fixed in DI chapter: - `AuthorMappers` referenced when `AuthorMapper` was intended - Class `APIController`, which is shown calling a parent class constructor, wasn't extending a class Also: - Added a DI introduction section - Added some new inline code comments Signed-off-by: Josh <[email protected]>
1 parent 9f7e854 commit a73bb1d

File tree

1 file changed

+116
-28
lines changed

1 file changed

+116
-28
lines changed

developer_manual/basics/dependency_injection.rst

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,158 @@
1-
====================
2-
Dependency injection
3-
====================
4-
5-
.. sectionauthor:: Bernhard Posselt <[email protected]>
6-
7-
The App Framework assembles the application by using a container based on the
8-
software pattern `Dependency Injection <https://en.wikipedia.org/wiki/Dependency_injection>`_.
9-
This makes the code easier to test and thus easier to maintain.
10-
11-
If you are unfamiliar with this pattern, watch the following video:
1+
=================================
2+
Containers / Dependency Injection
3+
=================================
4+
5+
Introduction
6+
------------
7+
8+
Modern software applications are composed of various components that need to interact
9+
with one another. Traditionally, objects create their own dependencies internally,
10+
which leads to tight coupling and makes code harder to test, maintain, and extend.
11+
`Dependency injection (DI) <https://en.wikipedia.org/wiki/Dependency_injection>`_ is
12+
a software design pattern that helps solve this problem by having dependencies provided
13+
from the outside, rather than being constructed inside the object itself.
14+
15+
Dependency injection may sound like a big concept, but it’s really just about making
16+
your code easier to work with and more flexible. Instead of each part of your app
17+
creating the things it needs by itself, those “dependencies” are handed to it -- usually
18+
by a special helper called a container. This means your classes don’t need to know how
19+
to create their collaborators; they just need to know how to use them.
20+
21+
The App Framework in Nextcloud assembles applications using a container based on this
22+
design pattern. This approach leads to more modular, testable, and maintainable code.
23+
24+
Using dependency injection is about more than just elegant code. When all apps follow
25+
this pattern:
26+
27+
- It’s easier to test and upgrade both apps and the server, since dependencies can be
28+
swapped out or mocked.
29+
- Apps stay decoupled from internal server details, making it safer for Nextcloud to
30+
evolve without breaking your app.
31+
- Core features like autowiring, service discovery, and new APIs become available to all
32+
apps without extra boilerplate.
33+
- Memory and resource usage can be reduced.
34+
- New services or APIs become easier to adopt as Nextcloud evolves.
35+
36+
By sharing a consistent approach to building and wiring up dependencies, everyone --
37+
core and app developers alike -- benefits from a more robust, secure, and future-proof
38+
platform.
39+
40+
If you are unfamiliar with the DI design pattern, don't worry -- it's widely used in
41+
modern frameworks, and you'll soon become comfortable with it. You can also watch the
42+
following video introduction:
1243

1344
* `Google Clean Code Talks <https://www.youtube.com/watch?v=RlfLCWKxHJ0>`_
1445

1546
.. _dependency-injection:
1647

17-
Dependency injection
18-
--------------------
48+
Basic Pattern of Dependency Injection
49+
-------------------------------------
50+
51+
The essence of dependency injection is: **don't instantiate dependencies directly inside
52+
your classes or methods, but instead pass them in as parameters**. This allows swapping
53+
out dependencies (such as with mocks in unit tests), makes dependencies explicit, and
54+
centralizes object creation logic.
1955

20-
Dependency Injection sounds pretty complicated but it just means: Don't put
21-
new dependencies in your constructor or methods but pass them in. So this:
56+
For example, consider the following pattern:
2257

2358
.. code-block:: php
2459
60+
/**
61+
* Without dependency injection:
62+
*/
63+
2564
use OCP\IDBConnection;
2665
27-
// without dependency injection
2866
class AuthorMapper {
67+
68+
// Define a property to store the dependency
2969
private IDBConnection $db;
3070
3171
public function __construct() {
72+
// The dependency is instantiated within the class
3273
$this->db = new Db();
3374
}
3475
}
3576
36-
would turn into this by using Dependency Injection:
77+
With dependency injection, you would instead pass the dependency into the constructor:
3778

3879
.. code-block:: php
3980
81+
/**
82+
* Using dependency injection:
83+
*/
84+
4085
use OCP\IDBConnection;
4186
42-
// with dependency injection
4387
class AuthorMapper {
88+
89+
// Define a property to store the dependency
4490
private IDBConnection $db;
4591
92+
// The dependency is passed in from outside (typically by the container)
4693
public function __construct(IDBConnection $db) {
94+
// Assigned to the property
4795
$this->db = $db;
4896
}
4997
}
5098
51-
Controller injection
99+
Or, more succinctly, by using constructor property promotion (since PHP 7.4):
100+
101+
.. code-block:: php
102+
103+
/**
104+
* Using dependency injection with constructor property promotion:
105+
*/
106+
107+
use OCP\IDBConnection;
108+
109+
class AuthorMapper {
110+
111+
/**
112+
* Constructor property promotion with DI reduces boilerplate code by
113+
* handling everything within the constructor parameters. The example below
114+
* does exactly the same thing as the prior example, but in less code:
115+
*
116+
* - The dependency is passed in from outside (by the container)
117+
* - The private property is established to store the dependency
118+
* - The dependency is assigned directly to that property
119+
*/
120+
public function __construct(private IDBConnection $db) {
121+
}
122+
}
123+
124+
Advantages
125+
----------
126+
127+
- **Testability:** You can inject mock objects for unit testing.
128+
- **Maintainability:** Changing how a dependency is constructed (in the container)
129+
updates it wherever it is injected in the application.
130+
- **Explicitness:** Dependencies are clearly listed in constructors or method signatures,
131+
improving readability and maintainability.
132+
133+
Controller Injection
52134
--------------------
53135

54-
For controllers it's possible to also have dependencies injected into methods.
136+
For controllers, Nextcloud allows dependencies to also be injected directly into
137+
individual methods, not just constructors. This is referred to as *method injection* and
138+
enables you to specify dependencies only where needed, potentially reducing resource
139+
usage for rarely required services.
55140

56141
.. code-block:: php
57142
:caption: lib/Controller/ApiController.php
58-
:emphasize-lines: 12-13, 16-17
143+
:emphasize-lines: 15-16, 19-20
59144
60145
<?php
61146
62147
namespace OCA\MyApp\Controller;
63148
149+
use OCA\MyApp\Service\BarService;
150+
use OCA\MyApp\Service\FooService;
151+
use OCP\AppFramework\Controller;
64152
use OCP\IRequest;
65153
66-
class ApiController {
67-
public function __construct($appName, IRequest $request) {
154+
class ApiController extends Controller {
155+
public function __construct(string $appName, IRequest $request) {
68156
parent::__construct($appName, $request);
69157
}
70158
@@ -110,7 +198,7 @@ use the **IRegistrationContext::registerService** method:
110198
use OCP\AppFramework\Bootstrap\IBootContext;
111199
use OCP\AppFramework\Bootstrap\IRegistrationContext;
112200
use OCP\IDBConnection;
113-
201+
use OCP\IRequest;
114202
use OCA\MyApp\Controller\AuthorController;
115203
use OCA\MyApp\Service\AuthorService;
116204
use OCA\MyApp\Db\AuthorMapper;
@@ -136,7 +224,7 @@ use the **IRegistrationContext::registerService** method:
136224
$context->registerService(AuthorController::class, function(ContainerInterface $c): AuthorController {
137225
return new AuthorController(
138226
$c->get('appName'),
139-
$c->get(Request::class),
227+
$c->get(IRequest::class),
140228
$c->get(AuthorService::class)
141229
);
142230
});
@@ -171,7 +259,7 @@ The container works in the following way:
171259

172260
return new AuthorController(
173261
$c->get('appName'),
174-
$c->get(Request::class),
262+
$c->get(IRequest::class),
175263
$c->get(AuthorService::class)
176264
);
177265

@@ -187,8 +275,8 @@ The container works in the following way:
187275

188276
* **AuthorMapper** is queried::
189277

190-
$container->registerService(AuthorMappers::class, function(ContainerInterface $c): AuthorMapper {
191-
return new AuthorService(
278+
$container->registerService(AuthorMapper::class, function(ContainerInterface $c): AuthorMapper {
279+
return new AuthorMapper(
192280
$c->get(IDBConnection::class)
193281
);
194282
});

0 commit comments

Comments
 (0)