Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions developer_manual/app_publishing_maintenance/upgrade-guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,59 @@ Once you've created and published the first version of your app, you will want t

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

Upgrading to Nextcloud 20
-------------------------

.. 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.

Back-end changes
^^^^^^^^^^^^^^^^

.. _upgrade-psr11:

PSR-11 integration
******************

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.

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:

.. code-block:: php

// old
$container->registerService('DecryptAll', function (IAppContainer $c) {
return new DecryptAll(
$c->query('Util'),
$c->query(KeyManager::class),
$c->query('Crypt'),
$c->query(ISession::class)
)
})

becomes

.. code-block:: php

// new
$container->registerService('DecryptAll', function (ContainerInterface $c) {
return new DecryptAll(
$c->get('Util'),
$c->get(KeyManage::class'),
$c->get('Crypt'),
$c->get(ISession::class)
)
})

.. 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.

Deprecated APIs
***************

* ``\OCP\AppFramework\IAppContainer``: see :ref:`upgrade-psr11`
* ``\OCP\IContainer``: see :ref:`upgrade-psr11`
* ``\OCP\IServerContainer``: see :ref:`upgrade-psr11`


Upgrading to Nextcloud 19
-------------------------

Expand Down
72 changes: 41 additions & 31 deletions developer_manual/basics/dependency_injection.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ new dependencies in your constructor or methods but pass them in. So this:
// without dependency injection
class AuthorMapper {

/** @var IDBConnection */
private $db;

public function __construct() {
Expand All @@ -42,12 +43,15 @@ would turn into this by using Dependency Injection:

<?php

use OCP\IDBConnection;

// with dependency injection
class AuthorMapper {

/** @var IDBConnection */
private $db;

public function __construct($db) {
public function __construct(IDBConnection $db) {
$this->db = $db;
}

Expand All @@ -69,6 +73,9 @@ 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:`lib/AppInfo/Application.php`.

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

To add the app's classes simply open the :file:`lib/AppInfo/Application.php` and
use the **registerService** method on the container object:
Expand All @@ -79,11 +86,12 @@ use the **registerService** method on the container object:

namespace OCA\MyApp\AppInfo;

use \OCP\AppFramework\App;
use OCP\AppFramework\App;

use \OCA\MyApp\Controller\AuthorController;
use \OCA\MyApp\Service\AuthorService;
use \OCA\MyApp\Db\AuthorMapper;
use OCA\MyApp\Controller\AuthorController;
use OCA\MyApp\Service\AuthorService;
use OCA\MyApp\Db\AuthorMapper;
use Psr\Container\ContainerInterface;

class Application extends App {

Expand All @@ -99,29 +107,29 @@ use the **registerService** method on the container object:
/**
* Controllers
*/
$container->registerService('AuthorController', function($c){
$container->registerService('AuthorController', function(ContainerInterface $c){
return new AuthorController(
$c->query('AppName'),
$c->query('Request'),
$c->query('AuthorService')
$c->get('AppName'),
$c->get('Request'),
$c->get('AuthorService')
);
});

/**
* Services
*/
$container->registerService('AuthorService', function($c){
$container->registerService('AuthorService', function(ContainerInterface $c){
return new AuthorService(
$c->query('AuthorMapper')
$c->get('AuthorMapper')
);
});

/**
* Mappers
*/
$container->registerService('AuthorMapper', function($c){
$container->registerService('AuthorMapper', function(ContainerInterface $c){
return new AuthorMapper(
$c->query('ServerContainer')->getDatabaseConnection()
$c->get('ServerContainer')->getDatabaseConnection()
);
});
}
Expand All @@ -136,26 +144,26 @@ The container works in the following way:
* The matched route queries **AuthorController** service from the container::

return new AuthorController(
$c->query('AppName'),
$c->query('Request'),
$c->query('AuthorService')
$c->get('AppName'),
$c->get('Request'),
$c->get('AuthorService')
);

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

$container->registerService('AuthorService', function($c){
$container->registerService('AuthorService', function(ContainerInterface $c){
return new AuthorService(
$c->query('AuthorMapper')
$c->get('AuthorMapper')
);
});

* **AuthorMapper** is queried::

$container->registerService('AuthorMappers', function($c){
$container->registerService('AuthorMappers', function(ContainerInterface $c){
return new AuthorService(
$c->query('ServerContainer')->getDatabaseConnection()
$c->get('ServerContainer')->getDatabaseConnection()
);
});

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

In Nextcloud it is possible to omit the **lib/AppInfo/Application.php** and use automatic dependency assembly instead.
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*.

How does automatic assembly work
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
How does auto-wiring work
^^^^^^^^^^^^^^^^^^^^^^^^^

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.:
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.:

* **SomeType $type** will use **$container->query('SomeType')**
* **$variable** will use **$container->query('variable')**
* **SomeType $type** will use **$container->get('SomeType')**
* **$variable** will use **$container->get('variable')**

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

Expand All @@ -203,12 +211,12 @@ So basically the following is now possible:

$app = new \OCP\AppFramework\App('myapp');

$class2 = $app->getContainer()->query('OCA\MyApp\MyTestClass2');
$class2 = $app->getContainer()->get('OCA\MyApp\MyTestClass2');

$class2 instanceof MyTestClass2; // true
$class2->class instanceof MyTestClass; // true
$class2->appName === 'appname'; // true
$class2 === $app->getContainer()->query('OCA\MyApp\MyTestClass2'); // true
$class2 === $app->getContainer()->get('OCA\MyApp\MyTestClass2'); // true

.. 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.

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

* A request is matched for the route, e.g. with the name **page#index**
* The appropriate container is being queried for the entry PageController (to keep backwards compatibility)
* 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
* 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

How does this affect controllers
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -284,8 +292,8 @@ Interfaces and primitive types can not be instantiated, so the container can not
$container->registerParameter('TableName', 'my_app_table');

// the interface is called IAuthorMapper and AuthorMapper implements it
$container->registerService('OCA\MyApp\Db\IAuthorMapper', function ($c) {
return $c->query('OCA\MyApp\Db\AuthorMapper');
$container->registerService('OCA\MyApp\Db\IAuthorMapper', function (ContainerInterface $c) {
return $c->get('OCA\MyApp\Db\AuthorMapper');
});
}

Expand Down Expand Up @@ -380,3 +388,5 @@ 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>`_

.. _`reflection`: https://www.php.net/manual/en/book.reflection.php
12 changes: 11 additions & 1 deletion developer_manual/digging_deeper/psr.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,14 @@ On this page you find information about the implemented `php standards recommend
PSR-3: Logger Interface
-----------------------

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.
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.

.. _psr11:

PSR-11: Container Interface
---------------------------

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.

.. _`PSR-3`: https://www.php-fig.org/psr/psr-3/
.. _`PSR-11`: https://www.php-fig.org/psr/psr-11/