定制 slava-basko/container 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

slava-basko/container

Composer 安装命令:

composer require slava-basko/container

包简介

Dependency injection container.

README 文档

README

For those who understand what it is and strive for simplicity.

Zero dependencies and PHP 7.1+.

Features

  • Simple API: Container API is as simple as regular array.
  • Tags: Organize services using tags and fetch them in groups (consider them aliases).
  • Shared Instances: Easily create singletons with a built-in helper.
  • Service Extenders: Modify/Extend services after creation and before they are fully resolved.
  • Circular Dependency Detection: Automatically prevents and reports circular dependencies.
  • Autowiring: Container will automatically resolve dependencies.
  • Rewrite Protection: Reports about accidental service rewrite.
  • Service Providers: Package your services into a provider for better organization.
  • Symlinks: Runtime hot-swap.
  • Graph: Generate DOT script to visualize dependencies (Graphviz).

Simple API

Consider Container as a regular array.

$container = new Container();

$container['cache-file-path'] = '/tmp/app.cache';
$container[CacheStorage::class] = fn (Container $c) => new CacheStorage($c['cache-file-path']);
$container[Cache::class] = fn(Container $c) => new Cache($c[CacheStorage::class]);

$cache = $container[Cache::class];

$cache->get('currency-rates');

Easier than ever for simple script/app.

Tags

Things get a bit more complicated when you need tags. Use the Container::add() method to setup tags.

$container = new Container();

$container['log-file-path'] = 'your.log';
$container->add('file-handler', fn (Container $c) => new StreamHandler($c['log-file-path']), ['handler']);
$container->add('stdout-handler', fn (Container $c) => new StreamHandler('php://output'), ['handler']);
$container[Logger::class] = function (Container $c) {
    $logger = new Logger('app');
    $logger->setHandlers($c->getByTag('handler'));

    return $logger;
};


$logger = $container[Logger::class];

$logger->warning('Oops!');

Use the Container::getByTag() method to retrieve services by tags. This separate method exists because regular get should return a specific service or throw a NotFoundException exception.

Clear and strict separation: Container::get() returns a service or throws NotFoundException, and the Container::getByTag() returns an array of services of throws NotFoundException.

Alias

This container has no separate feature like alias for two main reasons.

  • Container has a tags feature, basically alias.
  • To keep container code as simple as possible.

Use tagging functionality to achieve the same $service = $container->getByTag('tag')[0];. Don't worry about Warning: Undefined array key 0 because container does not return an empty array. It will return an array of services or throw NotFoundException like regular get.

Most likely, you want to use alias to avoid class name exposure. For example when you want to get service by requests param.

class GameController extends Controller {
    public function getPotion(string $potionType)
    {
        try {
            $potionService = $this->container->getByTag($potionType)[0];

            return new Response($potionService->getPotion());
        } catch (NotFoundException) {
            return new Response('Unknown potion type: ' . $potionType, 400);
        } catch (Exception $exception) {
            $this->logger->error($exception->getMessage());
            return new Response('Sorry, internal error', 500);
        }
    }
}

Shared Instances

Use Container::addShared() method to create a shareable service. Container will return the same instance each time.

$container = new Container();

$container->addShared(MySQLConnection::class, fn (Container $c) => new MySQLConnection());

$conn1 = $container->get(MySQLConnection::class);
$conn2 = $container->get(MySQLConnection::class);

assert(spl_object_hash($conn1) === spl_object_hash($conn2));

Service Extenders

Use Container::extend() to modify services by a common parent.

$container = new Container();
$container[\Logger::class] = fn () => new \Logger();
$container[\SomeRepository::class] = fn () => new \SomeRepository();
$container->extend(\BaseRepository::class, function (\BaseRepository $repository, Container $c) {
    $repository->setLogger($c[\Logger::class]);

    return $repository;
});

$repository = $container[\SomeRepository::class];
// From now on, every repository returned by the container and which `extends` BaseRepository has a Logger inside.

Circular Dependency Detection

Container will let you know if you have a circular dependency.

$container = new Container();
$container[A::class] = fn (Container $c) => new \A($c[B::class]);
$container[B::class] = fn (Container $c) => new \B($c[A::class]);

$a = $container[\A::class];
// CircularDependencyException: Circular dependency detected: A -> B -> A 

Autowiring

Use AutowireContainer for automatic dependency resolving.

class FilesystemAdapter {
    // some logic inside
}
class Filesystem {
    /**
     * @var \FilesystemAdapter
     */
    private $adapter;

    public function __construct(FilesystemAdapter $adapter)
    {
        $this->adapter = $adapter;
    }
    
    // use $this->adapter as you need
}

$container = new AutowireContainer();

$filesystem = $container[Filesystem::class]
// $filesystem will be an instance of Filesystem that contains FilesystemAdapter inside

Rewrite Protection

Container will throw an exception if you accidentally rewrite the service definition.

$container = new Container();
$container['k'] = 'v1';
$container['k'] = 'v2'; // RewriteAttemptException: The resource 'k' already defined.

But what if I need to overwrite definition!

Shift your mindset from "overwrite" to "setup based on X condition" and do it in the user-land code.

$container = new Container();
$container['mysql-dsn'] = getenv('ENVIRONMENT') === 'prod' ? 'mysql://production-dsn' : 'mysql://development-dsn';

This approach, together with Rewrite Protection, will keep your code strict and more understandable. There will be no hidden overwrites.

Service Providers

Providers give you the benefit of organizing your definitions.

class LoggerServiceProvider {
    public function __invoke(Container $container): void
    {
        // Define your handlers, formatters, processors, and logger itself.
        // All Logger-related things are in one place.
    }
}

Then, add the provider to the container.

$container->addProvider(new LoggerServiceProvider());

Symlinks

You can create symlinks that allow you to get different services on the run, without changing user-land code.

$container = new Container();
$container->add(MasterMySqlClient::class, fn (Container $c) => new MasterMySqlClient($c['mysql-dsn']));
$container->add(SlaveMySqlClient::class, fn (Container $c) => new SlaveMySqlClient($c['mysql-dsn']));

$container->symlink(MasterMySqlClient::class, MySqlInterface::class);
$container->get(MySqlInterface::class); // MasterMySqlClient

$container->symlink(SlaveMySqlClient::class, MySqlInterface::class);
$container->get(MySqlInterface::class); // SlaveMySqlClient

Note: Only one symlink to a specific service can exist at a time.

Graph

Using the DOT language, build a graph (as in nodes and edges, not as in bar charts). Note: Graph not working with AutowireContainer.

$container = new Container();
// Add services and define dependencies

$graph = new Graphviz($container);
$graph->build();

For example, you can visualize the output of Graphviz::build() with https://dreampuf.github.io/GraphvizOnline.

Graph element explanation:

  • Oval — parameter
  • Solid Rectangle — service
  • Dashed Rectangle — shared service
  • Parallelogram — non-existent service or param

graphviz.png

PSR-11

Install psr/container package first.

composer require psr/container

Then wrap Container in PsrContainer and pass it to whoever expected Psr\Container\ContainerInterface.

// Define services, dependencies, etc.
$container = new Container();

// Wrap it.
$psrContainer = new PsrContainer($container);

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

slava-basko/container 适用场景与选型建议

slava-basko/container 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 2.54k 次下载、GitHub Stars 达 1, 最近一次更新时间为 2025 年 03 月 08 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「container」 「dependency」 「di」 「injection」 「service」 「ioc」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 slava-basko/container 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 slava-basko/container 我们能提供哪些服务?
定制开发 / 二次开发

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

BUG 修复 & 性能优化

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

项目外包 & 长期维护

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

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

统计信息

  • 总下载量: 2.54k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 1
  • 点击次数: 4
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-03-08