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