diff --git a/doc/book/zend.di.configuration.md b/doc/book/zend.di.configuration.md new file mode 100644 index 00000000..3230c7e1 --- /dev/null +++ b/doc/book/zend.di.configuration.md @@ -0,0 +1,33 @@ +# Zend\\Di Configuration + +Most of the configuration for both the setup of Definitions as well as the setup of the Instance +Manager can be attained by a configuration file. This file will produce an array (typically) and +have a particular iterable structure. + +The top two keys are 'definition' and 'instance', each specifying values for respectively, +definition setup and instance manager setup. + +The definition section expects the following information expressed as a PHP array: + +```php +$config = array( + 'definition' => array( + 'compiler' => array(/* @todo compiler information */), + 'runtime' => array(/* @todo runtime information */), + 'class' => array( + 'instantiator' => '', // the name of the instantiator, by default this is __construct + 'supertypes' => array(), // an array of supertypes the class implements + 'methods' => array( + 'setSomeParameter' => array( // a method name + 'parameterName' => array( + 'name', // string parameter name + 'type', // type or null + 'is-required' // bool + ) + ) + + ) + ) + ) +); +``` diff --git a/doc/book/zend.di.debugging-and-complex-use-cases.md b/doc/book/zend.di.debugging-and-complex-use-cases.md new file mode 100644 index 00000000..4c8a74bb --- /dev/null +++ b/doc/book/zend.di.debugging-and-complex-use-cases.md @@ -0,0 +1,131 @@ +# Zend\\Di Debugging & Complex Use Cases + +## Debugging a DiC + +It is possible to dump the information contained within both the Definition and InstanceManager for +a Di instance. + +The easiest way is to do the following: + +```php +Zend\Di\Display\Console::export($di); +``` + +If you are using a RuntimeDefinition where upon you expect a particular definition to be resolve at +the first-call, you can see that information to the console display to force it to read that class: + +```php +Zend\Di\Display\Console::export($di, array('A\ClassIWantTo\GetTheDefinitionFor')); +``` + +## Complex Use Cases + +### Interface Injection + +```php +namespace Foo\Bar { + class Baz implements BamAwareInterface + { + public $bam; + + public function setBam(Bam $bam) + { + $this->bam = $bam; + } + } + class Bam + { + } + interface BamAwareInterface + { + public function setBam(Bam $bam); + } +} + +namespace { + include 'zf2bootstrap.php'; + $di = new Zend\Di\Di; + $baz = $di->get('Foo\Bar\Baz'); +} +``` + +### Setter Injection with Class Definition + +```php +namespace Foo\Bar { + class Baz + { + public $bam; + + public function setBam(Bam $bam) + { + $this->bam = $bam; + } + } + class Bam { + } +} + +namespace { + $di = new Zend\Di\Di; + $di->configure(new Zend\Di\Config(array( + 'definition' => array( + 'class' => array( + 'Foo\Bar\Baz' => array( + 'setBam' => array('required' => true) + ) + ) + ) + ))); + $baz = $di->get('Foo\Bar\Baz'); +} +``` + +### Multiple Injections To A Single Injection Point + +```php +namespace Application { + class Page + { + public $blocks; + + public function addBlock(Block $block) + { + $this->blocks[] = $block; + } + } + interface Block + { + } +} + +namespace MyModule { + class BlockOne implements \Application\Block {} + class BlockTwo implements \Application\Block {} +} + +namespace { + include 'zf2bootstrap.php'; + $di = new Zend\Di\Di; + $di->configure(new Zend\Di\Config(array( + 'definition' => array( + 'class' => array( + 'Application\Page' => array( + 'addBlock' => array( + 'block' => array('type' => 'Application\Block', 'required' => true) + ) + ) + ) + ), + 'instance' => array( + 'Application\Page' => array( + 'injections' => array( + 'MyModule\BlockOne', + 'MyModule\BlockTwo' + ) + ) + ) + ))); + $page = $di->get('Application\Page'); +} +``` diff --git a/doc/book/zend.di.definitions.md b/doc/book/zend.di.definitions.md new file mode 100644 index 00000000..d56f67c7 --- /dev/null +++ b/doc/book/zend.di.definitions.md @@ -0,0 +1,132 @@ +# Zend\\Di Definition + +Definitions are the place where Zend\\Di attempts to understand the structure of the code it is +attempting to wire. This means that if you've written non-ambiguous, clear and concise code; +Zend\\Di has a very good chance of understanding how to wire things up without much added +complexity. + +## DefinitionList + +Definitions are introduced to the Zend\\Di\\Di object through a definition list implemented as +Zend\\Di\\DefinitionList (SplDoublyLinkedList). Order is important. Definitions in the front of the +list will be consulted on a class before definitions at the end of the list. + +> ## Note +Regardless of what kind of Definition strategy you decide to use, it is important that your +autoloaders are already setup and ready to use. + +## RuntimeDefinition + +The default DefinitionList instantiated by Zend\\Di\\Di, when no other DefinitionList is provided, +has as Definition\\RuntimeDefinition baked-in. The RuntimeDefinition will respond to query's about +classes by using Reflection. This Runtime definitions uses any available information inside methods: +their signature, the names of parameters, the type-hints of the parameters, and the default values +to determine if something is optional or required when making a call to that method. The more +explicit you can be in your method naming and method signatures, the easier of a time +Zend\\Di\\Definition\\RuntimeDefinition will have determining the structure of your code. + +This is what the constructor of a RuntimeDefinition looks like: + +```php +public function __construct(IntrospectionStrategy $introspectionStrategy = null, array +$explicitClasses = null) +{ + $this->introspectionStrategy = ($introspectionStrategy) ?: new IntrospectionStrategy(); + if ($explicitClasses) { + $this->setExplicitClasses($explicitClasses); + } +} +``` + +The IntrospectionStrategy object is an object that determines the rules, or guidelines, for how the +RuntimeDefinition will introspect information about your classes. Here are the things it knows how +to do: + +- Whether or not to use Annotations (Annotations are expensive and off by default, read more about +these in the Annotations section) +- Which method names to include in the introspection, by default, the pattern /^set\[A-Z\]{1}\\w\*/ +is registered by default, this is a list of patterns. +- Which interface names represent the interface injection pattern. By default, the pattern +/\\w\*Aware\\w\*/ is registered, this is a list of patterns. + +The constructor for the IntrospectionStrategy looks like this: + +```php +public function __construct(AnnotationManager $annotationManager = null) +{ + $this->annotationManager = ($annotationManager) ?: $this->createDefaultAnnotationManager(); +} +``` + +This goes to say that an AnnotationManager is not required, but if you wish to create a special +AnnotationManager with your own annotations, and also wish to extend the RuntimeDefinition to look +for these special Annotations, this is the place to do it. + +The RuntimeDefinition also can be used to look up either all classes (implicitly, which is default), +or explicitly look up for particular pre-defined classes. This is useful when your strategy for +inspecting one set of classes might differ from those of another strategy for another set of +classes. This can be achieved by using the setExplicitClasses() method or by passing a list of +classes as a second argument to the constructor of the RuntimeDefinition. + +## CompilerDefinition + +The CompilerDefinition is very much similar in nature to the RuntimeDefinition with the exception +that it can be seeded with more information for the purposes of "compiling" a definition. This is +useful when you do not want to be making all those (sometimes expensive) calls to reflection and the +annotation scanning system during the request of your application. By using the compiler, a +definition can be created and written to disk to be used during a request, as opposed to the task of +scanning the actual code. + +For example, let's assume we want to create a script that will create definitions for some of our +library code: + +```php +// in "package name" format +$components = array( + 'My_MovieApp', + 'My_OtherClasses', +); + +foreach ($components as $component) { + $diCompiler = new Zend\Di\Definition\CompilerDefinition; + $diCompiler->addDirectory('/path/to/classes/' . str_replace('_', '/', $component)); + + $diCompiler->compile(); + file_put_contents( + __DIR__ . '/../data/di/' . $component . '-definition.php', + 'toArrayDefinition()->toArray(), true) . ';' + ); +} +``` + +This will create a couple of files that will return an array of the definition for that class. To +utilize this in an application, the following code will suffice: + +```php +protected function setupDi(Application $app) +{ + $definitionList = new DefinitionList(array( + new Definition\ArrayDefinition(include __DIR__ . +'/path/to/data/di/My_MovieApp-definition.php'), + new Definition\ArrayDefinition(include __DIR__ . +'/path/to/data/di/My_OtherClasses-definition.php'), + $runtime = new Definition\RuntimeDefinition(), + )); + $di = new Di($definitionList, null, new Config($this->config->di)); + $di->instanceManager()->addTypePreference('Zend\Di\LocatorInterface', $di); + $app->setLocator($di); +} +``` + +The above code would more than likely go inside your application's or module's bootstrap file. This +represents the simplest and most performant way of configuring your DiC for usage. + +## ClassDefinition + +The idea behind using a ClassDefinition is two-fold. First, you may want to override some +information inside of a RuntimeDefinition. Secondly, you might want to simply define your complete +class's definition with an xml, ini, or php file describing the structure. This class definition can +be fed in via Configuration or by directly instantiating and registering the Definition with the +DefinitionList. + +Todo - example diff --git a/doc/book/zend.di.instance-manager.md b/doc/book/zend.di.instance-manager.md new file mode 100644 index 00000000..8fe166b9 --- /dev/null +++ b/doc/book/zend.di.instance-manager.md @@ -0,0 +1,180 @@ +# Zend\\Di InstanceManager + +The InstanceManager is responsible for any runtime information associated with the Zend\\Di\\Di DiC. +This means that the information that goes into the instance manager is specific to both how the +particular consuming Application's needs and even more specifically to the environment in which the +application is running. + +## Parameters + +Parameters are simply entry points for either dependencies or instance configuration values. A class +consists of a set of parameters, each uniquely named. When writing your classes, you should attempt +to not use the same parameter name twice in the same class when you expect that that parameters is +used for either instance configuration or an object dependency. This leads to an ambiguous +parameter, and is a situation best avoided. + +Our movie finder example can be further used to explain these concepts: + +```php +namespace MyLibrary +{ + class DbAdapter + { + protected $username = null; + protected $password = null; + public function __construct($username, $password) + { + $this->username = $username; + $this->password = $password; + } + } +} + +namespace MyMovieApp +{ + class MovieFinder + { + protected $dbAdapter = null; + public function __construct(\MyLibrary\DbAdapter $dbAdapter) + { + $this->dbAdapter = $dbAdapter; + } + } + + class MovieLister + { + protected $movieFinder = null; + public function __construct(MovieFinder $movieFinder) + { + $this->movieFinder = $movieFinder; + } + } +} +``` + +In the above example, the class DbAdapter has 2 parameters: username and password; MovieFinder has +one parameter: dbAdapter, and MovieLister has one parameter: movieFinder. Any of these can be +utilized for injection of either dependencies or scalar values during instance configuration or +during call time. + +When looking at the above code, since the dbAdapter parameter and the movieFinder parameter are both +type-hinted with concrete types, the DiC can assume that it can fulfill these object tendencies by +itself. On the other hand, username and password do not have type-hints and are, more than likely, +scalar in nature. Since the DiC cannot reasonably know this information, it must be provided to the +instance manager in the form of parameters. Not doing so will force +$di->get('MyMovieApp\\MovieLister') to throw an exception. + +The following ways of using parameters are available: + +```php +// setting instance configuration into the instance manager +$di->instanceManager()->setParameters('MyLibrary\DbAdapter', array( + 'username' => 'myusername', + 'password' => 'mypassword' +)); + +// forcing a particular dependency to be used by the instance manager +$di->instanceManager()->setParameters('MyMovieApp\MovieFinder', array( + 'dbAdapter' => new MyLibrary\DbAdapter('myusername', 'mypassword') +)); + +// passing instance parameters at call time +$movieLister = $di->get('MyMovieApp\MovieLister', array( + 'username' => $config->username, + 'password' => $config->password +)); + +// passing a specific instance at call time +$movieLister = $di->get('MyMovieApp\MovieLister', array( + 'dbAdapter' => new MyLibrary\DbAdapter('myusername', 'mypassword') +)); +``` + +## Preferences + +In some cases, you might be using interfaces as type hints as opposed to concrete types. Lets assume +the movie example was modified in the following way: + +```php +namespace MyMovieApp +{ + interface MovieFinderInterface + { + // methods required for this type + } + + class GenericMovieFinder implements MovieFinderInterface + { + protected $dbAdapter = null; + public function __construct(\MyLibrary\DbAdapter $dbAdapter) + { + $this->dbAdapter = $dbAdapter; + } + } + + class MovieLister + { + protected $movieFinder = null; + public function __construct(MovieFinderInterface $movieFinder) + { + $this->movieFinder = $movieFinder; + } + } +} +``` + +What you'll notice above is that now the MovieLister type minimally expects that the dependency +injected implements the MovieFinderInterface. This allows multiple implementations of this base +interface to be used as a dependency, if that is what the consumer decides they want to do. As you +can imagine, Zend\\Di, by itself would not be able to determine what kind of concrete object to use +fulfill this dependency, so this type of 'preference' needs to be made known to the instance +manager. + +To give this information to the instance manager, see the following code example: + +```php +$di->instanceManager()->addTypePreference('MyMovieApp\MovieFinderInterface', +'MyMovieApp\GenericMovieFinder'); +// assuming all instance config for username, password is setup +$di->get('MyMovieApp\MovieLister'); +``` + +## Aliases + +In some situations, you'll find that you need to alias an instance. There are two main reasons to do +this. First, it creates a simpler, alternative name to use when using the DiC, as opposed to using +the full class name. Second, you might find that you need to have the same object type in two +separate contexts. This means that when you alias a particular class, you can then attach a specific +instance configuration to that alias; as opposed to attaching that configuration to the class name. + +To demonstrate both of these points, we'll look at a use case where we'll have two separate +DbAdapters, one will be for read-only operations, the other will be for read-write operations: + +> ## Note +Aliases can also have parameters registered at alias time + +```php +// assume the MovieLister example of code from the QuickStart + +$im = $di->instanceManager(); + +// add alias for short naming +$im->addAlias('movielister', 'MyMovieApp\MovieLister'); + +// add aliases for specific instances +$im->addAlias('dbadapter-readonly', 'MyLibrary\DbAdapter', array( + 'username' => $config->db->readAdapter->username, + 'password' => $config->db->readAdapter->password, +)); +$im->addAlias('dbadapter-readwrite', 'MyLibrary\DbAdapter', array( + 'username' => $config->db->readWriteAdapter->username, + 'password' => $config->db->readWriteAdapter->password, +)); + +// set a default type to use, pointing to an alias +$im->addTypePreference('MyLibrary\DbAdapter', 'dbadapter-readonly'); + +$movieListerRead = $di->get('MyMovieApp\MovieLister'); +$movieListerReadWrite = $di->get('MyMovieApp\MovieLister', array('dbAdapter' => +'dbadapter-readwrite')); +``` diff --git a/doc/book/zend.di.introduction.md b/doc/book/zend.di.introduction.md new file mode 100644 index 00000000..dbeba152 --- /dev/null +++ b/doc/book/zend.di.introduction.md @@ -0,0 +1,51 @@ +# Introduction to Zend\\Di + +## Dependency Injection + +Dependency Injection (here-in called DI) is a concept that has been talked about in numerous places +over the web. Simply put, we'll explain the act of injecting dependencies simply with this below +code: + +```php +$b = new MovieLister(new MovieFinder()); +``` + +Above, MovieFinder is a dependency of MovieLister, and MovieFinder was injected into MovieLister. If +you are not familiar with the concept of DI, here are a couple of great reads: [Matthew Weier +O'Phinney's +Analogy](http://weierophinney.net/matthew/archives/260-Dependency-Injection-An-analogy.html), [Ralph +Schindler's Learning +DI](http://ralphschindler.com/2011/05/18/learning-about-dependency-injection-and-php), or [Fabien +Potencier's Series](http://fabien.potencier.org/article/11/what-is-dependency-injection) on DI. + +> ## Note +`Zend\Di` is an example of an Inversion of Control (IoC) container. IoC containers are widely used +to create object instances that have all dependencies resolved and injected. Dependency Injection +containers are one form of IoC -- but not the only form. +Zend Framework 2 ships with another form of IoC as well, +\[ZendServiceManager\](zend.service-manager.intro). Unlike `Zend\Di`, The ServiceManager is +code-driven, meaning that you typically tell it what class to instantiate, or provide a factory for +the given class. This approach offers several benefits: +- Easier to debug (error stacks take you into your factories, not the dependency injection +container). +- Easier to setup (write code to instantiate objects, instead of configuration). +- Faster (`Zend\Di` has known performance issues due to the approaches used). +Unless you have specific needs for a dependency injection container versus more general Inversion of +Control, we recommend using `Zend\ServiceManager` for the above reasons. + +## Dependency Injection Containers + +When your code is written in such a way that all your dependencies are injected into consuming +objects, you might find that the simple act of wiring an object has gotten more complex. When this +becomes the case, and you find that this wiring is creating more boilerplate code, this makes for an +excellent opportunity to utilize a Dependency Injection Container. + +In it's simplest form, a Dependency Injection Container (here-in called a DiC for brevity), is an +object that is capable of creating objects on request and managing the "wiring", or the injection of +required dependencies, for those requested objects. Since the patterns that developers employ in +writing DI capable code vary, DiC's are generally either in the form of smallish objects that suit a +very specific pattern, or larger DiC frameworks. + +Zend\\Di is a DiC framework. While for the simplest code there is no configuration needed, and the +use cases are quite simple; for more complex code, Zend\\Di is capable of being configured to wire +these complex use cases diff --git a/doc/book/zend.di.quick-start.md b/doc/book/zend.di.quick-start.md new file mode 100644 index 00000000..6f4f82e1 --- /dev/null +++ b/doc/book/zend.di.quick-start.md @@ -0,0 +1,127 @@ +# Zend\\Di Quickstart + +This QuickStart is intended to get developers familiar with the concepts of the Zend\\Di DiC. +Generally speaking, code is never as simple as it is inside this example, so working knowledge of +the other sections of the manual is suggested. + +Assume for a moment, you have the following code as part of your application that you feel is a good +candidate for being managed by a DiC, after all, you are already injecting all your dependencies: + +```php +namespace MyLibrary +{ + class DbAdapter + { + protected $username = null; + protected $password = null; + public function __construct($username, $password) + { + $this->username = $username; + $this->password = $password; + } + } +} + +namespace MyMovieApp +{ + class MovieFinder + { + protected $dbAdapter = null; + public function __construct(\MyLibrary\DbAdapter $dbAdapter) + { + $this->dbAdapter = $dbAdapter; + } + } + + class MovieLister + { + protected $movieFinder = null; + public function __construct(MovieFinder $movieFinder) + { + $this->movieFinder = $movieFinder; + } + } +} +``` + +With the above code, you find yourself writing the following to wire and utilize this code: + +```php +// $config object is assumed + +$dbAdapter = new MyLibrary\DbAdapter($config->username, $config->password); +$movieFinder = new MyMovieApp\MovieFinder($dbAdapter); +$movieLister = new MyMovieApp\MovieLister($movieFinder); +foreach ($movieLister as $movie) { + // iterate and display $movie +} +``` + +If you are doing this above wiring in each controller or view that wants to list movies, not only +can this become repetitive and boring to write, but also unmaintainable if for example you want to +swap out one of these dependencies on a wholesale scale. + +Since this example of code already practices good dependency injection, with constructor injection, +it is a great candidate for using Zend\\Di. The usage is as simple as: + +```php +// inside a bootstrap somewhere +$di = new Zend\Di\Di(); +$di->instanceManager()->setParameters('MyLibrary\DbAdapter', array( + 'username' => $config->username, + 'password' => $config->password +)); + +// inside each controller +$movieLister = $di->get('MyMovieApp\MovieLister'); +foreach ($movieLister as $movie) { + // iterate and display $movie +} +``` + +In the above example, we are obtaining a default instance of Zend\\Di\\Di. By 'default', we mean +that Zend\\Di\\Di is constructed with a DefinitionList seeded with a RuntimeDefinition (uses +Reflection) and an empty instance manager and no configuration. Here is the Zend\\Di\\Di +constructor: + +```php +public function __construct(DefinitionList $definitions = null, InstanceManager $instanceManager = +null, Configuration $config = null) +{ + $this->definitions = ($definitions) ?: new DefinitionList(new Definition\RuntimeDefinition()); + $this->instanceManager = ($instanceManager) ?: new InstanceManager(); + + if ($config) { + $this->configure($config); + } +} +``` + +This means that when $di->get() is called, it will be consulting the RuntimeDefinition, which +uses reflection to understand the structure of the code. Once it knows the structure of the code, it +can then know how the dependencies fit together and how to go about wiring your objects for you. +Zend\\Di\\Definition\\RuntimeDefinition will utilize the names of the parameters in the methods as +the class parameter names. This is how both username and password key are mapped to the first and +second parameter, respectively, of the constructor consuming these named parameters. + +If you were to want to pass in the username and password at call time, this is achieved by passing +them as the second argument of get(): + +```php +// inside each controller +$di = new Zend\Di\Di(); +$movieLister = $di->get('MyMovieApp\MovieLister', array( + 'username' => $config->username, + 'password' => $config->password +)); +foreach ($movieLister as $movie) { + // iterate and display $movie +} +``` + +It is important to note that when using call time parameters, these parameter names will be applied +to any class that accepts a parameter of such name. + +By calling $di->get(), this instance of MovieLister will be automatically shared. This means +subsequent calls to get() will return the same instance as previous calls. If you wish to have +completely new instances of MovieLister, you can utilize $di->newInstance(). diff --git a/doc/bookdown.json b/doc/bookdown.json new file mode 100644 index 00000000..db90b0db --- /dev/null +++ b/doc/bookdown.json @@ -0,0 +1,12 @@ +{ + "title": "Zend\\Di", + "target": "html/", + "content": [ + "book/zend.di.introduction.md", + "book/zend.di.quick-start.md", + "book/zend.di.definitions.md", + "book/zend.di.instance-manager.md", + "book/zend.di.configuration.md", + "book/zend.di.debugging-and-complex-use-cases.md" + ] +} \ No newline at end of file