Осторожность!
Pimple закрыт на внесение изменений. Никаких новых функций добавляться не будет, а также не будут приняты никакие косметические изменения. Единственные принятые изменения — это совместимость с новыми версиями PHP и исправления проблем безопасности.
Осторожность!
Это документация для Pimple 3.x. Если вы используете Pimple 1.x, прочтите документацию Pimple 1.x. Чтение кода Pimple 1.x также является хорошим способом узнать больше о том, как создать простой контейнер внедрения зависимостей (последние версии Pimple больше ориентированы на производительность).
Pimple — это небольшой контейнер внедрения зависимостей для PHP.
Прежде чем использовать Pimple в своем проекте, добавьте его в файл composer.json :
$ ./composer.phar require pimple/pimple " ^3.0 " Создание контейнера — это вопрос создания экземпляра Container :
use Pimple Container ;
$ container = new Container ();Как и многие другие контейнеры внедрения зависимостей, Pimple управляет двумя разными типами данных: сервисами и параметрами .
Служба — это объект, который что-то делает как часть более крупной системы. Примеры сервисов: подключение к базе данных, шаблонизатор или почтовая программа. Практически любой глобальный объект может быть сервисом.
Сервисы определяются анонимными функциями , которые возвращают экземпляр объекта:
// define some services
$ container [ ' session_storage ' ] = fn ( $ c ) => new SessionStorage ( ' SESSION_ID ' );
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);Обратите внимание, что анонимная функция имеет доступ к текущему экземпляру контейнера, позволяя ссылаться на другие службы или параметры.
Поскольку объекты создаются только тогда, когда вы их получаете, порядок определений не имеет значения.
Использовать определенные сервисы также очень просто:
// 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); По умолчанию каждый раз, когда вы получаете услугу, Pimple возвращает один и тот же ее экземпляр . Если вы хотите, чтобы для всех вызовов возвращался другой экземпляр, оберните анонимную функцию методом factory() .
$ container [ ' session ' ] = $ container -> factory ( fn ( $ c ) => new Session ( $ c [ ' session_storage ' ])); Теперь каждый вызов $container['session'] возвращает новый экземпляр сеанса.
Определение параметра позволяет упростить настройку вашего контейнера снаружи и сохранить глобальные значения:
// define some parameters
$ container [ ' cookie_name ' ] = ' SESSION_ID ' ;
$ container [ ' session_storage_class ' ] = ' SessionStorage ' ; Если вы измените определение службы session_storage , как показано ниже:
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]); Теперь вы можете легко изменить имя файла cookie, переопределив параметр cookie_name вместо переопределения определения службы.
Поскольку Pimple рассматривает анонимные функции как определения сервисов, вам необходимо обернуть анонимные функции методом protect() чтобы сохранить их в качестве параметров:
$ container [ ' random_func ' ] = $ container -> protect ( fn () => rand ()); В некоторых случаях вам может потребоваться изменить определение службы после того, как оно было определено. Вы можете использовать метод extend() , чтобы определить дополнительный код, который будет запущен в вашем сервисе сразу после его создания:
$ container [ ' session_storage ' ] = fn ( $ c ) => new $ c [ ' session_storage_class ' ]( $ c [ ' cookie_name ' ]);
$ container -> extend ( ' session_storage ' , function ( $ storage , $ c ) {
$ storage ->. . .();
return $ storage ;
});Первый аргумент — это имя расширяемой службы, второй — функция, которая получает доступ к экземпляру объекта и контейнеру.
Если вы используете одни и те же библиотеки снова и снова, возможно, вам захочется повторно использовать некоторые сервисы из одного проекта в другой; упакуйте свои услуги в поставщика , реализовав PimpleServiceProviderInterface :
use Pimple Container ;
class FooProvider implements Pimple ServiceProviderInterface
{
public function register ( Container $ pimple )
{
// register some services and parameters
// on $pimple
}
}Затем зарегистрируйте провайдера в контейнере:
$ pimple -> register ( new FooProvider ()); Когда вы получаете доступ к объекту, Pimple автоматически вызывает определенную вами анонимную функцию, которая создает для вас объект службы. Если вы хотите получить необработанный доступ к этой функции, вы можете использовать метод raw() :
$ container [ ' session ' ] = fn ( $ c ) => new Session ( $ c [ ' session_storage ' ]);
$ sessionFunction = $ container -> raw ( ' session ' ); По историческим причинам класс Container не реализует ContainerInterface PSR-11. Однако Pimple предоставляет вспомогательный класс, который позволит вам отделить ваш код от класса-контейнера Pimple.
Класс PimplePsr11Container позволяет получить доступ к содержимому базового контейнера Pimple с помощью методов 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 );Иногда сервису требуется доступ к нескольким другим сервисам, но при этом нет уверенности, что все они действительно будут использоваться. В таких случаях вы можете захотеть, чтобы создание экземпляров служб было ленивым.
Традиционное решение — внедрить весь контейнер сервисов, чтобы получить только те сервисы, которые действительно необходимы. Однако это не рекомендуется, поскольку это дает службам слишком широкий доступ к остальной части приложения и скрывает их фактические зависимости.
ServiceLocator предназначен для решения этой проблемы, предоставляя доступ к набору предопределенных сервисов и создавая их экземпляры только тогда, когда это действительно необходимо.
Это также позволяет вам сделать ваши услуги доступными под именем, отличным от того, которое использовалось для их регистрации. Например, вы можете использовать объект, который ожидает, что экземпляр EventDispatcherInterface будет доступен под именем event_dispatcher , в то время как ваш диспетчер событий зарегистрирован под именем 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 );
};Передача коллекции экземпляров сервисов в массив может оказаться неэффективной, если классу, который использует коллекцию, нужно будет перебирать ее только на более позднем этапе, когда вызывается один из его методов. Это также может привести к проблемам, если существует циклическая зависимость между одним из сервисов, хранящихся в коллекции, и классом, который его использует.
Класс ServiceIterator поможет вам решить эти проблемы. Он получает список имен служб во время создания экземпляра и извлекает службы при повторении:
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 ' ));