定制 hd3r/container 二次开发

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

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

hd3r/container

最新稳定版本:v1.0.0

Composer 安装命令:

composer require hd3r/container

包简介

Lightweight PSR-11 dependency injection container with autowiring and caching.

README 文档

README

Lightweight PSR-11 dependency injection container for PHP. Autowiring, caching, zero bloat.

Installation

composer require hd3r/container

Usage

Basic Autowiring

use Hd3r\Container\Container;

$container = new Container();

// Automatically resolves dependencies via Reflection
$controller = $container->get(UserController::class);

The container analyzes constructor parameters and recursively resolves all dependencies:

class UserController {
    public function __construct(
        private UserService $userService,  // Auto-resolved
        private Logger $logger             // Auto-resolved
    ) {}
}

Manual Definitions

For services that need configuration or primitives:

$container = new Container();

// Factory receives the container for nested resolution
$container->set(Database::class, fn(Container $c) => new Database(
    host: $_ENV['DB_HOST'],
    logger: $c->get(Logger::class)
));

// Simple values
$container->set('app.name', fn() => 'My Application');

Interface Binding

Bind interfaces to concrete implementations:

$container = new Container();

// Short syntax
$container->bind(LoggerInterface::class, FileLogger::class);
$container->bind(CacheInterface::class, RedisCache::class);

// Fluent chaining
$container = Container::create()
    ->bind(LoggerInterface::class, FileLogger::class)
    ->bind(CacheInterface::class, RedisCache::class);

// Now autowiring resolves interfaces automatically
$service = $container->get(PaymentService::class);
// PaymentService receives FileLogger for LoggerInterface parameter

Singleton Behavior

All resolved instances are cached (singleton pattern):

$container = new Container();

$logger1 = $container->get(Logger::class);
$logger2 = $container->get(Logger::class);

$logger1 === $logger2; // true - same instance

Caching

The container can cache Reflection metadata to avoid analyzing classes on every request.

Enable via Config

$container = new Container([
    'cacheFile' => '/var/cache/container.php',
    'cacheSignature' => $_ENV['CONTAINER_CACHE_KEY'],  // Required in production!
    'debug' => false,
]);

// At end of bootstrap/request
$container->saveCache();

Security: A signature key is required when caching is enabled (debug=false). This prevents RCE attacks via tampered cache files. See Security section below.

Enable via Fluent API

$container = Container::create()
    ->setDebug(false)
    ->enableCache('/var/cache/container.php', $_ENV['CONTAINER_CACHE_KEY']);

// ... resolve services ...

$container->saveCache();

Enable via Environment Variables

CONTAINER_CACHE_FILE=/var/cache/container.php
CONTAINER_CACHE_KEY=your-secret-key-here  # Required in production!
APP_DEBUG=false
$container = new Container();  // Reads from $_ENV / getenv() automatically

Generate a secure key: php -r "echo bin2hex(random_bytes(32));"

Configuration Priority

Priority: $config array > $_ENV > getenv() > default

The library checks $_ENV first (thread-safe), then falls back to getenv() for legacy compatibility. Use a library like hd3r/env-loader to load .env files into $_ENV.

How Caching Works

  1. First request: Reflection analyzes classes, stores metadata
  2. Following requests: Metadata loaded from cache, no Reflection needed
  3. OPcache: Cache file is PHP code, optimized by OPcache

The cache stores "build instructions" (which dependencies each class needs), not the instances themselves.

Cache Management

// Save cache (only writes if new classes were resolved)
$container->saveCache();

// Clear cache
$container->clearCache();

Debug Mode

Debug mode disables caching for development:

// Explicit
$container = new Container(['debug' => true]);

// Or via fluent API
$container = Container::create()->setDebug(true);

// Or automatic detection via $_ENV
// APP_DEBUG=true or APP_ENV=local/dev/development
$container = new Container();  // Debug mode auto-enabled

Hooks

The container fires events at key points, allowing you to add logging, monitoring, or debugging without modifying your services.

Available Events

Event When Data
resolve New instance created ['id' => string, 'instance' => object]
error Exception during resolution ['id' => string, 'exception' => Throwable]
cacheHit Class metadata found in cache ['id' => string]
cacheMiss Class metadata not in cache ['id' => string]

Usage

$container = new Container();

// Log all resolved services
$container->on('resolve', function (array $data) {
    error_log("Resolved: {$data['id']}");
});

// Monitor cache performance
$container->on('cacheHit', fn($data) => $metrics->increment('container.cache.hit'));
$container->on('cacheMiss', fn($data) => $metrics->increment('container.cache.miss'));

// Log errors
$container->on('error', function (array $data) {
    error_log("Container error for {$data['id']}: " . $data['exception']->getMessage());
});

Note: Hooks only fire when a new instance is created. Singleton cache hits (returning an already-resolved instance) do not trigger resolve.

Security

Cache Signature Key (Required in Production)

When caching is enabled (debug=false), a signature key is required. This prevents Remote Code Execution (RCE) attacks via tampered cache files in shared hosting environments.

// Production: signature key required
$container = new Container([
    'cacheFile' => '/var/cache/container.php',
    'cacheSignature' => $_ENV['CONTAINER_CACHE_KEY'],
    'debug' => false,
]);

// Development: debug mode disables caching, no key needed
$container = new Container([
    'cacheFile' => '/var/cache/container.php',
    'debug' => true,  // No signature required
]);

Why? The cache file contains PHP code that gets executed via require. An attacker with write access to the cache file could inject malicious code. The HMAC-SHA256 signature ensures the file hasn't been modified.

Exception Debug Messages

Exceptions have two messages:

  • User message: Safe for end users, returned by getMessage()
  • Debug message: Contains technical details for logging, returned by getDebugMessage()
try {
    $container->get(SomeService::class);
} catch (ContainerException $e) {
    // Show to user
    echo $e->getMessage();  // "Cache signature key is required..."

    // Log for debugging
    error_log($e->getDebugMessage());  // "Provide key via CONTAINER_CACHE_KEY..."
}

Exceptions

All exceptions implement PSR-11 interfaces:

use Hd3r\Container\Container;
use Hd3r\Container\Exception\ContainerException;
use Hd3r\Container\Exception\NotFoundException;
use Hd3r\Container\Exception\CacheException;

try {
    $service = $container->get(SomeService::class);
} catch (NotFoundException $e) {
    // Class or service not found
} catch (CacheException $e) {
    // Cache read/write error or signature mismatch
} catch (ContainerException $e) {
    // Any other container error (not instantiable, unresolvable parameter, etc.)
}
Exception When
NotFoundException Class doesn't exist or service not defined
ContainerException Class not instantiable, unresolvable parameter, factory error, circular dependency
CacheException Cache write failed, directory not writable, invalid signature, missing signature key

Limitations

The container is intentionally minimal. It does not support:

Feature Status Alternative
Interface binding Supported bind() method
Autowiring Supported Automatic via Reflection
Singleton Supported Default behavior
Factories Supported set() method
Caching Supported enableCache() / config
Union types Default only Use set() for manual definition
Intersection types Not supported Use set() for manual definition
Attributes Not supported Use set() for configuration
Tagged services Not supported Not needed for simple DI
Lazy proxies Not supported Would require code generation
Compiler passes Not supported Framework territory

When to Use

  • Libraries without framework dependencies
  • Microservices and APIs
  • Projects where Symfony/Laravel DI is overkill
  • When you want PSR-11 compliance without the bloat

When NOT to Use

  • Large applications needing compiler passes
  • When you need lazy loading / proxies
  • When you need attribute-based configuration
  • When you're already using Symfony/Laravel

Requirements

  • PHP ^8.1
  • psr/container ^2.0

License

MIT

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-12-14