bhittani/container 问题修复 & 功能扩展

解决BUG、新增功能、兼容多环境部署,快速响应你的开发需求

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

bhittani/container

Composer 安装命令:

composer require bhittani/container

包简介

PSR-11 dependency injection container implementation with automatic resolution, service providers, facades and macros.

README 文档

README

Build Status Packagist Downloads License

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,

  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

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.

  1. Binding the custom argument into the container.
  2. 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 我们能提供哪些服务?
定制开发 / 二次开发

基于 bhittani/container 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 346
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 15
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2018-03-14