1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20:
21: class NDIContainer extends NFreezableObject 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 NCallback) {
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 = new NCallback($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 = NDIContainer::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 NMissingServiceException("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 = NDIContainer::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 NMissingServiceException("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 NMissingServiceException("Service of type $class not found.");
216: }
217: } elseif ($this->classes[$lower] === FALSE) {
218: throw new NMissingServiceException("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: 252: 253: 254:
255: public function createInstance($class, array $args = array())
256: {
257: $rc = NClassReflection::from($class);
258: if (!$rc->isInstantiable()) {
259: throw new NServiceCreationException("Class $class is not instantiable.");
260:
261: } elseif ($constructor = $rc->getConstructor()) {
262: return $rc->newInstanceArgs(NDIHelpers::autowireArguments($constructor, $args, $this));
263:
264: } elseif ($args) {
265: throw new NServiceCreationException("Unable to pass arguments, class $class has no constructor.");
266: }
267: return new $class;
268: }
269:
270:
271:
272: 273: 274: 275: 276: 277:
278: public function callMethod($function, array $args = array())
279: {
280: $callback = new NCallback($function);
281: return $callback->invokeArgs(NDIHelpers::autowireArguments($callback->toReflection(), $args, $this));
282: }
283:
284:
285:
286:
287:
288:
289:
290: 291: 292: 293: 294:
295: public function expand($s)
296: {
297: return NDIHelpers::expand($s, $this->parameters);
298: }
299:
300:
301:
302: 303: 304: 305: 306:
307: public function &__get($name)
308: {
309: if (!isset($this->registry[$name])) {
310: $this->getService($name);
311: }
312: return $this->registry[$name];
313: }
314:
315:
316:
317: 318: 319: 320: 321: 322:
323: public function __set($name, $service)
324: {
325: $this->updating();
326: if (!is_string($name) || $name === '') {
327: throw new InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
328:
329: } elseif (isset($this->registry[$name])) {
330: throw new InvalidStateException("Service '$name' has already been registered.");
331:
332: } elseif (!is_object($service)) {
333: throw new InvalidArgumentException("Service must be a object, " . gettype($service) . " given.");
334: }
335: $this->registry[$name] = $service;
336: }
337:
338:
339:
340: 341: 342: 343: 344:
345: public function __isset($name)
346: {
347: return $this->hasService($name);
348: }
349:
350:
351:
352: 353: 354: 355:
356: public function __unset($name)
357: {
358: $this->removeService($name);
359: }
360:
361:
362:
363: public static function getMethodName($name, $isService = TRUE)
364: {
365: $uname = ucfirst($name);
366: return ($isService ? 'createService' : 'create') . ($name === $uname ? '__' : '') . str_replace('.', '__', $uname);
367: }
368:
369: }
370: