Создайте сервис контейнер на PHP — минимальный контейнер
Что такое сервис контейнер
Сервис контейнер — это PHP объект, который отвечает за создание других объектов. Вы сообщаете контейнеру, как построить объект, а затем, когда вам нужен его экземпляр в программе, вы запрашиваете его.
Что такое PSR-11
PSR-11 — это документ, определяющий общий интерфейс для сервис контейнеров. Он также определяет два интерфейса Exception
, которые контейнер должен создавать, чтобы соответствовать требованиям.
Сам интерфейс определяет только два требования:
interface ContainerInterface
{
/**
* Finds an entry of the container by its identifier and returns it.
*
* @param string $id Identifier of the entry to look for.
*
* @throws NotFoundExceptionInterface No entry was found for **this** identifier.
* @throws ContainerExceptionInterface Error while retrieving the entry.
*
* @return mixed Entry.
*/
public function get(string $id);
/**
* Returns true if the container can return an entry for the given identifier.
* Returns false otherwise.
*
* `has($id)` returning true does not mean that `get($id)` will not throw an exception.
* It does however mean that `get($id)` will not throw a `NotFoundExceptionInterface`.
*
* @param string $id Identifier of the entry to look for.
*
* @return bool
*/
public function has(string $id): bool;
}
Интерфейс намеренно прост. Требуя только, чтобы контейнер имел эти два метода, он не диктует, как сервисы привязываются к контейнеру в первую очередь.
Создание минимального, PSR-11 совместимого, контейнера
Начнём с установки пакета psr/container
, предоставляющего ContainerInterface
, и создания собственного класса Container
, реализующего этот интерфейс.
composer require psr/container
use Psr\Container\ContainerInterface;
class Container implements ContainerInterface
{
public function get(string $id): mixed
{
// ?
}
public function has(string $id): bool
{
// ?
}
}
Мы хотим привязать
некие службы к контейнеру — поэтому подходящим именем метода будет bind()
. Этот метод должен принимать string $id
и, пока, object $service
.
Мы можем хранить привязки в массиве контейнера.
class Container implements ContainerInterface
{
protected array $bindings = [];
public function bind(string $id, object $service): void
{
$this->bindings[$id] = $service;
}
}
Теперь методы get()
и has()
могут читать из $bindings
.
class Container implements ContainerInterface
{
// ...
public function get(string $id): mixed
{
return $this->bindings[$id];
}
public function has(string $id): bool
{
return isset($this->bindings[$id]);
}
}
ContainerInterface
содержит пару комментариев DocBlock с аннотациями @throws
. Чтобы класс Container
был действительно совместим с PSR-11
, нужно убедиться, что выбрасывается Exception
, реализующее интерфейс NotFoundExceptionInterface
, при попытке вызвать get()
с несуществующим $id
.
use Psr\Container\NotFoundExceptionInterface;
use Exception;
class ServiceNotFoundException extends Exception implements NotFoundExceptionInterface
{
// ...
}
class Container implements ContainerInterface
{
// ...
public function get(string $id): mixed
{
if (! $this->has($id)) {
throw new ServiceNotFoundException($id);
}
return $this->bindings[$id];
}
}
PSR-11 также определяет ContainerExceptionInterface
, заявляя, что Exception
, создаваемые непосредственно контейнером, должны его реализовать. NotFoundExceptionInterface
уже расширяет этот интерфейс, поэтому ничего не нужно делать.
Можно написать несколько тестов Pest для класса Container
.
it('can be constructed', function () {
expect(new Container)->toBeInstanceOf(Container::class);
});
it('can bind and retrieve services', function () {
$container = new Container;
$container->bind('container', $container);
expect($container->has('container'))->toBeTrue();
expect($container->get('container'))->toBeInstanceOf(Container::class)->toBe($container);
});
it('throws a ServiceNotFoundException when trying to retrieve a non-existent service', function () {
$container = new Container;
expect(fn () => $container->get('foo'))
->toThrow(ServiceNotFoundException::class);
});
Таким образом, у нас есть сервис контейнер совместимый с PSR-11.
Если вы хотите посмотреть весь код, то можете найти его на GitHub ryangjchandler/container.