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 array
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 (($alias = $this->getServiceName($def->getFactory()->getEntity())) &&
345:                 (!$def->getImplement() || (!Strings::contains($alias, '\\') && $this->definitions[$alias]->getImplement()))
346:             ) {
347:                 $def->setAutowired(FALSE);
348:             }
349:         }
350: 
351:         // resolve and check classes
352:         foreach ($this->definitions as $name => $def) {
353:             $this->resolveServiceClass($name);
354:         }
355: 
356:         //  build auto-wiring list
357:         $this->classList = $preferred = [];
358:         foreach ($this->definitions as $name => $def) {
359:             if ($class = $def->getImplement() ?: $def->getClass()) {
360:                 $defAutowired = $def->getAutowired();
361:                 if (is_array($defAutowired)) {
362:                     foreach ($defAutowired as $k => $aclass) {
363:                         if ($aclass === self::THIS_SERVICE) {
364:                             $defAutowired[$k] = $class;
365:                         } elseif (!is_a($class, $aclass, TRUE)) {
366:                             throw new ServiceCreationException("Incompatible class $aclass in autowiring definition of service '$name'.");
367:                         }
368:                     }
369:                 }
370: 
371:                 foreach (class_parents($class) + class_implements($class) + [$class] as $parent) {
372:                     $autowired = $defAutowired && empty($this->excludedClasses[$parent]);
373:                     if ($autowired && is_array($defAutowired)) {
374:                         $autowired = FALSE;
375:                         foreach ($defAutowired as $aclass) {
376:                             if (is_a($parent, $aclass, TRUE)) {
377:                                 if (empty($preferred[$parent]) && isset($this->classList[$parent][TRUE])) {
378:                                     $this->classList[$parent][FALSE] = array_merge(...$this->classList[$parent]);
379:                                     $this->classList[$parent][TRUE] = [];
380:                                 }
381:                                 $preferred[$parent] = $autowired = TRUE;
382:                                 break;
383:                             }
384:                         }
385:                     } elseif (isset($preferred[$parent])) {
386:                         $autowired = FALSE;
387:                     }
388:                     $this->classList[$parent][$autowired][] = (string) $name;
389:                 }
390:             }
391:         }
392:     }
393: 
394: 
395:     private function resolveImplement(ServiceDefinition $def, $name)
396:     {
397:         $interface = $def->getImplement();
398:         if (!interface_exists($interface)) {
399:             throw new ServiceCreationException("Interface $interface used in service '$name' not found.");
400:         }
401:         self::checkCase($interface);
402:         $rc = new ReflectionClass($interface);
403:         $this->addDependency($rc);
404:         $method = $rc->hasMethod('create')
405:             ? $rc->getMethod('create')
406:             : ($rc->hasMethod('get') ? $rc->getMethod('get') : NULL);
407: 
408:         if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) {
409:             throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get().");
410:         }
411:         $def->setImplementMode($rc->hasMethod('create') ? $def::IMPLEMENT_MODE_CREATE : $def::IMPLEMENT_MODE_GET);
412:         $methodName = Reflection::toString($method) . '()';
413: 
414:         if (!$def->getClass() && !$def->getEntity()) {
415:             $returnType = Helpers::getReturnType($method);
416:             if (!$returnType) {
417:                 throw new ServiceCreationException("Method $methodName used in service '$name' has not return type hint or annotation @return.");
418:             } elseif (!class_exists($returnType)) {
419:                 throw new ServiceCreationException("Check a type hint or annotation @return of the $methodName method used in service '$name', class '$returnType' cannot be found.");
420:             }
421:             $def->setClass($returnType);
422:         }
423: 
424:         if ($rc->hasMethod('get')) {
425:             if ($method->getParameters()) {
426:                 throw new ServiceCreationException("Method $methodName used in service '$name' must have no arguments.");
427:             }
428:             if (!$def->getEntity()) {
429:                 $def->setFactory('@\\' . ltrim($def->getClass(), '\\'));
430:             } elseif (!$this->getServiceName($def->getFactory()->getEntity())) {
431:                 throw new ServiceCreationException("Invalid factory in service '$name' definition.");
432:             }
433:         }
434: 
435:         if (!$def->parameters) {
436:             $ctorParams = [];
437:             if (!$def->getEntity()) {
438:                 $def->setFactory($def->getClass(), $def->getFactory() ? $def->getFactory()->arguments : []);
439:             }
440:             if (($class = $this->resolveEntityClass($def->getFactory(), [$name => 1]))
441:                 && ($ctor = (new ReflectionClass($class))->getConstructor())
442:             ) {
443:                 foreach ($ctor->getParameters() as $param) {
444:                     $ctorParams[$param->getName()] = $param;
445:                 }
446:             }
447: 
448:             foreach ($method->getParameters() as $param) {
449:                 $hint = Reflection::getParameterType($param);
450:                 if (isset($ctorParams[$param->getName()])) {
451:                     $arg = $ctorParams[$param->getName()];
452:                     if ($hint !== Reflection::getParameterType($arg)) {
453:                         throw new ServiceCreationException("Type hint for \${$param->getName()} in $methodName doesn't match type hint in $class constructor.");
454:                     }
455:                     $def->getFactory()->arguments[$arg->getPosition()] = self::literal('$' . $arg->getName());
456:                 } elseif (!$def->getSetup()) {
457:                     $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys($ctorParams), $param->getName());
458:                     throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $methodName" . ($hint ? ", did you mean \${$hint}?" : '.'));
459:                 }
460:                 $nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== NULL);
461:                 $paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
462:                 if ($param->isDefaultValueAvailable()) {
463:                     $def->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
464:                 } else {
465:                     $def->parameters[] = $paramDef;
466:                 }
467:             }
468:         }
469:     }
470: 
471: 
472:     /** @return string|NULL */
473:     private function resolveServiceClass($name, $recursive = [])
474:     {
475:         if (isset($recursive[$name])) {
476:             throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive))));
477:         }
478:         $recursive[$name] = TRUE;
479: 
480:         $def = $this->definitions[$name];
481:         $factoryClass = $def->getFactory() ? $this->resolveEntityClass($def->getFactory()->getEntity(), $recursive) : NULL; // call always to check entities
482:         if ($class = $def->getClass() ?: $factoryClass) {
483:             if (!class_exists($class) && !interface_exists($class)) {
484:                 throw new ServiceCreationException("Class or interface '$class' used in service '$name' not found.");
485:             }
486:             self::checkCase($class);
487:             $def->setClass($class);
488:             if (count($recursive) === 1) {
489:                 $this->addDependency(new ReflectionClass($factoryClass ?: $class));
490:             }
491: 
492:         } elseif ($def->getAutowired()) {
493:             throw new ServiceCreationException("Unknown type of service '$name', declare return type of factory method (for PHP 5 use annotation @return)");
494:         }
495:         return $class;
496:     }
497: 
498: 
499:     /** @return string|NULL */
500:     private function resolveEntityClass($entity, $recursive = [])
501:     {
502:         $entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity);
503:         $serviceName = current(array_slice(array_keys($recursive), -1));
504: 
505:         if (is_array($entity)) {
506:             if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) {
507:                 $entity[0] = $this->resolveEntityClass($entity[0], $recursive);
508:                 if (!$entity[0]) {
509:                     return;
510:                 } elseif (isset($this->definitions[$service]) && $this->definitions[$service]->getImplement()) { // @Implement::create
511:                     return $entity[1] === 'create' ? $this->resolveServiceClass($service, $recursive) : NULL;
512:                 }
513:             }
514: 
515:             try {
516:                 $reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
517:                 $refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : NULL;
518:             } catch (\ReflectionException $e) {
519:             }
520: 
521:             if (isset($e) || ($refClass && (!$reflection->isPublic()
522:                 || ($refClass->isTrait() && !$reflection->isStatic())
523:             ))) {
524:                 throw new ServiceCreationException(sprintf("Method %s() used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $serviceName));
525:             }
526:             $this->addDependency($reflection);
527: 
528:             $type = Helpers::getReturnType($reflection);
529:             if ($type && !class_exists($type) && !interface_exists($type)) {
530:                 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));
531:             }
532:             return $type;
533: 
534:         } elseif ($service = $this->getServiceName($entity)) { // alias or factory
535:             if (Strings::contains($service, '\\')) { // @\Class
536:                 return ltrim($service, '\\');
537:             }
538:             return $this->definitions[$service]->getImplement()
539:                 ?: $this->definitions[$service]->getClass()
540:                 ?: $this->resolveServiceClass($service, $recursive);
541: 
542:         } elseif (is_string($entity)) { // class
543:             if (!class_exists($entity)) {
544:                 throw new ServiceCreationException("Class $entity used in service '$serviceName' not found.");
545:             }
546:             return ltrim($entity, '\\');
547:         }
548:     }
549: 
550: 
551:     /**
552:      * @return void
553:      */
554:     public function complete()
555:     {
556:         $this->prepareClassList();
557: 
558:         foreach ($this->definitions as $name => $def) {
559:             if ($def->isDynamic()) {
560:                 continue;
561:             }
562: 
563:             $this->currentService = NULL;
564:             $entity = $def->getFactory()->getEntity();
565:             $serviceRef = $this->getServiceName($entity);
566:             $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
567:                 ? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef])
568:                 : $def->getFactory();
569: 
570:             try {
571:                 $def->setFactory($this->completeStatement($factory));
572:                 $this->classListNeedsRefresh = FALSE;
573: 
574:                 $this->currentService = $name;
575:                 $setups = $def->getSetup();
576:                 foreach ($setups as &$setup) {
577:                     if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === FALSE) { // auto-prepend @self
578:                         $setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments);
579:                     }
580:                     $setup = $this->completeStatement($setup);
581:                 }
582:                 $def->setSetup($setups);
583: 
584:             } catch (\Exception $e) {
585:                 throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
586: 
587:             } finally {
588:                 $this->currentService = NULL;
589:             }
590:         }
591:     }
592: 
593: 
594:     /**
595:      * @return Statement
596:      */
597:     public function completeStatement(Statement $statement)
598:     {
599:         $entity = $this->normalizeEntity($statement->getEntity());
600:         $arguments = $statement->arguments;
601: 
602:         if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
603: 
604:         } elseif ($service = $this->getServiceName($entity)) { // factory calling
605:             $params = [];
606:             foreach ($this->definitions[$service]->parameters as $k => $v) {
607:                 $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
608:             }
609:             $rm = new \ReflectionFunction(create_function(implode(', ', $params), ''));
610:             $arguments = Helpers::autowireArguments($rm, $arguments, $this);
611:             $entity = '@' . $service;
612: 
613:         } elseif ($entity === 'not') { // operator
614: 
615:         } elseif (is_string($entity)) { // class name
616:             if (!class_exists($entity)) {
617:                 throw new ServiceCreationException("Class $entity not found.");
618:             } elseif ((new ReflectionClass($entity))->isAbstract()) {
619:                 throw new ServiceCreationException("Class $entity is abstract.");
620:             } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== NULL && !$rm->isPublic()) {
621:                 $visibility = $rm->isProtected() ? 'protected' : 'private';
622:                 throw new ServiceCreationException("Class $entity has $visibility constructor.");
623:             } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
624:                 $this->addDependency($constructor);
625:                 $arguments = Helpers::autowireArguments($constructor, $arguments, $this);
626:             } elseif ($arguments) {
627:                 throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
628:             }
629: 
630:         } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
631:             throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
632: 
633:         } elseif (!preg_match('#^\$?' . PhpHelpers::PHP_IDENT . '(\[\])?\z#', $entity[1])) {
634:             throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
635: 
636:         } elseif ($entity[0] === '') { // globalFunc
637:             if (!Nette\Utils\Arrays::isList($arguments)) {
638:                 throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
639:             } elseif (!function_exists($entity[1])) {
640:                 throw new ServiceCreationException("Function $entity[1] doesn't exist.");
641:             }
642: 
643:             $rf = new \ReflectionFunction($entity[1]);
644:             $this->addDependency($rf);
645:             $arguments = Helpers::autowireArguments($rf, $arguments, $this);
646: 
647:         } else {
648:             if ($entity[0] instanceof Statement) {
649:                 $entity[0] = $this->completeStatement($entity[0]);
650:             } elseif ($service = $this->getServiceName($entity[0])) { // service method
651:                 $entity[0] = '@' . $service;
652:             }
653: 
654:             if ($entity[1][0] === '$') { // property getter, setter or appender
655:                 Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
656:                 if (!$arguments && substr($entity[1], -2) === '[]') {
657:                     throw new ServiceCreationException("Missing argument for $entity[1].");
658:                 }
659:             } elseif ($class = empty($service) || $entity[1] === 'create'
660:                 ? $this->resolveEntityClass($entity[0])
661:                 : $this->definitions[$service]->getClass()
662:             ) {
663:                 $arguments = $this->autowireArguments($class, $entity[1], $arguments);
664:             }
665:         }
666: 
667:         array_walk_recursive($arguments, function (&$val) {
668:             if ($val instanceof Statement) {
669:                 $val = $this->completeStatement($val);
670: 
671:             } elseif ($val === $this) {
672:                 trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
673:                 $val = self::literal('$this');
674: 
675:             } elseif ($val instanceof ServiceDefinition) {
676:                 $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
677: 
678:             } elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
679:                 $pair = explode('::', $val, 2);
680:                 $name = $this->getServiceName($pair[0]);
681:                 if (!isset($pair[1])) { // @service
682:                     $val = '@' . $name;
683:                 } elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
684:                     $val = self::literal($this->getDefinition($name)->getClass() . '::' . $pair[1]);
685:                 } else { // @service::property
686:                     $val = new Statement(['@' . $name, '$' . $pair[1]]);
687:                 }
688:             }
689:         });
690: 
691:         return new Statement($entity, $arguments);
692:     }
693: 
694: 
695:     private function checkCase($class)
696:     {
697:         if ((class_exists($class) || interface_exists($class)) && $class !== ($name = (new ReflectionClass($class))->getName())) {
698:             throw new ServiceCreationException("Case mismatch on class name '$class', correct name is '$name'.");
699:         }
700:     }
701: 
702: 
703:     /**
704:      * Adds item to the list of dependencies.
705:      * @param  ReflectionClass|\ReflectionFunctionAbstract|string
706:      * @return static
707:      * @internal
708:      */
709:     public function addDependency($dep)
710:     {
711:         $this->dependencies[] = $dep;
712:         return $this;
713:     }
714: 
715: 
716:     /**
717:      * Returns the list of dependencies.
718:      * @return array
719:      */
720:     public function getDependencies()
721:     {
722:         return $this->dependencies;
723:     }
724: 
725: 
726:     /**
727:      * Expands %placeholders% in strings.
728:      * @return mixed
729:      * @deprecated
730:      */
731:     public function expand($value)
732:     {
733:         return Helpers::expand($value, $this->parameters);
734:     }
735: 
736: 
737:     /**
738:      * @return Nette\PhpGenerator\PhpLiteral
739:      */
740:     public static function literal($code, array $args = NULL)
741:     {
742:         return new Nette\PhpGenerator\PhpLiteral($args === NULL ? $code : PhpHelpers::formatArgs($code, $args));
743:     }
744: 
745: 
746:     /** @internal */
747:     public function normalizeEntity($entity)
748:     {
749:         if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
750:             $entity = explode('::', $entity);
751:         }
752: 
753:         if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
754:             $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], TRUE));
755: 
756:         } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
757:             $entity = '@' . current(array_keys($this->definitions, $entity, TRUE));
758: 
759:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
760:             trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED);
761:             $entity[0] = '@' . self::THIS_CONTAINER;
762:         }
763:         return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc], Statement
764:     }
765: 
766: 
767:     /**
768:      * Converts @service or @\Class -> service name and checks its existence.
769:      * @return string  of FALSE, if argument is not service name
770:      * @internal
771:      */
772:     public function getServiceName($arg)
773:     {
774:         if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) {
775:             return FALSE;
776:         }
777:         $service = substr($arg, 1);
778:         if ($service === self::THIS_SERVICE) {
779:             $service = $this->currentService;
780:         }
781:         if (Strings::contains($service, '\\')) {
782:             if ($this->classList === FALSE) { // may be disabled by prepareClassList
783:                 return $service;
784:             }
785:             $res = $this->getByType($service);
786:             if (!$res) {
787:                 throw new ServiceCreationException("Reference to missing service of type $service.");
788:             }
789:             return $res;
790:         }
791:         $service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service;
792:         if (!isset($this->definitions[$service])) {
793:             throw new ServiceCreationException("Reference to missing service '$service'.");
794:         }
795:         return $service;
796:     }
797: 
798: 
799:     /**
800:      * Creates a list of arguments using autowiring.
801:      * @return array
802:      * @internal
803:      */
804:     public function autowireArguments($class, $method, array $arguments)
805:     {
806:         $rc = new ReflectionClass($class);
807:         if (!$rc->hasMethod($method)) {
808:             if (!Nette\Utils\Arrays::isList($arguments)) {
809:                 throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
810:             }
811:             return $arguments;
812:         }
813: 
814:         $rm = $rc->getMethod($method);
815:         if (!$rm->isPublic()) {
816:             throw new ServiceCreationException("$class::$method() is not callable.");
817:         }
818:         $this->addDependency($rm);
819:         return Helpers::autowireArguments($rm, $arguments, $this);
820:     }
821: 
822: 
823:     /** @deprecated */
824:     public function generateClasses($className = 'Container', $parentName = NULL)
825:     {
826:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
827:         return (new PhpGenerator($this))->generate($className);
828:     }
829: 
830: 
831:     /** @deprecated */
832:     public function formatStatement(Statement $statement)
833:     {
834:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
835:         return (new PhpGenerator($this))->formatStatement($statement);
836:     }
837: 
838: 
839:     /** @deprecated */
840:     public function formatPhp($statement, $args)
841:     {
842:         array_walk_recursive($args, function (&$val) {
843:             if ($val instanceof Statement) {
844:                 $val = $this->completeStatement($val);
845: 
846:             } elseif ($val === $this) {
847:                 trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
848:                 $val = self::literal('$this');
849: 
850:             } elseif ($val instanceof ServiceDefinition) {
851:                 $val = '@' . current(array_keys($this->getDefinitions(), $val, TRUE));
852:             }
853:         });
854:         return (new PhpGenerator($this))->formatPhp($statement, $args);
855:     }
856: 
857: }
858: 
Nette 2.4-20170119 API API documentation generated by ApiGen 2.8.0