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
    • Diagnostics
    • Forms
      • Controls
      • Rendering
    • Http
    • Iterators
    • Latte
    • Loaders
    • Localization
    • Mail
    • Neon
    • PhpGenerator
    • Reflection
    • Security
    • Templating
    • Utils
  • NetteModule
  • none
  • Tracy
    • Bridges
      • Nette

Classes

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

Exceptions

  • MissingServiceException
  • ServiceCreationException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the Nette Framework (http://nette.org)
  5:  * Copyright (c) 2004 David Grudl (http://davidgrudl.com)
  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:  * Basic container builder.
 19:  *
 20:  * @author     David Grudl
 21:  */
 22: class ContainerBuilder extends Nette\Object
 23: {
 24:     const THIS_SERVICE = 'self',
 25:         THIS_CONTAINER = 'container';
 26: 
 27:     /** @var array */
 28:     public $parameters = array();
 29: 
 30:     /** @var string */
 31:     private $className = 'Container';
 32: 
 33:     /** @var ServiceDefinition[] */
 34:     private $definitions = array();
 35: 
 36:     /** @var array of alias => service */
 37:     private $aliases = array();
 38: 
 39:     /** @var array for auto-wiring */
 40:     private $classes;
 41: 
 42:     /** @var string[] of classes excluded from auto-wiring */
 43:     private $excludedClasses = array();
 44: 
 45:     /** @var array of file names */
 46:     private $dependencies = array();
 47: 
 48:     /** @var Nette\PhpGenerator\ClassType[] */
 49:     private $generatedClasses = array();
 50: 
 51:     /** @var string */
 52:     /*private in 5.4*/public $currentService;
 53: 
 54: 
 55:     /**
 56:      * Adds new service definition.
 57:      * @param  string
 58:      * @return ServiceDefinition
 59:      */
 60:     public function addDefinition($name, ServiceDefinition $definition = NULL)
 61:     {
 62:         if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0'
 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:      * Removes the specified service definition.
 75:      * @param  string
 76:      * @return void
 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:      * Gets the service definition.
 87:      * @param  string
 88:      * @return ServiceDefinition
 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:      * Gets all service definitions.
102:      * @return ServiceDefinition[]
103:      */
104:     public function getDefinitions()
105:     {
106:         return $this->definitions;
107:     }
108: 
109: 
110:     /**
111:      * Does the service definition or alias exist?
112:      * @param  string
113:      * @return bool
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:      * @param string
124:      * @param string
125:      */
126:     public function addAlias($alias, $service)
127:     {
128:         if (!is_string($alias) || !$alias) { // builder is not ready for falsy names such as '0'
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) { // builder is not ready for falsy names such as '0'
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:      * Gets all service aliases.
147:      * @return array
148:      */
149:     public function getAliases()
150:     {
151:         return $this->aliases;
152:     }
153: 
154: 
155:     /**
156:      * @return self
157:      */
158:     public function setClassName($name)
159:     {
160:         $this->className = (string) $name;
161:         return $this;
162:     }
163: 
164: 
165:     /**
166:      * @return string
167:      */
168:     public function getClassName()
169:     {
170:         return $this->className;
171:     }
172: 
173: 
174:     /********************* class resolving ****************d*g**/
175: 
176: 
177:     /**
178:      * Resolves service name by type.
179:      * @param  string  class or interface
180:      * @return string  service name or NULL
181:      * @throws ServiceCreationException
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:      * Gets the service names and definitions of the specified type.
209:      * @param string
210:      * @return ServiceDefinition[]
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:      * Gets the service objects of the specified tag.
230:      * @param  string
231:      * @return array of [service name => tag attributes]
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:      * Creates a list of arguments using autowiring.
247:      * @return array
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:      * Generates $dependencies, $classes and normalizes class names.
270:      * @return array
271:      * @internal
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:             // prepare generated factories
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:             // complete class-factory pairs
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:             // auto-disable autowiring for aliases
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:         // resolve and check classes
311:         foreach ($this->definitions as $name => $def) {
312:             $this->resolveServiceClass($name);
313:         }
314: 
315:         //  build auto-wiring list
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:     /** @return string|NULL */
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; // call always to check entities
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:     /** @return string|NULL */
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()) { // @Implement::create
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)) { // alias or factory
468:             if (Strings::contains($service, '\\')) { // @\Class
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:      * @param string[]
493:      * @return self
494:      */
495:     public function addExcludedClasses(array $classes)
496:     {
497:         $this->excludedClasses = array_merge($this->excludedClasses, $classes);
498:         return $this;
499:     }
500: 
501: 
502:     /**
503:      * Adds a file to the list of dependencies.
504:      * @return self
505:      * @internal
506:      */
507:     public function addDependency($file)
508:     {
509:         $this->dependencies[$file] = TRUE;
510:         return $this;
511:     }
512: 
513: 
514:     /**
515:      * Returns the list of dependent files.
516:      * @return array
517:      */
518:     public function getDependencies()
519:     {
520:         unset($this->dependencies[FALSE]);
521:         return array_keys($this->dependencies);
522:     }
523: 
524: 
525:     /********************* code generator ****************d*g**/
526: 
527: 
528:     /**
529:      * Generates PHP classes. First class is the container.
530:      * @return Nette\PhpGenerator\ClassType[]
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:      * Generates body of service method.
583:      * @return string
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) { // auto-prepend @self
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:      * Converts parameters from ServiceDefinition to PhpGenerator.
650:      * @return Nette\PhpGenerator\Parameter[]
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:      * Formats PHP code for class instantiating, function calling or property setting in PHP.
672:      * @return string
673:      * @internal
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, '?')) { // PHP literal
681:             return $this->formatPhp($entity, $arguments);
682: 
683:         } elseif ($service = $this->getServiceName($entity)) { // factory calling
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') { // operator
693:             return $this->formatPhp('!?', array($arguments[0]));
694: 
695:         } elseif (is_string($entity)) { // class name
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] === '') { // globalFunc
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], '$')) { // property setter
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])) { // service method
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 { // static method
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:      * Formats PHP statement.
748:      * @return string
749:      * @internal
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:      * Expands %placeholders% in strings.
795:      * @return mixed
796:      * @deprecated
797:      */
798:     public function expand($value)
799:     {
800:         return Helpers::expand($value, $this->parameters);
801:     }
802: 
803: 
804:     /**
805:      * @return Nette\PhpGenerator\PhpLiteral
806:      */
807:     public static function literal($phpCode)
808:     {
809:         return new Nette\PhpGenerator\PhpLiteral($phpCode);
810:     }
811: 
812: 
813:     /** @internal */
814:     public function normalizeEntity($entity)
815:     {
816:         if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
817:             $entity = explode('::', $entity);
818:         }
819: 
820:         if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
821:             $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], TRUE));
822: 
823:         } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
824:             $entity = '@' . current(array_keys($this->definitions, $entity, TRUE));
825: 
826:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
827:             $entity[0] = '@' . ContainerBuilder::THIS_CONTAINER;
828:         }
829:         return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc], Statement
830:     }
831: 
832: 
833:     /**
834:      * Converts @service or @\Class -> service name and checks its existence.
835:      * @return string  of FALSE, if argument is not service name
836:      * @internal
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) { // may be disabled by prepareClassList
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: 
Nette 2.3.1 API API documentation generated by ApiGen 2.8.0