1: <?php
2: /**
3: * Injector class for injecting dependencies of objects
4: *
5: * This class is responsible for injecting dependencies of objects. It is
6: * inspired by the bucket_Container's concept of child scopes, but written to
7: * support many different types of bindings as well as allowing for setter
8: * injection bindings.
9: *
10: * @author Bob Mckee <bmckee@bywires.com>
11: * @author James Pepin <james@jamespepin.com>
12: * @category Horde
13: * @package Injector
14: */
15: class Horde_Injector implements Horde_Injector_Scope
16: {
17: /**
18: * @var Horde_Injector_Scope
19: */
20: private $_parentInjector;
21:
22: /**
23: * @var array
24: */
25: private $_bindings = array();
26:
27: /**
28: * @var array
29: */
30: private $_instances;
31:
32: /**
33: * Create a new injector object.
34: *
35: * Every injector object has a parent scope. For the very first
36: * Horde_Injector, you should pass it a Horde_Injector_TopLevel object.
37: *
38: * @param Horde_Injector_Scope $injector The parent scope.
39: */
40: public function __construct(Horde_Injector_Scope $injector)
41: {
42: $this->_parentInjector = $injector;
43: $this->_instances = array(__CLASS__ => $this);
44: }
45:
46: /**
47: * Create a child injector that inherits this injector's scope.
48: *
49: * All child injectors inherit the parent scope. Any objects that were
50: * created using getInstance, will be available to the child container.
51: * The child container can set bindings to override the parent, and none
52: * of those bindings will leak to the parent.
53: *
54: * @return Horde_Injector A child injector with $this as its parent.
55: */
56: public function createChildInjector()
57: {
58: return new self($this);
59: }
60:
61: /**
62: * Method overloader. Handles $this->bind[BinderType] type calls.
63: *
64: * @return Horde_Injector_Binder See _bind().
65: * @throws BadMethodCallException
66: */
67: public function __call($name, $args)
68: {
69: if (substr($name, 0, 4) == 'bind') {
70: return $this->_bind(substr($name, 4), $args);
71: }
72:
73: throw new BadMethodCallException('Call to undefined method ' . __CLASS__ . '::' . $name . '()');
74: }
75:
76: /**
77: * Method that creates binders to send to addBinder(). This is called by
78: * the magic method __call() whenever a function is called that starts
79: * with bind.
80: *
81: * @param string $type The type of Horde_Injector_Binder_ to be created.
82: * Matches /^Horde_Injector_Binder_(\w+)$/.
83: * @param array $args The constructor arguments for the binder object.
84: *
85: * @return Horde_Injector_Binder The binder object created. Useful for
86: * method chaining.
87: * @throws BadMethodCallException
88: */
89: private function _bind($type, $args)
90: {
91: $interface = array_shift($args);
92:
93: if (!$interface) {
94: throw new BadMethodCallException('First parameter for "bind' . $type . '" must be the name of an interface or class');
95: }
96:
97: $reflectionClass = new ReflectionClass('Horde_Injector_Binder_' . $type);
98:
99: $this->_addBinder(
100: $interface,
101: $reflectionClass->getConstructor()
102: ? $reflectionClass->newInstanceArgs($args)
103: : $reflectionClass->newInstance()
104: );
105:
106: return $this->_getBinder($interface);
107: }
108:
109: /**
110: * Add a Horde_Injector_Binder to an interface
111: *
112: * This is the method by which we bind an interface to a concrete
113: * implentation or factory. For convenience, binders may be added by
114: * $this->bind[BinderType].
115: *
116: * bindFactory - Creates a Horde_Injector_Binder_Factory
117: * bindImplementation - Creates a Horde_Injector_Binder_Implementation
118: *
119: * All subsequent arguments are passed to the constructor of the
120: * Horde_Injector_Binder object.
121: *
122: * @param string $interface The interface to bind to.
123: * @param Horde_Injector_Binder $binder The binder to be bound to the
124: * specified $interface.
125: *
126: * @return Horde_Injector A reference to itself for method chaining.
127: */
128: public function addBinder($interface, Horde_Injector_Binder $binder)
129: {
130: $this->_addBinder($interface, $binder);
131: return $this;
132: }
133:
134: /**
135: * @see self::addBinder()
136: */
137: private function _addBinder($interface, Horde_Injector_Binder $binder)
138: {
139: // First we check to see if our parent already has an equal binder set.
140: // if so we don't need to do anything
141: if (!$binder->equals($this->_parentInjector->getBinder($interface))) {
142: $this->_bindings[$interface] = $binder;
143: }
144: }
145:
146: /**
147: * Get the Binder associated with the specified instance.
148: *
149: * Binders are objects responsible for binding a particular interface
150: * with a class. If no binding is set for this object, the parent scope is
151: * consulted.
152: *
153: * @param string $interface The interface to retrieve binding information
154: * for.
155: *
156: * @return Horde_Injector_Binder The binding set for the specified
157: * interface.
158: */
159: public function getBinder($interface)
160: {
161: return isset($this->_bindings[$interface])
162: ? $this->_bindings[$interface]
163: : $this->_parentInjector->getBinder($interface);
164: }
165:
166: /**
167: * Get the Binder associated with the specified instance.
168: *
169: * @param string $interface The interface to retrieve binding information
170: * for.
171: *
172: * @return Horde_Injector_Binder The binder object created. Useful for
173: * method chaining.
174: */
175: private function _getBinder($interface)
176: {
177: return $this->_bindings[$interface];
178: }
179:
180: /**
181: * Set the object instance to be retrieved by getInstance the next time
182: * the specified interface is requested.
183: *
184: * This method allows you to set the cached object instance so that all
185: * subsequent getInstance() calls return the object you have specified.
186: *
187: * @param string $interface The interface to bind the instance to.
188: * @param mixed $instance The object instance to be bound to the
189: * specified instance.
190: *
191: * @return Horde_Injector A reference to itself for method chaining.
192: */
193: public function setInstance($interface, $instance)
194: {
195: $this->_instances[$interface] = $instance;
196: return $this;
197: }
198:
199: /**
200: * Create a new instance of the specified object/interface.
201: *
202: * This method creates a new instance of the specified object/interface.
203: * NOTE: it does not save that instance for later retrieval. If your
204: * object should be re-used elsewhere, you should be using getInstance().
205: *
206: * @param string $interface The interface name, or object class to be
207: * created.
208: *
209: * @return mixed A new object that implements $interface.
210: */
211: public function createInstance($interface)
212: {
213: return $this->getBinder($interface)->create($this);
214: }
215:
216: /**
217: * Retrieve an instance of the specified object/interface.
218: *
219: * This method gets you an instance, and saves a reference to that
220: * instance for later requests.
221: *
222: * Interfaces must be bound to a concrete class to be created this way.
223: * Concrete instances may be created through reflection.
224: *
225: * It does not gaurantee that it is a new instance of the object. For a
226: * new instance see createInstance().
227: *
228: * @param string $interface The interface name, or object class to be
229: * created.
230: *
231: * @return mixed An object that implements $interface, but not
232: * necessarily a new one.
233: */
234: public function getInstance($interface)
235: {
236: // Do we have an instance?
237: if (!$this->hasInstance($interface)) {
238: // Do we have a binding for this interface? If so then we don't
239: // ask our parent
240: if (!isset($this->_bindings[$interface])) {
241: // Does our parent have an instance?
242: if ($instance = $this->_parentInjector->getInstance($interface)) {
243: return $instance;
244: }
245: }
246:
247: // We have to make our own instance
248: $this->setInstance($interface, $this->createInstance($interface));
249: }
250:
251: return $this->_instances[$interface];
252: }
253:
254: /**
255: * Has the interface for the specified object/interface been created yet?
256: *
257: * @param string $interface The interface name or object class.
258: *
259: * @return boolean True if the instance already has been created.
260: */
261: public function hasInstance($interface)
262: {
263: return isset($this->_instances[$interface]);
264: }
265:
266: }
267: