Documentation

Table Of Contents

Previous topic

< Liste d’exemple de projets

Next topic

L’architecture MVC >

This Page

Injection de dépendance/Localisation de Service

Phalcon\Di est un composant qui met en œuvre l’Injection de Dépendance et la Localisation de Service et il est lui-même un conteneur pour cela.

Comme Phalcon est fortement découplé, Phalcon\Di est essentiel pour intégrer les différents composants dans le framework. Le développeur peut également exploiter ce composant pour injecter des dépendances et gérer les instances globales des différentes classes utilisées dans l’application.

A la base, ce composant implémente le patron Inversion de Contrôle. En appliquant cela, les objets ne recoivent pas leur dépendances en utilisant des accesseurs ou des constructeurs, mais en interrogeant un service injecteur de dépendance. Ceci réduit la complexité tant qu’il n’y aura qu’une seule façon d’obtenir les dépendances nécessaires au composant.

De plus, ce patron augmente la testabilité du code, le rendant ainsi moins vulnérable aux erreurs.

Inscription de services dans le conteneur

Le framework comme le développeur peuvent inscrire des service. Lorqu’un composant A nécessite un composant B (ou une instance de cette classe) pour fonctionner, il peut demander le composant B au conteneur plutôt que créer une nouvelle instance du composant B.

Cette façon de faire procure plusieurs avantages:

  • Nous pouvons facilement remplacer un composant par un autre réalisé par nos soins ou un tiers.
  • Nous avons un contrôle complet sur l’initialisation de l’objet, nous permettant de préparer les objets comme nous le souhaitons avant de les livrer aux composants.
  • Nous pouvons récupérer des instances globales de composant, d’une manière structurée et unifiée.

Plusieurs styles de définitions permettent d’inscrire les services:

Inscription simple

Comme vu précédemment, il existe plusieurs façons d’inscrire un service. Voici ceux que nous appelons “simple”:

Chaîne de caractères (string)

Ce mode s’attend à un nom de classe valide, retournant un objet de la classe spécifiée, qui si elle n’est pas chargée, le sera en utilisant un chargeur automatique de classes. Ce mode de définition ne permet pas de spécifier des arguments pour constructeur de la classe ni des paramètres:

<?php

// Return new Phalcon\Http\Request();
$di->set(
    "request",
    "Phalcon\\Http\\Request"
);

Instance de classe

Ce mode s’attend à un objet. Comme l’objet n’a pas besoin d’être résolu puisqu’il est déjà un objet, certains diront que ce n’est pas vraiment une injection de dépendance. Toutefois, cela peut être utile si vous souhaitez forcer la dépendance retournée à être toujours le même objet ou la même valeur:

<?php

use Phalcon\Http\Request;

// Return new Phalcon\Http\Request();
$di->set(
    "request",
    new Request()
);

Fermetures (Closures)/Fonctions anonymes:

Cette méthode offre une grande liberté pour construire les dépendances comme désirées, cependant il est difficile de changer extérieurement sans avoir à changer complètement la définition de la dépendance:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di->set(
    "db",
    function () {
        return new PdoMysql(
            [
                "host"     => "localhost",
                "username" => "root",
                "password" => "secret",
                "dbname"   => "blog",
            ]
        );
    }
);

Certaines limites peuvent être contournées en passant des variables supplémentaires à l’environnement de la fermeture:

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$config = new Config(
    [
        "host"     => "127.0.0.1",
        "username" => "user",
        "password" => "pass",
        "dbname"   => "my_database",
    ]
);

// Utilisation de la variable $config dans la portée courante.
$di->set(
    "db",
    function () use ($config) {
        return new PdoMysql(
            [
                "host"     => $config->host,
                "username" => $config->username,
                "password" => $config->password,
                "dbname"   => $config->name,
            ]
        );
    }
);

Vous pouvez également accéder à d’autres services DI en utilisant la méthode get():

<?php

use Phalcon\Config;
use Phalcon\Db\Adapter\Pdo\Mysql as PdoMysql;

$di->set(
    "config",
    function () {
        return new Config(
            [
                "host"     => "127.0.0.1",
                "username" => "utilisateur",
                "password" => "mot_de_passe",
                "dbname"   => "ma_base_de_donnees",
            ]
        );
    }
);

// Avec le service 'config' du DI
$di->set(
    "db",
    function () {
        $config = $this->get("config");

        return new PdoMysql(
            [
                "host"     => $config->host,
                "username" => $config->username,
                "password" => $config->password,
                "dbname"   => $config->name,
            ]
        );
    }
);

Inscription Complexe

S’il est nécessaire de changer la définition d’un service sans devoir instancier/résoudre le service, nous devrons alors définir les services en utilisant la syntaxe tableau. La définition d’un service sous forme de tableau peut être un peu plus verbeuse:

<?php

use Phalcon\Logger\Adapter\File as LoggerFile;

// Inscription d'un service "logger" avec un nom de classe et ses paramètres
$di->set(
    "logger",
    [
        "className" => "Phalcon\\Logger\\Adapter\\File",
        "arguments" => [
            [
                "type"  => "parameter",
                "value" => "../apps/logs/error.log",
            ]
        ]
    ]
);

// En utilisant une fonction anonyme
$di->set(
    "logger",
    function () {
        return new LoggerFile("../apps/logs/error.log");
    }
);

Les deux inscriptions précédentes produisent le même résultat. Cependant, la définition sous forme de tableau permet une altération des paramètres du service si nécessaire:

<?php

// Changement du nom de service
$di->getService("logger")->setClassName("MyCustomLogger");

// Changement du premier paramètre sans instancier le logger
$di->getService("logger")->setParameter(
    0,
    [
        "type"  => "parameter",
        "value" => "../apps/logs/error.log",
    ]
);

De plus, en utilisant la syntaxe tableau, vous pouvez exploiter trois type d’injection de dépendance:

Injection de constructeur

Ce type d’injection transmet les dépendances au constructeur de la classe. Admettons que nous ayons le composant suivant:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    protected $_response;

    protected $_someFlag;



    public function __construct(Response $response, $someFlag)
    {
        $this->_response = $response;
        $this->_someFlag = $someFlag;
    }
}

Le service peut être inscrit de cette façon:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response"
    ]
);

$di->set(
    "someComponent",
    [
        "className" => "SomeApp\\SomeComponent",
        "arguments" => [
            [
                "type" => "service",
                "name" => "response",
            ],
            [
                "type"  => "parameter",
                "value" => true,
            ],
        ]
    ]
);

Le service “response” (Phalcon\Http\Response) est résolu pour être transmis en premier argument au constructeur, alors que le second est une valeur booléenne (true) transmise telle quelle.

Injection d’accesseur

Les classes peuvent posséder des accesseurs pour injecter des dépendances optionnelles. Nos précédentes classes peuvent être modifiées pour accepter des dépendances avec des accesseurs:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    protected $_response;

    protected $_someFlag;



    public function setResponse(Response $response)
    {
        $this->_response = $response;
    }

    public function setFlag($someFlag)
    {
        $this->_someFlag = $someFlag;
    }
}

Un service avec une injection par accesseur peut être inscrite comme suit:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response",
    ]
);

$di->set(
    "someComponent",
    [
        "className" => "SomeApp\\SomeComponent",
        "calls"     => [
            [
                "method"    => "setResponse",
                "arguments" => [
                    [
                        "type" => "service",
                        "name" => "response",
                    ]
                ]
            ],
            [
                "method"    => "setFlag",
                "arguments" => [
                    [
                        "type"  => "parameter",
                        "value" => true,
                    ]
                ]
            ]
        ]
    ]
);

Injection de propriétés

Une stratégie moins courante est d’injecter directement des dépendances ou des paramètres aux attributs publics de la classe:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{
    /**
     * @var Response
     */
    public $response;

    public $someFlag;
}

Un service avec un injection de propriétés peut être inscrite comme suit:

<?php

$di->set(
    "response",
    [
        "className" => "Phalcon\\Http\\Response",
    ]
);

$di->set(
    "someComponent",
    [
        "className"  => "SomeApp\\SomeComponent",
        "properties" => [
            [
                "name"  => "response",
                "value" => [
                    "type" => "service",
                    "name" => "response",
                ],
            ],
            [
                "name"  => "someFlag",
                "value" => [
                    "type"  => "parameter",
                    "value" => true,
                ],
            ]
        ]
    ]
);

Les différents types de paramètre supportés sont les suivants:

Type Description Exemple
paramètre Représente une valeur littérale transmise en paramètre ["type" => "parameter", "value" => 1234]
service Représente un autre service dans le conteneur de services ["type" => "service", "name" => "request"]
instance Représente un objet qui doit être construit dynamiquement ["type" => "instance", "className" => "DateTime", "arguments" => ["now"]]

La résolution d’un service dont la définition est complexe peut être légèrement plus lente que pour les définitions simples vues précédemment. Cependant, ceci fournit une approche plus robuste pour définir et injecter des services.

Le mélange de différents types de définitions est permis. Chacun décide de la méthode d’inscription des service la plus appropriée en fonction des besoins de l’application.

Forme tableau

L’écriture sous forme de tableau est possible pour inscrire des services:

<?php

use Phalcon\Di;
use Phalcon\Http\Request;

// Création du conteneur d'Injection de Dépendance
$di = new Di();

// D'après son nom
$di["request"] = "Phalcon\\Http\\Request";

// Chargement tardif avec une fonction anonyme
$di["request"] = function () {
    return new Request();
};

// En inscrivant directement une instance
$di["request"] = new Request();

// Avec un tableau de définition
$di["request"] = [
    "className" => "Phalcon\\Http\\Request",
];

Dans les exemples précédents, lorsque le framework doit accéder aux données demandées, il interroge le service identifié en tant que ‘request’ dans le conteneur. Le conteneur retourne une instance du service demandé. Le développeur peut éventuellement remplacer les composants selon ses besoins.

Chacune des méthodes (vues dans les exemples précédents) utilisée pour définir/inscrire un service a ses avantages et ses inconvénients. C’est au développeur de choisir laquelle utiliser en fonction des exigences.

Définir un service par une chaîne de caractères est simple mais manque de souplesse. Définir un service par un tableau offre plus de flexibilité mais rend le code plus compliqué. La fonction lambda est un bon équilibre entre les deux mais risque de nécessiter plus de maintenance que nécessaire.

Phalcon\Di offre un chargement tardif pour chaque service qu’il stocke. A moins que le développeur choisisse d’instancier directement et de le stocker dans le conteneur, chaque objet qui lui est confié (via tableau, chaîne de caractères, etc.) sera chargé tardivement c.à.d instancié lors de la demande.

Résolution de services

L’obtention d’un service à partir d’un conteneur peut se faire simplement en utilisant la méthode “get”. Une nouvelle instance du service sera retournée:

<?php $request = $di->get("request");

Ou en invoquant la méthode magique:

<?php

$request = $di->getRequest();

Ou en utilisant l’écriture tableau:

<?php

$request = $di["request"];

Les arguments sont transmis au constructeur en ajoutant un tableau en paramètre de la méthode “get”:

<?php

// new MyComponent("some-parameter", "other")
$component = $di->get(
    "MyComponent",
    [
        "some-parameter",
        "other",
    ]
);

Evénements

Phalcon\Di est capable d’envoyer des événements à un EventsManager s’il existe. Les événements sont déclenchés en utilisant le type “di”. Les événements qui retourne la valeur booléenne faux peuvent interrompre l’opération en cours. Les événements suivants son supportés:

Nom d’événement Déclenchement Stoppe l’opération ? Destinataire
beforeServiceResolve Déclenché avant la résolution de service. Les écouteurs reçoivent le nom du service ainsi que les paramètres qui lui sont transmis Non Ecouteurs
afterServiceResolve Déclenché avant la résolution de service. Les écouteurs reçoivent le nom du service, l’instance, ainsi que les paramètres qui lui sont transmis Non Ecouteurs

Services partagés

Les services peuvent être inscrits en tant que service “partagé”. Ceci signifie qu’ils se comporteront toujours comme des singletons. Une fois que le service est résolu une première fois la même instance est systématiquement retournée lorsqu’un consommateur récupère le service depuis le conteneur:

<?php

use Phalcon\Session\Adapter\Files as SessionFiles;

// Inscription du service de session comme "toujours partagé"
$di->setShared(
    "session",
    function () {
        $session = new SessionFiles();

        $session->start();

        return $session;
    }
);

// Localisation du service pour la première fois
$session = $di->get("session");

// Retourne l'objet instancié initialement
$session = $di->getSession();

Une autre façon d’inscrire des services partagés est de transmettre “true” au troisième paramètre de “set”:

<?php

// Inscription du service de session comme "toujours partagé"
$di->set(
    "session",
    function () {
        // ...
    },
    true
);

Si un service n’est pas inscrit comme partagé et vous voulez être sûr d’accéder à une instance partagée à chaque fois que le service est obtenu auprès de DI, vous pouvez utiliser la méthode ‘getShared’:

<?php

$request = $di->getShared("request");

Manipuler les services individuellement

Une fois qu’un service est inscrit dans le conteneur de services, vous pouvez le récupérer pour le manipuler individuellement:

<?php

use Phalcon\Http\Request;

// Inscription du service "request"
$di->set("request", "Phalcon\\Http\\Request");

// Récupère le service
$requestService = $di->getService("request");

// Modifie sa définition
$requestService->setDefinition(
    function () {
        return new Request();
    }
);

// Le transforme en "partagé"
$requestService->setShared(true);

// Résolution du service (retourne un instance de Phalcon\Http\Request)
$request = $requestService->resolve();

Instanciation de classes via le Conteneur de Services

Lorsque vous demandez un service au conteneur de services, s’il n’en trouve pas un avec le même nom, il tente de charger une classe avec le même nom. Grâce à ce comportement nous pouvons remplacer n’importe quelle autre simplement en inscrivant un service avec son nom:

<?php

// Inscription d'un contrôleur en tant que service
$di->set(
    "IndexController",
    function () {
        $component = new Component();

        return $component;
    },
    true
);

// Inscription d'un contrôleur en tant que service
$di->set(
    "MyOtherComponent",
    function () {
        // Actuellement retourne un autre composant
        $component = new AnotherComponent();

        return $component;
    }
);

// Création d'un instance via le conteneur de service.
$myComponent = $di->get("MyOtherComponent");

Vous pouvez profiter de ceci en instanciant toujours vos classes depuis le conteneur de services (même s’ils ne sont pas inscrits en tant que service). Le DI prendra par défaut un chargeur automatique valide pour charger la classe. En faisant comme ceci, vous pourrez aisément replacer n’importe quelle classe en implémentant une définition pour elle.

Injection automatique pour le DI lui-même

Si une classe ou un composant ai besoin que le DI localise lui-même les services, le DI peut automatiquement s’injecter les instances qu’il crée. Pour ceci, vous devez implémenter l’interface Phalcon\Di\InjectionAwareInterface dans vos classes:

<?php

use Phalcon\DiInterface;
use Phalcon\Di\InjectionAwareInterface;

class MyClass implements InjectionAwareInterface
{
    /**
     * @var DiInterface
     */
    protected $_di;



    public function setDi(DiInterface $di)
    {
        $this->_di = $di;
    }

    public function getDi()
    {
        return $this->_di;
    }
}

Une fois que le service est résolu, la variable $di sera transmise automatiquement à setDi():

<?php

// Inscription du service
$di->set("myClass", "MyClass");

// Résolution du service (NOTE: $myClass->setDi($di) est automatiquement appélée)
$myClass = $di->get("myClass");

Organisation des services en fichiers

Vous pouvez mieux organiser votre application en déplaçant l’inscription des services dans des fichiers distincts au lieu de tout mettre dans l’amorce de l’application:

<?php

$di->set(
    "router",
    function () {
        return include "../app/config/routes.php";
    }
);

Ainsi le fichier (”../app/config/routes.php”) renvoi l’objet résolu:

<?php

$router = new MyRouter();

$router->post("/login");

return $router;

Accès au DI de manière statique

Si nécessaire, vous pouvez accéder au dernier DI créé dans une fonction statique de la façon suivante:

<?php

use Phalcon\Di;

class SomeComponent
{
    public static function someMethod()
    {
        // Récupère le service de session
        $session = Di::getDefault()->getSession();
    }
}

Construction du DI par défaut

Bien que le caractère découplé de Phalcon offre une grande liberté et flexibilité, peut-être que nous voulons simplement l’utiliser comme un framework full-stack. Pour réaliser ceci, le framework fournit une variante de Phalcon\Di appelée Phalcon\Di\FactoryDefault. Cette classe inscrit automatiquement les services appropriés qui sont encapsulés dans le framework afin qu’il agisse comme un full-stack.

<?php

use Phalcon\Di\FactoryDefault;

$di = new FactoryDefault();

Convention de nommage des services

Bien que vous puissiez inscrire les services avec le nom que vous voulez, Phalcon a plusieurs conventions de nommage qui permettent d’obtenir le bon service (built-in) au bon moment.

Nom de service Description Par défaut Partagé
dispatcher Service de ventilation des contrôleurs Phalcon\Mvc\Dispatcher Oui
router Service de routage Phalcon\Mvc\Router Oui
url Service de génération d’URL Phalcon\Mvc\Url Oui
request HTTP Request Environment Service Phalcon\Http\Request Oui
response HTTP Response Environment Service Phalcon\Http\Response Oui
cookies HTTP Cookies Management Service Phalcon\Http\Response\Cookies Oui
filter Service de filtrage des entrées Phalcon\Filter Oui
flash Service des messages flash Phalcon\Flash\Direct Oui
flashSession Service de session des messages flash Phalcon\Flash\Session Oui
session Service de session Phalcon\Session\Adapter\Files Oui
eventsManager Service de gestion des événements Phalcon\Events\Manager Oui
db Service élémentaire de connexion aux bases de données Phalcon\Db Oui
security Auxiliaires de sécurité Phalcon\Security Oui
crypt Cryptage/Décryptage Phalcon\Crypt Oui
tag Aide de génération HTML Phalcon\Tag Oui
escaper Echappement contextuel Phalcon\Escaper Oui
annotations Analyseur d’annotations Phalcon\Annotations\Adapter\Memory Oui
modelsManager Service de gestion des modèles Phalcon\Mvc\Model\Manager Oui
modelsMetadata Service de métadonnées des modèles Phalcon\Mvc\Model\MetaData\Memory Oui
transactionManager Service de gestion des transactions Phalcon\Mvc\Model\Transaction\Manager Oui
modelsCache Cache pour les modèles coté serveur Aucun Non
viewsCache Cache des fragments de vue coté serveur Aucun Non

Création de votre propre DI

Pour remplacer le DI fournit par Phalcon, vous devez soit implementer l’interface Phalcon\DiInterface, soit étendre un existant.

Follow along: