Prudence!
Pimple est maintenant fermé pour modifications. Aucune nouvelle fonctionnalité ne sera ajoutée et aucune modification esthétique ne sera acceptée non plus. Les seules modifications acceptées sont la compatibilité avec les versions PHP les plus récentes et les correctifs de problèmes de sécurité.
Prudence!
Ceci est la documentation de Pimple 3.x. Si vous utilisez Pimple 1.x, lisez la documentation Pimple 1.x. La lecture du code de Pimple 1.x est également un bon moyen d'en savoir plus sur la création d'un simple conteneur d'injection de dépendances (les versions récentes de Pimple sont davantage axées sur les performances).
Pimple est un petit conteneur d'injection de dépendances pour PHP.
Avant d'utiliser Pimple dans votre projet, ajoutez-le à votre fichier composer.json
:
$ ./composer.phar require pimple/pimple " ^3.0 "
Créer un conteneur consiste à créer une instance Container
:
use Pimple Container ;
$ container = new Container ();
Comme beaucoup d'autres conteneurs d'injection de dépendances, Pimple gère deux types de données différents : les services et les paramètres .
Un service est un objet qui fait quelque chose dans le cadre d’un système plus vaste. Exemples de services : une connexion à une base de données, un moteur de modèles ou un logiciel de messagerie. Presque n'importe quel objet global peut être un service.
Les services sont définis par des fonctions anonymes qui renvoient une instance d'un objet :
// define some services
$ container [ ' session_storage ' ] = fn ( $ c ) => new SessionStorage ( ' SESSION_ID ' );
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);
Notez que la fonction anonyme a accès à l'instance de conteneur actuelle, autorisant les références à d'autres services ou paramètres.
Comme les objets ne sont créés que lorsque vous les obtenez, l'ordre des définitions n'a pas d'importance.
Utiliser les services définis est également très simple :
// get the session object
$ session = $ container [ ' session ' ];
// the above call is roughly equivalent to the following code:
// $storage = new SessionStorage('SESSION_ID');
// $session = new Session($storage);
Par défaut, chaque fois que vous obtenez un service, Pimple en renvoie la même instance . Si vous souhaitez qu'une instance différente soit renvoyée pour tous les appels, enveloppez votre fonction anonyme avec la méthode factory()
$ container [ ' session ' ] = $ container -> factory ( fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]));
Désormais, chaque appel à $container['session']
renvoie une nouvelle instance de la session.
Définir un paramètre permet de faciliter la configuration de votre conteneur de l'extérieur et de stocker des valeurs globales :
// define some parameters
$ container [ ' cookie_name ' ] = ' SESSION_ID ' ;
$ container [ ' session_storage_class ' ] = ' SessionStorage ' ;
Si vous modifiez la définition du service session_storage
comme ci-dessous :
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]);
Vous pouvez désormais facilement modifier le nom du cookie en remplaçant le paramètre cookie_name
au lieu de redéfinir la définition du service.
Étant donné que Pimple considère les fonctions anonymes comme des définitions de service, vous devez envelopper les fonctions anonymes avec la méthode protect()
pour les stocker en tant que paramètres :
$ container [ ' random_func ' ] = $ container -> protect ( fn () => rand ());
Dans certains cas, vous souhaiterez peut-être modifier une définition de service une fois qu'elle a été définie. Vous pouvez utiliser la méthode extend()
pour définir du code supplémentaire à exécuter sur votre service juste après sa création :
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]);
$ container -> extend ( ' session_storage ' , function ( $ storage , $ c ) {
$ storage ->. . .();
return $ storage ;
});
Le premier argument est le nom du service à étendre, le second une fonction qui accède à l'instance d'objet et au conteneur.
Si vous utilisez les mêmes bibliothèques encore et encore, vous souhaiterez peut-être réutiliser certains services d'un projet au suivant ; regroupez vos services dans un fournisseur en implémentant PimpleServiceProviderInterface
:
use Pimple Container ;
class FooProvider implements Pimple ServiceProviderInterface
{
public function register ( Container $ pimple )
{
// register some services and parameters
// on $pimple
}
}
Ensuite, enregistrez le fournisseur sur un conteneur :
$ pimple -> register ( new FooProvider ());
Lorsque vous accédez à un objet, Pimple appelle automatiquement la fonction anonyme que vous avez définie, qui crée l'objet de service pour vous. Si vous souhaitez obtenir un accès brut à cette fonction, vous pouvez utiliser la méthode raw()
:
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);
$ sessionFunction = $ container -> raw ( ' session ' );
Pour des raisons historiques, la classe Container
n'implémente pas le PSR-11 ContainerInterface
. Cependant, Pimple fournit une classe d'assistance qui vous permettra de découpler votre code de la classe conteneur Pimple.
La classe PimplePsr11Container
vous permet d'accéder au contenu d'un conteneur Pimple sous-jacent à l'aide des méthodes PsrContainerContainerInterface
:
use Pimple Container ;
use Pimple Psr11 Container as PsrContainer ;
$ container = new Container ();
$ container [ ' service ' ] = fn ( $ c ) => new Service ();
$ psr11 = new PsrContainer ( $ container );
$ controller = function ( PsrContainer $ container ) {
$ service = $ container -> get ( ' service ' );
};
$ controller ( $ psr11 );
Parfois, un service a besoin d'accéder à plusieurs autres services sans être sûr qu'ils seront tous réellement utilisés. Dans ces cas-là, vous souhaiterez peut-être que l’instanciation des services soit paresseuse.
La solution traditionnelle consiste à injecter l’intégralité du conteneur de services pour obtenir uniquement les services réellement nécessaires. Cependant, cela n'est pas recommandé car cela donne aux services un accès trop large au reste de l'application et cache leurs dépendances réelles.
Le ServiceLocator
est destiné à résoudre ce problème en donnant accès à un ensemble de services prédéfinis tout en les instanciant uniquement lorsqu'ils sont réellement nécessaires.
Il vous permet également de rendre vos services disponibles sous un nom différent de celui utilisé pour les enregistrer. Par exemple, vous souhaiterez peut-être utiliser un objet qui s'attend à ce qu'une instance de EventDispatcherInterface
soit disponible sous le nom event_dispatcher
alors que votre répartiteur d'événements a été enregistré sous le nom dispatcher
:
use Monolog Logger ;
use Pimple Psr11 ServiceLocator ;
use Psr Container ContainerInterface ;
use Symfony Component EventDispatcher EventDispatcher ;
class MyService
{
/**
* "logger" must be an instance of PsrLogLoggerInterface
* "event_dispatcher" must be an instance of SymfonyComponentEventDispatcherEventDispatcherInterface
*/
private $ services ;
public function __construct ( ContainerInterface $ services )
{
$ this -> services = $ services ;
}
}
$ container [ ' logger ' ] = fn ( $ c ) => new Monolog Logger ();
$ container [ ' dispatcher ' ] = fn ( $ c ) => new EventDispatcher ();
$ container [ ' service ' ] = function ( $ c ) {
$ locator = new ServiceLocator ( $ c , array ( ' logger ' , ' event_dispatcher ' => ' dispatcher ' ));
return new MyService ( $ locator );
};
Passer une collection d'instances de services dans un tableau peut s'avérer inefficace si la classe qui consomme la collection n'a besoin que de la parcourir ultérieurement, lorsqu'une de ses méthodes est appelée. Cela peut également entraîner des problèmes s'il existe une dépendance circulaire entre l'un des services stockés dans la collection et la classe qui le consomme.
La classe ServiceIterator
vous aide à résoudre ces problèmes. Il reçoit une liste de noms de services lors de l'instanciation et récupérera les services lors d'une itération :
use Pimple Container ;
use Pimple ServiceIterator ;
class AuthorizationService
{
private $ voters ;
public function __construct ( $ voters )
{
$ this -> voters = $ voters ;
}
public function canAccess ( $ resource )
{
foreach ( $ this -> voters as $ voter ) {
if ( true === $ voter -> canAccess ( $ resource )) {
return true ;
}
}
return false ;
}
}
$ container = new Container ();
$ container [ ' voter1 ' ] = fn ( $ c ) => new SomeVoter ();
$ container [ ' voter2 ' ] = fn ( $ c ) => new SomeOtherVoter ( $ c [ ' auth ' ]);
$ container [ ' auth ' ] = fn ( $ c ) => new AuthorizationService ( new ServiceIterator ( $ c , array ( ' voter1 ' , ' voter2 ' ));