Skip to content

Commit ecdb103

Browse files
Merge pull request #2202 from nextcloud/enhancement/psr-11
Document PSR-11 integration
2 parents 2efddbf + eb3228f commit ecdb103

File tree

3 files changed

+105
-32
lines changed

3 files changed

+105
-32
lines changed

developer_manual/app_publishing_maintenance/upgrade-guide.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,59 @@ Once you've created and published the first version of your app, you will want t
66

77
This document will cover the most important changes in Nextcloud, as well as some guides on how to upgrade existing apps.
88

9+
Upgrading to Nextcloud 20
10+
-------------------------
11+
12+
.. note:: Critical changes were collected `on Github <https://github.com/nextcloud/server/issues/20953>`_. See the original ticket for links to the pull requests and tickets.
13+
14+
Back-end changes
15+
^^^^^^^^^^^^^^^^
16+
17+
.. _upgrade-psr11:
18+
19+
PSR-11 integration
20+
******************
21+
22+
Nextcloud 20 is the first major release of Nextcloud that brings full compatibility with :ref:`psr11`. From this point on it is highly recommended to use this interface mainly as the old ``\OCP\AppFramework\IAppContainer``, ``\OCP\IContainer`` and ``\OCP\IServerContainer`` got deprecated with this change.
23+
24+
If your app requires Nextcloud 20 or later, you can replace any of the old type hints with one of ``\Psr\Container\ContainerInterface`` and replace calls of ``query`` with ``get``, e.g. on the closures used when registering services:
25+
26+
.. code-block:: php
27+
28+
// old
29+
$container->registerService('DecryptAll', function (IAppContainer $c) {
30+
return new DecryptAll(
31+
$c->query('Util'),
32+
$c->query(KeyManager::class),
33+
$c->query('Crypt'),
34+
$c->query(ISession::class)
35+
)
36+
})
37+
38+
becomes
39+
40+
.. code-block:: php
41+
42+
// new
43+
$container->registerService('DecryptAll', function (ContainerInterface $c) {
44+
return new DecryptAll(
45+
$c->get('Util'),
46+
$c->get(KeyManage::class'),
47+
$c->get('Crypt'),
48+
$c->get(ISession::class)
49+
)
50+
})
51+
52+
.. note:: For a smoother transition, the old interfaces were changed so they are based on ``ContainerInterface``, hence you can use ``has`` and ``get`` on ``IContainer`` and sub types.
53+
54+
Deprecated APIs
55+
***************
56+
57+
* ``\OCP\AppFramework\IAppContainer``: see :ref:`upgrade-psr11`
58+
* ``\OCP\IContainer``: see :ref:`upgrade-psr11`
59+
* ``\OCP\IServerContainer``: see :ref:`upgrade-psr11`
60+
61+
962
Upgrading to Nextcloud 19
1063
-------------------------
1164

developer_manual/basics/dependency_injection.rst

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ new dependencies in your constructor or methods but pass them in. So this:
2828
// without dependency injection
2929
class AuthorMapper {
3030
31+
/** @var IDBConnection */
3132
private $db;
3233
3334
public function __construct() {
@@ -42,12 +43,15 @@ would turn into this by using Dependency Injection:
4243
4344
<?php
4445
46+
use OCP\IDBConnection;
47+
4548
// with dependency injection
4649
class AuthorMapper {
4750
51+
/** @var IDBConnection */
4852
private $db;
4953
50-
public function __construct($db) {
54+
public function __construct(IDBConnection $db) {
5155
$this->db = $db;
5256
}
5357
@@ -69,6 +73,9 @@ The solution for this particular problem is to limit the **new AuthorMapper** to
6973
one file, the container. The container contains all the factories for creating
7074
these objects and is configured in :file:`lib/AppInfo/Application.php`.
7175

76+
Nextcloud 20 and later uses the :ref:`PSR-11 standard <psr11>` for the container interface, so working
77+
with the container might feel familiar if you've worked with other php applications
78+
before that also adhere to the convention.
7279

7380
To add the app's classes simply open the :file:`lib/AppInfo/Application.php` and
7481
use the **registerService** method on the container object:
@@ -79,11 +86,12 @@ use the **registerService** method on the container object:
7986
8087
namespace OCA\MyApp\AppInfo;
8188
82-
use \OCP\AppFramework\App;
89+
use OCP\AppFramework\App;
8390
84-
use \OCA\MyApp\Controller\AuthorController;
85-
use \OCA\MyApp\Service\AuthorService;
86-
use \OCA\MyApp\Db\AuthorMapper;
91+
use OCA\MyApp\Controller\AuthorController;
92+
use OCA\MyApp\Service\AuthorService;
93+
use OCA\MyApp\Db\AuthorMapper;
94+
use Psr\Container\ContainerInterface;
8795
8896
class Application extends App {
8997
@@ -99,29 +107,29 @@ use the **registerService** method on the container object:
99107
/**
100108
* Controllers
101109
*/
102-
$container->registerService('AuthorController', function($c){
110+
$container->registerService('AuthorController', function(ContainerInterface $c){
103111
return new AuthorController(
104-
$c->query('AppName'),
105-
$c->query('Request'),
106-
$c->query('AuthorService')
112+
$c->get('AppName'),
113+
$c->get('Request'),
114+
$c->get('AuthorService')
107115
);
108116
});
109117
110118
/**
111119
* Services
112120
*/
113-
$container->registerService('AuthorService', function($c){
121+
$container->registerService('AuthorService', function(ContainerInterface $c){
114122
return new AuthorService(
115-
$c->query('AuthorMapper')
123+
$c->get('AuthorMapper')
116124
);
117125
});
118126
119127
/**
120128
* Mappers
121129
*/
122-
$container->registerService('AuthorMapper', function($c){
130+
$container->registerService('AuthorMapper', function(ContainerInterface $c){
123131
return new AuthorMapper(
124-
$c->query('ServerContainer')->getDatabaseConnection()
132+
$c->get('ServerContainer')->getDatabaseConnection()
125133
);
126134
});
127135
}
@@ -136,26 +144,26 @@ The container works in the following way:
136144
* The matched route queries **AuthorController** service from the container::
137145

138146
return new AuthorController(
139-
$c->query('AppName'),
140-
$c->query('Request'),
141-
$c->query('AuthorService')
147+
$c->get('AppName'),
148+
$c->get('Request'),
149+
$c->get('AuthorService')
142150
);
143151

144152
* The **AppName** is queried and returned from the base class
145153
* The **Request** is queried and returned from the server container
146154
* **AuthorService** is queried::
147155

148-
$container->registerService('AuthorService', function($c){
156+
$container->registerService('AuthorService', function(ContainerInterface $c){
149157
return new AuthorService(
150-
$c->query('AuthorMapper')
158+
$c->get('AuthorMapper')
151159
);
152160
});
153161

154162
* **AuthorMapper** is queried::
155163

156-
$container->registerService('AuthorMappers', function($c){
164+
$container->registerService('AuthorMappers', function(ContainerInterface $c){
157165
return new AuthorService(
158-
$c->query('ServerContainer')->getDatabaseConnection()
166+
$c->get('ServerContainer')->getDatabaseConnection()
159167
);
160168
});
161169

@@ -170,15 +178,15 @@ So basically the container is used as a giant factory to build all the classes t
170178
Use automatic dependency assembly (recommended)
171179
-----------------------------------------------
172180

173-
In Nextcloud it is possible to omit the **lib/AppInfo/Application.php** and use automatic dependency assembly instead.
181+
In Nextcloud it is possible to build classes and their dependencies without having to explicitly register them on the container, as long as the container can `reflect <reflection>`_ the constructor and look up the parameters by their type. This concept is widely known as *auto-wiring*.
174182

175-
How does automatic assembly work
176-
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
183+
How does auto-wiring work
184+
^^^^^^^^^^^^^^^^^^^^^^^^^
177185

178-
Automatic assembly creates new instances of classes just by looking at the class name and its constructor parameters. For each constructor parameter the type or the variable name is used to query the container, e.g.:
186+
Automatic assembly creates new instances of classes just by looking at the class name and its constructor parameters. For each constructor parameter the type or the argument name is used to query the container, e.g.:
179187

180-
* **SomeType $type** will use **$container->query('SomeType')**
181-
* **$variable** will use **$container->query('variable')**
188+
* **SomeType $type** will use **$container->get('SomeType')**
189+
* **$variable** will use **$container->get('variable')**
182190

183191
If all constructor parameters are resolved, the class will be created, saved as a service and returned.
184192

@@ -203,12 +211,12 @@ So basically the following is now possible:
203211
204212
$app = new \OCP\AppFramework\App('myapp');
205213
206-
$class2 = $app->getContainer()->query('OCA\MyApp\MyTestClass2');
214+
$class2 = $app->getContainer()->get('OCA\MyApp\MyTestClass2');
207215
208216
$class2 instanceof MyTestClass2; // true
209217
$class2->class instanceof MyTestClass; // true
210218
$class2->appName === 'appname'; // true
211-
$class2 === $app->getContainer()->query('OCA\MyApp\MyTestClass2'); // true
219+
$class2 === $app->getContainer()->get('OCA\MyApp\MyTestClass2'); // true
212220
213221
.. note:: $AppName is resolved because the container registered a parameter under the key 'AppName' which will return the app id. The lookup is case sensitive so while $AppName will work correctly, using $appName as a constructor parameter will fail.
214222

@@ -223,7 +231,7 @@ How does it affect the request lifecycle
223231

224232
* A request is matched for the route, e.g. with the name **page#index**
225233
* The appropriate container is being queried for the entry PageController (to keep backwards compatibility)
226-
* If the entry does not exist, the container is queried for OCA\\AppName\\Controller\\PageController and if no entry exists, the container tries to create the class by using reflection on its constructor parameters
234+
* If the entry does not exist, the container is queried for OCA\\AppName\\Controller\\PageController and if no entry exists, the container tries to create the class by using `reflection`_ on its constructor parameters
227235

228236
How does this affect controllers
229237
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -284,8 +292,8 @@ Interfaces and primitive types can not be instantiated, so the container can not
284292
$container->registerParameter('TableName', 'my_app_table');
285293
286294
// the interface is called IAuthorMapper and AuthorMapper implements it
287-
$container->registerService('OCA\MyApp\Db\IAuthorMapper', function ($c) {
288-
return $c->query('OCA\MyApp\Db\AuthorMapper');
295+
$container->registerService('OCA\MyApp\Db\IAuthorMapper', function (ContainerInterface $c) {
296+
return $c->get('OCA\MyApp\Db\AuthorMapper');
289297
});
290298
}
291299
@@ -380,3 +388,5 @@ What not to inject:
380388

381389
* It is pure data and has methods that only act upon it (arrays, data objects)
382390
* It is a `pure function <http://en.wikipedia.org/wiki/Pure_function>`_
391+
392+
.. _`reflection`: https://www.php.net/manual/en/book.reflection.php

developer_manual/digging_deeper/psr.rst

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,14 @@ On this page you find information about the implemented `php standards recommend
99
PSR-3: Logger Interface
1010
-----------------------
1111

12-
As of Nextcloud 19, the dependency injection container can inject an instance of a ``\Psr\Log\LoggerInterface``. This is merely a wrapper of the existing (and strongly typed) ``\OCP\ILogger``. Apps may still use the Nextcloud logger, but the PSR-3 implementation shall easy the integration of 3rd party libraries that require the PSR-3 logger.
12+
As of Nextcloud 19, the dependency injection container can inject an instance of a ``\Psr\Log\LoggerInterface``. This is merely a wrapper of the existing (and strongly typed) ``\OCP\ILogger``. Apps may still use the Nextcloud logger, but the `PSR-3`_ implementation shall easy the integration of 3rd party libraries that require the `PSR-3`_ logger.
13+
14+
.. _psr11:
15+
16+
PSR-11: Container Interface
17+
---------------------------
18+
19+
As of Nextcloud 20, the dependency injection container follows the `PSR-11`_ container interface, so you may start type-hinting ``\Psr\Container\ContainerInterface`` whenever you want an instance of a container and use ``has($id)`` to check for existance and ``get($id)`` to retrieve an instance of a service. See the :ref:`dependency injection docs <dependency-injection>` for details.
20+
21+
.. _`PSR-3`: https://www.php-fig.org/psr/psr-3/
22+
.. _`PSR-11`: https://www.php-fig.org/psr/psr-11/

0 commit comments

Comments
 (0)