contributing.md 0000644 00000002504 13657626572 0007621 0 ustar 00 How to contribute & use the issue tracker ========================================= Nette welcomes your contributions. There are several ways to help out: * Create an issue on GitHub, if you have found a bug * Write test cases for open bug issues * Write fixes for open bug/feature issues, preferably with test cases included * Contribute to the [documentation](https://nette.org/en/writing) Issues ------ Please **do not use the issue tracker to ask questions**. We will be happy to help you on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). A good bug report shouldn't leave others needing to chase you up for more information. Please try to be as detailed as possible in your report. **Feature requests** are welcome. But take a moment to find out whether your idea fits with the scope and aims of the project. It's up to *you* to make a strong case to convince the project's developers of the merits of this feature. Contributing ------------ If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. Please do not fix whitespace, format code, or make a purely cosmetic patch. Thanks! :heart: src/DI/CompilerExtension.php 0000644 00000007616 13657626572 0012044 0 ustar 00 initialization = new Nette\PhpGenerator\Closure; $this->compiler = $compiler; $this->name = $name; return $this; } /** * @param array|object $config * @return static */ public function setConfig($config) { if (!is_array($config) && !is_object($config)) { throw new Nette\InvalidArgumentException; } $this->config = $config; return $this; } /** * Returns extension configuration. * @return array|object */ public function getConfig() { return $this->config; } /** * Returns configuration schema. */ public function getConfigSchema(): Nette\Schema\Schema { return is_object($this->config) ? Nette\Schema\Expect::from($this->config) : Nette\Schema\Expect::array(); } /** * Checks whether $config contains only $expected items and returns combined array. * @throws Nette\InvalidStateException * @deprecated use getConfigSchema() */ public function validateConfig(array $expected, array $config = null, string $name = null): array { if (func_num_args() === 1) { return $this->config = $this->validateConfig($expected, $this->config); } if ($extra = array_diff_key((array) $config, $expected)) { $name = $name ? str_replace('.', ' › ', $name) : $this->name; $hint = Nette\Utils\Helpers::getSuggestion(array_keys($expected), key($extra)); $extra = $hint ? key($extra) : implode("', '{$name} › ", array_keys($extra)); throw new Nette\DI\InvalidConfigurationException("Unknown configuration option '{$name} › {$extra}'" . ($hint ? ", did you mean '{$name} › {$hint}'?" : '.')); } return Nette\Schema\Helpers::merge($config, $expected); } public function getContainerBuilder(): ContainerBuilder { return $this->compiler->getContainerBuilder(); } /** * Reads configuration from file. */ public function loadFromFile(string $file): array { $loader = $this->createLoader(); $res = $loader->load($file); $this->compiler->addDependencies($loader->getDependencies()); return $res; } /** * Loads list of service definitions from configuration. * Prefixes its names and replaces @extension with name in definition. */ public function loadDefinitionsFromConfig(array $configList): void { $res = []; foreach ($configList as $key => $config) { $key = is_string($key) ? $this->name . '.' . $key : $key; $res[$key] = Helpers::prefixServiceName($config, $this->name); } $this->compiler->loadDefinitionsFromConfig($res); } protected function createLoader(): Config\Loader { return new Config\Loader; } public function getInitialization(): Nette\PhpGenerator\Closure { return $this->initialization; } /** * Prepend extension name to identifier or service name. */ public function prefix(string $id): string { return substr_replace($id, $this->name . '.', substr($id, 0, 1) === '@' ? 1 : 0, 0); } /** * Processes configuration data. Intended to be overridden by descendant. * @return void */ public function loadConfiguration() { } /** * Adjusts DI container before is compiled to PHP class. Intended to be overridden by descendant. * @return void */ public function beforeCompile() { } /** * Adjusts DI container compiled to PHP class. Intended to be overridden by descendant. * @return void */ public function afterCompile(Nette\PhpGenerator\ClassType $class) { } } src/DI/Autowiring.php 0000644 00000007620 13657626572 0010520 0 ustar 00 services, used by getByType() */ private $highPriority = []; /** @var array[] type => services, used by findByType() */ private $lowPriority = []; /** @var string[] of classes excluded from autowiring */ private $excludedClasses = []; public function __construct(ContainerBuilder $builder) { $this->builder = $builder; } /** * Resolves service name by type. * @param bool $throw exception if service not found? * @throws MissingServiceException when not found * @throws ServiceCreationException when multiple found */ public function getByType(string $type, bool $throw = false): ?string { $type = Helpers::normalizeClass($type); $types = $this->highPriority; if (empty($types[$type])) { if ($throw) { throw new MissingServiceException("Service of type '$type' not found."); } return null; } elseif (count($types[$type]) === 1) { return $types[$type][0]; } else { $list = $types[$type]; natsort($list); $hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.')) ? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.' : ''; throw new ServiceCreationException("Multiple services of type $type found: " . implode(', ', $list) . $hint); } } /** * Gets the service names and definitions of the specified type. * @return Definitions\Definition[] service name is key */ public function findByType(string $type): array { $type = Helpers::normalizeClass($type); $definitions = $this->builder->getDefinitions(); $names = array_merge($this->highPriority[$type] ?? [], $this->lowPriority[$type] ?? []); $res = []; foreach ($names as $name) { $res[$name] = $definitions[$name]; } return $res; } /** * @param string[] $types */ public function addExcludedClasses(array $types): void { foreach ($types as $type) { if (class_exists($type) || interface_exists($type)) { $type = Helpers::normalizeClass($type); $this->excludedClasses += class_parents($type) + class_implements($type) + [$type => $type]; } } } public function getClassList(): array { return [$this->lowPriority, $this->highPriority]; } public function rebuild(): void { $this->lowPriority = $this->highPriority = $preferred = []; foreach ($this->builder->getDefinitions() as $name => $def) { if (!($type = $def->getType())) { continue; } $autowired = $def->getAutowired(); if (is_array($autowired)) { foreach ($autowired as $k => $autowiredType) { if ($autowiredType === ContainerBuilder::THIS_SERVICE) { $autowired[$k] = $type; } elseif (!is_a($type, $autowiredType, true)) { throw new ServiceCreationException("Incompatible class $autowiredType in autowiring definition of service '$name'."); } } } foreach (class_parents($type) + class_implements($type) + [$type] as $parent) { if (!$autowired || isset($this->excludedClasses[$parent])) { continue; } elseif (is_array($autowired)) { $priority = false; foreach ($autowired as $autowiredType) { if (is_a($parent, $autowiredType, true)) { if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) { $this->lowPriority[$parent] = array_merge($this->lowPriority[$parent] ?? [], $this->highPriority[$parent]); $this->highPriority[$parent] = []; } $preferred[$parent] = $priority = true; break; } } } else { $priority = empty($preferred[$parent]); } $list = $priority ? 'highPriority' : 'lowPriority'; $this->$list[$parent][] = $name; } } } } src/DI/Definitions/Reference.php 0000644 00000001765 13657626572 0012545 0 ustar 00 value = $value; } public function getValue(): string { return $this->value; } public function isName(): bool { return strpos($this->value, '\\') === false && $this->value !== self::SELF; } public function isType(): bool { return strpos($this->value, '\\') !== false; } public function isSelf(): bool { return $this->value === self::SELF; } } src/DI/Definitions/ImportedDefinition.php 0000644 00000002001 13657626572 0014423 0 ustar 00 setReturnType('void') ->setBody( 'throw new Nette\\DI\\ServiceCreationException(?);', ["Unable to create imported service '{$this->getName()}', it must be added using addService()"] ); } /** @deprecated use '$def instanceof ImportedDefinition' */ public function isDynamic(): bool { return true; } } src/DI/Definitions/FactoryDefinition.php 0000644 00000021023 13657626572 0014254 0 ustar 00 resultDefinition = new ServiceDefinition; } /** @return static */ public function setImplement(string $type) { if (!interface_exists($type)) { throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found."); } $rc = new \ReflectionClass($type); $method = $rc->getMethods()[0] ?? null; if (!$method || $method->isStatic() || $method->name !== self::METHOD_CREATE || count($rc->getMethods()) > 1) { throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method create()."); } return parent::setType($type); } public function getImplement(): ?string { return $this->getType(); } final public function getResultType(): ?string { return $this->resultDefinition->getType(); } /** @return static */ public function setResultDefinition(Definition $definition) { $this->resultDefinition = $definition; return $this; } /** @return ServiceDefinition */ public function getResultDefinition(): Definition { return $this->resultDefinition; } /** * @deprecated use ->getResultDefinition()->setFactory() * @return static */ public function setFactory($factory, array $args = []) { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED); $this->resultDefinition->setFactory($factory, $args); return $this; } /** @deprecated use ->getResultDefinition()->getFactory() */ public function getFactory(): ?Statement { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED); return $this->resultDefinition->getFactory(); } /** * @deprecated use ->getResultDefinition()->getEntity() * @return mixed */ public function getEntity() { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getEntity()', $this->getName(), __METHOD__), E_USER_DEPRECATED); return $this->resultDefinition->getEntity(); } /** * @deprecated use ->getResultDefinition()->setArguments() * @return static */ public function setArguments(array $args = []) { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setArguments()', $this->getName(), __METHOD__), E_USER_DEPRECATED); $this->resultDefinition->setArguments($args); return $this; } /** * @deprecated use ->getResultDefinition()->setSetup() * @return static */ public function setSetup(array $setup) { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED); $this->resultDefinition->setSetup($setup); return $this; } /** @deprecated use ->getResultDefinition()->getSetup() */ public function getSetup(): array { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED); return $this->resultDefinition->getSetup(); } /** * @deprecated use ->getResultDefinition()->addSetup() * @return static */ public function addSetup($entity, array $args = []) { trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->addSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED); $this->resultDefinition->addSetup($entity, $args); return $this; } /** @return static */ public function setParameters(array $params) { $this->parameters = $params; return $this; } public function getParameters(): array { return $this->parameters; } public function resolveType(Nette\DI\Resolver $resolver): void { $resultDef = $this->resultDefinition; try { $resolver->resolveDefinition($resultDef); return; } catch (ServiceCreationException $e) { } if (!$resultDef->getType()) { $interface = $this->getType(); if (!$interface) { throw new ServiceCreationException('Type is missing in definition of service.'); } $method = new \ReflectionMethod($interface, self::METHOD_CREATE); $returnType = Nette\DI\Helpers::getReturnType($method); if (!$returnType) { throw new ServiceCreationException("Method $interface::create() has not return type hint or annotation @return."); } elseif (!class_exists($returnType) && !interface_exists($returnType)) { throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::create() method, class '$returnType' cannot be found."); } $resultDef->setType($returnType); } $resolver->resolveDefinition($resultDef); } public function complete(Nette\DI\Resolver $resolver): void { $resultDef = $this->resultDefinition; if ($resultDef instanceof ServiceDefinition) { if (!$this->parameters) { $this->completeParameters($resolver); } if ($resultDef->getEntity() instanceof Reference && !$resultDef->getFactory()->arguments) { $resultDef->setFactory([ // render as $container->createMethod() new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER), Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()), ]); } } $resolver->completeDefinition($resultDef); } private function completeParameters(Nette\DI\Resolver $resolver): void { $interface = $this->getType(); $method = new \ReflectionMethod($interface, self::METHOD_CREATE); $ctorParams = []; if ( ($class = $resolver->resolveEntityType($this->resultDefinition->getFactory())) && ($ctor = (new \ReflectionClass($class))->getConstructor()) ) { foreach ($ctor->getParameters() as $param) { $ctorParams[$param->name] = $param; } } foreach ($method->getParameters() as $param) { $hint = Reflection::getParameterType($param); if (isset($ctorParams[$param->name])) { $arg = $ctorParams[$param->name]; $argHint = Reflection::getParameterType($arg); if ($hint !== $argHint && !is_a($hint, (string) $argHint, true)) { throw new ServiceCreationException("Type hint for \${$param->name} in $interface::create() doesn't match type hint in $class constructor."); } $this->resultDefinition->getFactory()->arguments[$arg->getPosition()] = Nette\DI\ContainerBuilder::literal('$' . $arg->name); } elseif (!$this->resultDefinition->getSetup()) { $hint = Nette\Utils\Helpers::getSuggestion(array_keys($ctorParams), $param->name); throw new ServiceCreationException("Unused parameter \${$param->name} when implementing method $interface::create()" . ($hint ? ", did you mean \${$hint}?" : '.')); } $nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null); $paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->name; if ($param->isDefaultValueAvailable()) { $this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param); } else { $this->parameters[] = $paramDef; } } } public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void { $class = (new Nette\PhpGenerator\ClassType) ->addImplement($this->getType()); $class->addProperty('container') ->setPrivate(); $class->addMethod('__construct') ->addBody('$this->container = $container;') ->addParameter('container') ->setType($generator->getClassName()); $methodCreate = $class->addMethod(self::METHOD_CREATE); $this->resultDefinition->generateMethod($methodCreate, $generator); $body = $methodCreate->getBody(); $body = str_replace('$this', '$this->container', $body); $body = str_replace('$this->container->container', '$this->container', $body); $rm = new \ReflectionMethod($this->getType(), self::METHOD_CREATE); $methodCreate ->setParameters($generator->convertParameters($this->parameters)) ->setReturnType(Reflection::getReturnType($rm) ?: $this->getResultType()) ->setBody($body); $method->setBody('return new class ($this) ' . $class . ';'); } public function __clone() { parent::__clone(); $this->resultDefinition = unserialize(serialize($this->resultDefinition)); } } src/DI/Definitions/AccessorDefinition.php 0000644 00000006300 13657626572 0014410 0 ustar 00 getName()}': Interface '$type' not found."); } $rc = new \ReflectionClass($type); $method = $rc->getMethods()[0] ?? null; if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_GET || count($rc->getMethods()) > 1) { throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method get()."); } elseif ($method->getNumberOfParameters()) { throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Method $type::get() must have no parameters."); } return parent::setType($type); } public function getImplement(): ?string { return $this->getType(); } /** * @param string|Reference $reference * @return static */ public function setReference($reference) { if ($reference instanceof Reference) { $this->reference = $reference; } else { $this->reference = substr($reference, 0, 1) === '@' ? new Reference(substr($reference, 1)) : Reference::fromType($reference); } return $this; } public function getReference(): ?Reference { return $this->reference; } public function resolveType(Nette\DI\Resolver $resolver): void { } public function complete(Nette\DI\Resolver $resolver): void { if (!$this->reference) { $interface = $this->getType(); $method = new \ReflectionMethod($interface, self::METHOD_GET); $returnType = Nette\DI\Helpers::getReturnType($method); if (!$returnType) { throw new ServiceCreationException("Method $interface::get() has not return type hint or annotation @return."); } elseif (!class_exists($returnType) && !interface_exists($returnType)) { throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::get() method, class '$returnType' cannot be found."); } $this->setReference($returnType); } $this->reference = $resolver->normalizeReference($this->reference); } public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void { $class = (new Nette\PhpGenerator\ClassType) ->addImplement($this->getType()); $class->addProperty('container') ->setPrivate(); $class->addMethod('__construct') ->addBody('$this->container = $container;') ->addParameter('container') ->setType($generator->getClassName()); $rm = new \ReflectionMethod($this->getType(), self::METHOD_GET); $class->addMethod(self::METHOD_GET) ->setBody('return $this->container->getService(?);', [$this->reference->getValue()]) ->setReturnType(Reflection::getReturnType($rm)); $method->setBody('return new class ($this) ' . $class . ';'); } } src/DI/Definitions/Statement.php 0000644 00000003544 13657626572 0012610 0 ustar 00 entity = $entity; $this->arguments = $arguments; } /** @return string|array|Definition|Reference|null */ public function getEntity() { return $this->entity; } } class_exists(Nette\DI\Statement::class); src/DI/Definitions/LocatorDefinition.php 0000644 00000010615 13657626572 0014255 0 ustar 00 getName(), $type)); } $methods = (new \ReflectionClass($type))->getMethods(); if (!$methods) { throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $type)); } foreach ($methods as $method) { if ($method->isStatic() || !( (preg_match('#^(get|create)$#', $method->name) && $method->getNumberOfParameters() === 1) || (preg_match('#^(get|create)[A-Z]#', $method->name) && $method->getNumberOfParameters() === 0) )) { throw new Nette\InvalidArgumentException(sprintf( "Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.", $this->getName(), $type, $method->name )); } } return parent::setType($type); } public function getImplement(): ?string { return $this->getType(); } /** @return static */ public function setReferences(array $references) { $this->references = []; foreach ($references as $name => $ref) { $this->references[$name] = substr($ref, 0, 1) === '@' ? new Reference(substr($ref, 1)) : Reference::fromType($ref); } return $this; } /** @return Reference[] */ public function getReferences(): array { return $this->references; } /** @return static */ public function setTagged(?string $tagged) { $this->tagged = $tagged; return $this; } public function getTagged(): ?string { return $this->tagged; } public function resolveType(Nette\DI\Resolver $resolver): void { } public function complete(Nette\DI\Resolver $resolver): void { if ($this->tagged !== null) { $this->references = []; foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) { if (isset($this->references[$tag])) { trigger_error("Service '{$this->getName()}': duplicated tag '$this->tagged' with value '$tag'.", E_USER_NOTICE); } $this->references[$tag] = new Reference($name); } } foreach ($this->references as $name => $ref) { $this->references[$name] = $resolver->normalizeReference($ref); } } public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void { $class = (new Nette\PhpGenerator\ClassType) ->addImplement($this->getType()); $class->addProperty('container') ->setPrivate(); $class->addMethod('__construct') ->addBody('$this->container = $container;') ->addParameter('container') ->setType($generator->getClassName()); foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) { preg_match('#^(get|create)(.*)#', $rm->name, $m); $name = lcfirst($m[2]); $nullable = $rm->getReturnType()->allowsNull(); $methodInner = $class->addMethod($rm->name) ->setReturnType(Reflection::getReturnType($rm)) ->setReturnNullable($nullable); if (!$name) { $class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references)) ->setPrivate(); $methodInner->setBody('if (!isset($this->mapping[$name])) { ' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . ' } return $this->container->' . $m[1] . 'Service($this->mapping[$name]);') ->addParameter('name'); } elseif (isset($this->references[$name])) { $ref = $this->references[$name]->getValue(); if ($m[1] === 'get') { $methodInner->setBody('return $this->container->getService(?);', [$ref]); } else { $methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]); } } else { $methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");'); } } $method->setBody('return new class ($this) ' . $class . ';'); } } src/DI/Definitions/Definition.php 0000644 00000007347 13657626572 0012741 0 ustar 00 name) { throw new Nette\InvalidStateException('Name already has been set.'); } $this->name = $name; return $this; } final public function getName(): ?string { return $this->name; } /** @return static */ protected function setType(?string $type) { if ($this->autowired && $this->notifier && $this->type !== $type) { ($this->notifier)(); } if ($type === null) { $this->type = null; } elseif (!class_exists($type) && !interface_exists($type)) { throw new Nette\InvalidArgumentException("Service '$this->name': Class or interface '$type' not found."); } else { $this->type = Nette\DI\Helpers::normalizeClass($type); } return $this; } final public function getType(): ?string { return $this->type; } /** @return static */ final public function setTags(array $tags) { $this->tags = $tags; return $this; } final public function getTags(): array { return $this->tags; } /** * @param mixed $attr * @return static */ final public function addTag(string $tag, $attr = true) { $this->tags[$tag] = $attr; return $this; } /** @return mixed */ final public function getTag(string $tag) { return $this->tags[$tag] ?? null; } /** * @param bool|string|string[] $state * @return static */ final public function setAutowired($state = true) { if ($this->notifier && $this->autowired !== $state) { ($this->notifier)(); } $this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state; return $this; } /** @return bool|string[] */ final public function getAutowired() { return $this->autowired; } /** @return static */ public function setExported(bool $state = true) { return $this->addTag('nette.exported', $state); } public function isExported(): bool { return (bool) $this->getTag('nette.exported'); } public function __clone() { $this->notifier = $this->name = null; } /********************* life cycle ****************d*g**/ abstract public function resolveType(Nette\DI\Resolver $resolver): void; abstract public function complete(Nette\DI\Resolver $resolver): void; abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void; final public function setNotifier(?callable $notifier): void { $this->notifier = $notifier; } /********************* deprecated stuff from former ServiceDefinition ****************d*g**/ /** @deprecated Use setType() */ public function setClass(?string $type) { return $this->setType($type); } /** @deprecated Use getType() */ public function getClass(): ?string { return $this->getType(); } /** @deprecated Use '$def instanceof Nette\DI\Definitions\ImportedDefinition' */ public function isDynamic(): bool { return false; } /** @deprecated Use Nette\DI\Definitions\FactoryDefinition or AccessorDefinition */ public function getImplement(): ?string { return null; } /** @deprecated Use getAutowired() */ public function isAutowired() { return $this->autowired; } } src/DI/Definitions/ServiceDefinition.php 0000644 00000015006 13657626572 0014251 0 ustar 00 factory = new Statement(null); } /** @deprecated Use setType() */ public function setClass(?string $type) { $this->setType($type); if (func_num_args() > 1) { trigger_error(sprintf('Service %s: %s() second parameter $args is deprecated, use setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED); if ($args = func_get_arg(1)) { $this->setFactory($type, $args); } } return $this; } /** @return static */ public function setType(?string $type) { return parent::setType($type); } /** * @param string|array|Definition|Reference|Statement $factory * @return static */ public function setFactory($factory, array $args = []) { $this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args); return $this; } public function getFactory(): Statement { return $this->factory; } /** @return string|array|Definition|Reference|null */ public function getEntity() { return $this->factory->getEntity(); } /** @return static */ public function setArguments(array $args = []) { $this->factory->arguments = $args; return $this; } /** @return static */ public function setArgument($key, $value) { $this->factory->arguments[$key] = $value; return $this; } /** * @param Statement[] $setup * @return static */ public function setSetup(array $setup) { foreach ($setup as $v) { if (!$v instanceof Statement) { throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Definitions\Statement[].'); } } $this->setup = $setup; return $this; } /** @return Statement[] */ public function getSetup(): array { return $this->setup; } /** * @param string|array|Definition|Reference|Statement $entity * @return static */ public function addSetup($entity, array $args = []) { $this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args); return $this; } /** @deprecated */ public function setParameters(array $params) { throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__)); } /** @deprecated */ public function getParameters(): array { trigger_error(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__), E_USER_DEPRECATED); return []; } /** @deprecated use $builder->addImportedDefinition(...) */ public function setDynamic(): void { throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addImportedDefinition(...)', $this->getName(), __METHOD__)); } /** @deprecated use $builder->addFactoryDefinition(...) or addAccessorDefinition(...) */ public function setImplement(): void { throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addFactoryDefinition(...)', $this->getName(), __METHOD__)); } /** @deprecated use addTag('nette.inject') */ public function setInject(bool $state = true) { trigger_error(sprintf('Service %s: %s() is deprecated, use addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)', $this->getName(), __METHOD__), E_USER_DEPRECATED); return $this->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, $state); } public function resolveType(Nette\DI\Resolver $resolver): void { if (!$this->getEntity()) { if (!$this->getType()) { throw new ServiceCreationException('Factory and type are missing in definition of service.'); } $this->setFactory($this->getType(), $this->factory->arguments ?? []); } elseif (!$this->getType()) { $type = $resolver->resolveEntityType($this->factory); if (!$type) { throw new ServiceCreationException('Unknown service type, specify it or declare return type of factory.'); } $this->setType($type); $resolver->addDependency(new \ReflectionClass($type)); } // auto-disable autowiring for aliases if ($this->getAutowired() === true && $this->getEntity() instanceof Reference) { $this->setAutowired(false); } } public function complete(Nette\DI\Resolver $resolver): void { $entity = $this->factory->getEntity(); if ($entity instanceof Reference && !$this->factory->arguments && !$this->setup) { $ref = $resolver->normalizeReference($entity); $this->setFactory([new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER), 'getService'], [$ref->getValue()]); } $this->factory = $resolver->completeStatement($this->factory); foreach ($this->setup as &$setup) { if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === false) { // auto-prepend @self $setup = new Statement([new Reference(Reference::SELF), $setup->getEntity()], $setup->arguments); } $setup = $resolver->completeStatement($setup, true); } } public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void { $entity = $this->factory->getEntity(); $code = $generator->formatStatement($this->factory) . ";\n"; if (!$this->setup) { $method->setBody('return ' . $code); return; } $code = '$service = ' . $code; $type = $this->getType(); if ( $type !== $entity && !(is_array($entity) && $entity[0] instanceof Reference && $entity[0]->getValue() === Nette\DI\ContainerBuilder::THIS_CONTAINER) && !(is_string($entity) && preg_match('#^[\w\\\\]+$#D', $entity) && is_subclass_of($entity, $type)) ) { $code .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n" . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", ["Unable to create service '{$this->getName()}', value returned by factory is not $type type."] ); } foreach ($this->setup as $setup) { $code .= $generator->formatStatement($setup) . ";\n"; } $code .= 'return $service;'; $method->setBody($code); } public function __clone() { parent::__clone(); $this->factory = unserialize(serialize($this->factory)); $this->setup = unserialize(serialize($this->setup)); } } class_exists(Nette\DI\ServiceDefinition::class); src/DI/Resolver.php 0000644 00000042513 13657626572 0010171 0 ustar 00 builder = $builder; $this->recursive = new \SplObjectStorage; } public function getContainerBuilder(): ContainerBuilder { return $this->builder; } public function resolveDefinition(Definition $def): void { if ($this->recursive->contains($def)) { $names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive)); throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names))); } try { $this->recursive->attach($def); $def->resolveType($this); if (!$def->getType()) { throw new ServiceCreationException('Type of service is unknown.'); } } catch (\Exception $e) { throw $this->completeException($e, $def); } finally { $this->recursive->detach($def); } } public function resolveReferenceType(Reference $ref): ?string { if ($ref->isSelf()) { return $this->currentServiceType; } elseif ($ref->isType()) { return ltrim($ref->getValue(), '\\'); } $def = $this->resolveReference($ref); if (!$def->getType()) { $this->resolveDefinition($def); } return $def->getType(); } public function resolveEntityType(Statement $statement): ?string { $entity = $this->normalizeEntity($statement); if (is_array($entity)) { if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) { $entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0])); if (!$entity[0]) { return null; } } try { /** @var \ReflectionMethod|\ReflectionFunction $reflection */ $reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity); $refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : null; } catch (\ReflectionException $e) { $refClass = $reflection = null; } if (isset($e) || ($refClass && (!$reflection->isPublic() || ($refClass->isTrait() && !$reflection->isStatic()) ))) { throw new ServiceCreationException(sprintf('Method %s() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null); } $this->addDependency($reflection); $type = Helpers::getReturnType($reflection); if ($type && !class_exists($type) && !interface_exists($type)) { throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() correct?", $type, Nette\Utils\Callback::toString($entity))); } return $type; } elseif ($entity instanceof Reference) { // alias or factory return $this->resolveReferenceType($entity); } elseif (is_string($entity)) { // class if (!class_exists($entity)) { throw new ServiceCreationException(interface_exists($entity) ? "Interface $entity can not be used as 'factory', did you mean 'implement'?" : "Class $entity not found." ); } return $entity; } return null; } public function completeDefinition(Definition $def): void { $this->currentService = in_array($def, $this->builder->getDefinitions(), true) ? $def : null; $this->currentServiceType = $def->getType(); $this->currentServiceAllowed = false; try { $def->complete($this); $this->addDependency(new \ReflectionClass($def->getType())); } catch (\Exception $e) { throw $this->completeException($e, $def); } finally { $this->currentService = $this->currentServiceType = null; } } public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement { $this->currentServiceAllowed = $currentServiceAllowed; $entity = $this->normalizeEntity($statement); $arguments = $this->convertReferences($statement->arguments); $getter = function (string $type, bool $single) { return $single ? $this->getByType($type) : array_values(array_filter($this->builder->findAutowired($type), function ($obj) { return $obj !== $this->currentService; })); }; switch (true) { case is_string($entity) && Strings::contains($entity, '?'): // PHP literal break; case $entity === 'not': $entity = ['', '!']; break; case is_string($entity): // create class if (!class_exists($entity)) { throw new ServiceCreationException("Class $entity not found."); } elseif ((new ReflectionClass($entity))->isAbstract()) { throw new ServiceCreationException("Class $entity is abstract."); } elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) { $visibility = $rm->isProtected() ? 'protected' : 'private'; throw new ServiceCreationException("Class $entity has $visibility constructor."); } elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) { $arguments = self::autowireArguments($constructor, $arguments, $getter); $this->addDependency($constructor); } elseif ($arguments) { throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor."); } break; case $entity instanceof Reference: $entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())]; break; case is_array($entity): if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) { throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given."); } switch (true) { case $entity[0] === '': // function call if (!Nette\Utils\Arrays::isList($arguments)) { throw new ServiceCreationException("Unable to pass specified arguments to $entity[0]."); } elseif (!function_exists($entity[1])) { throw new ServiceCreationException("Function $entity[1] doesn't exist."); } $rf = new \ReflectionFunction($entity[1]); $arguments = self::autowireArguments($rf, $arguments, $getter); $this->addDependency($rf); break; case $entity[0] instanceof Statement: $entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed); // break omitted case is_string($entity[0]): // static method call case $entity[0] instanceof Reference: if ($entity[1][0] === '$') { // property getter, setter or appender Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'"); if (!$arguments && substr($entity[1], -2) === '[]') { throw new ServiceCreationException("Missing argument for $entity[1]."); } } elseif ( $type = $entity[0] instanceof Reference ? $this->resolveReferenceType($entity[0]) : $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0])) ) { $rc = new ReflectionClass($type); if ($rc->hasMethod($entity[1])) { $rm = $rc->getMethod($entity[1]); if (!$rm->isPublic()) { throw new ServiceCreationException("$type::$entity[1]() is not callable."); } $arguments = self::autowireArguments($rm, $arguments, $getter); $this->addDependency($rm); } elseif (!Nette\Utils\Arrays::isList($arguments)) { throw new ServiceCreationException("Unable to pass specified arguments to $type::$entity[1]()."); } } } } try { $arguments = $this->completeArguments($arguments); } catch (ServiceCreationException $e) { if (!strpos($e->getMessage(), ' (used in')) { $e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})"); } throw $e; } return new Statement($entity, $arguments); } public function completeArguments(array $arguments): array { array_walk_recursive($arguments, function (&$val): void { if ($val instanceof Statement) { $entity = $val->getEntity(); if ($entity === 'typed' || $entity === 'tagged') { $services = []; $current = $this->currentService ? $this->currentService->getName() : null; foreach ($val->arguments as $argument) { foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) { if ($name !== $current) { $services[] = new Reference($name); } } } $val = $this->completeArguments($services); } else { $val = $this->completeStatement($val, $this->currentServiceAllowed); } } elseif ($val instanceof Definition || $val instanceof Reference) { $val = $this->normalizeEntity(new Statement($val)); } }); return $arguments; } /** @return string|array|Reference literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */ private function normalizeEntity(Statement $statement) { $entity = $statement->getEntity(); if (is_array($entity)) { $item = &$entity[0]; } else { $item = &$entity; } if ($item instanceof Definition) { $name = current(array_keys($this->builder->getDefinitions(), $item, true)); if ($name == false) { throw new ServiceCreationException("Service '{$item->getName()}' not found in definitions."); } $item = new Reference($name); } if ($item instanceof Reference) { $item = $this->normalizeReference($item); } return $entity; } /** * Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service. */ public function normalizeReference(Reference $ref): Reference { $service = $ref->getValue(); if ($ref->isSelf()) { return $ref; } elseif ($ref->isName()) { if (!$this->builder->hasDefinition($service)) { throw new ServiceCreationException("Reference to missing service '$service'."); } return $this->currentService && $service === $this->currentService->getName() ? new Reference(Reference::SELF) : $ref; } try { return $this->getByType($service); } catch (NotAllowedDuringResolvingException $e) { return new Reference($service); } } public function resolveReference(Reference $ref): Definition { return $ref->isSelf() ? $this->currentService : $this->builder->getDefinition($ref->getValue()); } /** * Returns named reference to service resolved by type (or 'self' reference for local-autowiring). * @throws ServiceCreationException when multiple found * @throws MissingServiceException when not found */ public function getByType(string $type): Reference { if ( $this->currentService && $this->currentServiceAllowed && is_a($this->currentServiceType, $type, true) ) { return new Reference(Reference::SELF); } $name = $this->builder->getByType($type, true); if ( !$this->currentServiceAllowed && $this->currentService === $this->builder->getDefinition($name) ) { throw new MissingServiceException; } return new Reference($name); } /** * Adds item to the list of dependencies. * @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep * @return static */ public function addDependency($dep) { $this->builder->addDependency($dep); return $this; } private function completeException(\Exception $e, Definition $def): ServiceCreationException { if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) { return $e; } else { $name = $def->getName(); $type = $def->getType(); if (!$type) { $message = "Service '$name': " . $e->getMessage(); } elseif (!$name || ctype_digit($name)) { $message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage()); } else { $message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage()); } return $e instanceof ServiceCreationException ? $e->setMessage($message) : new ServiceCreationException($message, 0, $e); } } private function entityToString($entity): string { $referenceToText = function (Reference $ref): string { return $ref->isSelf() && $this->currentService ? '@' . $this->currentService->getName() : '@' . $ref->getValue(); }; if (is_string($entity)) { return $entity . '::__construct()'; } elseif ($entity instanceof Reference) { $entity = $referenceToText($entity); } elseif (is_array($entity)) { if (strpos($entity[1], '$') === false) { $entity[1] .= '()'; } if ($entity[0] instanceof Reference) { $entity[0] = $referenceToText($entity[0]); } elseif (!is_string($entity[0])) { return $entity[1]; } return implode('::', $entity); } return (string) $entity; } private function convertReferences(array $arguments): array { array_walk_recursive($arguments, function (&$val): void { if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') { $pair = explode('::', substr($val, 1), 2); if (!isset($pair[1])) { // @service $val = new Reference($pair[0]); } elseif (preg_match('#^[A-Z][A-Z0-9_]*$#D', $pair[1], $m)) { // @service::CONSTANT $val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]); } else { // @service::property $val = new Statement([new Reference($pair[0]), '$' . $pair[1]]); } } elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@ $val = substr($val, 1); } }); return $arguments; } /** * Add missing arguments using autowiring. * @param (callable(string $type, bool $single): object|object[]|null) $getter * @throws ServiceCreationException */ public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, callable $getter): array { $optCount = 0; $num = -1; $res = []; foreach ($method->getParameters() as $num => $param) { $paramName = $param->name; if (!$param->isVariadic() && array_key_exists($paramName, $arguments)) { $res[$num] = $arguments[$paramName]; unset($arguments[$paramName], $arguments[$num]); } elseif (array_key_exists($num, $arguments)) { $res[$num] = $arguments[$num]; unset($arguments[$num]); } else { $res[$num] = self::autowireArgument($param, $getter); } $optCount = $param->isOptional() && $res[$num] === ($param->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($param) : null) ? $optCount + 1 : 0; } // extra parameters while (array_key_exists(++$num, $arguments)) { $res[$num] = $arguments[$num]; unset($arguments[$num]); $optCount = 0; } if ($arguments) { throw new ServiceCreationException('Unable to pass specified arguments to ' . Reflection::toString($method) . '().'); } elseif ($optCount) { $res = array_slice($res, 0, -$optCount); } return $res; } /** * Resolves missing argument using autowiring. * @param (callable(string $type, bool $single): object|object[]|null) $getter * @throws ServiceCreationException * @return mixed */ private static function autowireArgument(\ReflectionParameter $parameter, callable $getter) { $type = Reflection::getParameterType($parameter); $method = $parameter->getDeclaringFunction(); $desc = '$' . $parameter->name . ' in ' . Reflection::toString($method) . '()'; if ($type && !Reflection::isBuiltinType($type)) { try { $res = $getter($type, true); } catch (MissingServiceException $e) { $res = null; } catch (ServiceCreationException $e) { throw new ServiceCreationException("{$e->getMessage()} (needed by $desc)", 0, $e); } if ($res !== null || $parameter->allowsNull()) { return $res; } elseif (class_exists($type) || interface_exists($type)) { throw new ServiceCreationException("Service of type $type needed by $desc not found. Did you register it in configuration file?"); } else { throw new ServiceCreationException("Class $type needed by $desc not found. Check type hint and 'use' statements."); } } elseif ( $method instanceof \ReflectionMethod && $parameter->isArray() && preg_match('#@param[ \t]+([\w\\\\]+)\[\][ \t]+\$' . $parameter->name . '#', (string) $method->getDocComment(), $m) && ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass())) && (class_exists($itemType) || interface_exists($itemType)) ) { return $getter($itemType, false); } elseif (($type && $parameter->allowsNull()) || $parameter->isOptional() || $parameter->isDefaultValueAvailable()) { // !optional + defaultAvailable = func($a = null, $b) since 5.4.7 // optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ... return $parameter->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($parameter) : null; } else { throw new ServiceCreationException("Parameter $desc has no class type hint or default value, so its value must be specified."); } } } src/DI/DynamicParameter.php 0000644 00000000514 13657626572 0011610 0 ustar 00 Expect::list(), 'tags' => Expect::array(), 'inject' => Expect::bool(), ]) ); } public function beforeCompile() { $this->getContainerBuilder()->resolve(); foreach ($this->config as $type => $info) { if ($info->inject !== null) { $info->tags[InjectExtension::TAG_INJECT] = $info->inject; } $this->addSetups($type, Nette\DI\Helpers::filterArguments($info->setup)); $this->addTags($type, Nette\DI\Helpers::filterArguments($info->tags)); } } public function addSetups(string $type, array $setups): void { foreach ($this->findByType($type) as $def) { if ($def instanceof Definitions\FactoryDefinition) { $def = $def->getResultDefinition(); } foreach ($setups as $setup) { if (is_array($setup)) { $setup = new Definitions\Statement(key($setup), array_values($setup)); } $def->addSetup($setup); } } } public function addTags(string $type, array $tags): void { $tags = Nette\Utils\Arrays::normalize($tags, true); foreach ($this->findByType($type) as $def) { $def->setTags($def->getTags() + $tags); } } private function findByType(string $type): array { return array_filter($this->getContainerBuilder()->getDefinitions(), function (Definitions\Definition $def) use ($type): bool { return is_a($def->getType(), $type, true) || ($def instanceof Definitions\FactoryDefinition && is_a($def->getResultType(), $type, true)); }); } } src/DI/Extensions/InjectExtension.php 0000644 00000011362 13657626572 0013636 0 ustar 00 getContainerBuilder()->getDefinitions() as $def) { if ($def->getTag(self::TAG_INJECT)) { $def = $def instanceof Definitions\FactoryDefinition ? $def->getResultDefinition() : $def; if ($def instanceof Definitions\ServiceDefinition) { $this->updateDefinition($def); } } } } private function updateDefinition(Definitions\ServiceDefinition $def): void { $resolvedType = (new DI\Resolver($this->getContainerBuilder()))->resolveEntityType($def->getFactory()); $class = is_subclass_of($resolvedType, $def->getType()) ? $resolvedType : $def->getType(); $setups = $def->getSetup(); foreach (self::getInjectProperties($class) as $property => $type) { $builder = $this->getContainerBuilder(); $inject = new Definitions\Statement('$' . $property, [Definitions\Reference::fromType((string) $type)]); foreach ($setups as $key => $setup) { if ($setup->getEntity() === $inject->getEntity()) { $inject = $setup; $builder = null; unset($setups[$key]); } } self::checkType($class, $property, $type, $builder); array_unshift($setups, $inject); } foreach (array_reverse(self::getInjectMethods($class)) as $method) { $inject = new Definitions\Statement($method); foreach ($setups as $key => $setup) { if ($setup->getEntity() === $inject->getEntity()) { $inject = $setup; unset($setups[$key]); } } array_unshift($setups, $inject); } $def->setSetup($setups); } /** * Generates list of inject methods. * @internal */ public static function getInjectMethods(string $class): array { $classes = []; foreach (get_class_methods($class) as $name) { if (substr($name, 0, 6) === 'inject') { $classes[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->name; } } $methods = array_keys($classes); uksort($classes, function (string $a, string $b) use ($classes, $methods): int { return $classes[$a] === $classes[$b] ? array_search($a, $methods, true) <=> array_search($b, $methods, true) : (is_a($classes[$a], $classes[$b], true) ? 1 : -1); }); return array_keys($classes); } /** * Generates list of properties with annotation @inject. * @internal */ public static function getInjectProperties(string $class): array { $res = []; foreach (get_class_vars($class) as $name => $foo) { $rp = new \ReflectionProperty($class, $name); if (DI\Helpers::parseAnnotation($rp, 'inject') !== null) { if ($type = Reflection::getPropertyType($rp)) { } elseif ($type = DI\Helpers::parseAnnotation($rp, 'var')) { $type = Reflection::expandClassName($type, Reflection::getPropertyDeclaringClass($rp)); } $res[$name] = $type; } } ksort($res); return $res; } /** * Calls all methods starting with with "inject" using autowiring. * @param object $service */ public static function callInjects(DI\Container $container, $service): void { if (!is_object($service)) { throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service))); } foreach (self::getInjectMethods(get_class($service)) as $method) { $container->callMethod([$service, $method]); } foreach (self::getInjectProperties(get_class($service)) as $property => $type) { self::checkType($service, $property, $type, $container); $service->$property = $container->getByType($type); } } /** * @param object|string $class * @param DI\Container|DI\ContainerBuilder|null $container */ private static function checkType($class, string $name, ?string $type, $container): void { $propName = Reflection::toString(new \ReflectionProperty($class, $name)); if (!$type) { throw new Nette\InvalidStateException("Property $propName has no @var annotation."); } elseif (!class_exists($type) && !interface_exists($type)) { throw new Nette\InvalidStateException("Class or interface '$type' used in @var annotation at $propName not found. Check annotation and 'use' statements."); } elseif ($container && !$container->getByType($type, false)) { throw new Nette\DI\MissingServiceException("Service of type $type used in @var annotation at $propName not found. Did you register it in configuration file?"); } } } src/DI/Extensions/SearchExtension.php 0000644 00000010730 13657626572 0013625 0 ustar 00 tempDir = $tempDir; } public function getConfigSchema(): Nette\Schema\Schema { return Expect::arrayOf( Expect::structure([ 'in' => Expect::string()->required(), 'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'exclude' => Expect::structure([ 'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), 'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]), ]), 'tags' => Expect::array(), ]) )->before(function ($val) { return is_string($val['in'] ?? null) ? ['default' => $val] : $val; }); } public function loadConfiguration() { foreach (array_filter($this->config) as $name => $batch) { if (!is_dir($batch->in)) { throw new Nette\DI\InvalidConfigurationException("Option '{$this->name} › {$name} › in' must be valid directory name, '{$batch->in}' given."); } foreach ($this->findClasses($batch) as $class) { $this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags); } } } public function findClasses(\stdClass $config): array { $robot = new RobotLoader; $robot->setTempDirectory($this->tempDir); $robot->addDirectory($config->in); $robot->acceptFiles = $config->files ?: ['*.php']; $robot->reportParseErrors(false); $robot->refresh(); $classes = array_unique(array_keys($robot->getIndexedClasses())); $exclude = $config->exclude; $acceptRE = self::buildNameRegexp($config->classes); $rejectRE = self::buildNameRegexp($exclude->classes); $acceptParent = array_merge($config->extends, $config->implements); $rejectParent = array_merge($exclude->extends, $exclude->implements); $found = []; foreach ($classes as $class) { if (!class_exists($class) && !interface_exists($class) && !trait_exists($class)) { throw new Nette\InvalidStateException("Class $class was found, but it cannot be loaded by autoloading."); } $rc = new \ReflectionClass($class); if ( ($rc->isInstantiable() || ($rc->isInterface() && count($methods = $rc->getMethods()) === 1 && $methods[0]->name === 'create') ) && (!$acceptRE || preg_match($acceptRE, $rc->name)) && (!$rejectRE || !preg_match($rejectRE, $rc->name)) && (!$acceptParent || Arrays::some($acceptParent, function ($nm) use ($rc) { return $rc->isSubclassOf($nm); })) && (!$rejectParent || Arrays::every($rejectParent, function ($nm) use ($rc) { return !$rc->isSubclassOf($nm); })) ) { $found[] = $rc->name; } } return $found; } public function beforeCompile() { $builder = $this->getContainerBuilder(); foreach ($this->classes as $class => $foo) { if ($builder->findByType($class)) { unset($this->classes[$class]); } } foreach ($this->classes as $class => $tags) { if (class_exists($class)) { $def = $builder->addDefinition(null) ->setType($class); } else { $def = $builder->addFactoryDefinition(null) ->setImplement($class); } $def->setTags(Arrays::normalize($tags, true)); } } private static function buildNameRegexp(array $masks): ?string { $res = []; foreach ((array) $masks as $mask) { $mask = (strpos($mask, '\\') === false ? '**\\' : '') . $mask; $mask = preg_quote($mask, '#'); $mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask); $mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask); $mask = str_replace('\*', '\w*', $mask); $res[] = $mask; } return $res ? '#^(' . implode('|', $res) . ')$#i' : null; } } src/DI/Extensions/ConstantsExtension.php 0000644 00000000741 13657626572 0014375 0 ustar 00 getConfig() as $name => $value) { $this->initialization->addBody('define(?, ?);', [$name, $value]); } } } src/DI/Extensions/ServicesExtension.php 0000644 00000014460 13657626572 0014207 0 ustar 00 getContainerBuilder())); } public function loadConfiguration() { $this->loadDefinitions($this->config); } /** * Loads list of service definitions. */ public function loadDefinitions(array $config) { foreach ($config as $key => $defConfig) { $this->loadDefinition($this->convertKeyToName($key), $defConfig); } } /** * Loads service definition from normalized configuration. */ private function loadDefinition(?string $name, \stdClass $config): void { try { if ((array) $config === [false]) { $this->getContainerBuilder()->removeDefinition($name); return; } elseif (!empty($config->alteration) && !$this->getContainerBuilder()->hasDefinition($name)) { throw new Nette\DI\InvalidConfigurationException('missing original definition for alteration.'); } $def = $this->retrieveDefinition($name, $config); static $methods = [ Definitions\ServiceDefinition::class => 'updateServiceDefinition', Definitions\AccessorDefinition::class => 'updateAccessorDefinition', Definitions\FactoryDefinition::class => 'updateFactoryDefinition', Definitions\LocatorDefinition::class => 'updateLocatorDefinition', Definitions\ImportedDefinition::class => 'updateImportedDefinition', ]; $this->{$methods[$config->defType]}($def, $config); $this->updateDefinition($def, $config); } catch (\Exception $e) { throw new Nette\DI\InvalidConfigurationException(($name ? "Service '$name': " : '') . $e->getMessage(), 0, $e); } } /** * Updates service definition according to normalized configuration. */ private function updateServiceDefinition(Definitions\ServiceDefinition $definition, \stdClass $config): void { if ($config->factory) { $definition->setFactory(Helpers::filterArguments([$config->factory])[0]); $definition->setType(null); } if ($config->type) { $definition->setType($config->type); } if ($config->arguments) { $arguments = Helpers::filterArguments($config->arguments); if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) { $arguments += $definition->getFactory()->arguments; } $definition->setArguments($arguments); } if (isset($config->setup)) { if (!empty($config->reset['setup'])) { $definition->setSetup([]); } foreach (Helpers::filterArguments($config->setup) as $id => $setup) { if (is_array($setup)) { $setup = new Statement(key($setup), array_values($setup)); } $definition->addSetup($setup); } } if (isset($config->inject)) { $definition->addTag(InjectExtension::TAG_INJECT, $config->inject); } } private function updateAccessorDefinition(Definitions\AccessorDefinition $definition, \stdClass $config): void { if (isset($config->implement)) { $definition->setImplement($config->implement); } if ($ref = $config->factory ?? $config->type ?? null) { $definition->setReference($ref); } } private function updateFactoryDefinition(Definitions\FactoryDefinition $definition, \stdClass $config): void { $resultDef = $definition->getResultDefinition(); if (isset($config->implement)) { $definition->setImplement($config->implement); $definition->setAutowired(true); } if ($config->factory) { $resultDef->setFactory(Helpers::filterArguments([$config->factory])[0]); } if ($config->type) { $resultDef->setFactory($config->type); } if ($config->arguments) { $arguments = Helpers::filterArguments($config->arguments); if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) { $arguments += $resultDef->getFactory()->arguments; } $resultDef->setArguments($arguments); } if (isset($config->setup)) { if (!empty($config->reset['setup'])) { $resultDef->setSetup([]); } foreach (Helpers::filterArguments($config->setup) as $id => $setup) { if (is_array($setup)) { $setup = new Statement(key($setup), array_values($setup)); } $resultDef->addSetup($setup); } } if (isset($config->parameters)) { $definition->setParameters($config->parameters); } if (isset($config->inject)) { $definition->addTag(InjectExtension::TAG_INJECT, $config->inject); } } private function updateLocatorDefinition(Definitions\LocatorDefinition $definition, \stdClass $config): void { if (isset($config->implement)) { $definition->setImplement($config->implement); } if (isset($config->references)) { $definition->setReferences($config->references); } if (isset($config->tagged)) { $definition->setTagged($config->tagged); } } private function updateImportedDefinition(Definitions\ImportedDefinition $definition, \stdClass $config): void { if ($config->type) { $definition->setType($config->type); } } private function updateDefinition(Definitions\Definition $definition, \stdClass $config): void { if (isset($config->autowired)) { $definition->setAutowired($config->autowired); } if (isset($config->tags)) { if (!empty($config->reset['tags'])) { $definition->setTags([]); } foreach ($config->tags as $tag => $attrs) { if (is_int($tag) && is_string($attrs)) { $definition->addTag($attrs); } else { $definition->addTag($tag, $attrs); } } } } private function convertKeyToName($key): ?string { if (is_int($key)) { return null; } elseif (preg_match('#^@[\w\\\\]+$#D', $key)) { return $this->getContainerBuilder()->getByType(substr($key, 1), true); } return $key; } private function retrieveDefinition(?string $name, \stdClass $config): Definitions\Definition { $builder = $this->getContainerBuilder(); if (!empty($config->reset['all'])) { $builder->removeDefinition($name); } return $name && $builder->hasDefinition($name) ? $builder->getDefinition($name) : $builder->addDefinition($name, new $config->defType); } } src/DI/Extensions/ExtensionsExtension.php 0000644 00000002076 13657626572 0014563 0 ustar 00 getConfig() as $name => $class) { if (is_int($name)) { $name = null; } $args = []; if ($class instanceof Nette\DI\Definitions\Statement) { [$class, $args] = [$class->getEntity(), $class->arguments]; } if (!is_a($class, Nette\DI\CompilerExtension::class, true)) { throw new Nette\DI\InvalidConfigurationException("Extension '$class' not found or is not Nette\\DI\\CompilerExtension descendant."); } $this->compiler->addExtension($name, (new \ReflectionClass($class))->newInstanceArgs($args)); } } } src/DI/Extensions/DIExtension.php 0000644 00000006122 13657626572 0012714 0 ustar 00 debugMode = $debugMode; $this->time = microtime(true); $this->config = new class { /** @var bool */ public $debugger; /** @var string[] */ public $excluded = []; /** @var ?string */ public $parentClass; /** @var object */ public $export; }; $this->config->export = new class { /** @var bool */ public $parameters = true; /** @var string[]|bool|null */ public $tags = true; /** @var string[]|bool|null */ public $types = true; }; $this->config->debugger = interface_exists(\Tracy\IBarPanel::class); } public function loadConfiguration() { $builder = $this->getContainerBuilder(); $builder->addExcludedClasses($this->config->excluded); } public function beforeCompile() { if (!$this->config->export->parameters) { $this->getContainerBuilder()->parameters = []; } } public function afterCompile(Nette\PhpGenerator\ClassType $class) { if ($this->config->parentClass) { $class->setExtends($this->config->parentClass); } $this->restrictTags($class); $this->restrictTypes($class); if ($this->debugMode && $this->config->debugger) { $this->enableTracyIntegration(); } $this->initializeTaggedServices(); } private function restrictTags(Nette\PhpGenerator\ClassType $class): void { $option = $this->config->export->tags; if ($option === true) { } elseif ($option === false) { $class->removeProperty('tags'); } elseif ($prop = $class->getProperties()['tags'] ?? null) { $prop->value = array_intersect_key($prop->value, $this->exportedTags + array_flip((array) $option)); } } private function restrictTypes(Nette\PhpGenerator\ClassType $class): void { $option = $this->config->export->types; if ($option === true) { return; } $prop = $class->getProperty('wiring'); $prop->value = array_intersect_key( $prop->value, $this->exportedTypes + (is_array($option) ? array_flip($option) : []) ); } private function initializeTaggedServices(): void { foreach (array_filter($this->getContainerBuilder()->findByTag('run')) as $name => $on) { trigger_error("Tag 'run' used in service '$name' definition is deprecated.", E_USER_DEPRECATED); $this->initialization->addBody('$this->getService(?);', [$name]); } } private function enableTracyIntegration(): void { Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time; $this->initialization->addBody($this->getContainerBuilder()->formatPhp('?;', [ new Nette\DI\Definitions\Statement('@Tracy\Bar::addPanel', [new Nette\DI\Definitions\Statement(Nette\Bridges\DITracy\ContainerPanel::class)]), ])); } } src/DI/Extensions/PhpExtension.php 0000644 00000002531 13657626572 0013147 0 ustar 00 getConfig() as $name => $value) { if ($value === null) { continue; } elseif ($name === 'include_path') { $this->initialization->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]); } elseif ($name === 'ignore_user_abort') { $this->initialization->addBody('ignore_user_abort(?);', [$value]); } elseif ($name === 'max_execution_time') { $this->initialization->addBody('set_time_limit(?);', [$value]); } elseif ($name === 'date.timezone') { $this->initialization->addBody('date_default_timezone_set(?);', [$value]); } elseif (function_exists('ini_set')) { $this->initialization->addBody('ini_set(?, ?);', [$name, $value === false ? '0' : (string) $value]); } elseif (ini_get($name) != $value) { // intentionally == throw new Nette\NotSupportedException('Required function ini_set() is disabled.'); } } } } src/DI/Extensions/ParametersExtension.php 0000644 00000004123 13657626572 0014522 0 ustar 00 compilerConfig = &$compilerConfig; } public function loadConfiguration() { $builder = $this->getContainerBuilder(); $params = $this->config; $resolver = new Nette\DI\Resolver($builder); $generator = new Nette\DI\PhpGenerator($builder); foreach ($this->dynamicParams as $key) { $params[$key] = array_key_exists($key, $params) ? new DynamicParameter($generator->formatPhp('($this->parameters[?] \?\? ?)', $resolver->completeArguments(Nette\DI\Helpers::filterArguments([$key, $params[$key]])))) : new DynamicParameter(Nette\PhpGenerator\Helpers::format('$this->parameters[?]', $key)); } $builder->parameters = Nette\DI\Helpers::expand($params, $params, true); // expand all except 'services' $slice = array_diff_key($this->compilerConfig, ['services' => 1]); $this->compilerConfig = Nette\DI\Helpers::expand($slice, $builder->parameters) + $this->compilerConfig; } public function afterCompile(Nette\PhpGenerator\ClassType $class) { $parameters = $this->getContainerBuilder()->parameters; array_walk_recursive($parameters, function (&$val): void { if ($val instanceof Nette\DI\Definitions\Statement || $val instanceof DynamicParameter) { $val = null; } }); $cnstr = $class->getMethod('__construct'); $cnstr->addBody('$this->parameters += ?;', [$parameters]); foreach ($this->dynamicValidators as [$param, $expected]) { if ($param instanceof Nette\DI\Definitions\Statement) { continue; } $cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']); } } } src/DI/PhpGenerator.php 0000644 00000012432 13657626572 0010763 0 ustar 00 builder = $builder; } /** * Generates PHP classes. First class is the container. */ public function generate(string $className): Php\ClassType { $this->className = $className; $class = new Php\ClassType($this->className); $class->setExtends(Container::class); $class->addMethod('__construct') ->addBody('parent::__construct($params);') ->addParameter('params', []) ->setType('array'); foreach ($this->builder->exportMeta() as $key => $value) { $class->addProperty($key) ->setProtected() ->setValue($value); } $definitions = $this->builder->getDefinitions(); ksort($definitions); foreach ($definitions as $def) { $class->addMember($this->generateMethod($def)); } $class->getMethod(Container::getMethodName(ContainerBuilder::THIS_CONTAINER)) ->setReturnType($className) ->setBody('return $this;'); $class->addMethod('initialize'); return $class; } public function toString(Php\ClassType $class): string { return '/** @noinspection PhpParamsInspection,PhpMethodMayBeStaticInspection */ declare(strict_types=1); ' . $class->__toString(); } public function addInitialization(Php\ClassType $class, CompilerExtension $extension): void { $closure = $extension->getInitialization(); if ($closure->getBody()) { $class->getMethod('initialize') ->addBody('// ' . $extension->prefix('')) ->addBody("($closure)();"); } } public function generateMethod(Definitions\Definition $def): Php\Method { $name = $def->getName(); try { $method = new Php\Method(Container::getMethodName($name)); $method->setPublic(); $method->setReturnType($def->getType()); $def->generateMethod($method, $this); return $method; } catch (\Exception $e) { throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); } } /** * Formats PHP code for class instantiating, function calling or property setting in PHP. */ public function formatStatement(Statement $statement): string { $entity = $statement->getEntity(); $arguments = $statement->arguments; switch (true) { case is_string($entity) && Strings::contains($entity, '?'): // PHP literal return $this->formatPhp($entity, $arguments); case is_string($entity): // create class return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []); case is_array($entity): switch (true) { case $entity[1][0] === '$': // property getter, setter or appender $name = substr($entity[1], 1); if ($append = (substr($name, -2) === '[]')) { $name = substr($name, 0, -2); } if ($entity[0] instanceof Reference) { $prop = $this->formatPhp('?->?', [$entity[0], $name]); } else { $prop = $this->formatPhp($entity[0] . '::$?', [$name]); } return $arguments ? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]]) : $prop; case $entity[0] instanceof Statement: $inner = $this->formatPhp('?', [$entity[0]]); if (substr($inner, 0, 4) === 'new ') { $inner = "($inner)"; } return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]); case $entity[0] instanceof Reference: return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]); case $entity[0] === '': // function call return $this->formatPhp("$entity[1](...?)", [$arguments]); case is_string($entity[0]): // static method call return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]); } } throw new Nette\InvalidStateException; } /** * Formats PHP statement. * @internal */ public function formatPhp(string $statement, array $args): string { array_walk_recursive($args, function (&$val): void { if ($val instanceof Statement) { $val = new Php\Literal($this->formatStatement($val)); } elseif ($val instanceof Reference) { $name = $val->getValue(); if ($val->isSelf()) { $val = new Php\Literal('$service'); } elseif ($name === ContainerBuilder::THIS_CONTAINER) { $val = new Php\Literal('$this'); } else { $val = ContainerBuilder::literal('$this->getService(?)', [$name]); } } }); return Php\Helpers::formatArgs($statement, $args); } /** * Converts parameters from Definition to PhpGenerator. * @return Php\Parameter[] */ public function convertParameters(array $parameters): array { $res = []; foreach ($parameters as $k => $v) { $tmp = explode(' ', is_int($k) ? $v : $k); $param = $res[] = new Php\Parameter(end($tmp)); if (!is_int($k)) { $param->setDefaultValue($v); } if (isset($tmp[1])) { $param->setType($tmp[0]); } } return $res; } public function getClassName(): ?string { return $this->className; } } src/DI/Helpers.php 0000644 00000013173 13657626572 0007772 0 ustar 00 $val) { $res[self::expand($key, $params, $recursive)] = self::expand($val, $params, $recursive); } return $res; } elseif ($var instanceof Statement) { return new Statement(self::expand($var->getEntity(), $params, $recursive), self::expand($var->arguments, $params, $recursive)); } elseif ($var === '%parameters%' && !array_key_exists('parameters', $params)) { return $recursive ? self::expand($params, $params, (is_array($recursive) ? $recursive : [])) : $params; } elseif (!is_string($var)) { return $var; } $parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE); $res = []; $php = false; foreach ($parts as $n => $part) { if ($n % 2 === 0) { $res[] = $part; } elseif ($part === '') { $res[] = '%'; } elseif (isset($recursive[$part])) { throw new Nette\InvalidArgumentException(sprintf('Circular reference detected for variables: %s.', implode(', ', array_keys($recursive)))); } else { $val = $params; foreach (explode('.', $part) as $key) { if (is_array($val) && array_key_exists($key, $val)) { $val = $val[$key]; } elseif ($val instanceof DynamicParameter) { $val = new DynamicParameter($val . '[' . var_export($key, true) . ']'); } else { throw new Nette\InvalidArgumentException("Missing parameter '$part'."); } } if ($recursive) { $val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]); } if (strlen($part) + 2 === strlen($var)) { return $val; } if ($val instanceof DynamicParameter) { $php = true; } elseif (!is_scalar($val)) { throw new Nette\InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$var'."); } $res[] = $val; } } if ($php) { $res = array_filter($res, function ($val): bool { return $val !== ''; }); $res = array_map(function ($val): string { return $val instanceof DynamicParameter ? "($val)" : var_export((string) $val, true); }, $res); return new DynamicParameter(implode(' . ', $res)); } return implode('', $res); } /** * Removes ... and process constants recursively. */ public static function filterArguments(array $args): array { foreach ($args as $k => $v) { if ($v === '...') { unset($args[$k]); } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*$#D', $v, $m)) { $args[$k] = constant(ltrim($v, ':')); } elseif (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) { $args[$k] = new Reference(substr($v, 1)); } elseif (is_array($v)) { $args[$k] = self::filterArguments($v); } elseif ($v instanceof Statement) { $tmp = self::filterArguments([$v->getEntity()]); $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments)); } } return $args; } /** * Replaces @extension with real extension name in service definition. * @param mixed $config * @return mixed */ public static function prefixServiceName($config, string $namespace) { if (is_string($config)) { if (strncmp($config, '@extension.', 10) === 0) { $config = '@' . $namespace . '.' . substr($config, 11); } } elseif ($config instanceof Reference) { if (strncmp($config->getValue(), 'extension.', 9) === 0) { $config = new Reference($namespace . '.' . substr($config->getValue(), 10)); } } elseif ($config instanceof Statement) { return new Statement( self::prefixServiceName($config->getEntity(), $namespace), self::prefixServiceName($config->arguments, $namespace) ); } elseif (is_array($config)) { foreach ($config as &$val) { $val = self::prefixServiceName($val, $namespace); } } return $config; } /** * Returns an annotation value. * @param \ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionClass $ref */ public static function parseAnnotation(\Reflector $ref, string $name): ?string { if (!Reflection::areCommentsAvailable()) { throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); } $re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#'; if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) { return $m[1] ?? ''; } return null; } public static function getReturnType(\ReflectionFunctionAbstract $func): ?string { if ($type = Reflection::getReturnType($func)) { return $type; } elseif ($type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'))) { if ($type === 'object' || $type === 'mixed') { return null; } elseif ($func instanceof \ReflectionMethod) { return $type === 'static' || $type === '$this' ? $func->getDeclaringClass()->name : Reflection::expandClassName($type, $func->getDeclaringClass()); } else { return $type; } } return null; } public static function normalizeClass(string $type): string { return class_exists($type) || interface_exists($type) ? (new \ReflectionClass($type))->name : $type; } } src/DI/Config/Helpers.php 0000644 00000001514 13657626572 0011173 0 ustar 00 process((array) Neon\Neon::decode(Nette\Utils\FileSystem::read($file))); } /** @throws Nette\InvalidStateException */ public function process(array $arr): array { $res = []; foreach ($arr as $key => $val) { if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING_SUFFIX) { if (!is_array($val) && $val !== null) { throw new Nette\DI\InvalidConfigurationException("Replacing operator is available only for arrays, item '$key' is not array."); } $key = substr($key, 0, -1); $val[Helpers::PREVENT_MERGING] = true; } if (is_array($val)) { $val = $this->process($val); } elseif ($val instanceof Neon\Entity) { if ($val->value === Neon\Neon::CHAIN) { $tmp = null; foreach ($this->process($val->attributes) as $st) { $tmp = new Statement( $tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')], $st->arguments ); } $val = $tmp; } else { $tmp = $this->process([$val->value]); if (is_string($tmp[0]) && strpos($tmp[0], '?') !== false) { trigger_error('Operator ? is deprecated in config files.', E_USER_DEPRECATED); } $val = new Statement($tmp[0], $this->process($val->attributes)); } } $res[$key] = $val; } return $res; } /** * Generates configuration in NEON format. */ public function dump(array $data): string { array_walk_recursive( $data, function (&$val): void { if ($val instanceof Statement) { $val = self::statementToEntity($val); } } ); return "# generated by Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK); } private static function statementToEntity(Statement $val): Neon\Entity { array_walk_recursive( $val->arguments, function (&$val): void { if ($val instanceof Statement) { $val = self::statementToEntity($val); } elseif ($val instanceof Reference) { $val = '@' . $val->getValue(); } } ); $entity = $val->getEntity(); if ($entity instanceof Reference) { $entity = '@' . $entity->getValue(); } elseif (is_array($entity)) { if ($entity[0] instanceof Statement) { return new Neon\Entity( Neon\Neon::CHAIN, [ self::statementToEntity($entity[0]), new Neon\Entity('::' . $entity[1], $val->arguments), ] ); } elseif ($entity[0] instanceof Reference) { $entity = '@' . $entity[0]->getValue() . '::' . $entity[1]; } elseif (is_string($entity[0])) { $entity = $entity[0] . '::' . $entity[1]; } } return new Neon\Entity($entity, $val->arguments); } } src/DI/Config/Loader.php 0000644 00000005775 13657626572 0011014 0 ustar 00 Adapters\PhpAdapter::class, 'neon' => Adapters\NeonAdapter::class, ]; private $dependencies = []; private $loadedFiles = []; private $parameters = []; /** * Reads configuration from file. */ public function load(string $file, ?bool $merge = true): array { if (!is_file($file) || !is_readable($file)) { throw new Nette\FileNotFoundException("File '$file' is missing or is not readable."); } if (isset($this->loadedFiles[$file])) { throw new Nette\InvalidStateException("Recursive included file '$file'"); } $this->loadedFiles[$file] = true; $this->dependencies[] = $file; $data = $this->getAdapter($file)->load($file); $res = []; if (isset($data[self::INCLUDES_KEY])) { Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'"); $includes = Nette\DI\Helpers::expand($data[self::INCLUDES_KEY], $this->parameters); foreach ($includes as $include) { $include = $this->expandIncludedFile($include, $file); $res = Nette\Schema\Helpers::merge($this->load($include, $merge), $res); } } unset($data[self::INCLUDES_KEY], $this->loadedFiles[$file]); if ($merge === false) { $res[] = $data; } else { $res = Nette\Schema\Helpers::merge($data, $res); } return $res; } /** * Save configuration to file. */ public function save(array $data, string $file): void { if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === false) { throw new Nette\IOException("Cannot write file '$file'."); } } /** * Returns configuration files. */ public function getDependencies(): array { return array_unique($this->dependencies); } /** * Expands included file name. */ public function expandIncludedFile(string $includedFile, string $mainFile): string { return preg_match('#([a-z]+:)?[/\\\\]#Ai', $includedFile) // is absolute ? $includedFile : dirname($mainFile) . '/' . $includedFile; } /** * Registers adapter for given file extension. * @param string|Adapter $adapter * @return static */ public function addAdapter(string $extension, $adapter) { $this->adapters[strtolower($extension)] = $adapter; return $this; } private function getAdapter(string $file): Adapter { $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); if (!isset($this->adapters[$extension])) { throw new Nette\InvalidArgumentException("Unknown file extension '$file'."); } return is_object($this->adapters[$extension]) ? $this->adapters[$extension] : new $this->adapters[$extension]; } /** @return static */ public function setParameters(array $params) { $this->parameters = $params; return $this; } } src/DI/Config/DefinitionSchema.php 0000644 00000015237 13657626572 0013011 0 ustar 00 builder = $builder; } public function complete($def, Context $context) { if ($def === [false]) { return (object) $def; } if (Helpers::takeParent($def)) { $def['reset']['all'] = true; } foreach (['arguments', 'setup', 'tags'] as $k) { if (isset($def[$k]) && Helpers::takeParent($def[$k])) { $def['reset'][$k] = true; } } $def = $this->expandParameters($def); $type = $this->sniffType(end($context->path), $def); $def = $this->getSchema($type)->complete($def, $context); if ($def) { $def->defType = $type; } return $def; } public function merge($def, $base) { if (!empty($def['alteration'])) { unset($def['alteration']); } return Nette\Schema\Helpers::merge($def, $base); } /** * Normalizes configuration of service definitions. */ public function normalize($def, Context $context) { if ($def === null || $def === false) { return (array) $def; } elseif (is_string($def) && interface_exists($def)) { return ['implement' => $def]; } elseif ($def instanceof Statement && is_string($def->getEntity()) && interface_exists($def->getEntity())) { $res = ['implement' => $def->getEntity()]; if (array_keys($def->arguments) === ['tagged']) { $res += $def->arguments; } elseif (count($def->arguments) > 1) { $res['references'] = $def->arguments; } elseif ($factory = array_shift($def->arguments)) { $res['factory'] = $factory; } return $res; } elseif (!is_array($def) || isset($def[0], $def[1])) { return ['factory' => $def]; } elseif (is_array($def)) { if (isset($def['class']) && !isset($def['type'])) { if ($def['class'] instanceof Statement) { $key = end($context->path); trigger_error("Service '$key': option 'class' should be changed to 'factory'.", E_USER_DEPRECATED); $def['factory'] = $def['class']; unset($def['class']); } elseif (!isset($def['factory']) && !isset($def['dynamic']) && !isset($def['imported'])) { $def['factory'] = $def['class']; unset($def['class']); } } foreach (['class' => 'type', 'dynamic' => 'imported'] as $alias => $original) { if (array_key_exists($alias, $def)) { if (array_key_exists($original, $def)) { throw new Nette\DI\InvalidConfigurationException("Options '$alias' and '$original' are aliases, use only '$original'."); } $def[$original] = $def[$alias]; unset($def[$alias]); } } return $def; } else { throw new Nette\DI\InvalidConfigurationException('Unexpected format of service definition'); } } public function completeDefault(Context $context) { } private function sniffType($key, array $def): string { if (is_string($key)) { $name = preg_match('#^@[\w\\\\]+$#D', $key) ? $this->builder->getByType(substr($key, 1), false) : $key; if ($name && $this->builder->hasDefinition($name)) { return get_class($this->builder->getDefinition($name)); } } if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) { return Definitions\LocatorDefinition::class; } elseif (isset($def['implement'])) { return method_exists($def['implement'], 'create') ? Definitions\FactoryDefinition::class : Definitions\AccessorDefinition::class; } elseif (isset($def['imported'])) { return Definitions\ImportedDefinition::class; } else { return Definitions\ServiceDefinition::class; } } private function expandParameters(array $config): array { $params = $this->builder->parameters; if (isset($config['parameters'])) { foreach ((array) $config['parameters'] as $k => $v) { $v = explode(' ', is_int($k) ? $v : $k); $params[end($v)] = $this->builder::literal('$' . end($v)); } } return Nette\DI\Helpers::expand($config, $params); } private static function getSchema(string $type): Schema { static $cache; $cache = $cache ?: [ Definitions\ServiceDefinition::class => self::getServiceSchema(), Definitions\AccessorDefinition::class => self::getAccessorSchema(), Definitions\FactoryDefinition::class => self::getFactorySchema(), Definitions\LocatorDefinition::class => self::getLocatorSchema(), Definitions\ImportedDefinition::class => self::getImportedSchema(), ]; return $cache[$type]; } private static function getServiceSchema(): Schema { return Expect::structure([ 'type' => Expect::type('string'), 'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'), 'arguments' => Expect::array(), 'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'), 'inject' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), 'reset' => Expect::array(), 'alteration' => Expect::bool(), ]); } private static function getAccessorSchema(): Schema { return Expect::structure([ 'type' => Expect::string(), 'implement' => Expect::string(), 'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), ]); } private static function getFactorySchema(): Schema { return Expect::structure([ 'type' => Expect::string(), 'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'), 'implement' => Expect::string(), 'arguments' => Expect::array(), 'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'), 'parameters' => Expect::array(), 'references' => Expect::array(), 'tagged' => Expect::string(), 'inject' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), 'reset' => Expect::array(), ]); } private static function getLocatorSchema(): Schema { return Expect::structure([ 'implement' => Expect::string(), 'references' => Expect::array(), 'tagged' => Expect::string(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), ]); } private static function getImportedSchema(): Schema { return Expect::structure([ 'type' => Expect::string(), 'imported' => Expect::bool(), 'autowired' => Expect::type('bool|string|array'), 'tags' => Expect::array(), ]); } } src/DI/Config/Adapter.php 0000644 00000000737 13657626572 0011157 0 ustar 00 message = $message; return $this; } } /** * Not allowed when container is resolving. */ class NotAllowedDuringResolvingException extends Nette\InvalidStateException { } /** * Error in configuration. */ class InvalidConfigurationException extends Nette\InvalidStateException { } src/DI/Compiler.php 0000644 00000021212 13657626572 0010133 0 ustar 00 array[]] */ private $configs = []; /** @var string */ private $sources = ''; /** @var DependencyChecker */ private $dependencies; /** @var string */ private $className = 'Container'; public function __construct(ContainerBuilder $builder = null) { $this->builder = $builder ?: new ContainerBuilder; $this->dependencies = new DependencyChecker; $this->addExtension(self::SERVICES, new Extensions\ServicesExtension); $this->addExtension(self::PARAMETERS, new Extensions\ParametersExtension($this->configs)); } /** * Add custom configurator extension. * @return static */ public function addExtension(?string $name, CompilerExtension $extension) { if ($name === null) { $name = '_' . count($this->extensions); } elseif (isset($this->extensions[$name])) { throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved."); } $lname = strtolower($name); foreach (array_keys($this->extensions) as $nm) { if ($lname === strtolower((string) $nm)) { throw new Nette\InvalidArgumentException("Name of extension '$name' has the same name as '$nm' in a case-insensitive manner."); } } $this->extensions[$name] = $extension->setCompiler($this, $name); return $this; } public function getExtensions(string $type = null): array { return $type ? array_filter($this->extensions, function ($item) use ($type): bool { return $item instanceof $type; }) : $this->extensions; } public function getContainerBuilder(): ContainerBuilder { return $this->builder; } /** @return static */ public function setClassName(string $className) { $this->className = $className; return $this; } /** * Adds new configuration. * @return static */ public function addConfig(array $config) { foreach ($config as $section => $data) { $this->configs[$section][] = $data; } $this->sources .= "// source: array\n"; return $this; } /** * Adds new configuration from file. * @return static */ public function loadConfig(string $file, Config\Loader $loader = null) { $sources = $this->sources . "// source: $file\n"; $loader = $loader ?: new Config\Loader; foreach ($loader->load($file, false) as $data) { $this->addConfig($data); } $this->dependencies->add($loader->getDependencies()); $this->sources = $sources; return $this; } /** * Returns configuration. * @deprecated */ public function getConfig(): array { return $this->config; } /** * Sets the names of dynamic parameters. * @return static */ public function setDynamicParameterNames(array $names) { assert($this->extensions[self::PARAMETERS] instanceof Extensions\ParametersExtension); $this->extensions[self::PARAMETERS]->dynamicParams = $names; return $this; } /** * Adds dependencies to the list. * @param array $deps of ReflectionClass|\ReflectionFunctionAbstract|string * @return static */ public function addDependencies(array $deps) { $this->dependencies->add(array_filter($deps)); return $this; } /** * Exports dependencies. */ public function exportDependencies(): array { return $this->dependencies->export(); } /** @return static */ public function addExportedTag(string $tag) { if (isset($this->extensions[self::DI])) { assert($this->extensions[self::DI] instanceof Extensions\DIExtension); $this->extensions[self::DI]->exportedTags[$tag] = true; } return $this; } /** @return static */ public function addExportedType(string $type) { if (isset($this->extensions[self::DI])) { assert($this->extensions[self::DI] instanceof Extensions\DIExtension); $this->extensions[self::DI]->exportedTypes[$type] = true; } return $this; } public function compile(): string { $this->processExtensions(); $this->processBeforeCompile(); return $this->generateCode(); } /** @internal */ public function processExtensions(): void { $first = $this->getExtensions(Extensions\ParametersExtension::class) + $this->getExtensions(Extensions\ExtensionsExtension::class); foreach ($first as $name => $extension) { $config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name); $extension->setConfig($this->config[$name] = $config); $extension->loadConfiguration(); } $last = $this->getExtensions(Extensions\InjectExtension::class); $this->extensions = array_merge(array_diff_key($this->extensions, $last), $last); if ($decorator = $this->getExtensions(Extensions\DecoratorExtension::class)) { Nette\Utils\Arrays::insertBefore($this->extensions, key($decorator), $this->getExtensions(Extensions\SearchExtension::class)); } $extensions = array_diff_key($this->extensions, $first, [self::SERVICES => 1]); foreach ($extensions as $name => $extension) { $config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name); $extension->setConfig($this->config[$name] = $config); } foreach ($extensions as $extension) { $extension->loadConfiguration(); } foreach ($this->getExtensions(Extensions\ServicesExtension::class) as $name => $extension) { $config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name); $extension->setConfig($this->config[$name] = $config); $extension->loadConfiguration(); } if ($extra = array_diff_key($this->extensions, $extensions, $first, [self::SERVICES => 1])) { $extra = implode("', '", array_keys($extra)); throw new Nette\DeprecatedException("Extensions '$extra' were added while container was being compiled."); } elseif ($extra = key(array_diff_key($this->configs, $this->extensions))) { $hint = Nette\Utils\Helpers::getSuggestion(array_keys($this->extensions), $extra); throw new InvalidConfigurationException( "Found section '$extra' in configuration, but corresponding extension is missing" . ($hint ? ", did you mean '$hint'?" : '.') ); } } private function processBeforeCompile(): void { $this->builder->resolve(); foreach ($this->extensions as $extension) { $extension->beforeCompile(); $this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]); } $this->builder->complete(); } /** * Merges and validates configurations against scheme. * @return array|object */ private function processSchema(Schema\Schema $schema, array $configs, $name = null) { $processor = new Schema\Processor; $processor->onNewContext[] = function (Schema\Context $context) use ($name) { $context->path = $name ? [$name] : []; $context->dynamics = &$this->extensions[self::PARAMETERS]->dynamicValidators; }; try { return $processor->processMultiple($schema, $configs); } catch (Schema\ValidationException $e) { throw new Nette\DI\InvalidConfigurationException($e->getMessage()); } } /** @internal */ public function generateCode(): string { $generator = $this->createPhpGenerator(); $class = $generator->generate($this->className); $this->dependencies->add($this->builder->getDependencies()); foreach ($this->extensions as $extension) { $extension->afterCompile($class); $generator->addInitialization($class, $extension); } return $this->sources . "\n" . $generator->toString($class); } /** * Loads list of service definitions from configuration. */ public function loadDefinitionsFromConfig(array $configList): void { $extension = $this->extensions[self::SERVICES]; assert($extension instanceof Extensions\ServicesExtension); $extension->loadDefinitions($this->processSchema($extension->getConfigSchema(), [$configList])); } protected function createPhpGenerator(): PhpGenerator { return new PhpGenerator($this->builder); } /** @deprecated use non-static Compiler::loadDefinitionsFromConfig() */ public static function loadDefinitions(): void { throw new Nette\DeprecatedException(__METHOD__ . '() is deprecated, use non-static Compiler::loadDefinitionsFromConfig(array $configList).'); } /** @deprecated use non-static Compiler::loadDefinitionsFromConfig() */ public static function loadDefinition(): void { throw new Nette\DeprecatedException(__METHOD__ . '() is deprecated, use non-static Compiler::loadDefinitionsFromConfig(array $configList).'); } } src/DI/ContainerLoader.php 0000644 00000006241 13657626572 0011437 0 ustar 00 tempDirectory = $tempDirectory; $this->autoRebuild = $autoRebuild; } /** * @param callable $generator function (Nette\DI\Compiler $compiler): string|null * @param mixed $key */ public function load(callable $generator, $key = null): string { $class = $this->getClassName($key); if (!class_exists($class, false)) { $this->loadFile($class, $generator); } return $class; } /** * @param mixed $key */ public function getClassName($key): string { return 'Container_' . substr(md5(serialize($key)), 0, 10); } private function loadFile(string $class, callable $generator): void { $file = "$this->tempDirectory/$class.php"; if (!$this->isExpired($file) && (@include $file) !== false) { // @ file may not exist return; } Nette\Utils\FileSystem::createDir($this->tempDirectory); $handle = @fopen("$file.lock", 'c+'); // @ is escalated to exception if (!$handle) { throw new Nette\IOException("Unable to create file '$file.lock'. " . Nette\Utils\Helpers::getLastError()); } elseif (!@flock($handle, LOCK_EX)) { // @ is escalated to exception throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'. " . Nette\Utils\Helpers::getLastError()); } if (!is_file($file) || $this->isExpired($file, $updatedMeta)) { if (isset($updatedMeta)) { $toWrite["$file.meta"] = $updatedMeta; } else { [$toWrite[$file], $toWrite["$file.meta"]] = $this->generate($class, $generator); } foreach ($toWrite as $name => $content) { if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) { @unlink("$name.tmp"); // @ - file may not exist throw new Nette\IOException("Unable to create file '$name'."); } elseif (function_exists('opcache_invalidate')) { @opcache_invalidate($name, true); // @ can be restricted } } } if ((@include $file) === false) { // @ - error escalated to exception throw new Nette\IOException("Unable to include '$file'."); } flock($handle, LOCK_UN); } private function isExpired(string $file, string &$updatedMeta = null): bool { if ($this->autoRebuild) { $meta = @unserialize((string) file_get_contents("$file.meta")); // @ - file may not exist $orig = $meta[2] ?? null; return empty($meta[0]) || DependencyChecker::isExpired(...$meta) || ($orig !== $meta[2] && $updatedMeta = serialize($meta)); } return false; } /** @return array of (code, file[]) */ protected function generate(string $class, callable $generator): array { $compiler = new Compiler; $compiler->setClassName($class); $code = $generator(...[&$compiler]) ?: $compiler->compile(); return [ "exportDependencies()), ]; } } src/DI/ContainerBuilder.php 0000644 00000022515 13657626572 0011621 0 ustar 00 service */ private $aliases = []; /** @var Autowiring */ private $autowiring; /** @var bool */ private $needsResolve = true; /** @var bool */ private $resolving = false; /** @var array */ private $dependencies = []; public function __construct() { $this->autowiring = new Autowiring($this); $this->addImportedDefinition(self::THIS_CONTAINER)->setType(Container::class); } /** * Adds new service definition. * @return Definitions\ServiceDefinition */ public function addDefinition(?string $name, Definition $definition = null): Definition { $this->needsResolve = true; if ($name === null) { for ($i = 1; isset($this->definitions['0' . $i]) || isset($this->aliases['0' . $i]); $i++); $name = '0' . $i; // prevents converting to integer in array key } elseif (is_int(key([$name => 1])) || !preg_match('#^\w+(\.\w+)*$#D', $name)) { throw new Nette\InvalidArgumentException(sprintf('Service name must be a alpha-numeric string and not a number, %s given.', gettype($name))); } else { $name = $this->aliases[$name] ?? $name; if (isset($this->definitions[$name])) { throw new Nette\InvalidStateException("Service '$name' has already been added."); } $lname = strtolower($name); foreach ($this->definitions as $nm => $foo) { if ($lname === strtolower($nm)) { throw new Nette\InvalidStateException("Service '$name' has the same name as '$nm' in a case-insensitive manner."); } } } $definition = $definition ?: new Definitions\ServiceDefinition; $definition->setName($name); $definition->setNotifier(function (): void { $this->needsResolve = true; }); return $this->definitions[$name] = $definition; } public function addAccessorDefinition(?string $name): Definitions\AccessorDefinition { return $this->addDefinition($name, new Definitions\AccessorDefinition); } public function addFactoryDefinition(?string $name): Definitions\FactoryDefinition { return $this->addDefinition($name, new Definitions\FactoryDefinition); } public function addLocatorDefinition(?string $name): Definitions\LocatorDefinition { return $this->addDefinition($name, new Definitions\LocatorDefinition); } public function addImportedDefinition(?string $name): Definitions\ImportedDefinition { return $this->addDefinition($name, new Definitions\ImportedDefinition); } /** * Removes the specified service definition. */ public function removeDefinition(string $name): void { $this->needsResolve = true; $name = $this->aliases[$name] ?? $name; unset($this->definitions[$name]); } /** * Gets the service definition. */ public function getDefinition(string $name): Definition { $service = $this->aliases[$name] ?? $name; if (!isset($this->definitions[$service])) { throw new MissingServiceException("Service '$name' not found."); } return $this->definitions[$service]; } /** * Gets all service definitions. * @return Definition[] */ public function getDefinitions(): array { return $this->definitions; } /** * Does the service definition or alias exist? */ public function hasDefinition(string $name): bool { $name = $this->aliases[$name] ?? $name; return isset($this->definitions[$name]); } public function addAlias(string $alias, string $service): void { if (!$alias) { // builder is not ready for falsy names such as '0' throw new Nette\InvalidArgumentException(sprintf('Alias name must be a non-empty string, %s given.', gettype($alias))); } elseif (!$service) { // builder is not ready for falsy names such as '0' throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($service))); } elseif (isset($this->aliases[$alias])) { throw new Nette\InvalidStateException("Alias '$alias' has already been added."); } elseif (isset($this->definitions[$alias])) { throw new Nette\InvalidStateException("Service '$alias' has already been added."); } $this->aliases[$alias] = $service; } /** * Removes the specified alias. */ public function removeAlias(string $alias): void { unset($this->aliases[$alias]); } /** * Gets all service aliases. */ public function getAliases(): array { return $this->aliases; } /** * @param string[] $types * @return static */ public function addExcludedClasses(array $types) { $this->needsResolve = true; $this->autowiring->addExcludedClasses($types); return $this; } /** * Resolves autowired service name by type. * @param bool $throw exception if service doesn't exist? * @throws MissingServiceException */ public function getByType(string $type, bool $throw = false): ?string { $this->needResolved(); return $this->autowiring->getByType($type, $throw); } /** * Gets autowired service definition of the specified type. * @throws MissingServiceException */ public function getDefinitionByType(string $type): Definition { return $this->getDefinition($this->getByType($type, true)); } /** * Gets the autowired service names and definitions of the specified type. * @return Definition[] service name is key * @internal */ public function findAutowired(string $type): array { $this->needResolved(); return $this->autowiring->findByType($type); } /** * Gets the service names and definitions of the specified type. * @return Definition[] service name is key */ public function findByType(string $type): array { $this->needResolved(); $found = []; foreach ($this->definitions as $name => $def) { if (is_a($def->getType(), $type, true)) { $found[$name] = $def; } } return $found; } /** * Gets the service names and tag values. * @return array of [service name => tag attributes] */ public function findByTag(string $tag): array { $found = []; foreach ($this->definitions as $name => $def) { if (($tmp = $def->getTag($tag)) !== null) { $found[$name] = $tmp; } } return $found; } /********************* building ****************d*g**/ /** * Checks services, resolves types and rebuilts autowiring classlist. */ public function resolve(): void { if ($this->resolving) { return; } $this->resolving = true; $resolver = new Resolver($this); foreach ($this->definitions as $def) { $resolver->resolveDefinition($def); } $this->autowiring->rebuild(); $this->resolving = $this->needsResolve = false; } private function needResolved(): void { if ($this->resolving) { throw new NotAllowedDuringResolvingException; } elseif ($this->needsResolve) { $this->resolve(); } } public function complete(): void { $this->resolve(); foreach ($this->definitions as $def) { $def->setNotifier(null); } $resolver = new Resolver($this); foreach ($this->definitions as $def) { $resolver->completeDefinition($def); } } /** * Adds item to the list of dependencies. * @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep * @return static * @internal */ public function addDependency($dep) { $this->dependencies[] = $dep; return $this; } /** * Returns the list of dependencies. */ public function getDependencies(): array { return $this->dependencies; } /** @internal */ public function exportMeta(): array { $defs = $this->definitions; ksort($defs); foreach ($defs as $name => $def) { if ($def instanceof Definitions\ImportedDefinition) { $meta['types'][$name] = $def->getType(); } foreach ($def->getTags() as $tag => $value) { $meta['tags'][$tag][$name] = $value; } } $meta['aliases'] = $this->aliases; ksort($meta['aliases']); $all = []; foreach ($this->definitions as $name => $def) { if ($type = $def->getType()) { foreach (class_parents($type) + class_implements($type) + [$type] as $class) { $all[$class][] = $name; } } } [$low, $high] = $this->autowiring->getClassList(); foreach ($all as $class => $names) { $meta['wiring'][$class] = array_filter([ $high[$class] ?? [], $low[$class] ?? [], array_diff($names, $low[$class] ?? [], $high[$class] ?? []), ]); } return $meta; } public static function literal(string $code, array $args = null): Nette\PhpGenerator\PhpLiteral { return new Nette\PhpGenerator\PhpLiteral( $args === null ? $code : Nette\PhpGenerator\Helpers::formatArgs($code, $args) ); } /** @deprecated */ public function formatPhp(string $statement, array $args): string { array_walk_recursive($args, function (&$val): void { if ($val instanceof Statement) { $val = (new Resolver($this))->completeStatement($val); } elseif ($val instanceof Definition) { $val = new Definitions\Reference($val->getName()); } }); return (new PhpGenerator($this))->formatPhp($statement, $args); } /** @deprecated use resolve() */ public function prepareClassList(): void { trigger_error(__METHOD__ . '() is deprecated, use resolve()', E_USER_DEPRECATED); $this->resolve(); } } src/DI/DependencyChecker.php 0000644 00000011025 13657626572 0011725 0 ustar 00 dependencies = array_merge($this->dependencies, $deps); return $this; } /** * Exports dependencies. */ public function export(): array { $files = $phpFiles = $classes = $functions = []; foreach ($this->dependencies as $dep) { if (is_string($dep)) { $files[] = $dep; } elseif ($dep instanceof ReflectionClass) { if (empty($classes[$name = $dep->name])) { $all = [$name] + class_parents($name) + class_implements($name); foreach ($all as &$item) { $all += class_uses($item); $phpFiles[] = (new ReflectionClass($item))->getFileName(); $classes[$item] = true; } } } elseif ($dep instanceof \ReflectionFunctionAbstract) { $phpFiles[] = $dep->getFileName(); $functions[] = Reflection::toString($dep); } else { throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep)); } } $classes = array_keys($classes); $functions = array_unique($functions, SORT_REGULAR); $hash = self::calculateHash($classes, $functions); $files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist $phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash]; } /** * Are dependencies expired? */ public static function isExpired(int $version, array $files, array &$phpFiles, array $classes, array $functions, string $hash): bool { try { $currentFiles = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist $origPhpFiles = $phpFiles; $phpFiles = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist return $version !== self::VERSION || $files !== $currentFiles || ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions)); } catch (\ReflectionException $e) { return true; } } private static function calculateHash(array $classes, array $functions): string { $hash = []; foreach ($classes as $name) { $class = new ReflectionClass($name); $hash[] = [ $name, Reflection::getUseStatements($class), $class->isAbstract(), get_parent_class($name), class_implements($name), class_uses($name), ]; foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { if ($prop->getDeclaringClass() == $class) { // intentionally == $hash[] = [$name, $prop->name, $prop->getDocComment()]; } } foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { if ($method->getDeclaringClass() == $class) { // intentionally == $hash[] = [ $name, $method->name, $method->getDocComment(), self::hashParameters($method), $method->hasReturnType() ? [$method->getReturnType()->getName(), $method->getReturnType()->allowsNull()] : null, ]; } } } $flip = array_flip($classes); foreach ($functions as $name) { if (strpos($name, '::')) { $method = new ReflectionMethod($name); $class = $method->getDeclaringClass(); if (isset($flip[$class->name])) { continue; } $uses = Reflection::getUseStatements($class); } else { $method = new \ReflectionFunction($name); $uses = null; } $hash[] = [ $name, $uses, $method->getDocComment(), self::hashParameters($method), $method->hasReturnType() ? [$method->getReturnType()->getName(), $method->getReturnType()->allowsNull()] : null, ]; } return md5(serialize($hash)); } private static function hashParameters(\ReflectionFunctionAbstract $method): array { $res = []; foreach ($method->getParameters() as $param) { $res[] = [ $param->name, Reflection::getParameterType($param), $param->allowsNull(), $param->isVariadic(), $param->isDefaultValueAvailable() ? [Reflection::getParameterDefaultValue($param)] : null, ]; } return $res; } } src/DI/Container.php 0000644 00000020243 13657626572 0010306 0 ustar 00 type (complete list of available services) */ protected $types = []; /** @var string[] alias => service name */ protected $aliases = []; /** @var array[] tag name => service name => tag value */ protected $tags = []; /** @var array[] type => level => services */ protected $wiring = []; /** @var object[] service name => instance */ private $instances = []; /** @var array circular reference detector */ private $creating; /** @var array */ private $methods; public function __construct(array $params = []) { $this->parameters = $params; $this->methods = array_flip(get_class_methods($this)); } public function getParameters(): array { return $this->parameters; } /** * Adds the service to the container. * @param object $service service or its factory * @return static */ public function addService(string $name, $service) { $name = $this->aliases[$name] ?? $name; if (isset($this->instances[$name])) { throw new Nette\InvalidStateException("Service '$name' already exists."); } elseif (!is_object($service)) { throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service))); } $type = $service instanceof \Closure ? (($tmp = (new \ReflectionFunction($service))->getReturnType()) ? $tmp->getName() : '') : get_class($service); if (!isset($this->methods[self::getMethodName($name)])) { $this->types[$name] = $type; } elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) { throw new Nette\InvalidArgumentException("Service '$name' must be instance of $expectedType, " . ($type ? "$type given." : 'add typehint to closure.')); } if ($service instanceof \Closure) { $this->methods[self::getMethodName($name)] = $service; $this->types[$name] = $type; } else { $this->instances[$name] = $service; } return $this; } /** * Removes the service from the container. */ public function removeService(string $name): void { $name = $this->aliases[$name] ?? $name; unset($this->instances[$name]); } /** * Gets the service object by name. * @return object * @throws MissingServiceException */ public function getService(string $name) { if (!isset($this->instances[$name])) { if (isset($this->aliases[$name])) { return $this->getService($this->aliases[$name]); } $this->instances[$name] = $this->createService($name); } return $this->instances[$name]; } /** * Gets the service object by name. * @return object * @throws MissingServiceException */ public function getByName(string $name) { return $this->getService($name); } /** * Gets the service type by name. * @throws MissingServiceException */ public function getServiceType(string $name): string { $method = self::getMethodName($name); if (isset($this->aliases[$name])) { return $this->getServiceType($this->aliases[$name]); } elseif (isset($this->types[$name])) { return $this->types[$name]; } elseif (isset($this->methods[$method])) { $type = (new \ReflectionMethod($this, $method))->getReturnType(); return $type ? $type->getName() : ''; } else { throw new MissingServiceException("Service '$name' not found."); } } /** * Does the service exist? */ public function hasService(string $name): bool { $name = $this->aliases[$name] ?? $name; return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]); } /** * Is the service created? */ public function isCreated(string $name): bool { if (!$this->hasService($name)) { throw new MissingServiceException("Service '$name' not found."); } $name = $this->aliases[$name] ?? $name; return isset($this->instances[$name]); } /** * Creates new instance of the service. * @return object * @throws MissingServiceException */ public function createService(string $name, array $args = []) { $name = $this->aliases[$name] ?? $name; $method = self::getMethodName($name); $cb = $this->methods[$method] ?? null; if (isset($this->creating[$name])) { throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating)))); } elseif ($cb === null) { throw new MissingServiceException("Service '$name' not found."); } try { $this->creating[$name] = true; $service = $cb instanceof \Closure ? $cb(...$args) : $this->$method(...$args); } finally { unset($this->creating[$name]); } if (!is_object($service)) { throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by " . ($cb instanceof \Closure ? 'closure' : "method $method()") . ' is not object.'); } return $service; } /** * Resolves service by type. * @param bool $throw exception if service doesn't exist? * @return object|null service * @throws MissingServiceException */ public function getByType(string $type, bool $throw = true) { $type = Helpers::normalizeClass($type); if (!empty($this->wiring[$type][0])) { if (count($names = $this->wiring[$type][0]) === 1) { return $this->getService($names[0]); } natsort($names); throw new MissingServiceException("Multiple services of type $type found: " . implode(', ', $names) . '.'); } elseif ($throw) { throw new MissingServiceException("Service of type $type not found."); } return null; } /** * Gets the autowired service names of the specified type. * @return string[] * @internal */ public function findAutowired(string $type): array { $type = Helpers::normalizeClass($type); return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []); } /** * Gets the service names of the specified type. * @return string[] */ public function findByType(string $type): array { $type = Helpers::normalizeClass($type); return empty($this->wiring[$type]) ? [] : array_merge(...array_values($this->wiring[$type])); } /** * Gets the service names of the specified tag. * @return array of [service name => tag attributes] */ public function findByTag(string $tag): array { return $this->tags[$tag] ?? []; } /********************* autowiring ****************d*g**/ /** * Creates new instance using autowiring. * @return object * @throws Nette\InvalidArgumentException */ public function createInstance(string $class, array $args = []) { $rc = new \ReflectionClass($class); if (!$rc->isInstantiable()) { throw new ServiceCreationException("Class $class is not instantiable."); } elseif ($constructor = $rc->getConstructor()) { return $rc->newInstanceArgs($this->autowireArguments($constructor, $args)); } elseif ($args) { throw new ServiceCreationException("Unable to pass arguments, class $class has no constructor."); } return new $class; } /** * Calls all methods starting with with "inject" using autowiring. * @param object $service */ public function callInjects($service): void { Extensions\InjectExtension::callInjects($this, $service); } /** * Calls method using autowiring. * @return mixed */ public function callMethod(callable $function, array $args = []) { return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args)); } private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array { return Resolver::autowireArguments($function, $args, function (string $type, bool $single) { return $single ? $this->getByType($type) : array_map([$this, 'getService'], $this->findAutowired($type)); }); } public static function getMethodName(string $name): string { if ($name === '') { throw new Nette\InvalidArgumentException('Service name must be a non-empty string.'); } return 'createService' . str_replace('.', '__', ucfirst($name)); } } src/compatibility.php 0000644 00000001544 13657626572 0010744 0 ustar 00 container = $container; $this->elapsedTime = self::$compilationTime ? microtime(true) - self::$compilationTime : null; } /** * Renders tab. */ public function getTab(): string { return Nette\Utils\Helpers::capture(function () { $elapsedTime = $this->elapsedTime; require __DIR__ . '/templates/ContainerPanel.tab.phtml'; }); } /** * Renders panel. */ public function getPanel(): string { $rc = new \ReflectionClass($this->container); $tags = []; $types = []; foreach ($rc->getMethods() as $method) { if (preg_match('#^createService(.+)#', $method->name, $m) && $method->getReturnType()) { $types[lcfirst(str_replace('__', '.', $m[1]))] = $method->getReturnType()->getName(); } } $types = $this->getContainerProperty('types') + $types; ksort($types); foreach ($this->getContainerProperty('tags') as $tag => $tmp) { foreach ($tmp as $service => $val) { $tags[$service][$tag] = $val; } } return Nette\Utils\Helpers::capture(function () use ($tags, $types, $rc) { $container = $this->container; $file = $rc->getFileName(); $instances = $this->getContainerProperty('instances'); $wiring = $this->getContainerProperty('wiring'); require __DIR__ . '/templates/ContainerPanel.panel.phtml'; }); } private function getContainerProperty(string $name) { $prop = (new \ReflectionClass(Nette\DI\Container::class))->getProperty($name); $prop->setAccessible(true); return $prop->getValue($this->container); } } src/Bridges/DITracy/templates/ContainerPanel.panel.phtml 0000644 00000004235 13657626572 0017304 0 ustar 00
Name | Autowired | Service | Tags |
---|---|---|---|
= is_numeric($name) ? "–" : Helpers::escapeHtml($name) ?> | = $autowired ? 'yes' : (isset($wiring[$type]) ? 'no' : '?') ?> |
= Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
= get_class($instances[$name]) ?>
= Helpers::escapeHtml($type) ?>
|
true]) : Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]); } ?> |
Source: = Helpers::editorLink($file) ?>