此包已被废弃,不再维护。作者建议使用 redcatphp/strategy 包代替。

策略 - 通用依赖注入

v5.1.1 2017-01-05 08:36 UTC

README

不再积极维护。我现在使用 NodeJS,我推荐您看看 di-ninja,它是将相同的范式移植到 JavaScript 的。

通用依赖注入容器

大量简化

硬编码方式

$a = new A(new Bnew Cnew D(new Enew F));  
    

策略方式

$a = $di->get('A');  
        

通过 逆向工程,实践 php 反射,所有依赖项以及递归依赖项都自动解决!

总结

  1. 关于策略的注意事项

    1. 起源
    2. 差异
  2. 范式

    1. 简化您的代码
    2. 提高可维护性
    3. 提高可伸缩性
    4. 明智之举
  3. 开始使用

    1. Di 实例化
    2. 类实例化
  4. 基本用法

    1. 创建对象图
    2. 向构造函数提供 Provistrategy 附加参数
  5. 共享依赖项

    1. 使用规则配置共享依赖项
  6. 使用规则配置容器

    1. 替换
    2. 继承
    3. 构造函数参数
    4. setter 注入(变异器)
    5. 默认规则
    6. 命名实例
    7. 在树中共享实例
  7. 规则级联策略

  8. 任意数据

    1. 简单变量定义
    2. 匿名函数
    3. 手动定义工厂
    4. 保护匿名函数
    5. 扩展定义
  9. PHP 配置

  10. 关于策略的注意事项

1.1 起源

策略主要受到 Dice 的启发,并增加了 Pimple 能力和重大改进。

1.2 差异

对于已经了解神奇 Dice 的人来说,以下是附加功能

  • 延迟加载级联规则解析(在实例化时使规则级联)
  • 与构造函数名称变量匹配的关联数组
  • 使用 Expander 对象而不是 instance 数组延迟加载实例
  • 完整的注册实现
  • 动态规则构造和调用变量
  • 任意数据和可以使用它们的规则的级联配置
  • 冻结配置优化

以下文档的许多章节与 Dice 文档 相对应,经过一些修改、重新表述、添加和新功能解释。与 Dice 的主要差异和新功能解释将带有“策略特定性”标签。

  1. 范式

2.1 简化您的代码!

考虑以下基类

class A {  
    private $a;  
    private $b;  
    private $c;  
    function \_\_construct(A $aB $bC $cD $d){  
        $this->a = $a;  
        $this->b = $b;  
        $this->c = $c;  
        $this->d = $d;  
    }  
}  
class D {  
    private $e;  
    private $f;  
    function \_\_construct(E $eF $f){  
        $this->e = $e;  
        $this->f = $f;  
    }  
}  
        

硬编码方式

$a = new A(new Bnew Cnew D(new Enew F));  
        

策略方式(零配置)

$a = $di->get('A');  
        

所有依赖项,以及那些依赖项的依赖项(递归),都自动解决。魔法!不是吗?

2.2 提高可维护性

使用策略后,您现在只需修改构造函数即可向任何类添加依赖。让我们举一个例子
C 的定义在开发生命周期中被修改,现在依赖于名为 X 的类。
不需要在创建 C 的任何地方都使用 finstrategy,并将 X 的实例传递给它,这由 IoC 容器自动处理。
这就是您需要做的全部内容

class C {  
    private $x;  
    function \_\_construct(X $x){  
        $this->x = $x;  
    }  
}  
        

2.3 提高灵活性

通过使用 依赖注入,您的类不再被硬编码到特定实例。

硬编码方式

class A {  
    private $b;  
    private $c;  
    private $d;  
    function \_\_construct(){  
        $this->b = new B;  
        $this->c = new C;  
        $this->d = new D(new Enew F);  
    }  
  
}  
        

使用此代码,无法使用 B 的子类替换 B 的实例。类 A 与其依赖项紧密耦合。通过依赖注入,任何组件都可以被替换。

依赖注入方式

class A {  
    private $b;  
    private $c;  
    private $d;  
    function \_\_construct(B $bC $cD $d){  
        $this->b = $b;  
        $this->c = $c;  
        $this->d = $d;  
    }  
  
}          
        

在这里,B 可以是任何配置方式可能的 B 的子类。这允许实现更大的 关注点分离。类 A 永远不必担心配置其依赖项,它们以可使用状态提供给它。通过减少类的责任,灵活性大大增强,因为 A 可以与任何变体的 BCD 实例一起重用。

2.4 提高可扩展性

忘记几乎所有的工厂、注册表和服务定位器。通过使用策略,您可以随心所欲地更改构造函数参数或删除依赖项,而无需担心代码中的副作用。
对象不再需要知道其他对象具有哪些依赖项,更改变得极其容易!

一旦策略开始处理应用程序的依赖项,您只需像这样向系统中添加一个类

class B {  
    function \_\_construct(PDO $pdoC $c){  
  
    }  
}  
        

并在您现有的某个类中要求它

class ExistingA {  
    function \_\_construct(B $b){  
          
    }  
}          
        

它将正常工作,甚至不需要告诉策略任何关于它的事情。您不必担心已经更改了现有类的构造函数,因为它将被自动解决,您也不必担心定位或配置新类所需的依赖项!

2.5 聪明一些

策略可以管理应用程序顶层上的依赖项,并通过紧密耦合组件的深层树来解决它们,帮助您避免 "信使" 反模式,这是使用依赖注入时常见的问题。
但这并不免除您在低级别解耦组件中使用纯 OO 封装,在这些组件中您不期望外部可扩展性,并且您有意识地选择紧密耦合东西。
最后,您需要明确定义解耦组件并添加单独的耦合层,以确定面向对象和面向组件方法之间的界限。

  1. 开始使用

3.1 依赖注入实例化

$di = \\Strategy\\Di::getInstance(); //global shared instance  
  
$di = new \\Strategy\\Di//classical new instance  
        

3.2 类实例化

$di->get('My\\Class');  
  
\\Strategy\\Di::getInstance()->get('My\\Class'); //global shared instance used via object  
  
\\Strategy\\Di::make('My\\Class'); //global shared instance used via static call  
        
  1. 基本用法

为什么策略(及其Dice父类)有所不同?许多DIC需要您为每个可能的组件提供一些配置才能正常工作。

策略采用约定优于配置的方法,并使用类型提示来推断对象具有哪些依赖项。因此,基本对象图不需要配置。

4.1 对象图创建

class A {  
    private $b;  
  
    function \_\_construct(B $b) {  
        $this->b = $b;  
    }  
}  
  
class B {  
    private $c,$d;  
  
    function \_\_construct(C $cD $d) {  
        $this->c = $c;  
        $this->d = $d;  
    }  
}  
  
class C {  
  
}  
  
class D {  
    private $e;  
      
    function \_\_construct(E $e) {  
        $this->e = $e;  
    }  
}  
  
class E {  
      
}  
  
$a = $di->get('A');  
print\_r($a);  
        

这创建了

A Object  
(  
    [b:A:private] => B Object  
        (  
            [c:B:private] => C Object  
                (  
                )  
  
            [d:B:private] => D Object  
                (  
                    [e:D:private] => E Object  
                        (  
                        )  
  
                )  
  
        )  
  
)  
        

在最简单的层面上,这已经删除了创建对象图所需的大量初始化代码。

4.2 Provistrategy向构造函数添加额外参数

构造函数通常需要公共依赖项以及特定于该实例的某些配置。例如

class A {  
    public $name;  
    public $b;  
      
    function \_\_construct(B $b$name) {  
        $this->name = $name;  
        $this->b = $b;  
    }  
}  
        

在这里,该类需要一个B的实例以及一个唯一的名称。策略允许这样做

$a1 = $di->get('A', ['FirstA']);  
$a2 = $di->get('A', ['SecondA']);  
  
echo $a1->name// "FirstA"  
echo $a2->name// "SecondA"  
        

B的依赖项将自动解决,第二个参数中的字符串将作为第二个参数传递。您可以使用第二个参数作为数组来传递任何数量的额外构造函数参数,使用$di->get();

策略的特定性
您还可以使用关联数组,其中参数的名称将匹配构造定义中的变量名称,甚至可以结合关联、数字索引和类型提示!
让我们举一个例子

class A {  
    public $name;  
    public $lastname;  
    public $pseudo;  
    public $b;     
  
    function \_\_construct(B $b$name$lastname$pseudo){  
        $this->name = $name;  
        $this->lastname = $lastname;  
        $this->pseudo = $pseudo;  
        $this->b = $b;  
    }  
}  
  
$a1 = $di->get('A', [ //order of associative keys doesn't matter  
    'lastname'=>'RedCat'  
    'name'=>'Jo',  
    'pseudo'=>'Surikat',  
]);  
$a2 = $di->get('A', [  
    'RedCat',  
    'Surikat'  
    'name'=>'Jo'//order of associative key doesn't matter  
]);  
$a3 = $di->get('A', [  
    'Jo',  
    'lastname'=>'RedCat' //order of associative key doesn't matter  
    'Surikat',  
]);  
  
echo $a1->name// "Jo"  
echo $a1->lastname// "RedCat"  
echo $a1->pseudo// "Surikat"  
  
echo $a2->name// "Jo"  
echo $a2->lastname// "RedCat"  
echo $a2->pseudo// "Surikat"  
  
echo $a3->name// "Jo"  
echo $a3->lastname// "RedCat"  
echo $a3->pseudo// "Surikat"
        

这个功能的限制是它只适用于用户定义的类,您不能在原生PHP类(如PDO)上使用关联键。反射API(提取构造函数变量的名称)不适用于它们,因为它们是预编译的。为了克服这一限制,您必须扩展它们,并在扩展的构造函数中为自己命名这些变量,可以直接调用其父构造函数,就这么简单。

  1. 共享依赖项

到目前为止,依赖注入在现实世界中最常见的用法是使单个对象实例可供应用程序的不同部分访问。例如,数据库对象和区域设置配置是常见的选择。

策略使创建在整个应用程序中共享的对象成为可能。任何传统上作为全局变量、单例、静态访问或通过服务定位器/仓库访问的任何内容都视为共享对象。

任何请求已标记为共享的类的实例的类构造函数将传递该对象的共享实例,而不是新实例。

5.1 使用规则配置共享依赖项

定义共享对象的方法是通过规则。有关更多信息,请参阅下面的规则部分。它们用于配置容器。以下是如何使用规则定义共享对象的示例。

策略接受特定类的规则,并在每次从容器请求该类的实例时应用它。规则是一个数组,包含在请求实例时将应用的一组选项。

此示例使用PDO,因为这是一个非常常见的用例。

//create a rule to apply to shared object  
$rule = ['shared' => true];  
  
//Apply the rule to instances of PDO  
$di->addRule('PDO'$rule);  
  
//Now any time PDO is requested from Strategy, the same instance will be returned  
$pdo = $di->get('PDO');  
$pdo2 = $di->get('PDO');  
var\_dump($pdo === $pdo2); //TRUE  
  
//And any class which asks for an instance of PDO will be given the same instance:  
class MyClass {  
    public $pdo;  
    function \_\_construct(PDO $pdo) {  
        $this->pdo = $pdo;  
    }  
}  
  
$myobj = $di->get('MyClass');  
var\_dump($pdo === $myobj->pdo); //TRUE  
        

在这里,两个PDO实例将是相同的。然而,因为这将很可能是该页面上引用次数最多的代码片段,为了使此示例完整,还需要配置PDO构造函数。

$rule = [  
    //Mark the class as shared so the same instance is returned each time  
    'shared' => true,   
    //The constructor arguments that will be supplied when the instance is created  
    'construct' => ['mysql:host=127.0.0.1;dbname=mydb''username''password']   
];  
  
//Apply the rule to the PDO class  
$di->addRule('PDO'$rule);  
  
//Now any time PDO is requested from Strategy, the same instance will be returned  
//And will havebeen constructed with the arugments supplied in 'construct'  
$pdo = $di->get('PDO');  
$pdo2 = $di->get('PDO');  
var\_dump($pdo === $pdo2); //TRUE  
  
  
//And any class which asks for an instance of PDO will be given the same instance:  
class MyClass {  
    public $pdo;  
    function \_\_construct(PDO $pdo) {  
        $this->pdo = $pdo;  
    }  
}  
  
class MyOtherClass {  
    public $pdo;  
    function \_\_construct(PDO $pdo) {  
        $this->pdo = $pdo;  
    }  
}  
  
  
//Note, Strategy is never told about the 'MyClass' or 'MyOtherClass' classes, it can  
//just automatically create them and inject the required PDO isntance  
  
$myobj = $di->get('MyClass');  
$myotherobj = $di->get('MyOtherClass');  
  
//When constructed, both objects will have been passed the same instance of PDO  
var\_dump($myotherobj->pdo === $myobj->pdo); //TRUE  
        

已添加construct规则以确保每次创建PDO实例时,都提供一个构造函数参数集。有关更多信息,请参阅construct部分。

策略的特定性
RedCat\Strategy\Di类的全局实例自然是可以共享的。

  1. 使用规则配置容器

为了允许完全的灵活性,可以使用关联数组提供的规则完全配置容器,这些规则通过addRule方法传递给容器。

$rule = ['name' => 'value'];  
$di->addRule('rulename'$rule);  
        

默认情况下,规则名称与类名称匹配,因此,要将规则应用于名为A的类,您将使用

$di->addRule('A'$rule);  
$a = $di->get('A');  
        

每次容器创建A的实例时,它将使用由$rule定义的规则。

策略规则可以使用以下属性进行配置

  • shared (布尔值) - 是否在整个容器中使用单个实例。查看示例
  • inherit (布尔值) - 是否将规则也应用于子类(默认为true)。查看示例
  • construct (数组) - 传递给构造函数的额外参数。查看示例
  • substitutions (数组) - 依赖项的键值替换。查看示例
  • call (多维数组) - 在对象构建后将被调用的方法和它们的参数列表。查看示例
  • instanceOf (字符串) - 要启动的类的名称。当未将类名称传递给$di->addRule()时使用。查看示例
  • shareInstances (数组) - 在单个对象树中共享的类名称列表。查看示例

6.1 替换

当使用接口或为了启用多态而使用构造函数参数时,容器需要确切地知道它将要传递什么。考虑以下类

class A {  
    function \_\_construct(Iterator $iterator) {  
      
    }  
}  
        

很明显,不能使用"Iterator"实例,因为它是一个接口。如果你想传递B的实例

class B implements Iterator {  
    //...  
}  
        

规则可以定义为如下

//When a constructor asks for an instance of Iterator pass it an instance of B instead  
$rule = ['substitutions' => ['Iterator' => new Expander('B')]];  
  
$di->addRule('A'$rule);  
$a = $di->get('A');  
        

策略的特定性
移除了['instance' => 'name']语法,因为它与参数的关联数组不兼容,并且在某些其他情况下并不一致,并且它被可以充当懒惰实例化器或(如果你向它传递闭包)懒惰回调解析器的扩展器对象所取代。
要使用它,只需通过其完整类名new \RedCat\Strategy\Expander()或通过在代码顶部调用use RedCat\Strategy\Expander;来实例化它,这样您就可以在以下代码中使用new Expander()

新的Expander('B')对象用于告诉策略在'Iterator'的位置创建'B'的实例。new Expander('B')可以读作'由策略创建的B的实例'。

不使用['substitutions' => ['iterator' => $di->get('B')]]的原因是这会立即创建B对象。使用扩展器对象意味着只有在需要时才会创建B的实例。

但是,如果应用程序需要这个呢?

$a = new A(new DirectoryIterator('/tmp'));  
        

可以使用策略的三种方法来实现这一点。

  1. 直接替换,将完全构建的对象传递给规则
$rule = ['substitutions' => ['Iterator' => new DirectoryIterator('/tmp')]];  
  
$di->addRule('A'$rule);  
$a = $di->get('A');  
        
  1. 使用闭包的工厂替换

您可以将闭包传递给Expander对象,并且当需要替换时,将调用它,并使用返回值作为替换。请注意,这是在即时进行的,因此将在应用到的类被实例化时调用。

$rule = ['substitutions' =>   
            ['Iterator' => new Expander(function() {  
                            return new DirectoryIterator('/tmp');  
                        })]  
            ]  
        ];  
  
$di->addRule('A'$rule);  
$a = $di->get('A');  
        
  1. 命名实例。有关如何工作的更详细解释,请参阅命名实例部分。
$namedDirectoryIteratorRule = [  
                    //An instance of the DirectoryIterator class will be created  
                    'instanceOf' => 'DirectoryIterator',  
                    //When the DirectoryIterator is created, it will be passed the string '/tmp' as the constructor argument  
                    'construct' => ['/tmp']  
];  
  
  
//Create a rule under the name "$MyDirectoryIterator" which can be referenced as a substitution for any other rule  
$di->addRule('$MyDirectoryIterator'$namedDirectoryIteratorRule);  
  
  
//This tells the DI Container to use the configuration for $MyDirectoryIterator when an Iterator is asked for in the constructor argument  
$aRule = ['substitutions' =>   
            [  
                'Iterator' => new Expander('$MyDirectoryIterator')  
            ]  
        ];  
  
//Apply the rule to the A class  
$di->addRule('A'$aRule);  
  
//Now, when $a is created, it will be passed the Iterator configured as $MyDirectoryIterator  
$a = $di->get('A');  
        

6.2 继承

默认情况下,所有规则都会应用于任何父类具有规则的子类。例如

class A {  
}  
  
class B extends A {  
}  
  
  
//Mark instances of A as shared  
$aRule = ['shared' => true];  
$di->addRule('A'$aRule);  
  
//Get the rule currently applied to 'B' objects  
$bRule = $di->getRule('B');  
  
//And B instances will also be shared  
var\_dump($bRule['shared']); //TRUE  
  
//And to test it:  
$b1 = $di->get('B');  
$b2 = $di->get('B');  
  
var\_dump($b1 === $b2); //TRUE (they are the same instance)  
        

可以使用规则的inherit属性来禁用此行为

class A {  
}  
  
class B extends A {  
}  
  
  
//This time mark A as shared, but turn off rule inheritance  
$aRule = ['shared' => true'inherit' => false];  
  
$di->addRule('A'$rule);  
$bRule = $di->getRule('B');  
  
//Now, B won't be marked as shared as the rule applied to A is not inherited  
var\_dump($bRule['shared']); //FALSE  
  
//And to test it:  
$b1 = $di->get('B');  
$b2 = $di->get('B');  
  
var\_dump($b1 === $b2); //FALSE (they are not the same instance)  
        

6.3 构造函数参数

策略的特定性
constructParams已被重命名为construct。

在定义规则时,必须提供任何未进行类型提示的构造函数参数,以便成功初始化类。例如

class A {  
    function \_\_construct(B $b$foo$bar) {  
    }  
}  
        

容器的任务是解析B。然而,如果没有配置,它不可能知道$foo和$bar应该是什么。

这些使用以下方式提供

$rule = ['construct' => ['Foo''Bar']];  
  
$di->addRule('A'$rule);  
  
$a = $di->get('A');  
        

这相当于

new A(new B'Foo''Bar');  
        

依赖项的构造函数参数顺序无关紧要

class A {  
    function \_\_construct($foo$barB $b) {  
    }  
}  
  
$rule = ['construct' => ['Foo''Bar']];  
  
$di->addRule('A'$rule);  
  
$a = $di->get('A')  
        

策略足够智能,可以解决参数顺序,并按预期执行,并等于

new A('Foo''Bar'new B);  
        

6.4 设置器注入

对象往往需要以它们的构造函数没有考虑的方式配置。例如:PDO::setAttribute()可能需要在PDO构造后进一步配置PDO。

为了解决这个问题,策略规则可以提供在对象构造后调用的方法列表,以及为这些方法提供参数。这通过使用$rule->call实现。

class A {  
    function \_\_construct(B $b) {  
      
      
    }  
      
    function method1($foo$bar) {  
        echo 'Method1 called with ' . $foo . ' and ' . $bar . "\\n";  
    }  
      
    function method2() {  
        echo "Method2 called\\n";  
    }  
}  
  
  
$rule = [  
            'call' => [  
                ['method1', ['Foo1' ,'Bar1']],  
                ['method1', ['Foo2' ,'Bar2']],  
                ['method2', []]  
            ]  
        ];  
  
  
$di->addRule('A'$rule);  
$a = $di->get('A');  
        

这将输出

Method1 called with Foo1 and Bar1   
Method1 called with Foo2 and Bar2  
Method2 called  
        

$rule['call']中定义的方法将按照提供的数组顺序被调用。

实际示例:PDO

这是一个创建PDO实例的实际世界示例。

$rule = [  
    'construct' => ['mysql:host=127.0.0.1;dbname=mydb''username''password'],  
    'shared' = true,  
    'call' => [  
        ['setAttribute', [PDO::ATTR\_DEFAULT\_FETCH\_MODEPDO::FETCH\_OBJ]]  
    ]  
];  
  
$di->addRule('PDO'$rule);  
  
class MyClass {  
    function \_\_construct(PDO $pdo) {  
      
    }  
}  
  
//MyObj will be constructed with a fully initialisd PDO object  
$myobj = $di->get('MyClass');  
        

策略的特定性
对于传递给调用参数,您也可以使用与构造规则类似的方法变量名匹配的关联数组。但您也可以使用将使用方法名作为键的关联数组,如果您必须传递给方法不是数组的单个参数,您可以直接传递它,而不需要将其包装在数组中,它将被自动转换为数组。
让我们看一些例子

    $rule = [  
        'call' => [  
            'methodName'=>[$arg1$arg2],  
            ['methodName',[$arg3$arg4]],  
            'otherMethodName'=>$singleArgThatIsNotAnArray,  
              
            'methodName'=>[  
                'varname2'=>$arg2  
                'varname'=>$arg1,  
            ],  
            ['methodName',[  
                'varname2'=>$arg2  
                'varname'=>$arg1,  
            ]],  
        ]  
    ];  

6.5 默认规则

策略还允许通过将其应用于'*'来将规则应用于它创建的任何对象。由于在PHP中无法命名类'*',这不会引起任何兼容性问题。

默认规则将应用于任何受其他规则影响的对象。

此的主要用途是允许应用级规则。这对于类型提示的参数很有用。例如,您可能希望任何将PDO对象作为构造函数参数的类使用您创建的替换子类。例如

class MyPDO extends PDO {  
    //...  
}  
        

策略允许您通过添加默认规则将"MyPDO"对象传递给任何需要PDO实例的构造函数

class Foo {  
    public $pdo;  
    function \_\_construct(PDO $pdo) {  
        $this->pdo = $pdo;  
    }  
}  
  
//When PDO is type hinted, supply an instance of MyPDO instead  
$rule = ['substitutions' => ['PDO' => new Expander('MyPDO')]];  
  
//Apply the rule to every class  
$di->addRule('\*'$rule);  
  
$foo = $di->get('Foo');  
echo get\_class($foo->pdo); // "MyPDO"  
        

默认规则的功能与所有其他规则相同。可以将对象默认设置为共享,例如。

6.6 命名实例

策略最强大的功能之一是命名实例。命名实例允许在应用程序中访问不同的依赖配置。当您的应用程序逻辑不需要使用相同配置的依赖项时,这非常有用。

例如,如果您需要从一个数据库复制数据到另一个数据库,您需要配置两个不同的数据库对象。使用命名实例,这成为可能。

class DataCopier {  
    function \_\_construct(PDO $database1PDO $database2) {  
          
    }  
}  
  
//A rule for the default PDO object  
$rule = [  
    'shared' => true,  
    'construct' = ['mysql:host=127.0.0.1;dbname=mydb''username''password']  
];  
  
$di->addRule('PDO'$rule);  
  
  
//And a rule for the second database  
$secondDBRule = [  
    'shared' => true,  
    'construct' = ['mysql:host=externaldatabase.com;dbname=foo''theusername''thepassword'],  
      
    //This rule will create an instance of the PDO class  
    'instanceOf' => 'PDO'  
];  
  
  
//Add named instance called $Database2  
//Notice that the name being applied to is not the name of class  
//but a chosen named instance  
$di->addRule('$Database2'$secondDBRule);  
  
//Now set DataCopier to use the two different databases:  
$dataCopierRule = [  
    'construct' => [  //Set the constructor parameters to the two database instances.  
            new Expander('PDO'),  
            new Expander('$Database2')  
    ]  
];  
  
  
$di->addRule('DataCopier'$dataCopierRule);  
  
$dataCopier = $di->get('DataCopier');  
        

$dataCopier现在将被创建,并将实例传递给两个数据库中的每一个。

一旦定义了命名实例,就可以通过使用new Expander('$name'),由其他规则在依赖注入容器中使用替换或构造参数来引用它。

命名实例不需要以美元符号开头,但是建议用不在类名中有效的字符作为前缀。

6.7 在树之间共享实例

在某些情况下,您可能希望在一个树的每个类之间共享单个类的单个实例,但如果创建了顶级类的另一个实例,则拥有树的第二个实例。

例如,想象一个MVC三联体,模型需要在控制器和视图之间共享,但如果创建了另一个控制器和视图的实例,它们需要在其之间共享一个新的模型实例。

最好的解释方法是实际演示。

class A {  
  
    public $b$c;  
      
    function \_\_construct(B $bC $c) {  
      
      
    }  
  
}  
  
  
class B {  
    public $d;  
      
    function \_\_construct(D $d) {  
        $this->d = $d;  
    }  
}  
  
class C {  
    public $d;  
      
    function \_\_construct(D $d) {  
        $this->d = $d;  
    }  
}  
  
  
class D {}  
        

通过使用$rule->shareInstances,可以在对象树的每个实例中标记D为共享。与全局共享对象的重要区别在于,此对象仅在对象树的单个实例中共享。

$rule = [  
    'shareInstances' = ['D']  
];  
  
$di->addRule('A'$rule);  
  
  
//Create an A object  
$a = $di->get('A');  
  
//Anywhere that asks for an instance D within the tree that existis within A will be given the same instance:  
//Both the B and C objects within the tree will share an instance of D  
var\_dumb($a->b->d === $a->c->d); //TRUE  
  
//However, create another instance of A and everything in this tree will get its own instance of D:  
  
$a2 = $di->get('A');  
var\_dumb($a2->b->d === $a2->c->d); //TRUE  
  
var\_dumb($a->b->d === $a2->b->d); //FALSE  
var\_dumb($a->c->d === $a2->c->d); //FALSE  
        

7 Cascastrategy 规则

当设置已经设置的规则时,Strategy将更新应用于该类的现有规则

$di->addRule('B', ['shared' => true]);  
$di->addRule('B', ['construct' => ['foo']]);  
        

这两个规则都将应用于B类。

这在哪里有用,当使用继承时

class A {  
  
}  
  
class B extends A {  
  
}  
        
$di->addRule('A', ['shared' => true]);  
$di->addRule('B', ['construct' => ['foo']]);  
        

因为B继承自A,所以应用于A的规则也将应用于B(此行为可以关闭,请参阅继承部分http://redcatphp.com/strategy-dependency-injection#config-rules-inheritance),因此在这种情况下,B将同时共享并设置构造参数。

但是,如果需要,可以关闭B的共享

$di->addRule('A', ['shared' => true]);  
$di->addRule('B', [  
    'construct' => 'foo'],  
    'shared' => false  
]);  
        

并且这保持了A的共享,但关闭了子类B的共享。

策略的特定性
这是Strategy对Dice做出的最重要的改进。
与Dice不同,Strategy在实例化时使规则级联,这种技术的优点是继承可以适应PHP原生继承(extends和implements),而无需加载类,如果我们使用自动加载器,Dice中的is_subclass_of在规则定义时调用这些类,并执行大量不必要的工作。
它还影响了规则的定义方式,使用“在调用时创建规则”实践,规则的定义顺序并不重要,级联将遵循自然的PHP继承,从祖先到最终类,通过在类定义中实现的接口顺序传递。
另一个区别是,规则将在级联过程中递归合并。
并且还有一个用于扩展规则但不是替换它的API功能:$di->extendRule($name, $key (shared|construct|shareInstances|call|inherit|substitutions|instanceOf|newInstances), $value, $push = null)。

8 任意数据

这些功能来自Pimple
任意变量用于在应用程序中共享特定配置。您也可以使用它们为工厂带来非常具体的更高灵活性,有时这可能会很方便,但在此处可以将这种做法视为反模式,您可以通过以下规则来避免这种做法的绝大多数情况:规则

所有Pimple API与原始文档相同,除非您尝试获取一个不存在的键,此时它将使用$di->get($key)进行填充。

8.1 简单变量定义

$di['foo'] = 'bar';  
echo $di['foo'];  
        

8.2 匿名函数

$container['session\_storage'] = function ($c) {  
    return new SessionStorage('SESSION\_ID');  
};  
  
$container['session'] = function ($c) {  
    return new Session($c['session\_storage']);  
};  
  
$session = $container['session']; //get the session object  
$session2 = $container['session']; //get the same session object  
var\_dump($session===$session2); //will show true  
        

8.3 手动定义工厂

$container['session'] = $container->factory(function ($c) {  
    return new Session($c['session\_storage']);  
});  
$session = $container['session']; //get a session object  
$session2 = $container['session']; //get a new session object  
var\_dump($session===$session2); //will show false  
        

8.4 保护匿名函数

因为Strategy将匿名函数视为服务定义,您需要使用protect()方法将其包装起来,以便将它们存储为参数并能够重用它们。

$container['random\_func'] = $container->protect(function () {  
    return rand();  
});  
  
        

8.5 扩展定义

$container['session\_storage'] = function ($c) {  
    return new $c['session\_storage\_class']($c['cookie\_name']);  
};  
  
$container->extend('session\_storage'function ($storage$c) {  
    $storage->...();  
  
    return $storage;  
});  
        

9 PHP配置

defineClass

您可以使用此API通过名称自动交换构造函数参数或设置器参数与Strategy中设置的任意变量(请参阅任意数据)。
这是一种方便地将一些常见配置变量从类规则定义中解耦的方法。
通过在关联数组或数字键的前面加上"$",该值将被用作指向要使用的变量的指针。您可以在指针中使用"."(点)来遍历数组。
让我们举一个例子
使用以下方法

$di['zero'] = 'foo';  
$di['varname'] = 'bar';  
$di['dotted']['sub'] = 'Sub data accessible by dot';  
$di->defineClass('A', [  
    'construct' =>[  
        '$0'=>'zero',  
        '$assoc'=>'varname',  
        '$other'=>'dotted.sub',  
        'assoc2'=>'realvar',  
    ],  
    'call' =>[  
        'method'=>[  
            '$assoc'=>'varname',  
            '$other'=>'dotted.sub',  
        ]  
    ],  
]);  
        

结果将如下所示

$di->addRule('A', [  
    'construct' =>[  
        'foo',  
        'assoc'=>'bar',  
        'assoc2'=>'realvar',  
        'other'=>'Sub data accessible by dot',  
    ],  
    'call' =>[  
        'method'=>[  
            'assoc'=>'bar'  
            'other'=>'Sub data accessible by dot',  
        ]  
    ],  
]);  
        

loadPhp

这是您的配置文件

<?php  
return [  
    '$'=>  
        'db\_name'=>'mydb',  
        'db\_user'=>'me',  
        'db\_password'=>'@FuçK1ngP@ssW0rd',  
    ],  
    'rules'=>[  
        'MyPDO'=>[  
            '$name'=>'db\_name',  
            '$user'=>'db\_user',  
            '$pass'=>'db\_password',  
        ],  
    ],  
];  
        

您可以通过以下方式加载它

$di->loadPhp('/my/path/to/config.php');  
        

loadPhpMap

此方法基于与loadPhp相同的原理,但您必须传递一个配置文件数组。不同之处在于,在应用规则之前,所有通过"$"定义的变量都将按map数组中的文件顺序递归合并,规则也将递归合并。

$di->loadPhpMap([  
    '/path/to/default\_config.php',  
    '/path/to/config.php',  
]);  
        

load

这是一个作用在全局Di实例上的静态方法。您必须传递一个类似于loadPhpMap的配置映射,但您还可以传递一个布尔值以启用或禁用frozen模式,以及一个用于存储frozen文件的路径。这是服务器在生产中的最后一个优化步骤,它将通过序列化容器来备份解析的配置,以便加载更快。如果您更改配置,您必须删除您的frozen文件以更新配置。

RedCat\\Strategy\\Di::load([  
    '/path/to/default\_config.php',  
    '/path/to/config.php',  
],true,'temp-path/to/myApplyConfig.svar');