redcatphp/strategy

此包已被废弃且不再维护。未建议替代包。

策略 - 使依赖注入通用化

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

README

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

依赖注入容器通用化

实质上

硬编码方式

$a = new A(new B, new C, new D(new E, new F));  
    

策略方式

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

通过 逆向工程,采用 PHP 反射 实现的,所有依赖项以及递归依赖项都自动解析!

摘要

  1. 关于策略的说明

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

    1. 简化代码
    2. 提高可维护性
    3. 提高可扩展性
    4. 明智之举
  3. 入门

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

    1. 创建对象图
    2. 向构造函数提供额外的策略参数
  5. 共享依赖项

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

    1. 替换
    2. 继承
    3. 构造函数参数
    4. 设置器注入(修改器)
    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, B $b, C $c, D $d){  
        $this->= $a;  
        $this->= $b;  
        $this->= $c;  
        $this->= $d;  
    }  
}  
class D {  
    private $e;  
    private $f;  
    function \_\_construct($e, F $f){  
        $this->= $e;  
        $this->= $f;  
    }  
}  
        

硬编码方式

$a = new A(new B, new C, new D(new E, new F));  
        

策略方式(零配置)

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

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

2.2 提高可维护性

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

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

2.3 提高灵活性

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

硬编码方式

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

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

依赖注入方法

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

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

2.4 提高可扩展性

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

一旦策略处理应用程序的依赖关系,您可以简单地像这样将一个类添加到系统中。

class B {  
    function \_\_construct(PDO $pdo, C $c){  
  
    }  
}  
        

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

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

它将正常工作,甚至不需要告诉策略任何关于它的信息。您不需要担心更改了现有类的构造函数,因为它将自动解析,您也不需要担心定位或配置新类需要的依赖关系!

2.5 聪明一点

策略可以在应用程序的最高层管理依赖关系,并通过深度耦合组件树解决它们,帮助您避免“信使” 反模式,这是使用依赖注入时常见的问题。
但这并不意味着您需要在低层解耦组件中使用纯面向对象 封装,在那里您不需要外部可扩展性,并且您有意识地选择紧密耦合事物。
最后,您需要清楚地定义解耦组件和添加单独的耦合接口,以决定面向对象和面向组件方法之间的界限。

  1. 入门

3.1 Di 实例化

$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) {  
        $this->= $b;  
    }  
}  
  
class B {  
    private $c,$d;  
  
    function \_\_construct($c, D $d) {  
        $this->= $c;  
        $this->= $d;  
    }  
}  
  
class C {  
  
}  
  
class D {  
    private $e;  
      
    function \_\_construct($e) {  
        $this->= $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, $name) {  
        $this->name = $name;  
        $this->= $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, $name, $lastname, $pseudo){  
        $this->name = $name;  
        $this->lastname = $lastname;  
        $this->pseudo = $pseudo;  
        $this->= $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  
        

已添加构造规则,以确保每次创建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']语法,因为它与参数的关联数组不兼容,并且在某些情况下不太一致,并且已被可以充当懒惰实例化器或传递闭包(匿名函数)时的懒惰回调解析器的Expander对象所取代。
要使用它,只需通过其全类名new \RedCat\Strategy\Expander()或通过在代码顶部调用use RedCat\Strategy\Expander;来实例化它,这样您就可以在以下代码中使用new Expander()

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

不使用['substitutions' => ['iterator' => $di->get('B')]]的原因是这会立即创建一个B对象。使用Expander对象意味着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)  
        

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

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, $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, $bar, B $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) {  
      
      
    }  
      
    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\_MODE, PDO::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 $database1, PDO $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现在将创建并传递给两个数据库的每个实例。

一旦定义了命名实例,就可以通过使用Dependency Injection Container中的new Expander('$name')引用其他规则,无论是替换参数还是构造函数参数。

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

6.7 在树之间共享实例

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

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

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

class A {  
  
    public $b, $c;  
      
    function \_\_construct($b, C $c) {  
      
      
    }  
  
}  
  
  
class B {  
    public $d;  
      
    function \_\_construct($d) {  
        $this->= $d;  
    }  
}  
  
class C {  
    public $d;  
      
    function \_\_construct($d) {  
        $this->= $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->=== $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->=== $a2->c->d); //TRUE  
  
var\_dumb($a->b->=== $a2->b->d); //FALSE  
var\_dumb($a->c->=== $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(此行为可以被关闭,请参阅继承部分),所以在这个例子中,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');