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
      • 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
  • ContainerLoader
  • DependencyChecker
  • PhpGenerator
  • ServiceDefinition
  • Statement

Exceptions

  • MissingServiceException
  • ServiceCreationException
  • Overview
  • Namespace
  • Class
  • Tree
  • Deprecated
  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\PhpGenerator\Helpers as PhpHelpers;
 12: use Nette\Utils\Strings;
 13: use Nette\Utils\Validators;
 14: use Nette\Utils\Reflection;
 15: use ReflectionClass;
 16: 
 17: 
 18: /**
 19:  * Container builder.
 20:  */
 21: class ContainerBuilder
 22: {
 23:     use Nette\SmartObject;
 24: 
 25:     const THIS_SERVICE = 'self',
 26:         THIS_CONTAINER = 'container';
 27: 
 28:     /** @var array */
 29:     public $parameters = [];
 30: 
 31:     /** @var ServiceDefinition[] */
 32:     private $definitions = [];
 33: 
 34:     /** @var array of alias => service */
 35:     private $aliases = [];
 36: 
 37:     /** @var array for auto-wiring */
 38:     private $classList = [];
 39: 
 40:     /** @var bool */
 41:     private $classListNeedsRefresh = TRUE;
 42: 
 43:     /** @var string[] of classes excluded from auto-wiring */
 44:     private $excludedClasses = [];
 45: 
 46:     /** @var array */
 47:     private $dependencies = [];
 48: 
 49:     /** @var string */
 50:     private $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:         $this->classListNeedsRefresh = TRUE;
 61:         if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0'
 62:             throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name)));
 63:         }
 64:         $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
 65:         if (isset($this->definitions[$name])) {
 66:             throw new Nette\InvalidStateException("Service '$name' has already been added.");
 67:         }
 68:         if (!$definition) {
 69:             $definition = new ServiceDefinition;
 70:         }
 71:         $definition->setNotifier(function () {
 72:             $this->classListNeedsRefresh = TRUE;
 73:         });
 74:         return $this->definitions[$name] = $definition;
 75:     }
 76: 
 77: 
 78:     /**
 79:      * Removes the specified service definition.
 80:      * @param  string
 81:      * @return void
 82:      */
 83:     public function removeDefinition($name)
 84:     {
 85:         $this->classListNeedsRefresh = TRUE;
 86:         $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
 87:         unset($this->definitions[$name]);
 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:      * @param  string[]
173:      * @return static
174:      */
175:     public function addExcludedClasses(array $classes)
176:     {
177:         foreach ($classes as $class) {
178:             if (class_exists($class) || interface_exists($class)) {
179:                 self::checkCase($class);
180:                 $this->excludedClasses += class_parents($class) + class_implements($class) + [$class => $class];
181:             }
182:         }
183:         return $this;
184:     }
185: 
186: 
187:     /**
188:      * @deprecated
189:      */
190:     public function setClassName($name)
191:     {
192:         trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED);
193:         return $this;
194:     }
195: 
196: 
197:     /**
198:      * @deprecated
199:      */
200:     public function getClassName()
201:     {
202:         trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED);
203:     }
204: 
205: 
206:     /********************* class resolving ****************d*g**/
207: 
208: 
209:     /**
210:      * Resolves service name by type.
211:      * @param  string  class or interface
212:      * @return string|NULL  service name or NULL
213:      * @throws ServiceCreationException
214:      */
215:     public function getByType($class)
216:     {
217:         $class = ltrim($class, '\\');
218: 
219:         if ($this->currentService !== NULL
220:             && is_a($this->definitions[$this->currentService]->getClass(), $class, TRUE)
221:         ) {
222:             return $this->currentService;
223:         }
224: 
225:         $classes = $this->getClassList();
226:         if (empty($classes[$class][TRUE])) {
227:             self::checkCase($class);
228:             return;
229: 
230:         } elseif (count($classes[$class][TRUE]) === 1) {
231:             return $classes[$class][TRUE][0];
232: 
233:         } else {
234:             $list = $classes[$class][TRUE];
235:             $hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.'))
236:                 ? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.'
237:                 : '';
238:             throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $list) . $hint);
239:         }
240:     }
241: 
242: 
243:     /**
244:      * Gets the service definition of the specified type.
245:      * @param  string
246:      * @return ServiceDefinition
247:      */
248:     public function getDefinitionByType($class)
249:     {
250:         $definitionName = $this->getByType($class);
251:         if (!$definitionName) {
252:             throw new MissingServiceException("Service of type '$class' not found.");
253:         }
254: 
255:         return $this->getDefinition($definitionName);
256:     }
257: 
258: 
259:     /**
260:      * Gets the service names and definitions of the specified type.
261:      * @param  string
262:      * @return ServiceDefinition[]
263:      */
264:     public function findByType($class)
265:     {
266:         $class = ltrim($class, '\\');
267:         self::checkCase($class);
268:         $found = [];
269:         $classes = $this->getClassList();
270:         if (!empty($classes[$class])) {
271:             foreach (array_merge(...array_values($classes[$class])) as $name) {
272:                 $found[$name] = $this->definitions[$name];
273:             }
274:         }
275:         return $found;
276:     }
277: 
278: 
279:     /**
280:      * Gets the service objects of the specified tag.
281:      * @param  string
282:      * @return array of [service name => tag attributes]
283:      */
284:     public function findByTag($tag)
285:     {
286:         $found = [];
287:         foreach ($this->definitions as $name => $def) {
288:             if (($tmp = $def->getTag($tag)) !== NULL) {
289:                 $found[$name] = $tmp;
290:             }
291:         }
292:         return $found;
293:     }
294: 
295: 
296:     /**
297:      * @internal
298:      */
299:     public function getClassList()
300:     {
301:         if ($this->classList !== FALSE && $this->classListNeedsRefresh) {
302:             $this->prepareClassList();
303:             $this->classListNeedsRefresh = FALSE;
304:         }
305:         return $this->classList ?: [];
306:     }
307: 
308: 
309:     /**
310:      * Generates $dependencies, $classes and normalizes class names.
311:      * @return void
312:      * @internal
313:      */
314:     public function prepareClassList()
315:     {
316:         unset($this->definitions[self::THIS_CONTAINER]);
317:         $this->addDefinition(self::THIS_CONTAINER)->setClass(Container::class);
318: 
319:         $this->classList = FALSE;
320: 
321:         foreach ($this->definitions as $name => $def) {
322:             // prepare generated factories
323:             if ($def->getImplement()) {
324:                 $this->resolveImplement($def, $name);
325:             }
326: 
327:             if ($def->isDynamic()) {
328:                 if (!$def->getClass()) {
329:                     throw new ServiceCreationException("Class is missing in definition of service '$name'.");
330:                 }
331:                 $def->setFactory(NULL);
332:                 continue;
333:             }
334: 
335:             // complete class-factory pairs
336:             if (!$def->getEntity()) {
337:                 if (!$def->getClass()) {
338:                     throw new ServiceCreationException("Class and factory are missing in definition of service '$name'.");
339:                 }
340:                 $def->setFactory($def->getClass(), ($factory = $def->getFactory()) ? $factory->arguments : []);
341:             }
342: 
343:             // auto-disable autowiring for aliases
344:             if ($def->getAutowired() === TRUE
345:                 && ($alias = $this->getServiceName($def->getFactory()->getEntity()))
346:                 && (!$def->getImplement() || (!Strings::contains($alias, '\\') && $this->definitions[$alias]->getImplement()))
347:             ) {
348:                 $def->setAutowired(FALSE);
349:             }
350:         }
351: 
352:         // resolve and check classes
353:         foreach ($this->definitions as $name => $def) {
354:             $this->resolveServiceClass($name);
355:         }
356: 
357:         //  build auto-wiring list
358:         $this->classList = $preferred = [];
359:         foreach ($this->definitions as $name => $def) {
360:             if ($class = $def->getImplement() ?: $def->getClass()) {
361:                 $defAutowired = $def->getAutowired();
362:                 if (is_array($defAutowired)) {
363:                     foreach ($defAutowired as $k => $aclass) {
364:                         if ($aclass === self::THIS_SERVICE) {
365:                             $defAutowired[$k] = $class;
366:                         } elseif (!is_a($class, $aclass, TRUE)) {
367:                             throw new ServiceCreationException("Incompatible class $aclass in autowiring definition of service '$name'.");
368:                         }
369:                     }
370:                 }
371: 
372:                 foreach (class_parents($class) + class_implements($class) + [$class] as $parent) {
373:                     $autowired = $defAutowired && empty($this->excludedClasses[$parent]);
374:                     if ($autowired && is_array($defAutowired)) {
375:                         $autowired = FALSE;
376:                         foreach ($defAutowired as $aclass) {
377:                             if (is_a($parent, $aclass, TRUE)) {
378:                                 if (empty($preferred[$parent]) && isset($this->classList[$parent][TRUE])) {
379:                                     $this->classList[$parent][FALSE] = array_merge(...$this->classList[$parent]);
380:                                     $this->classList[$parent][TRUE] = [];
381:                                 }
382:                                 $preferred[$parent] = $autowired = TRUE;
383:                                 break;
384:                             }
385:                         }
386:                     } elseif (isset($preferred[$parent])) {
387:                         $autowired = FALSE;
388:                     }
389:                     $this->classList[$parent][$autowired][] = (string) $name;
390:                 }
391:             }
392:         }
393:     }
394: 
395: 
396:     private function resolveImplement(ServiceDefinition $def, $name)
397:     {
398:         $interface = $def->getImplement();
399:         if (!interface_exists($interface)) {
400:             throw new ServiceCreationException("Interface $interface used in service '$name' not found.");
401:         }
402:         self::checkCase($interface);
403:         $rc = new ReflectionClass($interface);
404:         $this->addDependency($rc);
405:         $method = $rc->hasMethod('create')
406:             ? $rc->getMethod('create')
407:             : ($rc->hasMethod('get') ? $rc->getMethod('get') : NULL);
408: 
409:         if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) {
410:             throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get().");
411:         }
412:         $def->setImplementMode($rc->hasMethod('create') ? $def::IMPLEMENT_MODE_CREATE : $def::IMPLEMENT_MODE_GET);
413:         $methodName = Reflection::toString($method) . '()';
414: 
415:         if (!$def->getClass() && !$def->getEntity()) {
416:             $returnType = Helpers::getReturnType($method);
417:             if (!$returnType) {
418:                 throw new ServiceCreationException("Method $methodName used in service '$name' has not return type hint or annotation @return.");
419:             } elseif (!class_exists($returnType)) {
420:                 throw new ServiceCreationException("Check a type hint or annotation @return of the $methodName method used in service '$name', class '$returnType' cannot be found.");
421:             }
422:             $def->setClass($returnType);
423:         }
424: 
425:         if ($rc->hasMethod('get')) {
426:             if ($method->getParameters()) {
427:                 throw new ServiceCreationException("Method $methodName used in service '$name' must have no arguments.");
428:             }
429:             if (!$def->getEntity()) {
430:                 $def->setFactory('@\\' . ltrim($def->getClass(), '\\'));
431:             } elseif (!$this->getServiceName($def->getFactory()->getEntity())) {
432:                 throw new ServiceCreationException("Invalid factory in service '$name' definition.");
433:             }
434:         }
435: 
436:         if (!$def->parameters) {
437:             $ctorParams = [];
438:             if (!$def->getEntity()) {
439:                 $def->setFactory($def->getClass(), $def->getFactory() ? $def->getFactory()->arguments : []);
440:             }
441:             if (($class = $this->resolveEntityClass($def->getFactory(), [$name => 1]))
442:                 && ($ctor = (new ReflectionClass($class))->getConstructor())
443:             ) {
444:                 foreach ($ctor->getParameters() as $param) {
445:                     $ctorParams[$param->getName()] = $param;
446:                 }
447:             }
448: 
449:             foreach ($method->getParameters() as $param) {
450:                 $hint = Reflection::getParameterType($param);
451:                 if (isset($ctorParams[$param->getName()])) {
452:                     $arg = $ctorParams[$param->getName()];
453:                     if ($hint !== Reflection::getParameterType($arg)) {
454:                         throw new ServiceCreationException("Type hint for \${$param->getName()} in $methodName doesn't match type hint in $class constructor.");
455:                     }
456:                     $def->getFactory()->arguments[$arg->getPosition()] = self::literal('$' . $arg->getName());
457:                 } elseif (!$def->getSetup()) {
458:                     $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys($ctorParams), $param->getName());
459:                     throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $methodName" . ($hint ? ", did you mean \${$hint}?" : '.'));
460:                 }
461:                 $nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== NULL);
462:                 $paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
463:                 if ($param->isDefaultValueAvailable()) {
464:                     $def->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
465:                 } else {
466:                     $def->parameters[] = $paramDef;
467:                 }
468:             }
469:         }
470:     }
471: 
472: 
473:     /** @return string|NULL */
474:     private function resolveServiceClass($name, $recursive = [])
475:     {
476:         if (isset($recursive[$name])) {
477:             throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive))));
478:         }
479:         $recursive[$name] = TRUE;
480: 
481:         $def = $this->definitions[$name];
482:         $factoryClass = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities
483:         if ($class = $def->getClass() ?: $factoryClass) {
484:             if (!class_exists($class) && !interface_exists($class)) {
485:                 throw new ServiceCreationException("Class or interface '$class' used in service '$name' not found.");
486:             }
487:             self::checkCase($class);
488:             $def->setClass($class);
489:             if (count($recursive) === 1) {
490:                 $this->addDependency(new ReflectionClass($factoryClass ?: $class));
491:             }
492: 
493:         } elseif ($def->getAutowired()) {
494:             throw new ServiceCreationException("Unknown type of service '$name', declare return type of factory method (for PHP 5 use annotation @return)");
495:         }
496:         return $class;
497:     }
498: 
499: 
500:     /** @return string|NULL */
501:     private function resolveEntityClass($entity, $recursive = [])
502:     {
503:         $entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity);
504:         $serviceName = current(array_slice(array_keys($recursive), -1));
505: 
506:         if (is_array($entity)) {
507:             if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) {
508:                 $entity[0] = $this->resolveEntityClass($entity[0], $recursive);
509:                 if (!$entity[0]) {
510:                     return;
511:                 } elseif (isset($this->definitions[$service]) && $this->definitions[$service]->getImplement()) { // @Implement::create
512:                     return $entity[1] === 'create' ? $this->resolveServiceClass($service, $recursive) : NULL;
513:                 }
514:             }
515: 
516:             try {
517:                 $reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
518:                 $refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : NULL;
519:             } catch (\ReflectionException $e) {
520:             }
521: 
522:             if (isset($e) || ($refClass && (!$reflection->isPublic()
523:                 || ($refClass->isTrait() && !$reflection->isStatic())
524:             ))) {
525:                 throw new ServiceCreationException(sprintf("Method %s() used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $serviceName));
526:             }
527:             $this->addDependency($reflection);
528: 
529:             $type = Helpers::getReturnType($reflection);
530:             if ($type && !class_exists($type) && !interface_exists($type)) {
531:                 throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() used in service '%s' correct?", $type, Nette\Utils\Callback::toString($entity), $serviceName));
532:             }
533:             return $type;
534: 
535:         } elseif ($service = $this->getServiceName($entity)) { // alias or factory
536:             if (Strings::contains($service, '\\')) { // @\Class
537:                 return ltrim($service, '\\');
538:             }
539:             return $this->definitions[$service]->getImplement()
540:                 ?: $this->definitions[$service]->getClass()
541:                 ?: $this->resolveServiceClass($service, $recursive);
542: 
543:         } elseif (is_string($entity)) { // class
544:             if (!class_exists($entity)) {
545:                 throw new ServiceCreationException("Class $entity used in service '$serviceName' not found.");
546:             }
547:             return ltrim($entity, '\\');
548:         }
549:     }
550: 
551: 
552:     /**
553:      * @return void
554:      */
555:     public function complete()
556:     {
557:         $this->prepareClassList();
558: 
559:         foreach ($this->definitions as $name => $def) {
560:             if ($def->isDynamic()) {
561:                 continue;
562:             }
563: 
564:             $this->currentService = NULL;
565:             $entity = $def->getFactory()->getEntity();
566:             $serviceRef = $this->getServiceName($entity);
567:             $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
568:                 ? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef])
569:                 : $def->getFactory();
570: 
571:             try {
572:                 $def->setFactory($this->completeStatement($factory));
573:                 $this->classListNeedsRefresh = FALSE;
574: 
575:                 $this->currentService = $name;
576:                 $setups = $def->getSetup();
577:                 foreach ($setups as &$setup) {
578:                     if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self
579:                         $setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments);
580:                     }
581:                     $setup = $this->completeStatement($setup);
582:                 }
583:                 $def->setSetup($setups);
584: 
585:             } catch (\Exception $e) {
586:                 throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
587: 
588:             } finally {
589:                 $this->currentService = NULL;
590:             }
591:         }
592:     }
593: 
594: 
595:     /**
596:      * @return Statement
597:      */
598:     public function completeStatement(Statement $statement)
599:     {
600:         $entity = $this->normalizeEntity($statement->getEntity());
601:         $arguments = $statement->arguments;
602: 
603:         if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
604: 
605:         } elseif ($service = $this->getServiceName($entity)) { // factory calling
606:             $params = [];
607:             foreach ($this->definitions[$service]->parameters as $k => $v) {
608:                 $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
609:             }
610:             $rm = new \ReflectionFunction(create_function(implode(', ', $params), ''));
611:             $arguments = Helpers::autowireArguments($rm, $arguments, $this);
612:             $entity = '@' . $service;
613: 
614:         } elseif ($entity === 'not') { // operator
615: 
616:         } elseif (is_string($entity)) { // class name
617:             $entity = ltrim($entity, '\\');
618:             if (!class_exists($entity)) {
619:                 throw new ServiceCreationException("Class $entity not found.");
620:             } elseif ((new ReflectionClass($entity))->isAbstract()) {
621:                 throw new ServiceCreationException("Class $entity is abstract.");
622:             } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== NULL && !$rm->isPublic()) {
623:                 $visibility = $rm->isProtected() ? 'protected' : 'private';
624:                 throw new ServiceCreationException("Class $entity has $visibility constructor.");
625:             } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
626:                 $this->addDependency($constructor);
627:                 $arguments = Helpers::autowireArguments($constructor, $arguments, $this);
628:             } elseif ($arguments) {
629:                 throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
630:             }
631: 
632:         } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
633:             throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
634: 
635:         } elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '(\[\])?\z#', $entity[1])) {
636:             throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
637: 
638:         } elseif ($entity[0] === '') { // globalFunc
639:             if (!Nette\Utils\Arrays::isList($arguments)) {
640:                 throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
641:             } elseif (!function_exists($entity[1])) {
642:                 throw new ServiceCreationException("Function $entity[1] doesn't exist.");
643:             }
644: 
645:             $rf = new \ReflectionFunction($entity[1]);
646:             $this->addDependency($rf);
647:             $arguments = Helpers::autowireArguments($rf, $arguments, $this);
648: 
649:         } else {
650:             if ($entity[0] instanceof Statement) {
651:                 $entity[0] = $this->completeStatement($entity[0]);
652:             } elseif ($service = $this->getServiceName($entity[0])) { // service method
653:                 $entity[0] = '@' . $service;
654:             } elseif (is_string($entity[0])) {
655:                 $entity[0] = ltrim($entity[0], '\\');
656:             }
657: 
658:             if ($entity[1][0] === '$') { // property getter, setter or appender
659:                 Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
660:                 if (!$arguments && substr($entity[1], -2) === '[]') {
661:                     throw new ServiceCreationException("Missing argument for $entity[1].");
662:                 }
663:             } elseif ($class = empty($service) || $entity[1] === 'create'
664:                 ? $this->resolveEntityClass($entity[0])
665:                 : $this->definitions[$service]->getClass()
666:             ) {
667:                 $arguments = $this->autowireArguments($class, $entity[1], $arguments);
668:             }
669:         }
670: 
671:         array_walk_recursive($arguments, function (&$val) {
672:             if ($val instanceof Statement) {
673:                 $val = $this->completeStatement($val);
674: 
675:             } elseif ($val === $this) {
676:                 trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
677:                 $val = self::literal('$this');
678: 
679:             } elseif ($val instanceof ServiceDefinition) {
680:                 $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
681: 
682:             } elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
683:                 $pair = explode('::', $val, 2);
684:                 $name = $this->getServiceName($pair[0]);
685:                 if (!isset($pair[1])) { // @service
686:                     $val = '@' . $name;
687:                 } elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
688:                     $val = self::literal($this->getDefinition($name)->getClass() . '::' . $pair[1]);
689:                 } else { // @service::property
690:                     $val = new Statement(['@' . $name, '$' . $pair[1]]);
691:                 }
692:             }
693:         });
694: 
695:         return new Statement($entity, $arguments);
696:     }
697: 
698: 
699:     private function checkCase($class)
700:     {
701:         if ((class_exists($class) || interface_exists($class)) && $class !== ($name = (new ReflectionClass($class))->getName())) {
702:             throw new ServiceCreationException("Case mismatch on class name '$class', correct name is '$name'.");
703:         }
704:     }
705: 
706: 
707:     /**
708:      * Adds item to the list of dependencies.
709:      * @param  ReflectionClass|\ReflectionFunctionAbstract|string
710:      * @return static
711:      * @internal
712:      */
713:     public function addDependency($dep)
714:     {
715:         $this->dependencies[] = $dep;
716:         return $this;
717:     }
718: 
719: 
720:     /**
721:      * Returns the list of dependencies.
722:      * @return array
723:      */
724:     public function getDependencies()
725:     {
726:         return $this->dependencies;
727:     }
728: 
729: 
730:     /**
731:      * Expands %placeholders% in strings.
732:      * @return mixed
733:      * @deprecated
734:      */
735:     public function expand($value)
736:     {
737:         return Helpers::expand($value, $this->parameters);
738:     }
739: 
740: 
741:     /**
742:      * @return Nette\PhpGenerator\PhpLiteral
743:      */
744:     public static function literal($code, array $args = NULL)
745:     {
746:         return new Nette\PhpGenerator\PhpLiteral($args === NULL ? $code : PhpHelpers::formatArgs($code, $args));
747:     }
748: 
749: 
750:     /** @internal */
751:     public function normalizeEntity($entity)
752:     {
753:         if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
754:             $entity = explode('::', $entity);
755:         }
756: 
757:         if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
758:             $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], TRUE));
759: 
760:         } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
761:             $entity = '@' . current(array_keys($this->definitions, $entity, TRUE));
762: 
763:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
764:             trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED);
765:             $entity[0] = '@' . self::THIS_CONTAINER;
766:         }
767:         return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc], Statement
768:     }
769: 
770: 
771:     /**
772:      * Converts @service or @\Class -> service name and checks its existence.
773:      * @return string  of FALSE, if argument is not service name
774:      * @internal
775:      */
776:     public function getServiceName($arg)
777:     {
778:         if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) {
779:             return FALSE;
780:         }
781:         $service = substr($arg, 1);
782:         if ($service === self::THIS_SERVICE) {
783:             $service = $this->currentService;
784:         }
785:         if (Strings::contains($service, '\\')) {
786:             if ($this->classList === FALSE) { // may be disabled by prepareClassList
787:                 return $service;
788:             }
789:             $res = $this->getByType($service);
790:             if (!$res) {
791:                 throw new ServiceCreationException("Reference to missing service of type $service.");
792:             }
793:             return $res;
794:         }
795:         $service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service;
796:         if (!isset($this->definitions[$service])) {
797:             throw new ServiceCreationException("Reference to missing service '$service'.");
798:         }
799:         return $service;
800:     }
801: 
802: 
803:     /**
804:      * Creates a list of arguments using autowiring.
805:      * @return array
806:      * @internal
807:      */
808:     public function autowireArguments($class, $method, array $arguments)
809:     {
810:         $rc = new ReflectionClass($class);
811:         if (!$rc->hasMethod($method)) {
812:             if (!Nette\Utils\Arrays::isList($arguments)) {
813:                 throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
814:             }
815:             return $arguments;
816:         }
817: 
818:         $rm = $rc->getMethod($method);
819:         if (!$rm->isPublic()) {
820:             throw new ServiceCreationException("$class::$method() is not callable.");
821:         }
822:         $this->addDependency($rm);
823:         return Helpers::autowireArguments($rm, $arguments, $this);
824:     }
825: 
826: 
827:     /** @deprecated */
828:     public function generateClasses($className = 'Container', $parentName = NULL)
829:     {
830:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
831:         return (new PhpGenerator($this))->generate($className);
832:     }
833: 
834: 
835:     /** @deprecated */
836:     public function formatStatement(Statement $statement)
837:     {
838:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
839:         return (new PhpGenerator($this))->formatStatement($statement);
840:     }
841: 
842: 
843:     /** @deprecated */
844:     public function formatPhp($statement, $args)
845:     {
846:         array_walk_recursive($args, function (&$val) {
847:             if ($val instanceof Statement) {
848:                 $val = $this->completeStatement($val);
849: 
850:             } elseif ($val === $this) {
851:                 trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
852:                 $val = self::literal('$this');
853: 
854:             } elseif ($val instanceof ServiceDefinition) {
855:                 $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
856:             }
857:         });
858:         return (new PhpGenerator($this))->formatPhp($statement, $args);
859:     }
860: 
861: }
862: 
Nette 2.4-20170221 API API documentation generated by ApiGen 2.8.0