Skip to content

ghostwriter/container

Repository files navigation

Container

Automation Supported PHP Version GitHub Sponsors Mutation Coverage Code Coverage Type Coverage Latest Version on Packagist Downloads

Provides an extensible Dependency Injection Service Container for Automated Object Composition, Interception, and Lifetime Management.

Installation

You can install the package via composer:

composer require ghostwriter/container

Usage

Simple usage

Registering a service on the given container.

final readonly class Service
{
    public function __construct(
        private Dependency $dependency
    ) {}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}

$container = Container::getInstance();
$service = $container->get(Service::class);

assert($service instanceof Service); // true
assert($service->dependency() instanceof Dependency); // true

Attributes

Registering services using attributes.

#[Inject]

Registering a service on the container using attributes.

use Ghostwriter\Container\Attribute\Inject;

final readonly class Service
{
    public function __construct(
        #[Inject(Dependency::class)]
        private DependencyInterface $dependency
    ) {}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}

// the above is equivalent to the following
// $container->alias(Dependency::class, DependencyInterface::class);

final readonly class Service
{
    public function __construct(
        #[Inject(Dependency::class, Service::class)]
        private DependencyInterface $dependency
    ) {}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}

// the above is equivalent to the following
// $container->bind(Service::class, DependencyInterface::class, Dependency::class);

Registering a service factory on the container using attributes.

#[Factory]

use Ghostwriter\Container\Attribute\Factory;

#[Factory(ServiceFactory::class)]
final readonly class Service
{
    public function __construct(
        #[Factory(DependencyFactory::class)]
        private Dependency $dependency
    ) {}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}

// the above is equivalent to the following
// $container->factory(Dependency::class, DependencyFactory::class);
// $container->factory(Service::class, ServiceFactory::class);

#[Extension]

Registering a service extension on the container using attributes.

use Ghostwriter\Container\Attribute\Extension;

#[Extension(ServiceExtension::class)]
final readonly class Service
{
    public function __construct(
        #[Extension(DependencyExtension::class)]
        private Dependency $dependency
    ) {}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}

// the above is equivalent to the following
// $container->extend(Service::class, ServiceExtension::class);
// $container->extend(Dependency::class, DependencyExtension::class);

Service Providers

Registering a service provider on the container.

interface TaskInterface {}

final readonly class Task implements TaskInterface {}

final class Tasks
{
    private array $tasks = [];
    public function addTask(TaskInterface $task)
    {
        $this->tasks[] = $task;
    }
}

final readonly class TasksServiceProvider implements ServiceProviderInterface
{
    public function __invoke(ContainerInterface $container)
    {
        $container->alias(TaskInterface::class, Task::class);

        $container->set(Tasks::class, static function (Container $container) {
            /** @var Tasks $tasks */
            $tasks = $container->build(Tasks::class);

            foreach ($container->tagged(Task::class) as $service) {
                $tasks->addTask($service);
            }

            return $tasks;
        }, [Tasks::class, 'tasks']);
    }
}

$container->provide(TasksServiceProvider::class);

$service = $container->get(TaskInterface::class);

assert($service instanceof Task); // true

Contextual Bindings

Registering a Contextual Bindings on the container.

interface ClientInterface { }

final readonly class RestClient implements ClientInterface {
}

final readonly class GraphQLClient implements ClientInterface {
}

final readonly class GitHub
{
    public function __construct(
        private ClientInterface $client
    ) {
    }
    public function getClient(): ClientInterface
    {
        return $this->client;
    }
}

// When GitHub::class asks for ClientInterface::class, it would receive an instance of GraphQLClient::class.
$container->bind(GitHub::class, ClientInterface::class, GraphQLClient::class);

// When any other service asks for ClientInterface::class, it would receive an instance of RestClient::class.
$container->alias(ClientInterface::class, RestClient::class);

Service Extensions

Registering a service extension on the container.

/**
 * @implements ExtensionInterface<GitHubClient>
 */
final readonly class GitHubExtension implements ExtensionInterface
{
    /**
     * @param GitHubClient $service
     * @return GitHubClient
     */
    public function __invoke(ContainerInterface $container, object $service): object
    {
        $service->setEnterpriseUrl(
            $container->get(GitHubClient::GITHUB_HOST)
        );

        return $service;
    }
}

$container->alias(GitHubClientInterface::class, GitHubClient::class);
$container->extend(GitHubClientInterface::class, $container->get(GitHubExtention::class));

Service Factory

Registering a service factory on the container.

final readonly class Dependency {}
final readonly class Service
{
    public function __construct(
        private Dependency $dependency
    ){}

    public function dependency():Dependency
    {
        return $this->dependency;
    }
}
final readonly class ServiceFactory {
  public function __invoke(Container $container): Service
  {
     return new Service($container->get(Dependency::class));
  }
}

$container = Container::getInstance();

$container->factory(Service::class, ServiceFactory::class);

$service = $container->get(Service::class);

assert($service instanceof Service); // true
assert($service->dependency() instanceof Dependency); // true

Testing

composer test

Changelog

Please see CHANGELOG.md for more information what has changed recently.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Sponsors

[Become a GitHub Sponsor]

Credits

License

The BSD-3-Clause. Please see License File for more information.