1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20:
21: class DIContainer extends FreezableObject implements IDIContainer
22: {
23: const TAGS = 'tags';
24:
25:
26: public $parameters = array();
27:
28:
29: public $params = array();
30:
31:
32: public $classes = array();
33:
34:
35: private $registry = array();
36:
37:
38: private $factories = array();
39:
40:
41: public $meta = array();
42:
43:
44: private $creating;
45:
46:
47:
48: public function __construct(array $params = array())
49: {
50: $this->parameters = $params + $this->parameters;
51: $this->params = &$this->parameters;
52: }
53:
54:
55:
56: 57: 58:
59: public function getParameters()
60: {
61: return $this->parameters;
62: }
63:
64:
65:
66: 67: 68: 69: 70: 71: 72:
73: public function addService($name, $service, array $meta = NULL)
74: {
75: $this->updating();
76: if (!is_string($name) || $name === '') {
77: throw new InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
78: }
79:
80: if (isset($this->registry[$name])) {
81: throw new InvalidStateException("Service '$name' has already been registered.");
82: }
83:
84: if (is_object($service) && !$service instanceof Closure && !$service instanceof Callback) {
85: $this->registry[$name] = $service;
86: $this->meta[$name] = $meta;
87: return $this;
88:
89: } elseif (!is_string($service) || strpos($service, ':') !== FALSE|| $service[0] === "\0") {
90: $service = callback($service);
91: }
92:
93: $this->factories[$name] = array($service);
94: $this->registry[$name] = & $this->factories[$name][1];
95: $this->meta[$name] = $meta;
96: return $this;
97: }
98:
99:
100:
101: 102: 103: 104: 105:
106: public function removeService($name)
107: {
108: $this->updating();
109: unset($this->registry[$name], $this->factories[$name], $this->meta[$name]);
110: }
111:
112:
113:
114: 115: 116: 117: 118:
119: public function getService($name)
120: {
121: if (isset($this->registry[$name])) {
122: return $this->registry[$name];
123:
124: } elseif (isset($this->creating[$name])) {
125: throw new InvalidStateException("Circular reference detected for services: "
126: . implode(', ', array_keys($this->creating)) . ".");
127: }
128:
129: if (isset($this->factories[$name])) {
130: list($factory) = $this->factories[$name];
131: if (is_string($factory)) {
132: if (!class_exists($factory)) {
133: throw new InvalidStateException("Cannot instantiate service, class '$factory' not found.");
134: }
135: try {
136: $this->creating[$name] = TRUE;
137: $service = new $factory;
138: } catch (Exception $e) {}
139:
140: } elseif (!$factory->isCallable()) {
141: throw new InvalidStateException("Unable to create service '$name', factory '$factory' is not callable.");
142:
143: } else {
144: $this->creating[$name] = TRUE;
145: try {
146: $service = $factory->invoke($this);
147: } catch (Exception $e) {}
148: }
149:
150: } elseif (method_exists($this, $factory = DIContainer::getMethodName($name)) && $this->getReflection()->getMethod($factory)->getName() === $factory) {
151: $this->creating[$name] = TRUE;
152: try {
153: $service = $this->$factory();
154: } catch (Exception $e) {}
155:
156: } else {
157: throw new MissingServiceException("Service '$name' not found.");
158: }
159:
160: unset($this->creating[$name]);
161:
162: if (isset($e)) {
163: throw $e;
164:
165: } elseif (!is_object($service)) {
166: throw new UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not object.");
167: }
168:
169: return $this->registry[$name] = $service;
170: }
171:
172:
173:
174: 175: 176: 177: 178:
179: public function hasService($name)
180: {
181: return isset($this->registry[$name])
182: || isset($this->factories[$name])
183: || method_exists($this, $method = DIContainer::getMethodName($name)) && $this->getReflection()->getMethod($method)->getName() === $method;
184: }
185:
186:
187:
188: 189: 190: 191: 192:
193: public function isCreated($name)
194: {
195: if (!$this->hasService($name)) {
196: throw new MissingServiceException("Service '$name' not found.");
197: }
198: return isset($this->registry[$name]);
199: }
200:
201:
202:
203: 204: 205: 206: 207: 208: 209:
210: public function getByType($class, $need = TRUE)
211: {
212: $lower = ltrim(strtolower($class), '\\');
213: if (!isset($this->classes[$lower])) {
214: if ($need) {
215: throw new MissingServiceException("Service of type $class not found.");
216: }
217: } elseif ($this->classes[$lower] === FALSE) {
218: throw new MissingServiceException("Multiple services of type $class found.");
219: } else {
220: return $this->getService($this->classes[$lower]);
221: }
222: }
223:
224:
225:
226: 227: 228: 229: 230:
231: public function findByTag($tag)
232: {
233: $found = array();
234: foreach ($this->meta as $name => $meta) {
235: if (isset($meta[self::TAGS][$tag])) {
236: $found[$name] = $meta[self::TAGS][$tag];
237: }
238: }
239: return $found;
240: }
241:
242:
243:
244: 245: 246: 247: 248: 249: 250:
251: public function createInstance($class, array $args = array())
252: {
253: $rc = ClassReflection::from($class);
254: if (!$rc->isInstantiable()) {
255: throw new InvalidArgumentException("Class $class is not instantiable.");
256:
257: } elseif ($constructor = $rc->getConstructor()) {
258: return $rc->newInstanceArgs(DIHelpers::autowireArguments($constructor, $args, $this));
259:
260: } elseif ($args) {
261: throw new InvalidArgumentException("Unable to pass arguments, class $class has no constructor.");
262: }
263: return new $class;
264: }
265:
266:
267:
268: 269: 270: 271: 272: 273:
274: public function callMethod($function, array $args = array())
275: {
276: $callback = callback($function);
277: return $callback->invokeArgs(DIHelpers::autowireArguments($callback->toReflection(), $args, $this));
278: }
279:
280:
281:
282:
283:
284:
285:
286: 287: 288: 289: 290:
291: public function expand($s)
292: {
293: return DIHelpers::expand($s, $this->parameters);
294: }
295:
296:
297:
298: 299: 300: 301: 302:
303: public function &__get($name)
304: {
305: if (!isset($this->registry[$name])) {
306: $this->getService($name);
307: }
308: return $this->registry[$name];
309: }
310:
311:
312:
313: 314: 315: 316: 317: 318:
319: public function __set($name, $service)
320: {
321: $this->updating();
322: if (!is_string($name) || $name === '') {
323: throw new InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
324:
325: } elseif (isset($this->registry[$name])) {
326: throw new InvalidStateException("Service '$name' has already been registered.");
327:
328: } elseif (!is_object($service)) {
329: throw new InvalidArgumentException("Service must be a object, " . gettype($service) . " given.");
330: }
331: $this->registry[$name] = $service;
332: }
333:
334:
335:
336: 337: 338: 339: 340:
341: public function __isset($name)
342: {
343: return $this->hasService($name);
344: }
345:
346:
347:
348: 349: 350: 351:
352: public function __unset($name)
353: {
354: $this->removeService($name);
355: }
356:
357:
358:
359: public static function getMethodName($name, $isService = TRUE)
360: {
361: $uname = ucfirst($name);
362: return ($isService ? 'createService' : 'create') . ($name === $uname ? '_' : '') . strtr($uname, '.', '_');
363: }
364:
365: }
366: