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