From a73bb1dbe874ec63095ba95ece86d8e4e15686fb Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 6 Nov 2025 11:53:14 -0500 Subject: [PATCH 1/2] 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 --- .../basics/dependency_injection.rst | 144 ++++++++++++++---- 1 file changed, 116 insertions(+), 28 deletions(-) diff --git a/developer_manual/basics/dependency_injection.rst b/developer_manual/basics/dependency_injection.rst index adb738ea701..3211a61b51f 100644 --- a/developer_manual/basics/dependency_injection.rst +++ b/developer_manual/basics/dependency_injection.rst @@ -1,70 +1,158 @@ -==================== -Dependency injection -==================== - -.. sectionauthor:: Bernhard Posselt - -The App Framework assembles the application by using a container based on the -software pattern `Dependency Injection `_. -This makes the code easier to test and thus easier to maintain. - -If you are unfamiliar with this pattern, watch the following video: +================================= +Containers / Dependency Injection +================================= + +Introduction +------------ + +Modern software applications are composed of various components that need to interact +with one another. Traditionally, objects create their own dependencies internally, +which leads to tight coupling and makes code harder to test, maintain, and extend. +`Dependency injection (DI) `_ is +a software design pattern that helps solve this problem by having dependencies provided +from the outside, rather than being constructed inside the object itself. + +Dependency injection may sound like a big concept, but it’s really just about making +your code easier to work with and more flexible. Instead of each part of your app +creating the things it needs by itself, those “dependencies” are handed to it -- usually +by a special helper called a container. This means your classes don’t need to know how +to create their collaborators; they just need to know how to use them. + +The App Framework in Nextcloud assembles applications using a container based on this +design pattern. This approach leads to more modular, testable, and maintainable code. + +Using dependency injection is about more than just elegant code. When all apps follow +this pattern: + +- It’s easier to test and upgrade both apps and the server, since dependencies can be + swapped out or mocked. +- Apps stay decoupled from internal server details, making it safer for Nextcloud to + evolve without breaking your app. +- Core features like autowiring, service discovery, and new APIs become available to all + apps without extra boilerplate. +- Memory and resource usage can be reduced. +- New services or APIs become easier to adopt as Nextcloud evolves. + +By sharing a consistent approach to building and wiring up dependencies, everyone -- +core and app developers alike -- benefits from a more robust, secure, and future-proof +platform. + +If you are unfamiliar with the DI design pattern, don't worry -- it's widely used in +modern frameworks, and you'll soon become comfortable with it. You can also watch the +following video introduction: * `Google Clean Code Talks `_ .. _dependency-injection: -Dependency injection --------------------- +Basic Pattern of Dependency Injection +------------------------------------- + +The essence of dependency injection is: **don't instantiate dependencies directly inside +your classes or methods, but instead pass them in as parameters**. This allows swapping +out dependencies (such as with mocks in unit tests), makes dependencies explicit, and +centralizes object creation logic. -Dependency Injection sounds pretty complicated but it just means: Don't put -new dependencies in your constructor or methods but pass them in. So this: +For example, consider the following pattern: .. code-block:: php + /** + * Without dependency injection: + */ + use OCP\IDBConnection; - // without dependency injection class AuthorMapper { + + // Define a property to store the dependency private IDBConnection $db; public function __construct() { + // The dependency is instantiated within the class $this->db = new Db(); } } -would turn into this by using Dependency Injection: +With dependency injection, you would instead pass the dependency into the constructor: .. code-block:: php + /** + * Using dependency injection: + */ + use OCP\IDBConnection; - // with dependency injection class AuthorMapper { + + // Define a property to store the dependency private IDBConnection $db; + // The dependency is passed in from outside (typically by the container) public function __construct(IDBConnection $db) { + // Assigned to the property $this->db = $db; } } -Controller injection +Or, more succinctly, by using constructor property promotion (since PHP 7.4): + +.. code-block:: php + + /** + * Using dependency injection with constructor property promotion: + */ + + use OCP\IDBConnection; + + class AuthorMapper { + + /** + * Constructor property promotion with DI reduces boilerplate code by + * handling everything within the constructor parameters. The example below + * does exactly the same thing as the prior example, but in less code: + * + * - The dependency is passed in from outside (by the container) + * - The private property is established to store the dependency + * - The dependency is assigned directly to that property + */ + public function __construct(private IDBConnection $db) { + } + } + +Advantages +---------- + +- **Testability:** You can inject mock objects for unit testing. +- **Maintainability:** Changing how a dependency is constructed (in the container) + updates it wherever it is injected in the application. +- **Explicitness:** Dependencies are clearly listed in constructors or method signatures, + improving readability and maintainability. + +Controller Injection -------------------- -For controllers it's possible to also have dependencies injected into methods. +For controllers, Nextcloud allows dependencies to also be injected directly into +individual methods, not just constructors. This is referred to as *method injection* and +enables you to specify dependencies only where needed, potentially reducing resource +usage for rarely required services. .. code-block:: php :caption: lib/Controller/ApiController.php - :emphasize-lines: 12-13, 16-17 + :emphasize-lines: 15-16, 19-20 registerService(AuthorController::class, function(ContainerInterface $c): AuthorController { return new AuthorController( $c->get('appName'), - $c->get(Request::class), + $c->get(IRequest::class), $c->get(AuthorService::class) ); }); @@ -171,7 +259,7 @@ The container works in the following way: return new AuthorController( $c->get('appName'), - $c->get(Request::class), + $c->get(IRequest::class), $c->get(AuthorService::class) ); @@ -187,8 +275,8 @@ The container works in the following way: * **AuthorMapper** is queried:: - $container->registerService(AuthorMappers::class, function(ContainerInterface $c): AuthorMapper { - return new AuthorService( + $container->registerService(AuthorMapper::class, function(ContainerInterface $c): AuthorMapper { + return new AuthorMapper( $c->get(IDBConnection::class) ); }); From a57318c550889bc0cd86892caf387a316e521674 Mon Sep 17 00:00:00 2001 From: Josh Date: Thu, 6 Nov 2025 12:09:09 -0500 Subject: [PATCH 2/2] chore: add line highlights to more DI examples Signed-off-by: Josh --- developer_manual/basics/dependency_injection.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/developer_manual/basics/dependency_injection.rst b/developer_manual/basics/dependency_injection.rst index 3211a61b51f..fe3845d654d 100644 --- a/developer_manual/basics/dependency_injection.rst +++ b/developer_manual/basics/dependency_injection.rst @@ -56,6 +56,7 @@ centralizes object creation logic. For example, consider the following pattern: .. code-block:: php + :emphasize-lines: 10, 14 /** * Without dependency injection: @@ -77,6 +78,7 @@ For example, consider the following pattern: With dependency injection, you would instead pass the dependency into the constructor: .. code-block:: php + :emphasize-lines: 10, 13, 15 /** * Using dependency injection: @@ -99,6 +101,7 @@ With dependency injection, you would instead pass the dependency into the constr Or, more succinctly, by using constructor property promotion (since PHP 7.4): .. code-block:: php + :emphasize-lines: 18 /** * Using dependency injection with constructor property promotion: