Download the PHP package redcatphp/strategy without Composer
On this page you can find all versions of the php package redcatphp/strategy. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download redcatphp/strategy
More information about redcatphp/strategy
Files in redcatphp/strategy
Package strategy
Short Description Strategy - Dependency Injection Made Universal
License LGPL-3.0+ CC0-1.0
Homepage http://redcatphp.com/strategy-dependency-injection
Informations about the package strategy
No longer actively maintained. I work now with NodeJS and I recommand you to take a look at di-ninja, it's the same paradigm ported to Javascript
Dependency Injection Container made Universal
Substantially
Hardcoded way:
Strategy way:
By dint of reverse engineering practiced by php reflection, all the dependencies, and, recursively, dependencies of those dependencies, are automatically resolved !
Summary
- Notes about Strategy
- Paradigm
- Get Started
- Basic usage
- Shared dependencies
- Configuring the container with rules
- Rule cascastrategy
- Arbitrary Data
-
Notes about Strategy
1.1 Origin
Strategy is mainly inspired by Dice with added Pimple abilities and great improvements.
1.2 Differences
For those who allready knowing the marvellous Dice, here is the additionals features:
- lazy load cascade rules resolution (make rules cascade at instanciation)
- associative array fitting the constructor name variables
- lazy load instance with Expander object instead of instance array
- full registry implementation
- dynamic rules construct and call variables
- cascade config for arbitrary data and rules which can use them
-
freeze config optimisation
Many chapters of the following documentation correspond to Dice documentation with some modifications, reformulations, additions and new features explanations. The main differences from Dice and new features explanations will be foreword by Strategy specificity label.
-
Paradigm
2.1 Simplify your code !
Consider base classes like these:
Hardcoded way:
Strategy way (zero configuration):
All the dependencies, and dependencies of those dependencies (recursively), are automatically resolved. Magic! Isn't it?
2.2 Improve maintainability
With Strategy, you're now able to add dependencies to any class just by modification on its constructor. Let's take an example:
The definition of the class C is modified during the development lifecycles and now has a dependency on a class called X.
Instead of finstrategy everywhere that C is created and have to passing an instance of X to it, this is handled automatically by the IoC Container.
That's all you need to do:
2.3 Bring higher flexibility
By using dependency injection, your class isn't anymore hardcoded to a particular instance.
Hardcoded way:
With this code, it's impossible to use a subclass of B in place of the instance of B. A is very tightly coupled to its dependencies. With dependency injection, any of the components can be substituted.
Dependency injection way:
Here, B could be any subclass of B configured in any way possible. This allows for a far greater Separation of Concerns. A never has to worry about configuring its dependencies, they're given to it in a state that is ready to use. By giving less responsibility to the class, flexibility is greatly enhanced because A can be reused with any variation of B, C and D instances.
2.4 Improve scalability
Forget almost all factories, registries and service locators. By using Strategy you can change the constructor parameters adstrategy/removing dependencies on a whim without worrying about side-effects throughout your code.
Objects doesn't anymore need to know what dependencies other objects have and making changes become incredibly easy!
Once Strategy is handling your application's dependencies, you can simply add a class to the system like this:
And require it in one of your existing classes:
And it will just work without even telling Strategy anything about it. You don't need to worry that you've changed an existing class's constructor as it will automatically be resolved and you don't need to worry about locating or configuring the dependencies that the new class needs!
2.5 Be wise
Strategy can manage dependencies on top level of your application and resolve them through deep tree of coupled components helping you to avoid "couriers" anti-pattern which is a common problem when using dependency injection.
But it does not exempt you to use pure OO encapsulation inside low levels of a decoupled component where you don't expect external scalability and where you choose consciously to tightly couple things.
Finally, it belongs to you to rule what is the limit between object-oriented and component-oriented approach by clearly define decoupled components and add separately coupling couch.
-
Get Started
3.1 Di Instanciation
3.2 Classes Instanciation
-
Basic usage
Why is Strategy (and its Dice parent) different? A lot of DICs require that you provide some configuration for each possible component in order just to work.
Strategy takes a convention-over-configuration approach and uses type hinting to infer what dependencies an object has. As such, no configuration is required for basic object graphs.
4.1 Object graph creation
Which creates:
At its simplest level, this has removed a lot of the initialisation code that would otherwise be needed to create the object graph.
4.2 Provistrategy additional arguments to constructors
It's common for constructors to require both dependencies which are common to every instance as well as some configuration that is specific to that particular instance. For example:
Here, the class needs an instance of B as well as a unique name. Strategy allows this:
The dependency of B is automatically resolved and the string in the second parameter is passed as the second argument. You can pass any number of additional constructor arguments using the second argument as an array to $di->get();
Strategy specificity
You can also use associative array where the name of argument will fit the name of variable in construct definition and even combine associative, numeric index and type hinting!
Let's take an example:
There is a limitation on this feature that is that's only work on user's defined classes, you cannot use associative keys on native php classes (like PDO). The reflection API which extract constructor's variables names does'nt work on them because they are precompiled. To work around this limitation you have to extends them and name yourself theses variables in the extended constructor that can call directly it's parent constructor, it's that easy.
-
Shared dependencies
By far the most common real-world usage of Dependency Injection is to enable a single instance of an object to be accessible to different parts of the application. For example, Database objects and locale configuration are common candidates for this purpose.
Strategy makes it possible to create an object that is shared throughout the application. Anything which would traditionally be a global variable, a singleton, accessible statically or accessed through a Service Locator / Repository is considered a shared object.
Any class constructor which asks for an instance of a class that has been marked as shared will be passed the shared instance of the object rather than a new instance.
5.1 Using rules to configure shared dependencies
The method of defining shared objects is by Rules. See the section on Rules below for more information. They are used to configure the container. Here's how a shared object is defined using a rule.
Strategy accepts a rule for a given class an applies it each time it creates an instance of that class. A rule is an array with a set of options that will be applied when an instance is requested from the container.
This example uses PDO as this is a very common use-case.
Here, both instances of PDO would be the same. However, because this is likely to be the most commonly referenced piece of code on this page, to make this example complete, the PDO constructor would need to be configured as well:
The construct rule has been added to ensure that every time an instance of PDO is created, it's given a set of constructor arguments. See the section on construct for more information.
Strategy specificity
The global instance of RedCat\Strategy\Di class is naturally shared.
-
Configuring the container with Rules
In order to allow complete flexibility, the container can be fully configured using rules provided by associative arrays rules are passed to the container using the addRule method:
By default, rule names match class names so, to apply a rule to a class called A you would use:
Each time an instance of A is created by the container it will use the rule defined by $rule
Strategy Rules can be configured with these properties:
- shared (boolean) - Whether a single instance is used throughout the container. View Example
- inherit (boolean) - Whether the rule will also apply to subclasses (defaults to true). View Example
- construct (array) - Additional parameters passed to the constructor. View Example
- substitutions (array) - key->value substitutions for dependencies. View Example
- call (multidimensional array) - A list of methods and their arguments which will be called after the object has been constructed. View Example
- instanceOf (string) - The name of the class to initiate. Used when the class name is not passed to $di->addRule(). View Example
- shareInstances (array) - A list of class names that will be shared throughout a single object tree. View Example
6.1 Substitutions
When constructor arguments are type hinted using interfaces or to enable polymorpsim, the container needs to know exactly what it's going to pass. Consider the following class:
Clearly, an instance of "Iterator" cannot be used because it's an interface. If you wanted to pass an instance of B:
The rule can be defined like this:
Strategy specificity
['instance' => 'name'] syntax was removed because it was not compatible with associative array of arguments and not very consistent in some other cases and it was replaced by Expander object that can act like a lazy instanciator, or lazy callback resolver if you pass a Closure (anonymous function) to it.
To use it, simply instanciate it by is full class name new \RedCat\Strategy\Expander() or by call use RedCat\Strategy\Expander; at top of your code so you can use just new Expander() throughout the following code.
The new Expander('B') object is used to tell Strategy to create an instance of 'B' in place of 'Iterator'. new Expander('B') can be read as 'An instance of B created by the Strategy'.
The reason that ['substitutions' => ['iterator' => $di->get('B')]] is not used is that this creates a B object there and then. Using the Expander object means that an instance of B is only created at the time it's required.
However, what if If the application required this?
There are three ways this can be achieved using Strategy.
-
Direct substitution, pass the fully constructed object to the rule:
- factory substitution with closures
You can pass a closure into the Expander object and it will be called and the return value will be used as the substitution when it's required. Please note this is done just-in-time so will be called as the class it's been applied to is instantiated.
- Named instances. See the section on Named instances for a more detailed explanation of how this works.
6.2 Inheritance
By default, all rules are applied to any child classes whose parent has a rule. For example:
The rule's inherit property can be used to disable this behaviour:
6.3 Constructor parameters
Strategy specificity
constructParams was renamed construct.
When defining a rule, any constructor parameters which are not type hinted must be supplied in order for the class to be initialised successfully. For example:
The container's job is to resolve B. However, without configuration it cannot possibly know what $foo and $bar should be.
These are supplied using:
This is equivalent to:
Constructor parameter order for dependencies does not matter:
Strategy is smart enough to work out the parameter order and will execute as expected and be equal to:
6.4 Setter injection
Objects often need to be configured in ways that their constructor does not account for. For example:PDO::setAttribute() may need to be called to further configure PDO even after it's been constructed.
To account fo this, Strategy Rules can supply a list of methods to call on an object after it's been constructed as well as supply the arguments to those methods. This is achieved using $rule->call:
This will output:
The methods defined in $rule['call'] will get called in the order of the supplied array.
Practical example: PDO
Here is a real world example for creating an instance of PDO.
Strategy specificity
For passing parameters to calls, you can also use associative array fitting the methods variables names like for the construct rule. But you can also use associative array that will use method name for keys and if you have to pass just one argument to the method that is not an array you can pass it without wrap it in array, it will be casted automatically.
Let's take some examples:
6.5 Default rules
Strategy also allows for a rule to apply to any object it creates by applying it to '*'. As it's impossible to name a class '*' in php this will not cause any compatibility issues.
The default rule will apply to any object which isn't affected by another rule.
The primary use for this is to allow application-wide rules. This is useful for type-hinted arguments. For example, you may want any class that takes a PDO object as a constructor argument to use a substituted subclass you've created. For example:
Strategy allows you to pass a "MyPDO" object to any constructor that requires an instance of PDO by adstrategy a default rule:
The default rule is identical in functionality to all other rules. Objects could be set to shared by default, for instance.
6.6 Named instances
One of Strategy's most powerful features is Named instances. Named instances allow different configurations of dependencies to be accessible within the application. This is useful when not all your application logic needs to use the same configuration of a dependency.
For example, if you need to copy data from one database to another you'd need two database objects configured differently. With named instances this is possible:
$dataCopier will now be created and passed an instance to each of the two databases.
Once a named instance has been defined, it can be referenced using new Expander('$name') by other rules using the Dependency Injection Container in either substitutions or constructor parameters.
Named instances do not need to start with a dollar, however it is advisable to prefix them with a character that is not valid in class names.
6.7 Sharing instances across a tree
In some cases, you may want to share a a single instance of a class between every class in one tree but if another instance of the top level class is created, have a second instance of the tree.
For instance, imagine a MVC triad where the model needs to be shared between the controller and view, but if another instance of the controller and view are created, they need a new instance of their model shared between them.
The best way to explain this is a practical demonstration:
By using $rule->shareInstances it's possible to mark D as shared within each instance of an object tree. The important distinction between this and global shared objects is that this object is only shared within a single instance of the object tree.
7 Cascastrategy Rules
When adstrategy a rule that has already been set, Strategy will update the existing rule that is applied to that class
Both rules will be applied to the B class.
Where this is useful is when using inheritance
Because B inherits A, rules applied to A will applied to B (this behaviour can be turned off, see the section on inheritance) so in this instance, B will be both shared and have the constructor parameters set.
However if required, shared can be turned off for B:
And this keep A shared, but turn it off for the subclass B.
Strategy specificity
Here is the most significative improvement to Dice made in Strategy.
Unlike Dice, Strategy make rules cascade at instanciation, the advantage of that technique is the inheritance can fit php native inheritance (extends and implements) without to load class, in a case we use an autoloader, the use of is_subclass_of in Dice call theses classes at rule definition time and do a lot of unnecessary work.
It also affect how the rules are defined, with "making rules on call" practice the order of rules definition doesn't matter, the cascade will follow natural php heritance from ancestor to final class, passing by interfaces in the order they're implemented in the class definition.
An other difference is that the rules will be recursively merged during cascade.
And there is an other api feature for extenstrategy rule and not replacing it unlike to addRule: $di->extendRule($name, $key (shared|construct|shareInstances|call|inherit|substitutions|instanceOf|newInstances), $value, $push = null).
8 Arbitrary Data
These features comes from Pimple .
Arbitrary variables are used for share specific config across a whole application. You can also use them for bring very specific higher flexibility to factories, it can be convenient sometimes, but this practice can be considered here as an anti-pattern and you can avoid this most of time using rules.
All the Pimple API is the same as on original doc except when you "offsetGet" an unexistant key it will be filled with $di->get($key).
8.1 Simple variable definition
8.2 Anonymous functions
8.3 Defining factories manually
8.4 Protecting anonymous functions
Because Strategy sees anonymous functions as service definitions, you need to wrap them with the protect() method to store them as parameters and to be able to reuse them.
8.5 Extend definitions
9 PHP Config
defineClass
You can use this API to automatically interchange constructor params or setters params by name with arbitrary variables setted in Strategy (see arbitrary data).
It's a convenient way to decouple somes common configuration variables from classes rules definitions.
By prefixing an associative or numeric key of array with "$", the value will be used to point the variable that have to be used instead. You can use a "." (dot) in the pointer to traverse an array.
Let's take an example:
By using this:
The result will be like:
loadPhp
Here is your config file
And you can load it by:
loadPhpMap
This method is based on the same princile than loadPhp but you have to pass it an array of config files. The difference is that all variables defined by $ will be merged recursively by cascade following the order of files in map array before applying rules which will be merged recursively too.
load
This is a static method operant on global instance of Di. You have to pass it a config map like with loadPhpMap but you also can pass a boolean to enable or disable the frozen mode and a path to store the frozen file. This is the last optimization step for server in production, it will backup the resolved config by serializing the Container so it will be faster to load. You'll have to delete your frozen file to update config if you change it.