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