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
      • Traits
    • Reflection
    • Security
    • Tokenizer
    • Utils
  • Tracy
    • Bridges
      • Nette
  • none

Classes

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