Namespaces

  • Latte
    • Loaders
    • Macros
    • Runtime
  • Nette
    • Application
      • Responses
      • Routers
      • UI
    • Bridges
      • ApplicationDI
      • ApplicationLatte
      • ApplicationTracy
      • CacheDI
      • CacheLatte
      • DatabaseDI
      • DatabaseTracy
      • DITracy
      • FormsDI
      • FormsLatte
      • Framework
      • HttpDI
      • HttpTracy
      • MailDI
      • ReflectionDI
      • SecurityDI
      • SecurityTracy
    • Caching
      • Storages
    • ComponentModel
    • Database
      • Conventions
      • Drivers
      • Reflection
      • Table
    • DI
      • Config
        • Adapters
      • Extensions
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Utils
  • none
  • Tracy
    • Bridges
      • Nette

Classes

  • Compiler
  • CompilerExtension
  • Container
  • ContainerBuilder
  • ContainerFactory
  • ContainerLoader
  • ServiceDefinition
  • Statement

Exceptions

  • MissingServiceException
  • ServiceCreationException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  • Other releases
  • Nette homepage
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (https://nette.org)
  5:  * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  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:  * Basic container builder.
 19:  */
 20: class ContainerBuilder extends Nette\Object
 21: {
 22:     const THIS_SERVICE = 'self',
 23:         THIS_CONTAINER = 'container';
 24: 
 25:     /** @var array */
 26:     public $parameters = array();
 27: 
 28:     /** @var string */
 29:     private $className = 'Container';
 30: 
 31:     /** @var ServiceDefinition[] */
 32:     private $definitions = array();
 33: 
 34:     /** @var array of alias => service */
 35:     private $aliases = array();
 36: 
 37:     /** @var array for auto-wiring */
 38:     private $classes;
 39: 
 40:     /** @var string[] of classes excluded from auto-wiring */
 41:     private $excludedClasses = array();
 42: 
 43:     /** @var array of file names */
 44:     private $dependencies = array();
 45: 
 46:     /** @var Nette\PhpGenerator\ClassType[] */
 47:     private $generatedClasses = array();
 48: 
 49:     /** @var string */
 50:     /*private in 5.4*/public $currentService;
 51: 
 52: 
 53:     /**
 54:      * Adds new service definition.
 55:      * @param  string
 56:      * @return ServiceDefinition
 57:      */
 58:     public function addDefinition($name, ServiceDefinition $definition = NULL)
 59:     {
 60:         if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0'
 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:      * Removes the specified service definition.
 73:      * @param  string
 74:      * @return void
 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:      * Gets the service definition.
 93:      * @param  string
 94:      * @return ServiceDefinition
 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:      * Gets all service definitions.
108:      * @return ServiceDefinition[]
109:      */
110:     public function getDefinitions()
111:     {
112:         return $this->definitions;
113:     }
114: 
115: 
116:     /**
117:      * Does the service definition or alias exist?
118:      * @param  string
119:      * @return bool
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:      * @param  string
130:      * @param  string
131:      */
132:     public function addAlias($alias, $service)
133:     {
134:         if (!is_string($alias) || !$alias) { // builder is not ready for falsy names such as '0'
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) { // builder is not ready for falsy names such as '0'
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:      * Removes the specified alias.
153:      * @return void
154:      */
155:     public function removeAlias($alias)
156:     {
157:         unset($this->aliases[$alias]);
158:     }
159: 
160: 
161:     /**
162:      * Gets all service aliases.
163:      * @return array
164:      */
165:     public function getAliases()
166:     {
167:         return $this->aliases;
168:     }
169: 
170: 
171:     /**
172:      * @return static
173:      */
174:     public function setClassName($name)
175:     {
176:         $this->className = (string) $name;
177:         return $this;
178:     }
179: 
180: 
181:     /**
182:      * @return string
183:      */
184:     public function getClassName()
185:     {
186:         return $this->className;
187:     }
188: 
189: 
190:     /********************* class resolving ****************d*g**/
191: 
192: 
193:     /**
194:      * Resolves service name by type.
195:      * @param  string  class or interface
196:      * @return string|NULL  service name or NULL
197:      * @throws ServiceCreationException
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:      * Gets the service names and definitions of the specified type.
225:      * @param  string
226:      * @return ServiceDefinition[]
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:      * Gets the service objects of the specified tag.
244:      * @param  string
245:      * @return array of [service name => tag attributes]
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:      * Creates a list of arguments using autowiring.
261:      * @return array
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:      * Generates $dependencies, $classes and normalizes class names.
284:      * @return array
285:      * @internal
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:             // prepare generated factories
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:             // complete class-factory pairs
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:             // auto-disable autowiring for aliases
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:         // resolve and check classes
325:         foreach ($this->definitions as $name => $def) {
326:             $this->resolveServiceClass($name);
327:         }
328: 
329:         //  build auto-wiring list
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:     /** @return string|NULL */
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; // call always to check entities
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:     /** @return string|NULL */
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()) { // @Implement::create
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)) { // alias or factory
481:             if (Strings::contains($service, '\\')) { // @\Class
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:      * @param  string[]
506:      * @return static
507:      */
508:     public function addExcludedClasses(array $classes)
509:     {
510:         $this->excludedClasses = array_merge($this->excludedClasses, $classes);
511:         return $this;
512:     }
513: 
514: 
515:     /**
516:      * Adds a file to the list of dependencies.
517:      * @return static
518:      * @internal
519:      */
520:     public function addDependency($file)
521:     {
522:         $this->dependencies[$file] = TRUE;
523:         return $this;
524:     }
525: 
526: 
527:     /**
528:      * Returns the list of dependent files.
529:      * @return array
530:      */
531:     public function getDependencies()
532:     {
533:         unset($this->dependencies[FALSE]);
534:         return array_keys($this->dependencies);
535:     }
536: 
537: 
538:     /********************* code generator ****************d*g**/
539: 
540: 
541:     /**
542:      * Generates PHP classes. First class is the container.
543:      * @return Nette\PhpGenerator\ClassType[]
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:      * Generates body of service method.
596:      * @return string
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) { // auto-prepend @self
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:      * Converts parameters from ServiceDefinition to PhpGenerator.
665:      * @return Nette\PhpGenerator\Parameter[]
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:      * Formats PHP code for class instantiating, function calling or property setting in PHP.
687:      * @return string
688:      * @internal
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, '?')) { // PHP literal
696:             return $this->formatPhp($entity, $arguments);
697: 
698:         } elseif ($service = $this->getServiceName($entity)) { // factory calling
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') { // operator
708:             return $this->formatPhp('!?', array($arguments[0]));
709: 
710:         } elseif (is_string($entity)) { // class name
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] === '') { // globalFunc
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], '$')) { // property setter
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])) { // service method
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 { // static method
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:      * Formats PHP statement.
763:      * @return string
764:      * @internal
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:      * Expands %placeholders% in strings.
810:      * @return mixed
811:      * @deprecated
812:      */
813:     public function expand($value)
814:     {
815:         return Helpers::expand($value, $this->parameters);
816:     }
817: 
818: 
819:     /**
820:      * @return Nette\PhpGenerator\PhpLiteral
821:      */
822:     public static function literal($phpCode)
823:     {
824:         return new Nette\PhpGenerator\PhpLiteral($phpCode);
825:     }
826: 
827: 
828:     /** @internal */
829:     public function normalizeEntity($entity)
830:     {
831:         if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
832:             $entity = explode('::', $entity);
833:         }
834: 
835:         if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
836:             $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], TRUE));
837: 
838:         } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
839:             $entity = '@' . current(array_keys($this->definitions, $entity, TRUE));
840: 
841:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
842:             $entity[0] = '@' . self::THIS_CONTAINER;
843:         }
844:         return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc], Statement
845:     }
846: 
847: 
848:     /**
849:      * Converts @service or @\Class -> service name and checks its existence.
850:      * @return string  of FALSE, if argument is not service name
851:      * @internal
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) { // may be disabled by prepareClassList
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: 
Nette 2.3-20161221 API API documentation generated by ApiGen 2.8.0