华农之窗 · 2024年10月15日 0

一个 PHP 依赖注入包

这是一个简单高效的 PHP 依赖注入包,它使用了 PHP 的最新特性:注解。通过注解,我们可以将一个类标记为单例。

PHP 版本

最低支持的 PHP 版本:8.0

介绍

  • 无依赖
  • 支持单例模式
  • 支持类别名
  • 支持类映射
  • 反射对象缓存消除了重复创建相同类反射的需求,并提高了性能
  • 循环依赖检测,能够检测循环依赖问题(包括直接和间接循环依赖)

安装

composer require yolo-fx/di

使用方法

  • 创建类的实例。

你可以使用 Yolo\Di\DI::use() 来创建类的实例,例如:

Yolo\Di\DI::use(User::class)

  • 单例

你可以使用 Yolo\Di\Annotations\Singleton 注解来将一个类标记为单例。

  • 初始化器

你可以使用 Yolo\Di\Annotations\Initializer 注解来将一个方法标记为初始化器。

  • 使用类映射

当你的构造函数参数是一个接口类型时,这会非常有用。例如:

class TestRunner
{
    /**
     * @param LoggerInterface $logger It will be replaced by ConsoleLogger
     */
    public function __construct(private LoggerInterface $logger)
    {
    }

    public function sayHello(): void
    {
        $this->logger->log("Hello World");
    }
}

你可以使用 Yolo\Di\DI::bind() 来设置类映射。

DI::bind(LoggerInterface::class, ConsoleLogger::class);

这样 LoggerInterface::class 将被 ConsoleLogger::class 替换。

现在,你可以使用 Yolo\Di\DI::use() 来创建类的实例。

$runner = DI::use(TestRunner::class);
$runner->sayHello();
  • 使用类别名
DI::alias(TestRunner::class, 'runner');
$runner = DI::use('runner');
$runner->sayHello();

示例

  • 创建一个类: UserTest.php
<?php

namespace DI\Tests;

use Yolo\Di\Annotations\Initializer;

class UserTest
{
    public function __construct(private AnimalTest $animal)
    {
        echo "you can see me twice." . PHP_EOL;
    }

    #[Initializer]
    public function init()
    {
        echo "User.init" . PHP_EOL;
    }

    /**
     * Say hello
     * @return void
     */
    public function sayHello()
    {
        echo "Hello, I am a user." . PHP_EOL;
        $this->animal->sayHello();
    }
}
  • 创建另一个类: AnimalTest.php
<?php

namespace DI\Tests;

use Yolo\Di\Annotations\Singleton;

#[Singleton]
class AnimalTest
{
    public function __construct()
    {
        echo 'you can only see me once' . PHP_EOL;
    }
    public function sayHello()
    {
        echo 'Hello, I am an animal.' . PHP_EOL;
    }
}
  • 使用 DI 创建实例
<?php

use Yolo\Di\DI;

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/LoggerInterface.php';
require_once __DIR__ . '/ConsoleLogger.php';
require_once __DIR__ . '/FileLogger.php';

class TestRunner
{
    public function __construct(private LoggerInterface $logger)
    {
    }

    public function sayHello(): void
    {
        $this->logger->log("Hello World");
    }
}

$start = microtime(true);

DI::bind(LoggerInterface::class, ConsoleLogger::class);

// Use DI::alias to create an alias for a class
DI::alias(TestRunner::class, 'runner');
// And then use the alias to create an instance of TestRunner
$runner = DI::use('runner');
$runner->sayHello();

// Also, you can use the class name to create an instance of TestRunner
$runner = DI::use(TestRunner::class);
$runner->sayHello();

$end = microtime(true);

echo 'Spent ' . round(($end - $start) * 6, 3) . 'ms' . PHP_EOL;

echo 'Memory usage: ' . round(memory_get_usage() / 1024, 3) . 'kb' . PHP_EOL;

注意事项

  • 不要有循环依赖,包括直接和间接的循环依赖,比如 A 依赖于 B 而 B 又依赖于 A。

DI.php

<?php

namespace Yolo\Di;

use ReflectionException;
use Yolo\Di\Core\ApplicationContainer;
use Yolo\Di\Errors\CircularDependencyException;
use Yolo\Di\Errors\InvalidAttributeException;
use Yolo\Di\Errors\ParameterTypeEmptyException;

/**
 * Class DI
 *
 * This class acts as a static facade for interacting with the dependency injection container.
 * It provides a set of static methods to manage class bindings, instances, aliases, and custom property attributes.
 * The `DI` class simplifies the process of resolving dependencies and managing object lifecycles.
 *
 * Key Features:
 * - **Dependency Resolution**: Automatically resolve and instantiate classes with their dependencies.
 * - **Class Binding**: Bind abstract classes or interfaces to concrete implementations.
 * - **Instance Management**: Manually set or remove specific instances for classes.
 * - **Alias Management**: Create and manage aliases for classes to simplify usage.
 * - **Custom Property Attributes**: Add custom property injection attributes for advanced use cases.
 *
 * Usage:
 * - Use `DI::use()` to resolve and create instances of classes.
 * - Use `DI::bind()` to bind an abstract class or interface to a concrete implementation.
 * - Use `DI::instance()` to manually set a specific instance for a class.
 * - Use `DI::alias()` to create an alias for a class.
 * - Use `DI::addCustomPropertyAttribute()` to register custom property injection attributes.
 *
 * Example:
 * ```php
 * DI::bind(LoggerInterface::class, ConsoleLogger::class);
 * $logger = DI::use(LoggerInterface::class);
 *```
 * @package Yolo\Di
 */
class DI
{
    /**
     * Get instance of class.
     * @template T of object
     * @param class-string<T> $class Class name
     * @param bool $cache Whether to cache the reflection or not.
     * @return T
     * @throws ReflectionException|ParameterTypeEmptyException|CircularDependencyException|InvalidAttributeException
     */
    public static function use(string $class, bool $cache = true)
    {
        return ApplicationContainer::inst()->getInjection()->resolve($class, $cache);
    }

    /**
     * Bind an abstract class to a concrete implementation.
     * @param string $abstract
     * @param string $concrete
     * @return void
     */
    public static function bind(string $abstract, string $concrete): void
    {
        ApplicationContainer::inst()->getInjection()->bind($abstract, $concrete);
    }

    /**
     * Unbind a class.
     * @param string $abstract
     * @return void
     */
    public static function unbind(string $abstract): void
    {
        ApplicationContainer::inst()->getInjection()->unbind($abstract);
    }

    /**
     * Set an instance manually. It will override any existing instance for that class.
     *
     * It just like to set a singleton class.
     * @param string $class
     * @param object $instance
     * @return void
     */
    public static function instance(string $class, object $instance): void
    {
        ApplicationContainer::inst()->getInjection()->instance($class, $instance);
    }

    /**
     * Remove an instance.
     * @param string $class
     * @return void
     */
    public static function forget(string $class): void
    {
        ApplicationContainer::inst()->getInjection()->forget($class);
    }

    /**
     * Set an alias for a class.
     * @param string $class
     * @param string $alias
     * @return void
     */
    public static function alias(string $class, string $alias): void
    {
        ApplicationContainer::inst()->getInjection()->alias($class, $alias);
    }

    /**
     * Add a custom property attribute.
     * @param $class
     * @return void
     */
    public static function addCustomPropertyAttribute($class): void
    {
        ApplicationContainer::inst()->getInjection()->addCustomPropertyAttribute($class);
    }
}