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
  • Other releases
  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\ObjectMixin::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:                 $message = "Service '$name' (type of {$def->getType()}): " . $e->getMessage();
592:                 throw $e instanceof ServiceCreationException
593:                     ? $e->setMessage($message)
594:                     : new ServiceCreationException($message, 0, $e);
595: 
596:             } finally {
597:                 $this->currentService = null;
598:             }
599:         }
600:     }
601: 
602: 
603:     /**
604:      * @return Statement
605:      */
606:     public function completeStatement(Statement $statement)
607:     {
608:         $entity = $this->normalizeEntity($statement->getEntity());
609:         $arguments = $statement->arguments;
610: 
611:         if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
612: 
613:         } elseif ($service = $this->getServiceName($entity)) { // factory calling
614:             $params = [];
615:             foreach ($this->definitions[$service]->parameters as $k => $v) {
616:                 $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
617:             }
618:             $rm = new \ReflectionFunction(eval('return function(' . implode(', ', $params) . ') {};'));
619:             $arguments = Helpers::autowireArguments($rm, $arguments, $this);
620:             $entity = '@' . $service;
621: 
622:         } elseif ($entity === 'not') { // operator
623: 
624:         } elseif (is_string($entity)) { // class name
625:             if (!class_exists($entity)) {
626:                 throw new ServiceCreationException("Class $entity not found.");
627:             } elseif ((new ReflectionClass($entity))->isAbstract()) {
628:                 throw new ServiceCreationException("Class $entity is abstract.");
629:             } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
630:                 $visibility = $rm->isProtected() ? 'protected' : 'private';
631:                 throw new ServiceCreationException("Class $entity has $visibility constructor.");
632:             } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
633:                 $this->addDependency($constructor);
634:                 $arguments = Helpers::autowireArguments($constructor, $arguments, $this);
635:             } elseif ($arguments) {
636:                 throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
637:             }
638: 
639:         } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
640:             throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
641: 
642:         } elseif (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?\z#', $entity[1])) {
643:             throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
644: 
645:         } elseif ($entity[0] === '') { // globalFunc
646:             if (!Nette\Utils\Arrays::isList($arguments)) {
647:                 throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
648:             } elseif (!function_exists($entity[1])) {
649:                 throw new ServiceCreationException("Function $entity[1] doesn't exist.");
650:             }
651: 
652:             $rf = new \ReflectionFunction($entity[1]);
653:             $this->addDependency($rf);
654:             $arguments = Helpers::autowireArguments($rf, $arguments, $this);
655: 
656:         } else {
657:             if ($entity[0] instanceof Statement) {
658:                 $entity[0] = $this->completeStatement($entity[0]);
659:             } elseif ($service = $this->getServiceName($entity[0])) { // service method
660:                 $entity[0] = '@' . $service;
661:             }
662: 
663:             if ($entity[1][0] === '$') { // property getter, setter or appender
664:                 Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
665:                 if (!$arguments && substr($entity[1], -2) === '[]') {
666:                     throw new ServiceCreationException("Missing argument for $entity[1].");
667:                 }
668:             } elseif (
669:                 $type = empty($service) || $entity[1] === 'create'
670:                     ? $this->resolveEntityType($entity[0])
671:                     : $this->definitions[$service]->getType()
672:             ) {
673:                 $arguments = $this->autowireArguments($type, $entity[1], $arguments);
674:             }
675:         }
676: 
677:         try {
678:             array_walk_recursive($arguments, function (&$val) {
679:                 if ($val instanceof Statement) {
680:                     $val = $this->completeStatement($val);
681: 
682:                 } elseif ($val === $this) {
683:                     trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
684:                     $val = self::literal('$this');
685: 
686:                 } elseif ($val instanceof ServiceDefinition) {
687:                     $val = '@' . current(array_keys($this->getDefinitions(), $val, true));
688: 
689:                 } elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
690:                     $pair = explode('::', $val, 2);
691:                     $name = $this->getServiceName($pair[0]);
692:                     if (!isset($pair[1])) { // @service
693:                         $val = '@' . $name;
694:                     } elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
695:                         $val = self::literal($this->getDefinition($name)->getType() . '::' . $pair[1]);
696:                     } else { // @service::property
697:                         $val = new Statement(['@' . $name, '$' . $pair[1]]);
698:                     }
699:                 }
700:             });
701: 
702:         } catch (ServiceCreationException $e) {
703:             if ((is_string($entity) || is_array($entity)) && !strpos($e->getMessage(), ' (used in')) {
704:                 $desc = is_string($entity)
705:                     ? $entity . '::__construct'
706:                     : (is_string($entity[0]) ? ($entity[0] . '::') : 'method ') . $entity[1];
707:                 $e->setMessage($e->getMessage() . " (used in $desc)");
708:             }
709:             throw $e;
710:         }
711: 
712:         return new Statement($entity, $arguments);
713:     }
714: 
715: 
716:     /**
717:      * Adds item to the list of dependencies.
718:      * @param  ReflectionClass|\ReflectionFunctionAbstract|string
719:      * @return static
720:      * @internal
721:      */
722:     public function addDependency($dep)
723:     {
724:         $this->dependencies[] = $dep;
725:         return $this;
726:     }
727: 
728: 
729:     /**
730:      * Returns the list of dependencies.
731:      * @return array
732:      */
733:     public function getDependencies()
734:     {
735:         return $this->dependencies;
736:     }
737: 
738: 
739:     /**
740:      * Expands %placeholders% in strings.
741:      * @return mixed
742:      * @deprecated
743:      */
744:     public function expand($value)
745:     {
746:         return Helpers::expand($value, $this->parameters);
747:     }
748: 
749: 
750:     /**
751:      * @return Nette\PhpGenerator\PhpLiteral
752:      */
753:     public static function literal($code, array $args = null)
754:     {
755:         return new Nette\PhpGenerator\PhpLiteral($args === null ? $code : PhpHelpers::formatArgs($code, $args));
756:     }
757: 
758: 
759:     /**
760:      * @return string|array  Class, @service, [Class, member], [@service, member], [, globalFunc], [Statement, member]
761:      * @internal
762:      */
763:     public function normalizeEntity($entity)
764:     {
765:         if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
766:             $entity = explode('::', $entity);
767:         }
768: 
769:         if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
770:             $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], true));
771: 
772:         } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
773:             $entity = '@' . current(array_keys($this->definitions, $entity, true));
774: 
775:         } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
776:             trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED);
777:             $entity[0] = '@' . self::THIS_CONTAINER;
778:         }
779:         return $entity;
780:     }
781: 
782: 
783:     /**
784:      * Converts @service or @\Class -> service name and checks its existence.
785:      * @return string  of false, if argument is not service name
786:      * @internal
787:      */
788:     public function getServiceName($arg)
789:     {
790:         if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) {
791:             return false;
792:         }
793:         $service = substr($arg, 1);
794:         if ($service === self::THIS_SERVICE) {
795:             $service = $this->currentService;
796:         }
797:         if (Strings::contains($service, '\\')) {
798:             if ($this->classList === false) { // may be disabled by prepareClassList
799:                 return $service;
800:             }
801:             $res = $this->getByType($service);
802:             if (!$res) {
803:                 throw new ServiceCreationException("Reference to missing service of type $service.");
804:             }
805:             return $res;
806:         }
807:         $service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service;
808:         if (!isset($this->definitions[$service])) {
809:             throw new ServiceCreationException("Reference to missing service '$service'.");
810:         }
811:         return $service;
812:     }
813: 
814: 
815:     /**
816:      * Creates a list of arguments using autowiring.
817:      * @return array
818:      * @internal
819:      */
820:     public function autowireArguments($class, $method, array $arguments)
821:     {
822:         $rc = new ReflectionClass($class);
823:         if (!$rc->hasMethod($method)) {
824:             if (!Nette\Utils\Arrays::isList($arguments)) {
825:                 throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
826:             }
827:             return $arguments;
828:         }
829: 
830:         $rm = $rc->getMethod($method);
831:         if (!$rm->isPublic()) {
832:             throw new ServiceCreationException("$class::$method() is not callable.");
833:         }
834:         $this->addDependency($rm);
835:         return Helpers::autowireArguments($rm, $arguments, $this);
836:     }
837: 
838: 
839:     /** @deprecated */
840:     public function generateClasses($className = 'Container', $parentName = null)
841:     {
842:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
843:         return (new PhpGenerator($this))->generate($className);
844:     }
845: 
846: 
847:     /** @deprecated */
848:     public function formatStatement(Statement $statement)
849:     {
850:         trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
851:         return (new PhpGenerator($this))->formatStatement($statement);
852:     }
853: 
854: 
855:     /** @deprecated */
856:     public function formatPhp($statement, $args)
857:     {
858:         array_walk_recursive($args, function (&$val) {
859:             if ($val instanceof Statement) {
860:                 $val = $this->completeStatement($val);
861: 
862:             } elseif ($val === $this) {
863:                 trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
864:                 $val = self::literal('$this');
865: 
866:             } elseif ($val instanceof ServiceDefinition) {
867:                 $val = '@' . current(array_keys($this->getDefinitions(), $val, true));
868:             }
869:         });
870:         return (new PhpGenerator($this))->formatPhp($statement, $args);
871:     }
872: }
873: 
Nette 2.4-20180918 API API documentation generated by ApiGen 2.8.0