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