mirror of
https://github.com/nextcloud/documentation.git
synced 2026-01-03 02:09:45 +07:00
166 lines
5.4 KiB
ReStructuredText
166 lines
5.4 KiB
ReStructuredText
=========
|
|
Container
|
|
=========
|
|
|
|
.. sectionauthor:: Bernhard Posselt <dev@bernhard-posselt.com>
|
|
|
|
The App Framework assembles the application by using a container based on the software pattern `Dependency Injection <https://en.wikipedia.org/wiki/Dependency_injection>`_. This makes the code easier to test and thus easier to maintain.
|
|
|
|
If you are unfamiliar with this pattern, watch the following videos:
|
|
|
|
* `Dependency Injection and the art of Services and Containers Tutorial <http://www.youtube.com/watch?v=DcNtg4_i-2w>`_
|
|
* `Google Clean Code Talks <http://www.youtube.com/watch?v=RlfLCWKxHJ0>`_
|
|
|
|
Dependency Injection
|
|
====================
|
|
Dependency Injection sounds pretty complicated but it just means: Don't new dependencies in your constructor or methods but pass them in. So this:
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
// without dependency injection
|
|
class AuthorMapper {
|
|
|
|
private $db;
|
|
|
|
public function __construct() {
|
|
$this->db = new Db();
|
|
}
|
|
|
|
}
|
|
|
|
would turn into this by using Dependency Injection:
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
// with dependency injection
|
|
class AuthorMapper {
|
|
|
|
private $db;
|
|
|
|
public function __construct($db) {
|
|
$this->db = $db;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
Using a container
|
|
=================
|
|
Passing dependencies into the constructor rather than newing them in the constructor has the following drawback: Every line in the source code where **new AuthorMapper** is being used has to be changed, once a new constructor argument is being added to it.
|
|
|
|
The solution for this particular problem is to limit the **new AuthorMapper** to one file, the container. The container contains all the factories for creating these objects and is configured in :file:`appinfo/application.php`.
|
|
|
|
|
|
To add the app's classes simply open the :file:`appinfo/application.php` use the **registerService** method on the container object:
|
|
|
|
.. code-block:: php
|
|
|
|
<?php
|
|
|
|
namespace OCA\MyApp\AppInfo;
|
|
|
|
use \OCP\AppFramework\App;
|
|
|
|
use \OCA\MyApp\Controller\AuthorController;
|
|
use \OCA\MyApp\Service\AuthorService;
|
|
use \OCA\MyApp\Db\AuthorMapper;
|
|
|
|
class MyApp extends App {
|
|
|
|
|
|
/**
|
|
* Define your dependencies in here
|
|
*/
|
|
public function __construct(array $urlParams=array()){
|
|
parent::__construct('myapp', $urlParams);
|
|
|
|
$container = $this->getContainer();
|
|
|
|
/**
|
|
* Controllers
|
|
*/
|
|
$container->registerService('AuthorController', function($c){
|
|
return new AuthorController(
|
|
$c->query('AppName'),
|
|
$c->query('Request'),
|
|
$c->query('AuthorService')
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Services
|
|
*/
|
|
$container->registerService('AuthorService', function($c){
|
|
return new AuthorService(
|
|
$c->query('AuthorMapper')
|
|
);
|
|
});
|
|
|
|
/**
|
|
* Services
|
|
*/
|
|
$container->registerService('AuthorMappers', function($c){
|
|
return new AuthorService(
|
|
$c->query('ServerContainer')->getDb()
|
|
);
|
|
});
|
|
}
|
|
}
|
|
|
|
How the container works
|
|
=======================
|
|
|
|
The container works in the following way:
|
|
|
|
* :doc:`A request comes in and is matched against a route <request>` (for the AuthorController in this case)
|
|
* The matched route queries **AuthorController** service form the container::
|
|
|
|
return new AuthorController(
|
|
$c->query('AppName'),
|
|
$c->query('Request'),
|
|
$c->query('AuthorService')
|
|
);
|
|
|
|
* The **AppName** is queried and returned from the baseclass
|
|
* The **Request** is queried and returned from the server container
|
|
* **AuthorService** is queried::
|
|
|
|
$container->registerService('AuthorService', function($c){
|
|
return new AuthorService(
|
|
$c->query('AuthorMapper')
|
|
);
|
|
});
|
|
|
|
* **AuthorMapper** is queried::
|
|
|
|
$container->registerService('AuthorMappers', function($c){
|
|
return new AuthorService(
|
|
$c->query('ServerContainer')->getDb()
|
|
);
|
|
});
|
|
|
|
* The **database connection** is returned from the server container
|
|
* Now **AuthorMapper** has all of its dependencies and the object is being returned
|
|
* **AuthorService** gets the **AuthorMapper** and returns the object
|
|
* **AuthorController** gets the **AuthorService** and finally the controller can be newed and the object is being returned
|
|
|
|
So basically the container is used as a giant factory to build all the classes that are needed for the application. Because it centralizes all the creation of objects (the **new Class()** lines), it is very easy to add new constructor parameters without breaking existing code: only the **__construct** method and the container line where the **new** is being called need to be changed.
|
|
|
|
Which classes should be added
|
|
=============================
|
|
In general all of the app's controllers need to be registered inside the container. Then following question is: What goes into the constructor of the controller? Pass everything into the controller constructor that is responsible matches one of the following criteria:
|
|
|
|
* It does I/O (database, write/read to files)
|
|
* It is a global (e.g. $_POST, etc. This is in the request class by the way)
|
|
* The output does not depend on the input variables (also called `impure function <http://en.wikipedia.org/wiki/Pure_function>`_), e.g. time, random number generator
|
|
* It is a service, basically it would make sense to swap it out for a different object
|
|
|
|
What not to inject:
|
|
|
|
* It is pure data and has methods that only act upon it (arrays, data objects)
|
|
* It is a `pure function <http://en.wikipedia.org/wiki/Pure_function>`_
|