ajaxray/magic

适用于PHP 8的简单自动装配、PSR-11兼容的依赖注入库。

v0.0.2 2021-11-06 13:48 UTC

This package is not auto-updated.

Last update: 2024-09-22 07:09:03 UTC


README

一个小于200行的PHP 8依赖注入容器。为了乐趣和探索PHP反射特性而创建。但它确实做到了它所宣称的。

它真的有效吗?
是的,它确实有效!这里是一个使用Magic作为依赖注入容器的Slim Framework示例应用程序。

特性

  • 兼容PSR-11: 容器接口
  • 为PHP8制作
  • 通过类、接口或匿名函数进行服务绑定
  • 使用以下方式解析依赖项
    • 通过类/接口名称进行自动装配
    • 服务名称/标识符的映射
    • 服务的接口
    • 服务的实现
  • 基于类型提示的构造函数DI能力
  • 对象的生命周期控制(单例/每个请求一个新实例)
  • 易于与任何框架(使用PSR-11兼容的容器)或普通PHP文件一起使用
  • 符合PSR-4自动加载的结构

注意:此库仍在初期开发阶段!

安装

只需使用composer将其拉入您的项目中。

composer require ajaxray/magic --with-all-dependencies

或者您甚至可以下载它并手动包含。

如何使用

基础

首先,创建一个Magic实例并绑定服务类。

$magic = new Magic();
$magic->map('logger', MyLogger::class);

现在您可以使用服务名称logger获取MyLogger的实例。

$logger = $magic->get('logger');
$logger->info('Using Magic as dependency injection container');

如果MyLogger的构造函数需要一些参数,Magic将尝试实例化并提供这些参数。有关参数的更多详细信息,请参阅下一节。

解析服务参数

服务构造函数可能需要一些参数来实例化它。容器将尝试根据参数类型的不同策略提供这些参数。

对象参数

将使用类型提示来确定对象类型。Magic将根据以下方式尝试实例化对象参数:

  • 如果参数名称与任何定义的服务标识符匹配。将检查对象类型的安全性。
  • 如果有任何使用类/接口名称定义的服务。在这种情况下,接口应映射到具体的类。
  • 自动装配。标量参数应可以从容器全局定义的参数中解析。

例如,假设我们有一个类的以下构造函数-

public function __construct(\Doctrine\DBAL\Connection $dbConn)   

将依次尝试以下定义。
基于名称匹配-

$container->map('dbConn', function($m, $params) {
    return \Doctrine\DBAL\DriverManager::getConnection(['url' => $params['dsn']]);
});

基于类型匹配-

$container->map(Connection::class, function($m, $params) {
    return \Doctrine\DBAL\DriverManager::getConnection(['url' => $params['dsn']]);
});

最后,如果在以上所有定义中都没有找到,将尝试自动装配。

标量参数

您必须手动设置标量参数。参数可以在容器范围内设置,也可以在服务定义期间设置。

容器范围内设置的参数将用于所有具有相同参数名称的服务。

// e,g, new MyDbConnection($user, $password, $host = 'localhost', $port = 3306);
$magic->map('db', MyDbConnection::class);

$magic->param('host', 'theHostNameOrIP');
$magic->param('user', 'root');
$magic->param('password', 'TheSecret');

// parameters will be supplied by name matching automatically
$magic->get('db');  

特定于服务的参数值可以在服务绑定时提供。这些参数将仅用于此特定服务。

$magic->map('db', MyDbConnection::class, [
    'host' => 'theHostNameOrIP',
    'user' => 'root',
    'pass' => 'TheSecret',
]);

提示:从即将发布的版本中,可以从.env文件中指定参数。

自动装配

在大多数情况下,如果服务的依赖项(构造函数参数,如果有)满足以下标准,则无需绑定任何内容即可加载服务

  • 标量依赖可以由全局设置的参数解析。
  • 对象/接口依赖具有类型提示和自动加载功能。
  • 对象/接口依赖(及其依赖)满足自动绑定或显式映射的先决条件。
$magic = new Magic();
$magic->get(MyDbConnection::class);

接口绑定

您可以将接口绑定为一个服务。在这种情况下,您必须将接口与其要实例化的实现进行映射。

$magic->map('notifier', NotifierInterface::class, ['receiver' => 'receiver@xyz.tld']);
$magic->mapInterface(NotifierInterface::class, MailNotification::class);

$magic->get('notifier')->notify('The message to send');

使用匿名函数进行绑定

您可以使用Pimple/Laravel风格的匿名函数绑定服务。该函数将接收容器实例和参数数组。

// Simple
$magic->map('greeter', fn($m, $params) => new Greeter($params['name']), ['name' => 'ajaxray']);

// Complex
$magic->param('user', 'sysadmin');
$magic->param('pass', 'TheSecret');

$magic->map('mailer', function ($m, $params) {
        $transport = (new Swift_SmtpTransport($params['smtp.host'], 25))
            ->setUsername($params['user'])
            ->setPassword($params['pass'])
        ;

        return new Swift_Mailer($transport);        
    }, ['smtp.host' => 'smtp.example.tld']);

在上面的示例中,userpass将从全局设置的参数中解析。这意味着,全局设置的参数将与特定服务的参数合并,在解析或传递给服务绑定函数时使用。

服务生命周期(单例或工厂)

默认情况下,如果服务被实例化一次,它将在后续的get()调用或解析其他构造函数参数时被重用。但您可以通过传递@cacheable参数来禁用此行为。

$magic->map('dbMapper', ActiveRecord::class, ['@cacheable' => false]);

// dbMapper will not be cached and will return new instance for every get() call
$aUser = $magic->get('dbMapper')->load('User', 3);
$otherUser = $magic->get('dbMapper')->load('User', 26);

Testdox

PHPUnit 9.5.10 by Sebastian Bergmann and contributors.

Auto Wiring (Ajaxray\Test\AutoWiring)
 ✔ Resolve class by name without constructor
 ✔ Resolve class by name with scalar param constructor
 ✔ Resolve class by name with object param constructor

Basic Class (Ajaxray\Test\BasicClass)
 ✔ Service mapping without constructor
 ✔ Service mapping with scalar param constructor
 ✔ Service mapping with object param constructor

Object Chaining (Ajaxray\Test\ObjectChaining)
 ✔ Resolve classes in chained object graph

Object Lifecycle (Ajaxray\Test\ObjectLifecycle)
 ✔ Provides same instance for multiple get call by default
 ✔ Provides same instance for multiple get call of interface
 ✔ Provides same instance for multiple get call of callback binding
 ✔ Service caching can be disabled for class mapping
 ✔ Service caching can be disabled for interface
 ✔ Service caching can be disabled for callback binding

Resolve Interface (Ajaxray\Test\ResolveInterface)
 ✔ Service loading by interface if single implementation
 ✔ Resolve interface type hint to implementation if single implementation
 ✔ Service loading by mapped interface
 ✔ Resolve mapped interface type hint to implementation

Service Binding By Callable (Ajaxray\Test\ServiceBindingByCallable)
 ✔ Service mapping without constructor
 ✔ Service mapping with scalar param constructor
 ✔ Service mapping with object param constructor
 ✔ Callable can serve non object types

Time: 00:00.015, Memory: 6.00 MB

OK (21 tests, 23 assertions)