Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/kamalkhan/container
PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros
https://github.com/kamalkhan/container
container dependency facades injection macros psr-11 service-providers
Last synced: about 1 month ago
JSON representation
PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros
- Host: GitHub
- URL: https://github.com/kamalkhan/container
- Owner: kamalkhan
- License: mit
- Created: 2018-03-14T14:28:20.000Z (almost 7 years ago)
- Default Branch: master
- Last Pushed: 2024-11-06T18:49:34.000Z (about 2 months ago)
- Last Synced: 2024-11-21T15:11:47.309Z (about 1 month ago)
- Topics: container, dependency, facades, injection, macros, psr-11, service-providers
- Language: PHP
- Size: 104 KB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: .github/CONTRIBUTING.md
- License: LICENSE.md
Awesome Lists containing this project
README
# PSR-11 Container
[![Build Status][icon-status]][link-status]
[![Packagist Downloads][icon-downloads]][link-downloads]
[![License][icon-license]](LICENSE.md)PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros. This package does not require any external dependencies.
- [Install](#install)
- [Usage](#usage)
- [PSR-11 Implementation](#psr-11-implementation)
- [Container](#container)
- [Binding resolution](#binding-resolution)
- [Automatic dependency resolution](#automatic-dependency-resolution)
- [Interface resolution](#interface-resolution)
- [Callable resolution](#callable-resolution)
- [Custom parameter resolution](#custom-parameter-resolution)
- [Factory bindings](#factory-bindings)
- [Shared bindings](#shared-bindings)
- [Delegates](#delegates)
- [Service providers](#service-providers)
- [Facades](#facades)
- [Macros](#macros)
- [Deferred Service Providers](#deferred-service-providers)
- [Changelog](#changelog)
- [Testing](#testing)
- [Contributing](#contributing)
- [Security](#security)
- [Inspiration](#inspiration)
- [Credits](#credits)
- [License](#license)## Install
You may install this package using [composer][link-composer].
```shell
$ composer require bhittani/container --prefer-dist
```## Usage
### PSR-11 Implementation
This package implements the [PSR-11](https://github.com/php-fig/container) container interface, hence, you can easily swap any existing implementation with the container provided in this package.
### Container
In its simplest form, the container stores key value pairs so that it can be accessed later during your application life cycle.
```php
add('foo', 'bar');echo $container->get('foo'); // 'bar'
```### Binding resolution
Practically, a dependency injection container is more useful by storing class factories/instances so that they are automatically resolved.
```php
add(FooDatabase::class, new FooDatabase);$db = $container->get(FooDatabase::class);
```The key being used `FooDatabase` is significant. This key will act as a look-up against any class typehints during resolution attempts of binding parameters (in class constructors, methods, closures, callables, ..., etc).
Still confused? Lets take a closer look at a practical example.
```php
db = $db;
}
}$container = new Bhittani\Container\Container;
$container->add(FooDatabase::class, new FooDatabase);
$query = $container->get(Query::class); // Query
```Here, `$db` is automatically resolved by the container as it is type hinted with the 'FooDatabase' class which the container is aware of.
### Automatic dependency resolution
Binding resolution is all handy and dandy but we can do much better and improve on our first iteration.
If we take a closer look at the previous code example, we see that we bind the `FooDatabase` class explicitly into the container but the `Query` class is implicitly resolved without any explicit binding.
This means, we should also be able to resolve the `FooDatabase` class implicitly.
Let's apply our first refactor.
```php
db = $db;
}
}$container = new Bhittani\Container\Container;
$query = $container->get(Query::class); // Query
```In case you didn't notice, the code line `$container->add(FooDatabase::class, new FooDatabase);` is completely removed as no binding is required.
How does this work? Lets go behind the scenes for a moment to see what actually happens.
When you call the `get` method on the container,
1. The container identifies the key as a class that exists.
2. It takes a peek into the constructor parameters and notices a parameter type-hinted as the class `FooDatabase`.
3. In order to resolve this parameter, it repeats step 1 and 2 using the type-hint as the key.
4. It doesn't see any constructor for the `FooDatabase` class so it instantiates it and uses that instance to instantiate the `Query` class.> A binding will take precedence over a new instantiation.
### Interface resolution
Wouldn't it be nice if we could implement to an interface so that we could easily swap the underlying implementation?
```php
db = $db;
}
}$container = new Bhittani\Container\Container;
$container->add(DatabaseInterface::class, new FooDatabase);
$query = $container->get(Query::class);
echo $query->db instanceof FooDatabase; // true
$container->add(DatabaseInterface::class, new BarDatabase);
$query = $container->get(Query::class);
echo $query->db instanceof BarDatabase; // true
```We have easily swapped the underlying database implementation from `FooDatabase` to `BarDatabase`.
### Callable resolution
To resolve a callable/closure, we can invoke it directly.
```php
$container = new Bhittani\Container\Container;class Acme
{
// ...
}$container->call(function (Acme $acme) {
echo $acme instanceof Acme; // true
});
```### Custom parameter resolution
We can resolve entities that require custom arguments in two ways.
1. Binding the custom argument into the container.
2. Passing explicit arguments.```php
class Acme
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
}$container = new Bhittani\Container\Container;
$container->add('foo', 'bar');
$acme = $container->get(Acme::class);
echo $acme->foo; // bar
$acme = $container->get(Acme::class, ['foo' => 'baz']);
echo $acme->foo; // baz
```> Explicit arguments will take precedence over bindings.
### Factory bindings
Factory bindings allow lazy loading of your instances. Which means that resolution will only occur when it is needed.
```php
add(DatabaseInterface::class, function () {
return new FooDatabase;
});$query = $container->get(Query::class); // will trigger the factory closure above.
```This way, you can add as many bindings as you want in your container but only trigger/resolve them when needed. Hence, lazy loaded.
### Shared bindings
Shared bindings allow the same instance to be resolved when accessed instead of a new instance every time it is needed.
```php
$container = new Bhittani\Container\Container;// This will be shared as we are not using a factory.
$container->add(DatabaseInterface::class, new FooDatabase);// This will be shared as we are using the method 'share' explicitly.
$container->share(DatabaseInterface::class, function () {
return new FooDatabase;
});// This will NOT be shared as we are using a factory.
$container->add(DatabaseInterface::class, function () {
return new FooDatabase;
});
```### Delegates
Delegated containers serve as fallback containers that are looked-up for binding resolutions when it can not be found in the container.
```php
use Bhittani\Container\Container;$container = new Container;
$container->has('foo'); // false
$delegateContainer = new Container; // Or any PSR-11 container.
$delegateContainer->add('foo', 'bar');
$container->delegate($delegateContainer);
$container->has('foo'); // true
```### Service providers
This package also ships with a service provider container which allows registering of service providers (Think of [laravel](https://laravel.com) service providers) in order to have a smooth and easy application development process.
In order to make use of service providers, we will work with a `ServiceContainer` instead of a simple `Container`.
```php
use Bhittani\Container\ServiceContainer;
use Bhittani\Container\ServiceProvider;class DatabaseServiceProvider extends ServiceProvider
{
public function boot($container)
{
// This method will be called when all service providers are registered.
}
public function register($container)
{
$container->share(DatabaseInterface::class, function () {
return new SqliteDatabase;
});
}
}$container = new ServiceContainer;
$container->addServiceProvider(DatabaseServiceProvider::class);
$container->bootstrap();
$db = $container->get(DatabaseInterface::class);
echo $db instanceof SqliteDatabase; // true
```### Facades
Facades extend the container by assigning custom properties onto the service container. When accessed, it will be resolved out of the service container.
```php
use Bhittani\Container\ServiceContainer;$container = new ServiceContainer;
$container->share(DatabaseInterface::class, function () {
return new SqliteDatabase;
});$container->db = DatabaseInterface::class;
echo $container->db instanceof SqliteDatabase; // true
```### Macros
Macros extend the container by assigning custom methods onto the service container.
```php
use Bhittani\Container\ServiceContainer;$container = new ServiceContainer;
$container->share(DatabaseInterface::class, function () {
return new SqliteDatabase;
});$container->macro('query', function ($sql) {
// $this will be set to the underlying ServiceContainer.
return $this->get(DatabaseInterface::class)->query($sql);
});echo $container->query('SELECT * FROM users'); // Invokes the 'query' macro.
```### Deferred Service Providers
Service providers can be deferred so that services can be lazy loaded.
```php
use Bhittani\Container\ServiceContainer;
use Bhittani\Container\ServiceProvider;class DatabaseServiceProvider extends ServiceProvider
{
// A non empty $provides array will defer this service provider.
protected $provides = [
DatabaseInterface::class,
// If setting a facade, use the facade key as the index.
// 'db' => DatabaseInterface::class,
];// A non empty $macros array will defer this service provider as well.
protected $macros = [
'query',
];public function register($container)
{
$container->share(DatabaseInterface::class, function () {
return new SqliteDatabase;
});$container->macro('query', function ($sql) {
return $this->get(DatabaseInterface::class)->query($sql);
});
}
}$container = new ServiceContainer;
$container->addServiceProvider(DatabaseServiceProvider::class);
$container->bootstrap();
// This will register and boot the service provider.
$container->query('SELECT * FROM users');
```
Using deferred service providers is an efficient way to build up your application as these services will be lazy loaded and act as plug and play while having a minimimum impact on your application performance.## Changelog
Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed.
## Testing
```shell
git clone https://github.com/kamalkhan/containercd container
composer install
composer test
```## Contributing
Please see [CONTRIBUTING](CONTRIBUTING.md) and [CONDUCT](CONDUCT.md) for details.
## Security
If you discover any security related issues, please email `[email protected]` instead of using the issue tracker.
## Inspiration
- [league/container](http://container.thephpleague.com)
- [Laravel](https://laravel.com)## Credits
- [Kamal Khan](http://bhittani.com)
- [All Contributors](https://github.com/kamalkhan/container/contributors)## License
The MIT License (MIT). Please see the [License File](LICENSE.md) for more information.
[icon-status]: https://github.com/kamalkhan/container/workflows/Test/badge.svg
[link-status]: https://github.com/kamalkhan/container/actions[icon-downloads]: https://img.shields.io/packagist/dt/bhittani/container.svg?style=flat-square
[link-downloads]: https://packagist.org/packages/bhittani/container[icon-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square
[link-composer]: https://getcomposer.org