bhittani/container
Composer 安装命令:
composer require bhittani/container
包简介
PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros.
README 文档
README
PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros. This package does not require any external dependencies.
Install
You may install this package using composer.
$ composer require bhittani/container --prefer-dist
Usage
PSR-11 Implementation
This package implements the PSR-11 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 require_once __DIR__ . '/vendor/autload.php'; $container = new \Bhittani\Container\Container; $container->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 require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } $container = new Bhittani\Container\Container; $container->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 require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } class Query { protected $db; public function __construct(FooDatabase $db) { $this->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 require_once __DIR__ . '/vendor/autload.php'; class FooDatabase { // ... } class Query { protected $db; public function __construct(FooDatabase $db) { $this->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,
- The container identifies the key as a class that exists.
- It takes a peek into the constructor parameters and notices a parameter type-hinted as the class
FooDatabase. - In order to resolve this parameter, it repeats step 1 and 2 using the type-hint as the key.
- It doesn't see any constructor for the
FooDatabaseclass so it instantiates it and uses that instance to instantiate theQueryclass.
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 require_once __DIR__ . '/vendor/autload.php'; interface DatabaseInterface { // ... } class FooDatabase implements DatabaseInterface { // ... } class BarDatabase implements DatabaseInterface { // ... } class Query { public $db; public function __construct(DatabaseInterface $db) { $this->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.
$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.
- Binding the custom argument into the container.
- Passing explicit arguments.
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 require_once __DIR__ . '/vendor/autload.php'; interface DatabaseInterface { // ... } class FooDatabase implements DatabaseInterface { // ... } class Query { public function __construct(DatabaseInterface $db) { // ... } } $container = new Bhittani\Container\Container; $container->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.
$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.
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 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.
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.
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.
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.
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 for more information on what has changed.
Testing
git clone https://github.com/kamalkhan/container cd container composer install composer test
Contributing
Please see CONTRIBUTING and CONDUCT for details.
Security
If you discover any security related issues, please email shout@bhittani.com instead of using the issue tracker.
Inspiration
Credits
License
The MIT License (MIT). Please see the License File for more information.
bhittani/container 适用场景与选型建议
bhittani/container 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 346 次下载、GitHub Stars 达 0, 最近一次更新时间为 2018 年 03 月 14 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「container」 「dependency」 「di」 「injection」 「ioc」 「service-provider」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 bhittani/container 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 bhittani/container 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 bhittani/container 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
A fast and intuitive dependency injection container.
Dependency injection container for the Monolith framework.
The PSR-11 container bridges
Http Microframework for Hack
PHP Dependency Injection Container
统计信息
- 总下载量: 346
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 15
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2018-03-14