Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 594 lines (458 sloc) 20.981 kB
20503fa @beberlei Convert README to Markdown
authored
1 Yadif - Yet Another Dependency Injection Framework
2
3 * Originally by Thomas McKelvey (https://github.com/tsmckelvey/yadif/tree)
4 * Fork by Benjamin Eberlei (https://github.com/beberlei/yadif/tree)
5
6 Inject dependencies via a very simple configuration mechanism.
7
8 Table of Contents
9 =============
10
11 - Basic Syntax
12 - Object Configuration
13 - Scope Config
14 - Setting non-object parameters
15 - Creating entities or value objects through Container
16 - Zend_Application Integration
17 - Zend Framework Front Controller Example
18 - Zend_Config Support
19 - Open Questions
20 - Instantiation with Factory methods
21 - Injecting Container Reference or Clones
ae655f1 @beberlei Update README with information on Decorators
authored
22 - Decorating Components
20503fa @beberlei Convert README to Markdown
authored
23 - Using the Builder for a fluent configuration interface
24 - Modules
25 - Clearing Instances
26 - TODOs
27
28 1. Basic Syntax
29 =============
30
31 Take this constructor and setter-less class:
32
33 class Foo
34 {
35 }
36
37 Creating a Yadif Container configured to create this class looks:
38
39 $config = array('Foo' => array());
40 $yadif = Yadif_Container::create($config);
41 $foo = $yadif->getComponent('Foo');
42
43 2. Object Configuration
44 =============
45
46 This current fork has a slighty different configuration syntax than the original:
47
48 class Foo
49 {
50 public function __construct($arg1, $arg2) { }
51
52 public function setA($arg1) { }
53
54 public function setB($arg2, $arg3) { }
55 }
56
57 $config = array(
58 'Foo' => array(
59 'class' => 'Foo',
60 'arguments' => array('ConstructorArg1', 'ConstructorArg2'),
61 'methods' => array(
62 array(
63 'method' => 'setA',
64 'arguments' => array('Inject1'),
65 ),
66 array(
67 'method' => 'setB',
68 'arguments' => array('Inject2', 'Inject3'),
69 ),
70 ),
71 'scope' => 'singleton',
72 ),
73 'ConstructorArg1' => array('class' => 'ConstructorArg1'),
74 'ConstructorArg2' => array('class' => 'ConstructorArg2'),
75 'Inject1' => array('class' => 'Inject1'),
76 'Inject2' => array('class' => 'Inject2'),
77 'Inject3' => array('class' => 'Inject3'),
78 );
79
80 $yadif = new Yadif_Container($config);
81 $foo = $yadif->getComponent("Foo");
82
83 Would do internally:
84
85 $foo = new Foo($yadif->getComponent('ConstructorArg1'), $yadif->getComponent('ConstructorArg2'));
86 $foo->setA($yadif->getComponent('Inject1'));
87 $foo->setB($yadif->getComponent('Inject2'), $yadif->getComponent('Inject3'));
88
89 Now 'ConstructorArg1', 'ConstructorArg2', 'Inject1', 'Inject2' and 'Inject3' would
90 also have to be defined as classes to be constructed correctly.
91
92 3. Scope Config
93 =============
94
95 Currently there are two different scopes: 'singleton' and 'prototype'. The first
96 one enforces the creation of only one object of the given type. The second one
97 creates new objects on each call of getComponent().
98
99 4. Setting non-object parameters
100 =============
101
102 Non-object parameters are bound to methods and constructors in a PDO like binding syntax.
103 For any non-oject parameter the syntax "double-colon name" has to be used to indicate
104 the parameter as non-object parameter.
105
106 As an example we take this Class:
107
108 class Foo {
109 public function __construct($bar, $number) { }
110
111 public function setNumber($number) { }
112 }
113
114 There are different scopes for these bounded parameters. A global scope and a
115 method aware only scope.
116
117 i.) Global Scope
118
119 Via Yadif_Container::bindParam() it is possible to set global parameters to a container.
120
121 $config = array(
122 'Foo' => array(
123 'class' => 'Foo',
124 'arguments' => array(':bar', ':number'),
125 ),
126 );
127
128 $yadif = new Yadif_Container($config);
129 $yadif->bindParam(':bar', 'BarName');
130 $yadif->bindParam(':number', 1234);
131
132 $foo = $yadif->getComponent('Foo');
133
134 ii.) Method aware scope
135
136 Via the configuration mechanism you can bind parameters to a specific method where the parameters occour.
137 An example for the object constructor looks like this:
138
139 $config = array(
140 'Foo' => array(
141 'class' => 'Foo',
142 'arguments' => array(':bar', ':number'),
143 'params' => array(':bar' => 'BarName', ':number' => 1234),
144 ),
145 );
146
147 $yadif = new Yadif_Container($config);
148 $foo = $yadif->getComponent('Foo');
149
150 For a non-object parameter in a method the syntax looks like this:
151
152 $config = array(
153 'Foo' => array(
154 'class' => 'Foo',
155 'arguments' => array(':bar', ':number'),
156 'params' => array(':bar' => 'BarName', ':number' => 1234),
157 'methods' => array(
158 array(
159 'method' => 'setNumber',
160 'arguments' => array(':number'),
161 'params' => array(':number' => 1138),
162 ),
163 ),
164 ),
165 );
166
167 $yadif = new Yadif_Container($config);
168 $foo = $yadif->getComponent('Foo');
169
170 5. Creating entities or value objects through Container
171 =============
172
173 The creation of entity or value objects mostly requires lots of arguments passed to the constructor
174 or setter methods, paired with dependencies for example in an Active Record example with the Database Connection.
175
176 class UserActiveRecord
177 {
178 public function setAdapter($db) { }
179
180 public function setName($name) { }
181
182 public function setEmail($email) { }
183
184 public function setId($id) { }
185 }
186
187 $config = array(
188 'DatabaseConn' => array(
189 'class' => 'PDO',
190 'arguments' => array('mysql://localhost', 'username', 'password'),
191 'scope' => 'singleton',
192 ),
193 'User' => array(
194 'class' => 'UserActiveRecord',
195 'arguments' => array(),
196 'methods' => array(
197 array('method' => 'setAdapter', 'arguments' => array('DatabaseConn'),
198 array('method' => 'setName', 'arguments' => ':name'),
199 array('method' => 'setEmail', 'arguments' => ':email'),
200 array('method' => 'setId', 'arguments' => ':id'),
201 ),
202 'scope' => 'prototype' // instantiate new object on each call of getComponent()
203 ),
204 );
205
206 $yadif = new Yadif_Container($config);
207 $db = $yadif->getComponent('DatabaseConn');
208 $stmt = $db->query("SELECT * FROM Users");
209
210 $users = array();
211 while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
212 $user = $yadif->bindParam(':name', $row['name'])
213 ->bindParam(':id', $row['id'])
214 ->bindParam(':email', $row['email'])
215 ->getComponent('User');
216 }
217
218 You could also use:
219
220 $user = $yadif->bindParams(array(':name' => $row['name'], ':id' => $row['id'], ':email' => $row['email']))->getComponent('User');
221
222 6. Zend_Application Integration
223 =============
224
225 With Zend Framework 1.8 the new Application component is sort of the dependency creation method for MVC (or any other)
226 application. Internally it uses a Container based on "Zend_Registry" by default and uses the __isset, __set and __get
227 magic methods to check, set and get instantiated resources. This Container is injected into the Front Controller and
228 accessible inside the Action Controller by:
229
230 $container = $this->getInvokeArg('bootstrap')->getContainer();
231
232 This Container can be replaced by the Yadif_Container and be a hybrid of instances injected by Zend_Application Resources
233 (via their resource name) and configured components that can be lazy loaded from the Yadif DI Container. You can
234 switch the Zend_Registry with Yadif_Container by calling:
235
236 $objects = new Zend_Config_Xml(APPLICATION_PATH."/config/objects.xml");
237 $container = new Yadif_Container($objects);
238
239 // Create application, bootstrap, and run
240 $application = new Zend_Application(
241 APPLICATION_ENV,
242 APPLICATION_PATH . '/config/application.xml'
243 );
244 $application->getBootstrap()->setContainer($container);
245 $application->bootstrap()->run();
246
247 How are resources injected into the container? Take for an example the "Db" resource.
248 If you are using it inside your Zend_Application configuration it will be set as the key:
249
250 $container->db;
251
252 All resource names are "strtolower"'ed upon setting them into the container, aswell
253 as the retrieval keys, so you don't have to think about case-sensitivy when retrieving
254 the components from the container.
255
256 7. Zend Framework Front Controller Example
257 =============
258
259 $config = array(
260 'Request' => array(
261 'class' => 'Zend_Controller_Request_Http',
262 ),
263 'Response' => array(
264 'class' => 'Zend_Controller_Response_Http',
265 ),
266 'Router' => array(
267 'class' => 'Zend_Controller_Router_Rewrite',
268 'methods' => array(
269 array('method' => 'addConfig', 'arguments' => array('RouterConfig', 'routes')),
270 ),
271 ),
272 'RouterConfig' => array(
273 'class' => 'Zend_Config_Ini',
274 'arguments' => array(':routerConfigFile'),
275 'params' => array(':routerConfigFile' => '/var/www/application/config/routes.ini'),
276 ),
277 'FrontController' => array(
278 'class' => 'Zend_Controller_Front',
279 'arguments' => array(),
280 'methods' => array(
281 // Inject Request, Response and Router
282 array('method' => 'setRequest', 'arguments' => array('Request')),
283 array('method' => 'setResponse', 'arguments' => array('Response')),
284 array('method' => 'setRouter', 'arguments' => array('Router')),
285
286 // Inject Plugins
287 array('method' => 'registerPlugin', 'arguments' => array('ControllerPluginAccessControl')),
288 array('method' => 'registerPlugin', 'arguments' => array('ControllerPluginLogging')),
289
290 // Inject Parameters which will be used throughout controllers
291 array('method' => 'setParam', 'arguments' => array('db', 'DatabaseConn')),
292 array('method' => 'setParam', 'arguments' => array('logger', 'Logger')),
293
294 // Set Controller Directory
295 array(
296 'method' => 'setControllerDirectory',
297 'arguments' => array(':controllerDirectory')
298 'params' => array(':controllerDirectory' => '/var/www/application/controllers/'),
299 ),
300 ),
301 'factory' => array('Zend_Controller_Front', 'getInstance'),
302 ),
303 'ControllerPluginAccessControl' => array('class' => 'ControllerPluginAccessControlImplementation'),
304 'ControllerPluginLogging' => array(
305 'class' => 'ControllerPluginAccessControlLogging',
306 'arguments' => array('Logger'),
307 ),
308 'Logger' => array(
309 'class' => 'Zend_Log',
310 'methods' => array(
311 array('method' => 'addWriter', 'arguments' => array('LoggerWriter')),
312 ),
313 ),
314 'LoggerWriter' => array(
315 'class' => 'Zend_Log_Writer_Stream',
316 'arguments' => array(':loggerStream'),
317 'params' => array(':loggerStream' => '/var/log/myapplication/errors.log'),
318 ),
319 'DatabaseConn' => array(
320 'class' => 'Zend_Db_Adapter_Mysql',
321 'arguments' => array( '%database.config%'),
322 ),
323 );
324
325 $yadif = new Yadif_Container($config);
326 $front = $yadif->getComponent('FrontController');
327 $front->dispatch();
328
329 Will do the following internally:
330
331 $front = Zend_Controller_Front::getInstance(); // because 'factory' config is set for this one
332
333 $request = new Zend_Controller_Request_Http();
334 $front->setRequest($request);
335
336 $response = new Zend_Controller_Response_Http();
337 $front->setResponse($response);
338
339 $routerConfig = new Zend_Config('/var/www/application/config/routes.ini');
340 $router = new Zend_Controller_Router_Rewrite();
341 $router->addConfig($routerConfig);
342 $front->addRouter($router);
343
344 $aclPlugin = new ControllerPluginAccessControlImplementation();
345 $front->registerPlugin($aclPlugin);
346
347 $logger = new Zend_Log();
348 $writer = new Zend_Log_Writer_Stream('/var/log/myapplication/errors.log');
349 $logger->addWriter($writer);
350 $loggerPlugin = new ControllerPluginAccessControlLogging($logger);
351 $front->registerPlugin($loggerPlugin);
352
353 $db = new Zend_Db_Adapter_Mysql('pdo_mysql', array(..));
354 $front->setParam('db', $db);
355
356 // $logger already exists and is singleton
357 $front->setParam('logger', $logger);
358
359 $front->setControllerDirectory('/var/www/application/controllers/');
360
361 8. Zend_Config Support
362 =============
363
364 You can use Zend_config objects as the primary configuration mechanism:
365
366 $config = new Zend_Config_Xml("objects.xml");
367 $yadif = new Yadif_Container($config);
368
369 Also you can use specific arguments marked with %arg% to replace with values
370 inside a given application config object.
371
372 $components = array(
373 'YadifBaz' => array(
374 'class' => 'YadifBaz',
375 'methods' => array(
376 array('method' => 'setA', 'arguments' => array('%foo.bar%'))
377 ),
378 ),
379 );
380 $config = new Zend_Config(array('foo' => array('bar' => 'baz')));
381
382 $yadif = new Yadif_Container($components, $config);
383 $baz = $yadif->getComponent("YadifBaz");
384
385 9. FAQ
386 =============
387
388 1. String and Component Name Ambigoutiy - How to solve it?
389 Solved! Allow non-object parameters only through 'arguments' key of configuration and bound via ':name' syntax.
390 See point 4 with more details on this topic.
391
392 2. Components Nested in Arrays, should they be instantiated through the Container? Currently they are not:
393
394 $foo = new Foo(array('bar' => new Bar())); // <- not possible
395
396 Solved! Through introduction of the injectParameters() method it is possible to implement this through recursion and
397 even support both object and non-object parameters in the nested array.
398
399 3. Calling static registries for example not possible:
400
401 Zend_Registry::set('foo', $foo);
402 Zend_Controller_Action_HelperStack::addHelper($helper);
403
404 4. How to solve references to the container itsself? Example: The Front Controller is using a container to instantiate
405 the main part of the application and then goes on in the Dispatcher to use another container to instantiate the controller.
406 How should back-references to containers be injected into an object tree?
407
408 $container = $container->getComponent('ThisContainer'); <- magic key?
409
410 5. Session Scope: Add a third parameter to the Container which accepts a Zend_Session_Namespace. All objects inside this
411 namespace are added to the list of instances where the session key corresponds to the Component Name. Instantiated objects
412 that are scoped "session" are saved inside the session, not just the container.
413
414 6. Currently no loading of the Classes through Zend_Loader::loadClass() - How to handle this?
415
416 7. Known Contexts
417 There are known contexts in which a container already knows he has to have implementations of x,y,z
418 different components. For example a Zend_Front_Controller at least needs request, response, router.
419
420 How should the configuration of known contexts be handled. Maybe a class "MyContext extends Yadif_Container"
421 but in this case the addComponent() functionality has to be extended to allow for a convention over configuration
422 compatible merging of existing with added component definitions.
423
424 10. Instantiation with Factory methods
425 =============
426
427 Sometimes you have to create certain objects through a factory method or a singleton
428 creation facility. You can do that with a specific factory key:
429
430 $options = array(
431 'Foo' => array(
432 'class' => 'Foo',
433 'factory' => array('FooFactory', 'create'),
434 ),
435 );
436
437 The factory key has to hold a valid PHP callback. Then not the constructor, but
438 the factory method is called to create the object. No check is performed if the object
439 is created successfully.
440
441 11. Injecting Container Reference or Clones
442 =============
443
444 Using 'ThisContainer' or 'CloneContainer' creates a reference to the current container
445 or clones the container and injects it.
446
ae655f1 @beberlei Update README with information on Decorators
authored
447 12. Decorating Components
448 =============
449
450 You can decorate components with other components by specifiying the 'decorateWith' option
451 on a components configuration:
452
453 $config = array(
454 'YadifBaz' => array(
455 'class' => 'YadifBaz',
456 'decorateWith' => array('YadifBar', 'YadifFoo')
457 ),
458 'YadifBar' => array(
459 'arguments' => array('DecoratedInstance')
460 ),
461 'YadifFoo' => array(
462 'arguments' => array('DecoratedInstance')
463 ),
464 );
465
466 This leads to the following object tree:
467
468 1. YadifBaz is instantiated.
469 2. YadifBar is instantiated and YadifBaz is used as the first argument (DecoratedInstance)
470 3. YadifFoo is instantiated and YadifBar is used as the first argument (DecoratedInstance)
471
472 13. Using the Builder for a fluent configuration interface
20503fa @beberlei Convert README to Markdown
authored
473 =============
474
475 At some point you end up with a huge XML or PHP Array configuration for all your
476 dependency injection requirements, which may be unmanagable and annoying to work with.
477
478 The "Yadif_Builder" class offers a fluent interface to create a Yadif compatible component
479 configuration. This gives a more explicit mechanism to create the configuration, which
480 can also be type-hinted in any IDE and therefore might offer more security when
481 working with the configuration.
482
483 $builder = new Yadif_Builder();
484 $builder->bind("InterfaceName")
485 ->to("Implementation")
486 ->args("ConstructorDependencyA", "ConstructorDependencyB", ":paramA")
487 ->param(":paramA", "foo")
488 ->method("setFoo")->args("foo");
489 ->method("setBar")->args("bar");
490 ->scope("singleton");
491
492 This translates into a config array internally and which can be retrieved by calling:
493
494 $config = $builder->finalize();
495 $yadif = new Yadif_Container($config);
496
497 The following methods are available on the builder:
498
499 - bind($componentName)
500 Registers a component with the name and automatically sets it as implementation also.
501
502 - to($implementationName)
503 Overwrite the implementation with this class
504
505 - args($arg1, $arg2, ...)
506 Depending on the context, set constructor or last registered method arguments. If you
507 have set a method, you can't set constructor arguments anymore.
508
509 - param($paramName, $paramValue)
510 Depending on the context, set constructor or last registered method parameter.
511
512 - method($methodName)
513 Register a method to be called when instantiating the implementation.
514
515 - scope($scope)
516 Set the scope of the component to "singleton" or "prototype"
517
518 - toProvider($callback)
519 Set a factory PHP callback variable, which is used instead of a new operation via reflection
520 to create the object.
521
42cc9a1 @beberlei Add decorateWith() method to Yadif_Builder
authored
522 - decorateWith($decoratorComponentName)
523 Set a decorator component name for the current component.
524
ae655f1 @beberlei Update README with information on Decorators
authored
525 14. Modules
20503fa @beberlei Convert README to Markdown
authored
526 =============
527
528 Configuration of single instances and interfaces can be quite cumbersome in large applications.
529 If you want to switch module implementations with each other it might be nice to be able
530 to group configurations. This is possible by extending the "Yadif_Module".
531
532 class MyModule extends Yadif_Module
533 {
534 protected function configure()
535 {
536 $this->bind("Foo")->to("Bar")->args("foo", "bar");
537 $this->bind("Bar")->to("Baz");
538 }
539 }
540
541 Internally the Module uses a Builder instance to create the config. To stuff this module into
542 Yadif, you have to transform it to a config and instantiate the Container:
543
544 $module = new MyModule();
545 $yadif = new Yadif_Container($module->getConfig());
546
547 Additionally the Module allows for a nice separation of different instance types, which
548 might help to structure your dependency injection needs:
549
550 class MyNewModule extends Yadif_Module
551 {
552 protected function configure()
553 {
554 $this->bindHelpers();
555 $this->bindDataAccessObjects();
556 $this->bindServices();
557 $this->bindDomainObjects();
558 }
559
560 private function bindHelpers()
561 {
562 $this->bind("Foo")->to("Bar");
563 }
564
565 private function bindDataAccessObjects()
566 {
567 // bindings
568 }
569
570 private function bindServices()
571 {
572 // bindings
573 }
574
575 private function bindDomainObjects()
576 {
577 // bindings
578 }
579 }
580
ae655f1 @beberlei Update README with information on Decorators
authored
581 15. Clearing Instances
20503fa @beberlei Convert README to Markdown
authored
582 =============
583
584 Sometimes services have lots of internal state which is expensive memory wise, especially for long
585 running scripts like cron jobs. You can clear instances by calling:
586
587 $container->clear('ComponentName');
588
ae655f1 @beberlei Update README with information on Decorators
authored
589 16. TODOs
20503fa @beberlei Convert README to Markdown
authored
590 =============
591
592 - Add Dependency Management object, which allows to lazy load modules and their dependend
593 on modules.
594 - Add Contextual Bindings via "Annotations" or "Names".
Something went wrong with that request. Please try again.