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

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