1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI;
9:
10: use Nette;
11: use Nette\Utils\Validators;
12: use Nette\Utils\Strings;
13: use Nette\PhpGenerator\Helpers as PhpHelpers;
14: use ReflectionClass;
15:
16:
17: 18: 19:
20: class ContainerBuilder extends Nette\Object
21: {
22: const THIS_SERVICE = 'self',
23: THIS_CONTAINER = 'container';
24:
25:
26: public $parameters = array();
27:
28:
29: private $className = 'Container';
30:
31:
32: private $definitions = array();
33:
34:
35: private $aliases = array();
36:
37:
38: private $classes;
39:
40:
41: private $excludedClasses = array();
42:
43:
44: private $dependencies = array();
45:
46:
47: private $generatedClasses = array();
48:
49:
50: public $currentService;
51:
52:
53: 54: 55: 56: 57:
58: public function addDefinition($name, ServiceDefinition $definition = NULL)
59: {
60: if (!is_string($name) || !$name) {
61: throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name)));
62: }
63: $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
64: if (isset($this->definitions[$name])) {
65: throw new Nette\InvalidStateException("Service '$name' has already been added.");
66: }
67: return $this->definitions[$name] = $definition ?: new ServiceDefinition;
68: }
69:
70:
71: 72: 73: 74: 75:
76: public function removeDefinition($name)
77: {
78: $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
79: unset($this->definitions[$name]);
80:
81: if ($this->classes) {
82: foreach ($this->classes as & $tmp) {
83: foreach ($tmp as & $names) {
84: $names = array_values(array_diff($names, array($name)));
85: }
86: }
87: }
88: }
89:
90:
91: 92: 93: 94: 95:
96: public function getDefinition($name)
97: {
98: $service = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
99: if (!isset($this->definitions[$service])) {
100: throw new MissingServiceException("Service '$name' not found.");
101: }
102: return $this->definitions[$service];
103: }
104:
105:
106: 107: 108: 109:
110: public function getDefinitions()
111: {
112: return $this->definitions;
113: }
114:
115:
116: 117: 118: 119: 120:
121: public function hasDefinition($name)
122: {
123: $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
124: return isset($this->definitions[$name]);
125: }
126:
127:
128: 129: 130: 131:
132: public function addAlias($alias, $service)
133: {
134: if (!is_string($alias) || !$alias) {
135: throw new Nette\InvalidArgumentException(sprintf('Alias name must be a non-empty string, %s given.', gettype($alias)));
136:
137: } elseif (!is_string($service) || !$service) {
138: throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($service)));
139:
140: } elseif (isset($this->aliases[$alias])) {
141: throw new Nette\InvalidStateException("Alias '$alias' has already been added.");
142:
143: } elseif (isset($this->definitions[$alias])) {
144: throw new Nette\InvalidStateException("Service '$alias' has already been added.");
145:
146: }
147: $this->aliases[$alias] = $service;
148: }
149:
150:
151: 152: 153: 154:
155: public function getAliases()
156: {
157: return $this->aliases;
158: }
159:
160:
161: 162: 163:
164: public function setClassName($name)
165: {
166: $this->className = (string) $name;
167: return $this;
168: }
169:
170:
171: 172: 173:
174: public function getClassName()
175: {
176: return $this->className;
177: }
178:
179:
180:
181:
182:
183: 184: 185: 186: 187: 188:
189: public function getByType($class)
190: {
191: $class = ltrim($class, '\\');
192:
193: if ($this->currentService !== NULL) {
194: $rc = new ReflectionClass($this->definitions[$this->currentService]->getClass());
195: if ($class === $rc->getName() || $rc->isSubclassOf($class)) {
196: return $this->currentService;
197: }
198: }
199:
200: if (!isset($this->classes[$class][TRUE])) {
201: self::checkCase($class);
202: return;
203:
204: } elseif (count($this->classes[$class][TRUE]) === 1) {
205: return $this->classes[$class][TRUE][0];
206:
207: } else {
208: throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $this->classes[$class][TRUE]));
209: }
210: }
211:
212:
213: 214: 215: 216: 217:
218: public function findByType($class)
219: {
220: $class = ltrim($class, '\\');
221: self::checkCase($class);
222: $found = array();
223: foreach (array(TRUE, FALSE) as $mode) {
224: if (!empty($this->classes[$class][$mode])) {
225: foreach ($this->classes[$class][$mode] as $name) {
226: $found[$name] = $this->definitions[$name];
227: }
228: }
229: }
230: return $found;
231: }
232:
233:
234: 235: 236: 237: 238:
239: public function findByTag($tag)
240: {
241: $found = array();
242: foreach ($this->definitions as $name => $def) {
243: if (($tmp = $def->getTag($tag)) !== NULL) {
244: $found[$name] = $tmp;
245: }
246: }
247: return $found;
248: }
249:
250:
251: 252: 253: 254:
255: public function autowireArguments($class, $method, array $arguments)
256: {
257: $rc = new ReflectionClass($class);
258: if (!$rc->hasMethod($method)) {
259: if (!Nette\Utils\Arrays::isList($arguments)) {
260: throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
261: }
262: return $arguments;
263: }
264:
265: $rm = $rc->getMethod($method);
266: if (!$rm->isPublic()) {
267: throw new ServiceCreationException("$class::$method() is not callable.");
268: }
269: $this->addDependency($rm->getFileName());
270: return Helpers::autowireArguments($rm, $arguments, $this);
271: }
272:
273:
274: 275: 276: 277: 278:
279: public function prepareClassList()
280: {
281: unset($this->definitions[self::THIS_CONTAINER]);
282: $this->addDefinition(self::THIS_CONTAINER)->setClass('Nette\DI\Container');
283:
284: $this->classes = FALSE;
285:
286: foreach ($this->definitions as $name => $def) {
287:
288: if ($def->getImplement()) {
289: $this->resolveImplement($def, $name);
290: }
291:
292: if ($def->isDynamic()) {
293: if (!$def->getClass()) {
294: throw new ServiceCreationException("Class is missing in definition of service '$name'.");
295: }
296: $def->setFactory(NULL);
297: continue;
298: }
299:
300:
301: if (!$def->getEntity()) {
302: if (!$def->getClass()) {
303: throw new ServiceCreationException("Class and factory are missing in definition of service '$name'.");
304: }
305: $def->setFactory($def->getClass(), ($factory = $def->getFactory()) ? $factory->arguments : array());
306: }
307:
308:
309: if (($alias = $this->getServiceName($def->getFactory()->getEntity())) &&
310: (!$def->getImplement() || (!Strings::contains($alias, '\\') && $this->definitions[$alias]->getImplement()))
311: ) {
312: $def->setAutowired(FALSE);
313: }
314: }
315:
316:
317: foreach ($this->definitions as $name => $def) {
318: $this->resolveServiceClass($name);
319: }
320:
321:
322: $excludedClasses = array();
323: foreach ($this->excludedClasses as $class) {
324: self::checkCase($class);
325: $excludedClasses += class_parents($class) + class_implements($class) + array($class => $class);
326: }
327:
328: $this->classes = array();
329: foreach ($this->definitions as $name => $def) {
330: if ($class = $def->getImplement() ?: $def->getClass()) {
331: foreach (class_parents($class) + class_implements($class) + array($class) as $parent) {
332: $this->classes[$parent][$def->isAutowired() && empty($excludedClasses[$parent])][] = (string) $name;
333: }
334: }
335: }
336:
337: foreach ($this->classes as $class => $foo) {
338: $rc = new ReflectionClass($class);
339: $this->addDependency($rc->getFileName());
340: }
341: }
342:
343:
344: private function resolveImplement(ServiceDefinition $def, $name)
345: {
346: $interface = $def->getImplement();
347: if (!interface_exists($interface)) {
348: throw new ServiceCreationException("Interface $interface used in service '$name' not found.");
349: }
350: self::checkCase($interface);
351: $rc = new ReflectionClass($interface);
352: $method = $rc->hasMethod('create') ? $rc->getMethod('create') : ($rc->hasMethod('get') ? $rc->getMethod('get') : NULL);
353: if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) {
354: throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get().");
355: }
356: $def->setImplementType($methodName = $rc->hasMethod('create') ? 'create' : 'get');
357:
358: if (!$def->getClass() && !$def->getEntity()) {
359: $returnType = PhpReflection::parseAnnotation($method, 'return');
360: if (!$returnType) {
361: throw new ServiceCreationException("Method $interface::$methodName() used in service '$name' has no @return annotation.");
362: }
363:
364: $returnType = PhpReflection::expandClassName(preg_replace('#[|\s].*#', '', $returnType), $rc);
365: if (!class_exists($returnType)) {
366: throw new ServiceCreationException("Please check a @return annotation of the $interface::$methodName() method used in service '$name'. Class '$returnType' cannot be found.");
367: }
368: $def->setClass($returnType);
369: }
370:
371: if ($methodName === 'get') {
372: if ($method->getParameters()) {
373: throw new ServiceCreationException("Method $interface::get() used in service '$name' must have no arguments.");
374: }
375: if (!$def->getEntity()) {
376: $def->setFactory('@\\' . ltrim($def->getClass(), '\\'));
377: } elseif (!$this->getServiceName($def->getFactory()->getEntity())) {
378: throw new ServiceCreationException("Invalid factory in service '$name' definition.");
379: }
380: }
381:
382: if (!$def->parameters) {
383: $ctorParams = array();
384: if (!$def->getEntity()) {
385: $def->setFactory($def->getClass(), $def->getFactory() ? $def->getFactory()->arguments : array());
386: }
387: if (($class = $this->resolveEntityClass($def->getFactory(), array($name => 1)))
388: && ($rc = new ReflectionClass($class)) && ($ctor = $rc->getConstructor())
389: ) {
390: foreach ($ctor->getParameters() as $param) {
391: $ctorParams[$param->getName()] = $param;
392: }
393: }
394:
395: foreach ($method->getParameters() as $param) {
396: $hint = $param->isArray() ? 'array' : PhpReflection::getPropertyType($param);
397: if (isset($ctorParams[$param->getName()])) {
398: $arg = $ctorParams[$param->getName()];
399: if ($hint !== ($arg->isArray() ? 'array' : PhpReflection::getPropertyType($arg))) {
400: throw new ServiceCreationException("Type hint for \${$param->getName()} in $interface::$methodName() doesn't match type hint in $class constructor.");
401: }
402: $def->getFactory()->arguments[$arg->getPosition()] = self::literal('$' . $arg->getName());
403: }
404: $paramDef = $hint . ' ' . $param->getName();
405: if ($param->isOptional()) {
406: $def->parameters[$paramDef] = $param->getDefaultValue();
407: } else {
408: $def->parameters[] = $paramDef;
409: }
410: }
411: }
412: }
413:
414:
415:
416: private function resolveServiceClass($name, $recursive = array())
417: {
418: if (isset($recursive[$name])) {
419: throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive))));
420: }
421: $recursive[$name] = TRUE;
422:
423: $def = $this->definitions[$name];
424: $class = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL;
425: if ($class = $def->getClass() ?: $class) {
426: $def->setClass($class);
427: if (!class_exists($class) && !interface_exists($class)) {
428: throw new ServiceCreationException("Class or interface $class used in service '$name' not found.");
429: }
430: self::checkCase($class);
431:
432: } elseif ($def->isAutowired()) {
433: trigger_error("Type of service '$name' is unknown.", E_USER_NOTICE);
434: }
435: return $class;
436: }
437:
438:
439:
440: private function resolveEntityClass($entity, $recursive = array())
441: {
442: $entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity);
443:
444: if (is_array($entity)) {
445: if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) {
446: $entity[0] = $this->resolveEntityClass($entity[0], $recursive);
447: if (!$entity[0]) {
448: return;
449: } elseif (isset($this->definitions[$service]) && $this->definitions[$service]->getImplement()) {
450: return $entity[1] === 'create' ? $this->resolveServiceClass($service, $recursive) : NULL;
451: }
452: }
453:
454: try {
455: $reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
456: $refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : NULL;
457: } catch (\ReflectionException $e) {
458: }
459:
460: if (isset($e) || ($refClass && (!$reflection->isPublic()
461: || (PHP_VERSION_ID >= 50400 && $refClass->isTrait() && !$reflection->isStatic())
462: ))) {
463: $name = array_slice(array_keys($recursive), -1);
464: throw new ServiceCreationException(sprintf("Factory '%s' used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $name[0]));
465: }
466:
467: $class = preg_replace('#[|\s].*#', '', PhpReflection::parseAnnotation($reflection, 'return'));
468: if ($class) {
469: $class = $refClass ? PhpReflection::expandClassName($class, $refClass) : ltrim($class, '\\');
470: }
471: return $class;
472:
473: } elseif ($service = $this->getServiceName($entity)) {
474: if (Strings::contains($service, '\\')) {
475: return ltrim($service, '\\');
476: }
477: return $this->definitions[$service]->getImplement() ?: $this->resolveServiceClass($service, $recursive);
478:
479: } elseif (is_string($entity)) {
480: if (!class_exists($entity) || !($rc = new ReflectionClass($entity)) || !$rc->isInstantiable()) {
481: $name = array_slice(array_keys($recursive), -1);
482: throw new ServiceCreationException("Class $entity used in service '$name[0]' not found or is not instantiable.");
483: }
484: return ltrim($entity, '\\');
485: }
486: }
487:
488:
489: private function checkCase($class)
490: {
491: if (class_exists($class) && ($rc = new ReflectionClass($class)) && $class !== $rc->getName()) {
492: throw new ServiceCreationException("Case mismatch on class name '$class', correct name is '{$rc->getName()}'.");
493: }
494: }
495:
496:
497: 498: 499: 500:
501: public function addExcludedClasses(array $classes)
502: {
503: $this->excludedClasses = array_merge($this->excludedClasses, $classes);
504: return $this;
505: }
506:
507:
508: 509: 510: 511: 512:
513: public function addDependency($file)
514: {
515: $this->dependencies[$file] = TRUE;
516: return $this;
517: }
518:
519:
520: 521: 522: 523:
524: public function getDependencies()
525: {
526: unset($this->dependencies[FALSE]);
527: return array_keys($this->dependencies);
528: }
529:
530:
531:
532:
533:
534: 535: 536: 537:
538: public function generateClasses($className = NULL, $parentName = NULL)
539: {
540: $this->prepareClassList();
541:
542: $this->generatedClasses = array();
543: $this->className = $className ?: $this->className;
544: $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className);
545: $containerClass->setExtends($parentName ?: 'Nette\DI\Container');
546: $containerClass->addMethod('__construct')
547: ->addBody('parent::__construct(?);', array($this->parameters));
548:
549: $definitions = $this->definitions;
550: ksort($definitions);
551:
552: $meta = $containerClass->addProperty('meta', array())
553: ->setVisibility('protected')
554: ->setValue(array(Container::TYPES => $this->classes));
555:
556: foreach ($definitions as $name => $def) {
557: $meta->value[Container::SERVICES][$name] = $def->getClass() ?: NULL;
558: foreach ($def->getTags() as $tag => $value) {
559: $meta->value[Container::TAGS][$tag][$name] = $value;
560: }
561: }
562:
563: foreach ($definitions as $name => $def) {
564: try {
565: $name = (string) $name;
566: $methodName = Container::getMethodName($name);
567: if (!PhpHelpers::isIdentifier($methodName)) {
568: throw new ServiceCreationException('Name contains invalid characters.');
569: }
570: $containerClass->addMethod($methodName)
571: ->addDocument('@return ' . ($def->getImplement() ?: $def->getClass()))
572: ->setBody($name === self::THIS_CONTAINER ? 'return $this;' : $this->generateService($name))
573: ->setParameters($def->getImplement() ? array() : $this->convertParameters($def->parameters));
574: } catch (\Exception $e) {
575: throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
576: }
577: }
578:
579: $aliases = $this->aliases;
580: ksort($aliases);
581: $meta->value[Container::ALIASES] = $aliases;
582:
583: return $this->generatedClasses;
584: }
585:
586:
587: 588: 589: 590:
591: private function generateService($name)
592: {
593: $this->currentService = NULL;
594: $def = $this->definitions[$name];
595:
596: if ($def->isDynamic()) {
597: return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);',
598: array("Unable to create dynamic service '$name', it must be added using addService()")
599: );
600: }
601:
602: $entity = $def->getFactory()->getEntity();
603: $serviceRef = $this->getServiceName($entity);
604: $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementType() !== 'create'
605: ? new Statement(array('@' . self::THIS_CONTAINER, 'getService'), array($serviceRef))
606: : $def->getFactory();
607:
608: $code = '$service = ' . $this->formatStatement($factory) . ";\n";
609: $this->currentService = $name;
610:
611: if (($class = $def->getClass()) && !$serviceRef && $class !== $entity
612: && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $class))
613: ) {
614: $code .= PhpHelpers::formatArgs("if (!\$service instanceof $class) {\n"
615: . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
616: array("Unable to create service '$name', value returned by factory is not $class type.")
617: );
618: }
619:
620: foreach ($def->getSetup() as $setup) {
621: if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) {
622: $setup->setEntity(array('@self', $setup->getEntity()));
623: }
624: $code .= $this->formatStatement($setup) . ";\n";
625: }
626: $this->currentService = NULL;
627:
628: $code .= 'return $service;';
629:
630: if (!$def->getImplement()) {
631: return $code;
632: }
633:
634: $factoryClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType;
635: $factoryClass->setName(str_replace(array('\\', '.'), '_', "{$this->className}_{$def->getImplement()}Impl_{$name}"))
636: ->addImplement($def->getImplement())
637: ->setFinal(TRUE);
638:
639: $factoryClass->addProperty('container')
640: ->setVisibility('private');
641:
642: $factoryClass->addMethod('__construct')
643: ->addBody('$this->container = $container;')
644: ->addParameter('container')
645: ->setTypeHint($this->className);
646:
647: $factoryClass->addMethod($def->getImplementType())
648: ->setParameters($this->convertParameters($def->parameters))
649: ->setBody(str_replace('$this', '$this->container', $code));
650:
651: return "return new {$factoryClass->getName()}(\$this);";
652: }
653:
654:
655: 656: 657: 658:
659: private function convertParameters(array $parameters)
660: {
661: $res = array();
662: foreach ($parameters as $k => $v) {
663: $tmp = explode(' ', is_int($k) ? $v : $k);
664: $param = $res[] = new Nette\PhpGenerator\Parameter;
665: $param->setName(end($tmp));
666: if (!is_int($k)) {
667: $param = $param->setOptional(TRUE)->setDefaultValue($v);
668: }
669: if (isset($tmp[1])) {
670: $param->setTypeHint($tmp[0]);
671: }
672: }
673: return $res;
674: }
675:
676:
677: 678: 679: 680: 681:
682: public function formatStatement(Statement $statement)
683: {
684: $entity = $this->normalizeEntity($statement->getEntity());
685: $arguments = $statement->arguments;
686:
687: if (is_string($entity) && Strings::contains($entity, '?')) {
688: return $this->formatPhp($entity, $arguments);
689:
690: } elseif ($service = $this->getServiceName($entity)) {
691: $params = array();
692: foreach ($this->definitions[$service]->parameters as $k => $v) {
693: $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
694: }
695: $rm = new \ReflectionFunction(create_function(implode(', ', $params), ''));
696: $arguments = Helpers::autowireArguments($rm, $arguments, $this);
697: return $this->formatPhp('$this->?(?*)', array(Container::getMethodName($service), $arguments));
698:
699: } elseif ($entity === 'not') {
700: return $this->formatPhp('!?', array($arguments[0]));
701:
702: } elseif (is_string($entity)) {
703: $rc = new ReflectionClass($entity);
704: if ($constructor = $rc->getConstructor()) {
705: $this->addDependency($constructor->getFileName());
706: $arguments = Helpers::autowireArguments($constructor, $arguments, $this);
707: } elseif ($arguments) {
708: throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
709: }
710: return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), array($arguments));
711:
712: } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
713: throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
714:
715: } elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '\z#', $entity[1])) {
716: throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
717:
718: } elseif ($entity[0] === '') {
719: return $this->formatPhp("$entity[1](?*)", array($arguments));
720:
721: } elseif ($entity[0] instanceof Statement) {
722: $inner = $this->formatPhp('?', array($entity[0]));
723: if (substr($inner, 0, 4) === 'new ') {
724: $inner = PHP_VERSION_ID < 50400 ? "current(array($inner))" : "($inner)";
725: }
726: return $this->formatPhp("$inner->?(?*)", array($entity[1], $arguments));
727:
728: } elseif (Strings::contains($entity[1], '$')) {
729: Validators::assert($arguments, 'list:1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
730: if ($this->getServiceName($entity[0])) {
731: return $this->formatPhp('?->? = ?', array($entity[0], substr($entity[1], 1), $arguments[0]));
732: } else {
733: return $this->formatPhp($entity[0] . '::$? = ?', array(substr($entity[1], 1), $arguments[0]));
734: }
735:
736: } elseif ($service = $this->getServiceName($entity[0])) {
737: $class = $this->definitions[$service]->getImplement();
738: if (!$class || !method_exists($class, $entity[1])) {
739: $class = $this->definitions[$service]->getClass();
740: }
741: if ($class) {
742: $arguments = $this->autowireArguments($class, $entity[1], $arguments);
743: }
744: return $this->formatPhp('?->?(?*)', array($entity[0], $entity[1], $arguments));
745:
746: } else {
747: $arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
748: return $this->formatPhp("$entity[0]::$entity[1](?*)", array($arguments));
749: }
750: }
751:
752:
753: 754: 755: 756: 757:
758: public function formatPhp($statement, $args)
759: {
760: $that = $this;
761: array_walk_recursive($args, function (& $val) use ($that) {
762: if ($val instanceof Statement) {
763: $val = ContainerBuilder::literal($that->formatStatement($val));
764:
765: } elseif ($val === $that) {
766: $val = ContainerBuilder::literal('$this');
767:
768: } elseif ($val instanceof ServiceDefinition) {
769: $val = '@' . current(array_keys($that->getDefinitions(), $val, TRUE));
770: }
771:
772: if (!is_string($val)) {
773: return;
774:
775: } elseif (substr($val, 0, 2) === '@@') {
776: $val = substr($val, 1);
777:
778: } elseif (substr($val, 0, 1) === '@') {
779: $pair = explode('::', $val, 2);
780: $name = $that->getServiceName($pair[0]);
781: if (isset($pair[1]) && preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) {
782: $val = $that->getDefinition($name)->getClass() . '::' . $pair[1];
783: } else {
784: if ($name === ContainerBuilder::THIS_CONTAINER) {
785: $val = '$this';
786: } elseif ($name === $that->currentService) {
787: $val = '$service';
788: } else {
789: $val = $that->formatStatement(new Statement(array('@' . ContainerBuilder::THIS_CONTAINER, 'getService'), array($name)));
790: }
791: $val .= (isset($pair[1]) ? PhpHelpers::formatArgs('->?', array($pair[1])) : '');
792: }
793: $val = ContainerBuilder::literal($val);
794: }
795: });
796: return PhpHelpers::formatArgs($statement, $args);
797: }
798:
799:
800: 801: 802: 803: 804:
805: public function expand($value)
806: {
807: return Helpers::expand($value, $this->parameters);
808: }
809:
810:
811: 812: 813:
814: public static function literal($phpCode)
815: {
816: return new Nette\PhpGenerator\PhpLiteral($phpCode);
817: }
818:
819:
820:
821: public function normalizeEntity($entity)
822: {
823: if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) {
824: $entity = explode('::', $entity);
825: }
826:
827: if (is_array($entity) && $entity[0] instanceof ServiceDefinition) {
828: $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], TRUE));
829:
830: } elseif ($entity instanceof ServiceDefinition) {
831: $entity = '@' . current(array_keys($this->definitions, $entity, TRUE));
832:
833: } elseif (is_array($entity) && $entity[0] === $this) {
834: $entity[0] = '@' . self::THIS_CONTAINER;
835: }
836: return $entity;
837: }
838:
839:
840: 841: 842: 843: 844:
845: public function getServiceName($arg)
846: {
847: $arg = $this->normalizeEntity($arg);
848: if (!is_string($arg) || !preg_match('#^@[\w\\\\.].*\z#', $arg)) {
849: return FALSE;
850: }
851: $service = substr($arg, 1);
852: if ($service === self::THIS_SERVICE) {
853: $service = $this->currentService;
854: }
855: if (Strings::contains($service, '\\')) {
856: if ($this->classes === FALSE) {
857: return $service;
858: }
859: $res = $this->getByType($service);
860: if (!$res) {
861: throw new ServiceCreationException("Reference to missing service of type $service.");
862: }
863: return $res;
864: }
865: $service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service;
866: if (!isset($this->definitions[$service])) {
867: throw new ServiceCreationException("Reference to missing service '$service'.");
868: }
869: return $service;
870: }
871:
872: }
873: