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