定制 agussuroyo/container 二次开发

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

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

agussuroyo/container

最新稳定版本:1.0.0

Composer 安装命令:

composer require agussuroyo/container

包简介

Simple dependency injection container

README 文档

README

A simple dependency injection container for PHP 7.4+.

PHP Version License

Overview

This is a lightweight dependency injection (DI) container. It provides automatic dependency resolution, interface binding, and singleton management.

Key Features

  • Auto-wiring: Automatically resolves constructor dependencies
  • Interface Binding: Bind interfaces to concrete implementations
  • Singleton Support: Manage singleton instances with automatic caching and clearing
  • Zero Configuration: Works out of the box with no setup
  • Type-Safe: Full type hints and generics support
  • Thoroughly Tested: Comprehensive unit tests across PHP 7.4-8.4

Installation

Install via Composer:

composer require agussuroyo/container

Requirements

  • PHP 7.4 or higher
  • No additional dependencies

Quick Start

<?php

use AgusSuroyo\Container\Container;

// Create a container instance
$container = new Container();

// Resolve a simple class
$instance = $container->get(MyClass::class);

// The container automatically resolves dependencies
$service = $container->get(MyService::class);

Usage

Basic Resolution

The container can automatically resolve classes with no dependencies or with resolvable dependencies:

class SimpleService
{
    public function doSomething(): void
    {
        echo "Doing something!";
    }
}

$service = $container->get(SimpleService::class);
$service->doSomething();

Automatic Dependency Injection

The container automatically resolves constructor dependencies:

class Database
{
    public function query(string $sql): array
    {
        // Execute query
        return [];
    }
}

class UserRepository
{
    public function __construct(
        private Database $database
    ) {}
    
    public function findAll(): array
    {
        return $this->database->query('SELECT * FROM users');
    }
}

// Container automatically injects Database into UserRepository
$repository = $container->get(UserRepository::class);

Interface Binding

Bind interfaces to concrete implementations:

interface LoggerInterface
{
    public function log(string $message): void;
}

class FileLogger implements LoggerInterface
{
    public function log(string $message): void
    {
        file_put_contents('app.log', $message . PHP_EOL, FILE_APPEND);
    }
}

// Bind interface to implementation
$container->bind(LoggerInterface::class, FileLogger::class);

// Now you can resolve the interface
$logger = $container->get(LoggerInterface::class);
$logger->log('Application started');

Binding with Closures

Use closures for custom instantiation logic:

$container->bind(Database::class, function () {
    return new Database(
        host: 'localhost',
        username: 'root',
        password: 'secret'
    );
});

$db = $container->get(Database::class);

Singleton Management

The get() method automatically returns the same instance on subsequent calls:

$instance1 = $container->get(MyService::class);
$instance2 = $container->get(MyService::class);

// Both variables reference the same instance
assert($instance1 === $instance2);

You can also explicitly declare singletons:

$container->singleton(Cache::class, RedisCache::class);

Creating New Instances

Use make() to create a new instance each time (bypasses singleton cache):

$instance1 = $container->make(MyClass::class);
$instance2 = $container->make(MyClass::class);

// Different instances
assert($instance1 !== $instance2);

Checking Bindings

Check if a class or interface is bound:

if ($container->bound(LoggerInterface::class)) {
    $logger = $container->get(LoggerInterface::class);
}

Clearing Singletons

Clear a specific singleton instance or all singleton instances:

// Clear a specific singleton
$instance1 = $container->get(MyService::class);
$container->clearInstance(MyService::class);
$instance2 = $container->get(MyService::class);
// $instance1 !== $instance2 (new instance created)

// Clear all singletons
$container->clearInstance();

This is useful for testing scenarios or when you need to reset the container state without creating a new container instance.

Default Parameter Values

The container respects default parameter values:

class ConfigService
{
    public function __construct(
        private string $environment = 'production'
    ) {}
    
    public function getEnv(): string
    {
        return $this->environment;
    }
}

// Uses the default value 'production'
$config = $container->get(ConfigService::class);
echo $config->getEnv(); // Outputs: production

Advanced Usage

Nested Dependencies

The container recursively resolves nested dependencies:

class Logger { }

class Database
{
    public function __construct(private Logger $logger) {}
}

class UserRepository
{
    public function __construct(private Database $database) {}
}

class UserService
{
    public function __construct(private UserRepository $repository) {}
}

// Automatically resolves: UserService -> UserRepository -> Database -> Logger
$service = $container->get(UserService::class);

Complex Binding Scenarios

// Bind with factory pattern
$container->bind(Connection::class, function () use ($config) {
    return match($config['driver']) {
        'mysql' => new MySQLConnection($config['mysql']),
        'pgsql' => new PostgresConnection($config['pgsql']),
        default => throw new Exception('Unknown driver')
    };
});

// Bind multiple implementations
$container->bind('logger.file', FileLogger::class);
$container->bind('logger.email', EmailLogger::class);

API Reference

bind(string $abstract, callable|string $concrete): void

Bind an abstract type (interface or class name) to a concrete implementation.

  • $abstract: Interface or class name
  • $concrete: Class name (string) or factory closure

singleton(string $abstract, callable|string $concrete): void

Bind a singleton (same as bind(), included for semantic clarity).

get(string $abstract): object

Resolve and return an instance. Subsequent calls return the same instance (singleton behavior).

  • Returns: Instance of the requested type
  • Throws: InvalidArgumentException if class doesn't exist
  • Throws: RuntimeException if class is not instantiable

make(string $class): object

Create a new instance each time, bypassing the singleton cache.

  • Returns: New instance of the requested type
  • Throws: InvalidArgumentException if class doesn't exist
  • Throws: RuntimeException if class is not instantiable

bound(string $abstract): bool

Check if an abstract type has been bound or resolved.

  • Returns: true if bound, false otherwise

clearInstance(?string $abstract = null): void

Clear a specific singleton instance or all singleton instances.

  • $abstract: The abstract to clear, or null to clear all instances
  • Note: This does not remove bindings, only clears cached instances

Error Handling

The container throws clear exceptions for common issues:

InvalidArgumentException

Thrown when a class doesn't exist:

try {
    $container->get('NonExistentClass');
} catch (InvalidArgumentException $e) {
    echo $e->getMessage(); // "Class NonExistentClass not found"
}

RuntimeException

Thrown when a class cannot be instantiated:

// Abstract class
try {
    $container->get(AbstractLogger::class);
} catch (RuntimeException $e) {
    echo $e->getMessage(); // "Class AbstractLogger is not instantiable"
}

// Unresolvable parameter
class NeedsString
{
    public function __construct(string $name) {}
}

try {
    $container->get(NeedsString::class);
} catch (RuntimeException $e) {
    echo $e->getMessage(); // "Cannot resolve parameter name"
}

Limitations

What the Container Can Resolve

✅ Classes with no constructor
✅ Classes with constructor dependencies (other classes)
✅ Classes with interface dependencies (if bound)
✅ Classes with optional parameters (default values)
✅ Nested dependencies (recursive resolution)

What the Container Cannot Resolve

❌ Abstract classes
❌ Interfaces without bindings
❌ Primitive types without default values (string, int, bool, etc.)
❌ Union types
❌ Intersection types
❌ Variadic parameters
❌ Classes that don't exist

Workarounds

For unresolvable dependencies, use bindings with closures:

// Problem: Cannot resolve primitive type
class EmailService
{
    public function __construct(
        private string $apiKey,
        private string $fromEmail
    ) {}
}

// Solution: Use closure binding
$container->bind(EmailService::class, function () {
    return new EmailService(
        apiKey: $_ENV['EMAIL_API_KEY'],
        fromEmail: 'noreply@example.com'
    );
});

Testing

The project includes comprehensive tests:

# Run all tests
composer test

# Run PHPStan static analysis
composer phpstan

# Run both tests and static analysis
composer check

Test Coverage

  • Unit Tests: Core functionality, edge cases, error handling
  • Feature Tests: Integration and feature tests
  • Static Analysis: PHPStan level max with strict rules

Development

Requirements

  • PHP 7.4+
  • Composer
  • PHPUnit 9.5+ (10+ for PHP 8.1+)
  • PHPStan 1.10+

Setup

# Clone the repository
git clone https://github.com/agussuroyo/container.git
cd container

# Install dependencies
composer install

# Run tests
composer test

Code Quality

The project uses:

  • PSR-4 autoloading
  • Strict types declaration
  • PHPStan level max
  • PHPStan strict rules
  • PHPUnit for testing
  • CI/CD testing across PHP 7.4, 8.0, 8.1, 8.2, 8.3, and 8.4

Architecture

Design Principles

  1. Simplicity: Minimal API surface, easy to understand
  2. Type Safety: Full PHP type hints and strict types
  3. Single Responsibility: Each method has one clear purpose
  4. Fail Fast: Clear exceptions for invalid operations

Internal Structure

Container
├── $instances    // Singleton cache (array<string, object>)
├── $bindings     // Interface bindings (array<string, callable>)
├── get()         // Resolve with caching
├── make()        // Create new instance
├── bind()        // Register binding
├── singleton()   // Register singleton
├── bound()       // Check if bound
├── clearInstance() // Clear singleton cache
└── resolveDependencies() // Recursive resolution

Contributing

Contributions are welcome! Please follow these guidelines:

  1. Fork the repository
  2. Create a feature branch
  3. Write tests for new functionality
  4. Ensure all tests pass (composer check)
  5. Submit a pull request

FAQ

Q: How is this different from other DI containers?

This container focuses on simplicity. It provides only essential features without unnecessary complexity.

Q: Does it support constructor promotion?

Yes! The container fully supports PHP 8.0+ constructor property promotion.

Q: How do I resolve circular dependencies?

The container doesn't handle circular dependencies. Design your classes to avoid circular references, or use setter injection as a workaround.

Q: Can I clear the singleton cache?

Yes! Use clearInstance() to clear a specific singleton or clearInstance(null) to clear all singletons. Bindings are preserved, so resolved instances will be recreated on next get() call.

Q: Is it thread-safe?

PHP doesn't have true multi-threading, but each request gets its own container instance, so there are no concurrency issues in typical PHP applications.

Support

For issues, questions, or contributions, please visit the GitHub repository.

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-11-09