1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Autarky\Container;
12:
13: use Autarky\Container\Factory\Definition;
14: use Autarky\Container\Factory\FactoryInterface;
15:
16: use ReflectionClass;
17: use ReflectionException;
18: use ReflectionFunction;
19: use ReflectionFunctionAbstract;
20: use ReflectionMethod;
21: use ReflectionParameter;
22:
23: 24: 25:
26: class Container implements ContainerInterface
27: {
28: 29: 30: 31: 32:
33: protected $instances = [];
34:
35: 36: 37: 38: 39:
40: protected $factories = [];
41:
42: 43: 44: 45: 46:
47: protected $shared = [];
48:
49: 50: 51: 52: 53:
54: protected $aliases = [];
55:
56: 57: 58: 59: 60:
61: protected $params = [];
62:
63: 64: 65: 66: 67:
68: protected $resolvingCallbacks = [];
69:
70: 71: 72: 73: 74:
75: protected $resolvingAnyCallbacks = [];
76:
77: 78: 79: 80: 81:
82: protected $internals = [];
83:
84: 85: 86: 87: 88:
89: protected $protectInternals = true;
90:
91: 92: 93: 94: 95:
96: protected $autowire = true;
97:
98: 99: 100: 101: 102: 103:
104: public function __construct()
105: {
106: $this->instance('Autarky\Container\Container', $this);
107: $this->alias('Autarky\Container\Container', 'Autarky\Container\ContainerInterface');
108: $this->alias('Autarky\Container\Container', 'Autarky\Container\ClassResolverInterface');
109: $this->alias('Autarky\Container\Container', 'Autarky\Container\CallableInvokerInterface');
110: }
111:
112: 113: 114:
115: public function define($class, $factory, array $params = array())
116: {
117: if ($params) {
118: $this->params($class, $params);
119: }
120:
121: if (!$factory instanceof FactoryInterface) {
122: $factory = Definition::getDefaultForCallable($factory);
123: }
124:
125: return $this->factories[$class] = $factory;
126: }
127:
128: 129: 130:
131: public function invoke($callable, array $params = array())
132: {
133:
134:
135: if (is_string($callable) && !is_callable($callable)) {
136: $callable = [$callable, 'invoke'];
137: }
138:
139:
140: if (is_string($callable) && strpos($callable, '::') !== false) {
141: $callable = explode('::', $callable);
142: }
143:
144: $class = null;
145: $object = null;
146:
147: if (is_array($callable)) {
148: $class = $callable[0];
149: $method = $callable[1];
150:
151: if (is_object($class)) {
152: $object = $class;
153: $class = get_class($object);
154: } else {
155: $object = $this->resolve($class);
156: }
157:
158: $reflFunc = new ReflectionMethod($object, $method);
159:
160: if ($reflFunc->isStatic()) {
161: $object = null;
162: }
163:
164: $callableString = $class.'::'.$method;
165: if (isset($this->params[$callableString])) {
166: $params = array_replace($this->params[$callableString], $params);
167: }
168: } else if (is_callable($callable)) {
169: $reflFunc = new ReflectionFunction($callable);
170: } else {
171: $type = is_object($callable) ? get_class($callable) : gettype($callable);
172: throw new \InvalidArgumentException("Callable must be a callable or array, $type given");
173: }
174:
175: $args = $this->getFunctionArguments($reflFunc, $params);
176:
177: if ($class) {
178: return $reflFunc->invokeArgs($object, $args);
179: }
180:
181: return $reflFunc->invokeArgs($args);
182: }
183:
184: 185: 186:
187: public function resolve($class, array $params = array())
188: {
189: $alias = null;
190:
191: if (isset($this->aliases[$class])) {
192: $alias = $class;
193: $class = $this->aliases[$class];
194: }
195:
196:
197: $this->checkProtected($class, $alias);
198:
199:
200: if (isset($this->instances[$class])) {
201: return $this->instances[$class];
202: }
203:
204: if (isset($this->params[$class])) {
205: $params = array_replace($this->params[$class], $params);
206: }
207:
208:
209:
210:
211: $previousState = $this->protectInternals;
212: $this->protectInternals = false;
213:
214:
215: if (!isset($this->factories[$class]) && $this->autowire) {
216: $this->factories[$class] = Definition::getDefaultForClass($class);
217: }
218:
219: if (!isset($this->factories[$class])) {
220: if ($alias) {
221: $class = "$class (via $alias)";
222: }
223: throw new Exception\ResolvingException("No factory defined for $class");
224: }
225:
226: $object = $this->callFactory($this->factories[$class], $params);
227:
228: $this->protectInternals = $previousState;
229:
230: if ($object instanceof ContainerAwareInterface) {
231: $object->setContainer($this);
232: }
233:
234: if ($alias) {
235: $this->callResolvingCallbacks($alias, $object);
236: }
237: $this->callResolvingCallbacks($class, $object);
238:
239: if ($this->isShared($class)) {
240: $this->instances[$class] = $object;
241: }
242:
243: return $object;
244: }
245:
246: 247: 248: 249: 250: 251: 252: 253:
254: protected function callFactory(Factory\FactoryInterface $factory, array $params = array())
255: {
256: return $factory->invoke($this, $params);
257: }
258:
259: 260: 261: 262: 263: 264: 265: 266:
267: public function makeFactory($callable, $reflect = false)
268: {
269: if ($reflect) {
270: return Definition::getFromReflection($callable, null);
271: }
272:
273: return new Definition($callable);
274: }
275:
276: 277: 278: 279: 280: 281: 282: 283: 284:
285: public function getFactory($class, array $params = array())
286: {
287: if (!isset($this->factories[$class]) && $this->autowire) {
288: $this->factories[$class] = Definition::getDefaultForClass($class);
289: }
290:
291: $factory = $this->factories[$class];
292:
293:
294:
295: if ($params) {
296: $factory = $factory->getFactory($params);
297: }
298:
299: return $factory;
300: }
301:
302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312:
313: protected function getFunctionArguments(ReflectionFunctionAbstract $func, array $params = array())
314: {
315: $args = [];
316:
317: foreach ($func->getParameters() as $param) {
318: $class = $param->getClass();
319:
320: if ($class) {
321: $args[] = $this->resolveClassArg($class, $param, $params);
322: } else {
323: $args[] = $this->resolveNonClassArg($param, $params, $func);
324: }
325: }
326:
327: return $args;
328: }
329:
330: 331: 332: 333: 334: 335: 336: 337: 338:
339: protected function resolveClassArg(ReflectionClass $class, ReflectionParameter $param, array $params)
340: {
341: $name = '$'.$param->getName();
342: $class = $class->getName();
343:
344:
345:
346:
347: while ($name !== null) {
348: if ($params && array_key_exists($name, $params)) {
349: $class = $params[$name];
350: }
351:
352: if ($class instanceof Factory\FactoryInterface) {
353: return $class->invoke($this);
354: }
355:
356: if (is_object($class)) {
357: return $class;
358: }
359:
360: $name = ($name != $class) ? $class : null;
361: }
362:
363: try {
364: return $this->resolve($class);
365: } catch (ReflectionException $exception) {
366: if ($param->isOptional()) {
367: return null;
368: }
369:
370: throw $exception;
371: }
372: }
373:
374: 375: 376: 377: 378: 379: 380: 381: 382:
383: protected function resolveNonClassArg(ReflectionParameter $param, array $params, ReflectionFunctionAbstract $func)
384: {
385: $name = '$'.$param->getName();
386:
387: if ($params && array_key_exists($name, $params)) {
388: $argument = $params[$name];
389:
390: if (is_array($argument) && isset($this->factories[$argument[0]])) {
391: $argument = $this->callFactory($argument[0], $argument[1]);
392: }
393:
394: return $argument;
395: }
396:
397: if ($param->isDefaultValueAvailable()) {
398: return $param->getDefaultValue();
399: }
400:
401: throw Exception\UnresolvableArgumentException::fromReflectionParam($param, $func);
402: }
403:
404: 405: 406:
407: public function resolving($classOrClasses, callable $callback)
408: {
409: foreach ((array) $classOrClasses as $class) {
410: $this->resolvingCallbacks[$class][] = $callback;
411: }
412: }
413:
414: 415: 416:
417: public function resolvingAny(callable $callback)
418: {
419: $this->resolvingAnyCallbacks[] = $callback;
420: }
421:
422: 423: 424: 425: 426: 427: 428: 429:
430: protected function callResolvingCallbacks($key, $object)
431: {
432: foreach ($this->resolvingAnyCallbacks as $callback) {
433: call_user_func($callback, $object, $this);
434: }
435:
436: if (isset($this->resolvingCallbacks[$key])) {
437: foreach ($this->resolvingCallbacks[$key] as $callback) {
438: call_user_func($callback, $object, $this);
439: }
440: }
441: }
442:
443: 444: 445: 446: 447:
448: public function setAutowire($autowire)
449: {
450: $this->autowire = (bool) $autowire;
451: }
452:
453: 454: 455:
456: public function isBound($class)
457: {
458: if (isset($this->aliases[$class])) {
459: $class = $this->aliases[$class];
460: }
461:
462: return isset($this->instances[$class])
463: || isset($this->factories[$class])
464: || isset($this->shared[$class]);
465: }
466:
467: 468: 469:
470: public function instance($class, $instance)
471: {
472: $this->shared[$class] = true;
473: $this->instances[$class] = $instance;
474: }
475:
476: 477: 478:
479: public function share($classOrClasses)
480: {
481: foreach ((array) $classOrClasses as $class) {
482: $this->shared[$class] = true;
483: }
484: }
485:
486: 487: 488:
489: public function internal($classOrClasses)
490: {
491: foreach ((array) $classOrClasses as $class) {
492: $this->internals[$class] = true;
493: }
494: }
495:
496: 497: 498:
499: public function alias($original, $aliasOrAliases)
500: {
501: foreach ((array) $aliasOrAliases as $alias) {
502: $this->aliases[$alias] = $original;
503: }
504: }
505:
506: 507: 508:
509: public function params($keys, array $params)
510: {
511: foreach ((array) $keys as $key) {
512: if (is_array($key)) {
513: $key = $key[0].'::'.$key[1];
514: }
515:
516: if (!isset($this->params[$key])) {
517: $this->params[$key] = $params;
518: } else {
519: $this->params[$key] = array_replace($this->params[$key], $params);
520: }
521: }
522: }
523:
524: 525: 526: 527: 528: 529: 530:
531: protected function isShared($class)
532: {
533: return isset($this->shared[$class]) && $this->shared[$class];
534: }
535:
536: 537: 538: 539: 540: 541: 542: 543: 544: 545: 546:
547: protected function checkProtected($class, $alias)
548: {
549: if (!$this->protectInternals) {
550: return;
551: }
552:
553: if ($this->isProtected($class) || ($alias && $this->isProtected($alias))) {
554: if ($alias) {
555: $class = "$class (via alias $alias)";
556: }
557: $msg = "Class $class is internal and cannot be resolved.";
558: throw new Exception\ResolvingInternalException($msg);
559: }
560: }
561:
562: 563: 564: 565: 566: 567: 568:
569: protected function isProtected($class)
570: {
571: return isset($this->internals[$class]) && $this->internals[$class];
572: }
573: }
574: