contributing.md 0000666 00000002504 13436756201 0007611 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 0000666 00000005545 13436756201 0012033 0 ustar 00 compiler = $compiler; $this->name = $name; return $this; } /** * @return static */ public function setConfig(array $config) { $this->config = $config; return $this; } /** * Returns extension configuration. * @return array */ public function getConfig() { if (func_num_args()) { // deprecated return Config\Helpers::merge($this->config, $this->getContainerBuilder()->expand(func_get_arg(0))); } return $this->config; } /** * Checks whether $config contains only $expected items and returns combined array. * @return array * @throws Nette\InvalidStateException */ public function validateConfig(array $expected, array $config = null, $name = null) { if (func_num_args() === 1) { return $this->config = $this->validateConfig($expected, $this->config); } if ($extra = array_diff_key((array) $config, $expected)) { $name = $name ?: $this->name; $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys($expected), key($extra)); $extra = $hint ? key($extra) : implode(", $name.", array_keys($extra)); throw new Nette\InvalidStateException("Unknown configuration option $name.$extra" . ($hint ? ", did you mean $name.$hint?" : '.')); } return Config\Helpers::merge($config, $expected); } /** * @return ContainerBuilder */ public function getContainerBuilder() { return $this->compiler->getContainerBuilder(); } /** * Reads configuration from file. * @param string file name * @return array */ public function loadFromFile($file) { $loader = new Config\Loader; $res = $loader->load($file); $this->compiler->addDependencies($loader->getDependencies()); return $res; } /** * Prepend extension name to identifier or service name. * @param string * @return string */ public function prefix($id) { 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/Extensions/DecoratorExtension.php 0000666 00000003057 13436756201 0014336 0 ustar 00 [], 'tags' => [], 'inject' => null, ]; public function beforeCompile() { foreach ($this->getConfig() as $type => $info) { $info = $this->validateConfig($this->defaults, $info, $this->prefix($type)); if ($info['inject'] !== null) { $info['tags'][InjectExtension::TAG_INJECT] = $info['inject']; } $info = Nette\DI\Helpers::filterArguments($info); $this->addSetups($type, (array) $info['setup']); $this->addTags($type, (array) $info['tags']); } } public function addSetups($type, array $setups) { foreach ($this->findByType($type) as $def) { foreach ($setups as $setup) { if (is_array($setup)) { $setup = new Nette\DI\Statement(key($setup), array_values($setup)); } $def->addSetup($setup); } } } public function addTags($type, array $tags) { $tags = Nette\Utils\Arrays::normalize($tags, true); foreach ($this->findByType($type) as $def) { $def->setTags($def->getTags() + $tags); } } private function findByType($type) { return array_filter($this->getContainerBuilder()->getDefinitions(), function ($def) use ($type) { return is_a($def->getImplement(), $type, true) || ($def->getImplementMode() !== $def::IMPLEMENT_MODE_GET && is_a($def->getType(), $type, true)); }); } } src/DI/Extensions/InjectExtension.php 0000666 00000007612 13436756201 0013631 0 ustar 00 getContainerBuilder()->getDefinitions() as $def) { if ($def->getTag(self::TAG_INJECT) && $def->getType()) { $this->updateDefinition($def); } } } private function updateDefinition(DI\ServiceDefinition $def) { $class = $def->getType(); $setups = $def->getSetup(); foreach (self::getInjectProperties($class) as $property => $type) { $builder = $this->getContainerBuilder(); $inject = new DI\Statement('$' . $property, ['@\\' . ltrim($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($def->getType())) as $method) { $inject = new DI\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. * @return array * @internal */ public static function getInjectMethods($class) { $res = []; foreach (get_class_methods($class) as $name) { if (substr($name, 0, 6) === 'inject') { $res[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->getName(); } } uksort($res, function ($a, $b) use ($res) { return $res[$a] === $res[$b] ? strcmp($a, $b) : (is_a($res[$a], $res[$b], true) ? 1 : -1); }); return array_keys($res); } /** * Generates list of properties with annotation @inject. * @return array * @internal */ public static function getInjectProperties($class) { $res = []; foreach (get_class_vars($class) as $name => $foo) { $rp = new \ReflectionProperty($class, $name); if (DI\Helpers::parseAnnotation($rp, 'inject') !== null) { if ($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. * @return void */ public static function callInjects(DI\Container $container, $service) { if (!is_object($service)) { throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service))); } foreach (self::getInjectMethods($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); } } /** @internal */ private static function checkType($class, $name, $type, $container = null) { $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\InvalidStateException("Service of type $type used in @var annotation at $propName not found. Did you register it in configuration file?"); } } } src/DI/Extensions/ConstantsExtension.php 0000666 00000000751 13436756201 0014366 0 ustar 00 getConfig() as $name => $value) { $class->getMethod('initialize')->addBody('define(?, ?);', [$name, $value]); } } } src/DI/Extensions/ExtensionsExtension.php 0000666 00000001315 13436756201 0014546 0 ustar 00 getConfig() as $name => $class) { if (is_int($name)) { $name = null; } if ($class instanceof Nette\DI\Statement) { $rc = new \ReflectionClass($class->getEntity()); $this->compiler->addExtension($name, $rc->newInstanceArgs($class->arguments)); } else { $this->compiler->addExtension($name, new $class); } } } } src/DI/Extensions/DIExtension.php 0000666 00000002717 13436756201 0012712 0 ustar 00 true, 'accessors' => false, 'excluded' => [], 'parentClass' => null, ]; /** @var bool */ private $debugMode; /** @var int */ private $time; public function __construct($debugMode = false) { $this->debugMode = $debugMode; $this->time = microtime(true); } public function loadConfiguration() { $config = $this->validateConfig($this->defaults); $builder = $this->getContainerBuilder(); $builder->addExcludedClasses($config['excluded']); } public function afterCompile(Nette\PhpGenerator\ClassType $class) { if ($this->config['parentClass']) { $class->setExtends($this->config['parentClass']); } $initialize = $class->getMethod('initialize'); $builder = $this->getContainerBuilder(); if ($this->debugMode && $this->config['debugger']) { Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time; $initialize->addBody($builder->formatPhp('?;', [ new Nette\DI\Statement('@Tracy\Bar::addPanel', [new Nette\DI\Statement(Nette\Bridges\DITracy\ContainerPanel::class)]), ])); } foreach (array_filter($builder->findByTag('run')) as $name => $on) { $initialize->addBody('$this->getService(?);', [$name]); } } } src/DI/Extensions/PhpExtension.php 0000666 00000002516 13436756201 0013142 0 ustar 00 getMethod('initialize'); foreach ($this->getConfig() as $name => $value) { if ($value === null) { continue; } elseif (!is_scalar($value)) { throw new Nette\InvalidStateException("Configuration value for directive '$name' is not scalar."); } elseif ($name === 'include_path') { $initialize->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]); } elseif ($name === 'ignore_user_abort') { $initialize->addBody('ignore_user_abort(?);', [$value]); } elseif ($name === 'max_execution_time') { $initialize->addBody('set_time_limit(?);', [$value]); } elseif ($name === 'date.timezone') { $initialize->addBody('date_default_timezone_set(?);', [$value]); } elseif (function_exists('ini_set')) { $initialize->addBody('ini_set(?, ?);', [$name, $value]); } elseif (ini_get($name) != $value) { // intentionally == throw new Nette\NotSupportedException('Required function ini_set() is disabled.'); } } } } src/DI/PhpGenerator.php 0000666 00000020664 13436756201 0010761 0 ustar 00 builder = $builder; } /** * Generates PHP classes. First class is the container. * @return Nette\PhpGenerator\ClassType[] */ public function generate($className) { $this->builder->complete(); $this->generatedClasses = []; $this->className = $className; $containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className); $containerClass->setExtends(Container::class); $containerClass->addMethod('__construct') ->addBody('$this->parameters = $params;') ->addBody('$this->parameters += ?;', [$this->builder->parameters]) ->addParameter('params', []) ->setTypeHint('array'); $definitions = $this->builder->getDefinitions(); ksort($definitions); $meta = $containerClass->addProperty('meta') ->setVisibility('protected') ->setValue([Container::TYPES => $this->builder->getClassList()]); foreach ($definitions as $name => $def) { $meta->value[Container::SERVICES][$name] = $def->getType() ?: null; foreach ($def->getTags() as $tag => $value) { $meta->value[Container::TAGS][$tag][$name] = $value; } } foreach ($definitions as $name => $def) { try { $name = (string) $name; $methodName = Container::getMethodName($name); if (!PhpHelpers::isIdentifier($methodName)) { throw new ServiceCreationException('Name contains invalid characters.'); } $containerClass->addMethod($methodName) ->addComment(PHP_VERSION_ID < 70000 ? '@return ' . ($def->getImplement() ?: $def->getType()) : '') ->setReturnType(PHP_VERSION_ID >= 70000 ? ($def->getImplement() ?: $def->getType()) : null) ->setBody($name === ContainerBuilder::THIS_CONTAINER ? 'return $this;' : $this->generateService($name)) ->setParameters($def->getImplement() ? [] : $this->convertParameters($def->parameters)); } catch (\Exception $e) { throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); } } $aliases = $this->builder->getAliases(); ksort($aliases); $meta->value[Container::ALIASES] = $aliases; return $this->generatedClasses; } /** * Generates body of service method. * @return string */ private function generateService($name) { $def = $this->builder->getDefinition($name); if ($def->isDynamic()) { return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);', ["Unable to create dynamic service '$name', it must be added using addService()"] ); } $entity = $def->getFactory()->getEntity(); $serviceRef = $this->builder->getServiceName($entity); $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE ? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef]) : $def->getFactory(); $this->currentService = null; $code = '$service = ' . $this->formatStatement($factory) . ";\n"; if ( (PHP_VERSION_ID < 70000 || $def->getSetup()) && ($type = $def->getType()) && !$serviceRef && $type !== $entity && !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $type)) ) { $code .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n" . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n", ["Unable to create service '$name', value returned by factory is not $type type."] ); } $this->currentService = $name; foreach ($def->getSetup() as $setup) { $code .= $this->formatStatement($setup) . ";\n"; } $code .= 'return $service;'; if (!$def->getImplement()) { return $code; } $factoryClass = (new Nette\PhpGenerator\ClassType) ->addImplement($def->getImplement()); $factoryClass->addProperty('container') ->setVisibility('private'); $factoryClass->addMethod('__construct') ->addBody('$this->container = $container;') ->addParameter('container') ->setTypeHint($this->className); $rm = new \ReflectionMethod($def->getImplement(), $def->getImplementMode()); $factoryClass->addMethod($def->getImplementMode()) ->setParameters($this->convertParameters($def->parameters)) ->setBody(str_replace('$this', '$this->container', $code)) ->setReturnType(PHP_VERSION_ID >= 70000 ? (Reflection::getReturnType($rm) ?: $def->getType()) : null); if (PHP_VERSION_ID < 70000) { $this->generatedClasses[] = $factoryClass; $factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}")); return "return new {$factoryClass->getName()}(\$this);"; } return 'return new class ($this) ' . $factoryClass . ';'; } /** * Formats PHP code for class instantiating, function calling or property setting in PHP. * @return string */ private function formatStatement(Statement $statement) { $entity = $statement->getEntity(); $arguments = $statement->arguments; if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal return $this->formatPhp($entity, $arguments); } elseif ($service = $this->builder->getServiceName($entity)) { // factory calling return $this->formatPhp('$this->?(...?)', [Container::getMethodName($service), $arguments]); } elseif ($entity === 'not') { // operator return $this->formatPhp('!?', [$arguments[0]]); } elseif (is_string($entity)) { // class name return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []); } elseif ($entity[0] === '') { // globalFunc return $this->formatPhp("$entity[1](...?)", [$arguments]); } elseif ($entity[0] instanceof Statement) { $inner = $this->formatPhp('?', [$entity[0]]); if (substr($inner, 0, 4) === 'new ') { $inner = "($inner)"; } return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]); } elseif ($entity[1][0] === '$') { // property getter, setter or appender $name = substr($entity[1], 1); if ($append = (substr($name, -2) === '[]')) { $name = substr($name, 0, -2); } if ($this->builder->getServiceName($entity[0])) { $prop = $this->formatPhp('?->?', [$entity[0], $name]); } else { $prop = $this->formatPhp($entity[0] . '::$?', [$name]); } return $arguments ? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]]) : $prop; } elseif ($service = $this->builder->getServiceName($entity[0])) { // service method return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]); } else { // static method return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]); } } /** * Formats PHP statement. * @return string * @internal */ public function formatPhp($statement, $args) { array_walk_recursive($args, function (&$val) { if ($val instanceof Statement) { $val = new PhpLiteral($this->formatStatement($val)); } elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@ $val = substr($val, 1); } elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) { // service reference $name = substr($val, 1); if ($name === ContainerBuilder::THIS_CONTAINER) { $val = new PhpLiteral('$this'); } elseif ($name === $this->currentService) { $val = new PhpLiteral('$service'); } else { $val = new PhpLiteral($this->formatStatement(new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$name]))); } } }); return PhpHelpers::formatArgs($statement, $args); } /** * Converts parameters from ServiceDefinition to PhpGenerator. * @return Nette\PhpGenerator\Parameter[] */ private function convertParameters(array $parameters) { $res = []; foreach ($parameters as $k => $v) { $tmp = explode(' ', is_int($k) ? $v : $k); $param = $res[] = new Nette\PhpGenerator\Parameter(end($tmp)); if (!is_int($k)) { $param->setOptional(true)->setDefaultValue($v); } if (isset($tmp[1])) { $param->setTypeHint($tmp[0]); } } return $res; } } src/DI/Helpers.php 0000666 00000016512 13436756201 0007762 0 ustar 00 $val) { $res[$key] = 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 (!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 { try { $val = Nette\Utils\Arrays::get($params, explode('.', $part)); } catch (Nette\InvalidArgumentException $e) { throw new Nette\InvalidArgumentException("Missing parameter '$part'.", 0, $e); } if ($recursive) { $val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]); } if (strlen($part) + 2 === strlen($var)) { return $val; } if ($val instanceof PhpLiteral) { $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) { return $val !== ''; }); $res = array_map(function ($val) { return $val instanceof PhpLiteral ? "($val)" : var_export((string) $val, true); }, $res); return new PhpLiteral(implode(' . ', $res)); } return implode('', $res); } /** * Generates list of arguments using autowiring. * @return array * @throws ServiceCreationException */ public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, $container) { $optCount = 0; $num = -1; $res = []; $methodName = Reflection::toString($method) . '()'; foreach ($method->getParameters() as $num => $parameter) { $paramName = $parameter->getName(); if (!$parameter->isVariadic() && array_key_exists($paramName, $arguments)) { $res[$num] = $arguments[$paramName]; unset($arguments[$paramName], $arguments[$num]); $optCount = 0; } elseif (array_key_exists($num, $arguments)) { $res[$num] = $arguments[$num]; unset($arguments[$num]); $optCount = 0; } elseif (($type = Reflection::getParameterType($parameter)) && !Reflection::isBuiltinType($type)) { try { $res[$num] = $container->getByType($type, false); } catch (ServiceCreationException $e) { throw new ServiceCreationException("{$e->getMessage()} (needed by $$paramName in $methodName)", 0, $e); } if ($res[$num] === null) { if ($parameter->allowsNull()) { $optCount++; } elseif (class_exists($type) || interface_exists($type)) { throw new ServiceCreationException("Service of type $type needed by $$paramName in $methodName not found. Did you register it in configuration file?"); } else { throw new ServiceCreationException("Class $type needed by $$paramName in $methodName not found. Check type hint and 'use' statements."); } } else { if ($container instanceof ContainerBuilder) { $res[$num] = '@' . $res[$num]; } $optCount = 0; } } 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, ... $res[$num] = $parameter->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($parameter) : null; $optCount++; } else { throw new ServiceCreationException("Parameter $$paramName in $methodName has no class type hint or default value, so its value must be specified."); } } // 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 $methodName."); } return $optCount ? array_slice($res, 0, -$optCount) : $res; } /** * Removes ... and process constants recursively. * @return array */ public static function filterArguments(array $args) { foreach ($args as $k => $v) { if ($v === '...') { unset($args[$k]); } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) { $args[$k] = constant(ltrim($v, ':')); } 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 * @param string * @return mixed */ public static function prefixServiceName($config, $namespace) { if (is_string($config)) { if (strncmp($config, '@extension.', 10) === 0) { $config = '@' . $namespace . '.' . substr($config, 11); } } 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. * @return string|null */ public static function parseAnnotation(\Reflector $ref, $name) { if (!Reflection::areCommentsAvailable()) { throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); } $name = preg_quote($name, '#'); if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) { return isset($m[1]) ? $m[1] : ''; } } /** * @return string|null */ public static function getReturnType(\ReflectionFunctionAbstract $func) { 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()->getName() : Reflection::expandClassName($type, $func->getDeclaringClass()); } else { return $type; } } } public static function normalizeClass($type) { return class_exists($type) || interface_exists($type) ? (new \ReflectionClass($type))->getName() : $type; } } src/DI/Config/Helpers.php 0000666 00000003240 13436756201 0011161 0 ustar 00 $val) { if (is_int($key)) { $right[] = $val; } else { if (is_array($val) && isset($val[self::EXTENDS_KEY])) { if ($val[self::EXTENDS_KEY] === self::OVERWRITE) { unset($val[self::EXTENDS_KEY]); } } elseif (isset($right[$key])) { $val = static::merge($val, $right[$key]); } $right[$key] = $val; } } return $right; } elseif ($left === null && is_array($right)) { return $right; } else { return $left; } } /** * Finds out and removes information about the parent. * @return mixed */ public static function takeParent(&$data) { if (is_array($data) && isset($data[self::EXTENDS_KEY])) { $parent = $data[self::EXTENDS_KEY]; unset($data[self::EXTENDS_KEY]); return $parent; } } /** * @return bool */ public static function isOverwriting(&$data) { return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] === self::OVERWRITE; } /** * @return bool */ public static function isInheriting(&$data) { return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] !== self::OVERWRITE; } } src/DI/Config/Adapters/PhpAdapter.php 0000666 00000001255 13436756201 0013356 0 ustar 00 process($ini); } /** * @return array * @throws Nette\InvalidStateException */ public function process(array $arr) { $data = []; foreach ($arr as $secName => $secData) { if (is_array($secData)) { // is section? if (substr($secName, -1) === self::RAW_SECTION) { $secName = substr($secName, 0, -1); } else { // process key nesting separator (key1.key2.key3) $tmp = []; foreach ($secData as $key => $val) { $cursor = &$tmp; $key = str_replace(self::ESCAPED_KEY_SEPARATOR, "\xFF", $key); foreach (explode(self::KEY_SEPARATOR, $key) as $part) { $part = str_replace("\xFF", self::KEY_SEPARATOR, $part); if (!isset($cursor[$part]) || is_array($cursor[$part])) { $cursor = &$cursor[$part]; } else { throw new Nette\InvalidStateException("Invalid key '$key' in section [$secName]."); } } $cursor = $val; } $secData = $tmp; } $parts = explode(self::INHERITING_SEPARATOR, $secName); if (count($parts) > 1) { $secName = trim($parts[0]); $secData[Helpers::EXTENDS_KEY] = trim($parts[1]); } } $cursor = &$data; // nesting separator in section name foreach (explode(self::KEY_SEPARATOR, $secName) as $part) { if (!isset($cursor[$part]) || is_array($cursor[$part])) { $cursor = &$cursor[$part]; } else { throw new Nette\InvalidStateException("Invalid section [$secName]."); } } if (is_array($secData) && is_array($cursor)) { $secData = Helpers::merge($secData, $cursor); } $cursor = $secData; } return $data; } /** * Generates configuration in INI format. * @return string */ public function dump(array $data) { $output = []; foreach ($data as $name => $secData) { if (!is_array($secData)) { $output = []; self::build($data, $output, ''); break; } if ($parent = Helpers::takeParent($secData)) { $output[] = "[$name " . self::INHERITING_SEPARATOR . " $parent]"; } else { $output[] = "[$name]"; } self::build($secData, $output, ''); $output[] = ''; } return "; generated by Nette\n\n" . implode(PHP_EOL, $output); } /** * Recursive builds INI list. * @return void */ private static function build($input, &$output, $prefix) { foreach ($input as $key => $val) { $key = str_replace(self::KEY_SEPARATOR, self::ESCAPED_KEY_SEPARATOR, $key); if (is_array($val)) { self::build($val, $output, $prefix . $key . self::KEY_SEPARATOR); } elseif (is_bool($val)) { $output[] = "$prefix$key = " . ($val ? 'true' : 'false'); } elseif (is_numeric($val)) { $output[] = "$prefix$key = $val"; } elseif (is_string($val)) { $output[] = "$prefix$key = \"$val\""; } else { throw new Nette\InvalidArgumentException(sprintf("The '%s' item must be scalar or array, %s given.", $prefix . $key, gettype($val))); } } } } src/DI/Config/Adapters/NeonAdapter.php 0000666 00000006460 13436756201 0013531 0 ustar 00 process((array) Neon\Neon::decode(file_get_contents($file))); } /** * @return array * @throws Nette\InvalidStateException */ public function process(array $arr) { $res = []; foreach ($arr as $key => $val) { if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING) { if (!is_array($val) && $val !== null) { throw new Nette\InvalidStateException("Replacing operator is available only for arrays, item '$key' is not array."); } $key = substr($key, 0, -1); $val[Helpers::EXTENDS_KEY] = Helpers::OVERWRITE; } elseif (is_string($key) && preg_match('#^(\S+)\s+' . self::INHERITING_SEPARATOR . '\s+(\S+)\z#', $key, $matches)) { if (!is_array($val) && $val !== null) { throw new Nette\InvalidStateException("Inheritance operator is available only for arrays, item '$key' is not array."); } list(, $key, $val[Helpers::EXTENDS_KEY]) = $matches; if (isset($res[$key])) { throw new Nette\InvalidStateException("Duplicated key '$key'."); } } 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($st->getEntity(), ':')], $st->arguments ); } $val = $tmp; } else { $tmp = $this->process([$val->value]); $val = new Statement($tmp[0], $this->process($val->attributes)); } } $res[$key] = $val; } return $res; } /** * Generates configuration in NEON format. * @return string */ public function dump(array $data) { $tmp = []; foreach ($data as $name => $secData) { if ($parent = Helpers::takeParent($secData)) { $name .= ' ' . self::INHERITING_SEPARATOR . ' ' . $parent; } $tmp[$name] = $secData; } array_walk_recursive( $tmp, function (&$val) { if ($val instanceof Statement) { $val = self::statementToEntity($val); } } ); return "# generated by Nette\n\n" . Neon\Neon::encode($tmp, Neon\Neon::BLOCK); } /** * @return Neon\Entity */ private static function statementToEntity(Statement $val) { array_walk_recursive( $val->arguments, function (&$val) { if ($val instanceof Statement) { $val = self::statementToEntity($val); } } ); if (is_array($val->getEntity()) && $val->getEntity()[0] instanceof Statement) { return new Neon\Entity( Neon\Neon::CHAIN, [ self::statementToEntity($val->getEntity()[0]), new Neon\Entity('::' . $val->getEntity()[1], $val->arguments), ] ); } else { return new Neon\Entity($val->getEntity(), $val->arguments); } } } src/DI/Config/IAdapter.php 0000666 00000000723 13436756201 0011253 0 ustar 00 Adapters\PhpAdapter::class, 'ini' => Adapters\IniAdapter::class, 'neon' => Adapters\NeonAdapter::class, ]; private $dependencies = []; private $loadedFiles = []; /** * Reads configuration from file. * @param string file name * @param string optional section to load * @return array */ public function load($file, $section = null) { 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); if ($section) { if (isset($data[self::INCLUDES_KEY])) { throw new Nette\InvalidStateException("Section 'includes' must be placed under some top section in file '$file'."); } $data = $this->getSection($data, $section, $file); } // include child files $merged = []; if (isset($data[self::INCLUDES_KEY])) { Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'"); foreach ($data[self::INCLUDES_KEY] as $include) { if (!preg_match('#([a-z]+:)?[/\\\\]#Ai', $include)) { $include = dirname($file) . '/' . $include; } $merged = Helpers::merge($this->load($include), $merged); } } unset($data[self::INCLUDES_KEY], $this->loadedFiles[$file]); return Helpers::merge($data, $merged); } /** * Save configuration to file. * @param array * @param string file * @return void */ public function save($data, $file) { if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === false) { throw new Nette\IOException("Cannot write file '$file'."); } } /** * Returns configuration files. * @return array */ public function getDependencies() { return array_unique($this->dependencies); } /** * Registers adapter for given file extension. * @param string file extension * @param string|IAdapter * @return static */ public function addAdapter($extension, $adapter) { $this->adapters[strtolower($extension)] = $adapter; return $this; } /** @return IAdapter */ private function getAdapter($file) { $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]; } private function getSection(array $data, $key, $file) { Validators::assertField($data, $key, 'array|null', "section '%' in file '$file'"); $item = $data[$key]; if ($parent = Helpers::takeParent($item)) { $item = Helpers::merge($item, $this->getSection($data, $parent, $file)); } return $item; } } src/DI/exceptions.php 0000666 00000000744 13436756201 0010541 0 ustar 00 message = $message; return $this; } } src/DI/Compiler.php 0000666 00000034025 13436756201 0010131 0 ustar 00 1, 'parameters' => 1]; public function __construct(ContainerBuilder $builder = null) { $this->builder = $builder ?: new ContainerBuilder; $this->dependencies = new DependencyChecker; } /** * Add custom configurator extension. * @param string|null * @return static */ public function addExtension($name, CompilerExtension $extension) { if ($name === null) { $name = '_' . count($this->extensions); } elseif (isset($this->extensions[$name]) || isset(self::$reserved[$name])) { throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved."); } $this->extensions[$name] = $extension->setCompiler($this, $name); return $this; } /** * @return array */ public function getExtensions($type = null) { return $type ? array_filter($this->extensions, function ($item) use ($type) { return $item instanceof $type; }) : $this->extensions; } /** * @return ContainerBuilder */ public function getContainerBuilder() { return $this->builder; } /** * @return static */ public function setClassName($className) { $this->className = $className; return $this; } /** * Adds new configuration. * @return static */ public function addConfig(array $config) { $this->config = Config\Helpers::merge($config, $this->config); return $this; } /** * Adds new configuration from file. * @return static */ public function loadConfig($file) { $loader = new Config\Loader; $this->addConfig($loader->load($file)); $this->dependencies->add($loader->getDependencies()); return $this; } /** * Returns configuration. * @return array */ public function getConfig() { return $this->config; } /** * Sets the names of dynamic parameters. * @return static */ public function setDynamicParameterNames(array $names) { $this->dynamicParams = $names; return $this; } /** * Adds dependencies to the list. * @param array of ReflectionClass|\ReflectionFunctionAbstract|string * @return static */ public function addDependencies(array $deps) { $this->dependencies->add(array_filter($deps)); return $this; } /** * Exports dependencies. * @return array */ public function exportDependencies() { return $this->dependencies->export(); } /** * @return string */ public function compile() { if (func_num_args()) { trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::addConfig() and Compiler::setClassName().', E_USER_DEPRECATED); $this->config = func_get_arg(0) ?: $this->config; $this->className = @func_get_arg(1) ?: $this->className; } $this->processParameters(); $this->processExtensions(); $this->processServices(); $classes = $this->generateCode(); return implode("\n\n\n", $classes); } /** @internal */ public function processParameters() { $params = isset($this->config['parameters']) ? $this->config['parameters'] : []; foreach ($this->dynamicParams as $key) { $params[$key] = array_key_exists($key, $params) ? ContainerBuilder::literal('isset($this->parameters[?]) \? $this->parameters[?] : ?', [$key, $key, $params[$key]]) : ContainerBuilder::literal('$this->parameters[?]', [$key]); } $this->builder->parameters = Helpers::expand($params, $params, true); } /** @internal */ public function processExtensions() { $this->config = Helpers::expand(array_diff_key($this->config, self::$reserved), $this->builder->parameters) + array_intersect_key($this->config, self::$reserved); foreach ($first = $this->getExtensions(Extensions\ExtensionsExtension::class) as $name => $extension) { $extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : []); $extension->loadConfiguration(); } $last = $this->getExtensions(Extensions\InjectExtension::class); $this->extensions = array_merge(array_diff_key($this->extensions, $last), $last); $extensions = array_diff_key($this->extensions, $first); foreach (array_intersect_key($extensions, $this->config) as $name => $extension) { $extension->setConfig($this->config[$name] ?: []); } foreach ($extensions as $extension) { $extension->loadConfiguration(); } if ($extra = array_diff_key($this->extensions, $extensions, $first)) { $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->config, self::$reserved, $this->extensions))) { $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys(self::$reserved + $this->extensions), $extra); throw new Nette\InvalidStateException( "Found section '$extra' in configuration, but corresponding extension is missing" . ($hint ? ", did you mean '$hint'?" : '.') ); } } /** @internal */ public function processServices() { if (isset($this->config['services'])) { self::loadDefinitions($this->builder, $this->config['services']); } } /** @internal */ public function generateCode() { if (func_num_args()) { trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::setClassName().', E_USER_DEPRECATED); $this->className = func_get_arg(0) ?: $this->className; } $this->builder->prepareClassList(); foreach ($this->extensions as $extension) { $extension->beforeCompile(); $this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]); } $generator = new PhpGenerator($this->builder); $classes = $generator->generate($this->className); $classes[0]->addMethod('initialize'); $this->dependencies->add($this->builder->getDependencies()); foreach ($this->extensions as $extension) { $extension->afterCompile($classes[0]); } return $classes; } /********************* tools ****************d*g**/ /** * Adds service definitions from configuration. * @return void */ public static function loadDefinitions(ContainerBuilder $builder, array $services, $namespace = null) { $depths = []; foreach ($services as $name => $def) { $path = []; while (Config\Helpers::isInheriting($def)) { $path[] = $def; $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : []; if (in_array($def, $path, true)) { throw new ServiceCreationException("Circular reference detected for service '$name'."); } } $depths[$name] = count($path) + preg_match('#^@[\w\\\\]+\z#', $name); } @array_multisort($depths, $services); // @ may trigger E_NOTICE: Object of class Nette\DI\Statement could not be converted to int foreach ($services as $name => $def) { if (is_int($name)) { $postfix = $def instanceof Statement && is_string($def->getEntity()) ? '.' . $def->getEntity() : (is_scalar($def) ? ".$def" : ''); $name = (count($builder->getDefinitions()) + 1) . preg_replace('#\W+#', '_', $postfix); } elseif (preg_match('#^@[\w\\\\]+\z#', $name)) { $name = $builder->getByType(substr($name, 1), true); } elseif ($namespace) { $name = $namespace . '.' . $name; } if ($def === false) { $builder->removeDefinition($name); continue; } if ($namespace) { $def = Helpers::prefixServiceName($def, $namespace); } $params = $builder->parameters; if (is_array($def) && isset($def['parameters'])) { foreach ((array) $def['parameters'] as $k => $v) { $v = explode(' ', is_int($k) ? $v : $k); $params[end($v)] = $builder::literal('$' . end($v)); } } $def = Helpers::expand($def, $params); if (is_array($def) && !empty($def['alteration']) && !$builder->hasDefinition($name)) { throw new ServiceCreationException("Service '$name': missing original definition for alteration."); } if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) { if ($parent !== Config\Helpers::OVERWRITE) { trigger_error("Section inheritance $name < $parent is deprecated.", E_USER_DEPRECATED); } $builder->removeDefinition($name); $definition = $builder->addDefinition( $name, $parent === Config\Helpers::OVERWRITE ? null : clone $builder->getDefinition($parent) ); } elseif ($builder->hasDefinition($name)) { $definition = $builder->getDefinition($name); } else { $definition = $builder->addDefinition($name); } try { static::loadDefinition($definition, $def); } catch (\Exception $e) { throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e); } } } /** * Parses single service definition from configuration. * @return void */ public static function loadDefinition(ServiceDefinition $definition, $config) { if ($config === null) { return; } elseif (is_string($config) && interface_exists($config)) { $config = ['class' => null, 'implement' => $config]; } elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) { $config = ['class' => null, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments)]; } elseif (!is_array($config) || isset($config[0], $config[1])) { $config = ['class' => null, 'factory' => $config]; } if (array_key_exists('create', $config)) { trigger_error("Key 'create' is deprecated, use 'factory' or 'type' in configuration.", E_USER_DEPRECATED); $config['factory'] = $config['create']; unset($config['create']); } $known = ['type', 'class', 'factory', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags', 'alteration']; if ($error = array_diff(array_keys($config), $known)) { $hints = array_filter(array_map(function ($error) use ($known) { return Nette\Utils\ObjectMixin::getSuggestion($known, $error); }, $error)); $hint = $hints ? ", did you mean '" . implode("', '", $hints) . "'?" : '.'; throw new Nette\InvalidStateException(sprintf("Unknown key '%s' in definition of service$hint", implode("', '", $error))); } $config = Helpers::filterArguments($config); if (array_key_exists('class', $config) || array_key_exists('factory', $config)) { $definition->setType(null); $definition->setFactory(null); } if (array_key_exists('type', $config)) { Validators::assertField($config, 'type', 'string|null'); $definition->setType($config['type']); if (array_key_exists('class', $config)) { throw new Nette\InvalidStateException("Unexpected 'class' when 'type' is used."); } } if (array_key_exists('class', $config)) { Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null'); if (!$config['class'] instanceof Statement) { $definition->setType($config['class']); } $definition->setFactory($config['class']); } if (array_key_exists('factory', $config)) { Validators::assertField($config, 'factory', 'callable|Nette\DI\Statement|null'); $definition->setFactory($config['factory']); } if (array_key_exists('arguments', $config)) { Validators::assertField($config, 'arguments', 'array'); $arguments = $config['arguments']; if (!Config\Helpers::takeParent($arguments) && !Nette\Utils\Arrays::isList($arguments) && $definition->getFactory()) { $arguments += $definition->getFactory()->arguments; } $definition->setArguments($arguments); } if (isset($config['setup'])) { if (Config\Helpers::takeParent($config['setup'])) { $definition->setSetup([]); } Validators::assertField($config, 'setup', 'list'); foreach ($config['setup'] as $id => $setup) { Validators::assert($setup, 'callable|Nette\DI\Statement|array:1', "setup item #$id"); if (is_array($setup)) { $setup = new Statement(key($setup), array_values($setup)); } $definition->addSetup($setup); } } if (isset($config['parameters'])) { Validators::assertField($config, 'parameters', 'array'); $definition->setParameters($config['parameters']); } if (isset($config['implement'])) { Validators::assertField($config, 'implement', 'string'); $definition->setImplement($config['implement']); $definition->setAutowired(true); } if (isset($config['autowired'])) { Validators::assertField($config, 'autowired', 'bool|string|array'); $definition->setAutowired($config['autowired']); } if (isset($config['dynamic'])) { Validators::assertField($config, 'dynamic', 'bool'); $definition->setDynamic($config['dynamic']); } if (isset($config['inject'])) { Validators::assertField($config, 'inject', 'bool'); $definition->addTag(Extensions\InjectExtension::TAG_INJECT, $config['inject']); } if (isset($config['run'])) { trigger_error("Option 'run' is deprecated, use 'run' as tag.", E_USER_DEPRECATED); $config['tags']['run'] = (bool) $config['run']; } if (isset($config['tags'])) { Validators::assertField($config, 'tags', 'array'); if (Config\Helpers::takeParent($config['tags'])) { $definition->setTags([]); } foreach ($config['tags'] as $tag => $attrs) { if (is_int($tag) && is_string($attrs)) { $definition->addTag($attrs); } else { $definition->addTag($tag, $attrs); } } } } /** @deprecated */ public static function filterArguments(array $args) { return Helpers::filterArguments($args); } /** @deprecated */ public static function parseServices(ContainerBuilder $builder, array $config, $namespace = null) { self::loadDefinitions($builder, isset($config['services']) ? $config['services'] : [], $namespace); } /** @deprecated */ public static function parseService(ServiceDefinition $definition, $config) { self::loadDefinition($definition, $config); } } src/DI/PhpReflection.php 0000666 00000015023 13436756201 0011116 0 ustar 00 getDocComment()) { throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.'); } $ok = true; } $name = preg_quote($name, '#'); if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) { return isset($m[1]) ? $m[1] : ''; } } /** * Returns declaring class or trait. * @return \ReflectionClass */ public static function getDeclaringClass(\ReflectionProperty $prop) { foreach ($prop->getDeclaringClass()->getTraits() as $trait) { if ($trait->hasProperty($prop->getName())) { return self::getDeclaringClass($trait->getProperty($prop->getName())); } } return $prop->getDeclaringClass(); } /** * @return string|null */ public static function getParameterType(\ReflectionParameter $param) { if (PHP_VERSION_ID >= 70000) { $type = $param->hasType() ? (string) $param->getType() : null; return strtolower($type) === 'self' ? $param->getDeclaringClass()->getName() : $type; } elseif ($param->isArray() || $param->isCallable()) { return $param->isArray() ? 'array' : 'callable'; } else { try { return ($ref = $param->getClass()) ? $ref->getName() : null; } catch (\ReflectionException $e) { if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) { return $m[1]; } throw $e; } } } /** * @return string|null */ public static function getReturnType(\ReflectionFunctionAbstract $func) { if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) { $type = (string) $func->getReturnType(); return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type; } $type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return')); if ($type) { return $func instanceof \ReflectionMethod ? self::expandClassName($type, $func->getDeclaringClass()) : ltrim($type, '\\'); } } /** * @param string * @return bool */ public static function isBuiltinType($type) { return in_array(strtolower($type), ['string', 'int', 'float', 'bool', 'array', 'callable'], true); } /** * Returns class and all its descendants. * @return string[] */ public static function getClassTree(\ReflectionClass $class) { $addTraits = function ($types) use (&$addTraits) { if ($traits = array_merge(...array_map('class_uses', array_values($types)))) { $types += $traits + $addTraits($traits); } return $types; }; $class = $class->getName(); return array_values($addTraits([$class] + class_parents($class) + class_implements($class))); } /** * Expands class name into full name. * @param string * @return string full name * @throws Nette\InvalidArgumentException */ public static function expandClassName($name, \ReflectionClass $rc) { $lower = strtolower($name); if (empty($name)) { throw new Nette\InvalidArgumentException('Class name must not be empty.'); } elseif (self::isBuiltinType($lower)) { return $lower; } elseif ($lower === 'self' || $lower === 'static' || $lower === '$this') { return $rc->getName(); } elseif ($name[0] === '\\') { // fully qualified name return ltrim($name, '\\'); } $uses = self::getUseStatements($rc); $parts = explode('\\', $name, 2); if (isset($uses[$parts[0]])) { $parts[0] = $uses[$parts[0]]; return implode('\\', $parts); } elseif ($rc->inNamespace()) { return $rc->getNamespaceName() . '\\' . $name; } else { return $name; } } /** * @return array of [alias => class] */ public static function getUseStatements(\ReflectionClass $class) { static $cache = []; if (!isset($cache[$name = $class->getName()])) { if ($class->isInternal()) { $cache[$name] = []; } else { $code = file_get_contents($class->getFileName()); $cache = self::parseUseStatements($code, $name) + $cache; } } return $cache[$name]; } /** * Parses PHP code. * @param string * @return array of [class => [alias => class, ...]] */ public static function parseUseStatements($code, $forClass = null) { $tokens = token_get_all($code); $namespace = $class = $classLevel = $level = null; $res = $uses = []; while ($token = current($tokens)) { next($tokens); switch (is_array($token) ? $token[0] : $token) { case T_NAMESPACE: $namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\'); $uses = []; break; case T_CLASS: case T_INTERFACE: case T_TRAIT: if ($name = self::fetch($tokens, T_STRING)) { $class = $namespace . $name; $classLevel = $level + 1; $res[$class] = $uses; if ($class === $forClass) { return $res; } } break; case T_USE: while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) { $name = ltrim($name, '\\'); if (self::fetch($tokens, '{')) { while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) { if (self::fetch($tokens, T_AS)) { $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; } else { $tmp = explode('\\', $suffix); $uses[end($tmp)] = $name . $suffix; } if (!self::fetch($tokens, ',')) { break; } } } elseif (self::fetch($tokens, T_AS)) { $uses[self::fetch($tokens, T_STRING)] = $name; } else { $tmp = explode('\\', $name); $uses[end($tmp)] = $name; } if (!self::fetch($tokens, ',')) { break; } } break; case T_CURLY_OPEN: case T_DOLLAR_OPEN_CURLY_BRACES: case '{': $level++; break; case '}': if ($level === $classLevel) { $class = $classLevel = null; } $level--; } } return $res; } private static function fetch(&$tokens, $take) { $res = null; while ($token = current($tokens)) { list($token, $s) = is_array($token) ? $token : [$token, $token]; if (in_array($token, (array) $take, true)) { $res .= $s; } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) { break; } next($tokens); } return $res; } } src/DI/ContainerLoader.php 0000666 00000006404 13436756201 0011430 0 ustar 00 tempDirectory = $tempDirectory; $this->autoRebuild = $autoRebuild; } /** * @param callable function (Nette\DI\Compiler $compiler): string|null * @param mixed * @return string */ public function load($generator, $key = null) { if (!is_callable($generator)) { // back compatiblity trigger_error(__METHOD__ . ': order of arguments has been swapped.', E_USER_DEPRECATED); list($generator, $key) = [$key, $generator]; } $class = $this->getClassName($key); if (!class_exists($class, false)) { $this->loadFile($class, $generator); } return $class; } /** * @return string */ public function getClassName($key) { return 'Container_' . substr(md5(serialize($key)), 0, 10); } /** * @return void */ private function loadFile($class, $generator) { $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'. " . error_get_last()['message']); } elseif (!@flock($handle, LOCK_EX)) { // @ is escalated to exception throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'. " . error_get_last()['message']); } if (!is_file($file) || $this->isExpired($file, $updatedMeta)) { if (isset($updatedMeta)) { $toWrite["$file.meta"] = $updatedMeta; } else { list($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($file, &$updatedMeta = null) { if ($this->autoRebuild) { $meta = @unserialize((string) file_get_contents("$file.meta")); // @ - file may not exist $orig = $meta[2]; return empty($meta[0]) || DependencyChecker::isExpired(...$meta) || ($orig !== $meta[2] && $updatedMeta = serialize($meta)); } return false; } /** * @return array of (code, file[]) */ protected function generate($class, $generator) { $compiler = new Compiler; $compiler->setClassName($class); $code = call_user_func_array($generator, [&$compiler]) ?: $compiler->compile(); return [ "exportDependencies()), ]; } } src/DI/Statement.php 0000666 00000002431 13436756201 0010317 0 ustar 00 entity = $entity; $this->arguments = $arguments; } /** @deprecated */ public function setEntity($entity) { trigger_error(__METHOD__ . ' is deprecated, change Statement object itself.', E_USER_DEPRECATED); $this->__construct($entity, $this->arguments); return $this; } public function getEntity() { return $this->entity; } } src/DI/ContainerBuilder.php 0000666 00000065237 13436756201 0011621 0 ustar 00 service */ private $aliases = []; /** @var array for auto-wiring */ private $classList = []; /** @var bool */ private $classListNeedsRefresh = true; /** @var string[] of classes excluded from auto-wiring */ private $excludedClasses = []; /** @var array */ private $dependencies = []; /** @var string */ private $currentService; /** * Adds new service definition. * @param string * @return ServiceDefinition */ public function addDefinition($name, ServiceDefinition $definition = null) { $this->classListNeedsRefresh = true; if (!is_string($name) || !$name) { // 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($name))); } $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; if (isset($this->definitions[$name])) { throw new Nette\InvalidStateException("Service '$name' has already been added."); } if (!$definition) { $definition = new ServiceDefinition; } $definition->setNotifier(function () { $this->classListNeedsRefresh = true; }); return $this->definitions[$name] = $definition; } /** * Removes the specified service definition. * @param string * @return void */ public function removeDefinition($name) { $this->classListNeedsRefresh = true; $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; unset($this->definitions[$name]); } /** * Gets the service definition. * @param string * @return ServiceDefinition */ public function getDefinition($name) { $service = isset($this->aliases[$name]) ? $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 ServiceDefinition[] */ public function getDefinitions() { return $this->definitions; } /** * Does the service definition or alias exist? * @param string * @return bool */ public function hasDefinition($name) { $name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name; return isset($this->definitions[$name]); } /** * @param string * @param string */ public function addAlias($alias, $service) { if (!is_string($alias) || !$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 (!is_string($service) || !$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. * @return void */ public function removeAlias($alias) { unset($this->aliases[$alias]); } /** * Gets all service aliases. * @return array */ public function getAliases() { return $this->aliases; } /** * @param string[] * @return static */ public function addExcludedClasses(array $types) { 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]; } } return $this; } /** * @deprecated */ public function setClassName($name) { trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED); return $this; } /** * @deprecated */ public function getClassName() { trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED); } /********************* class resolving ****************d*g**/ /** * Resolves service name by type. * @param string class or interface * @param bool throw exception if service doesn't exist? * @return string|null service name or null * @throws ServiceCreationException */ public function getByType($type, $throw = false) { $type = Helpers::normalizeClass($type); if ( $this->currentService !== null && is_a($this->definitions[$this->currentService]->getType(), $type, true) ) { return $this->currentService; } $types = $this->getClassList(); if (empty($types[$type][true])) { if ($throw) { throw new MissingServiceException("Service of type '$type' not found."); } return; } elseif (count($types[$type][true]) === 1) { return $types[$type][true][0]; } else { $list = $types[$type][true]; 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 definition of the specified type. * @param string * @return ServiceDefinition */ public function getDefinitionByType($type) { return $this->getDefinition($this->getByType($type, true)); } /** * Gets the service names and definitions of the specified type. * @param string * @return ServiceDefinition[] */ public function findByType($type) { $type = Helpers::normalizeClass($type); $found = []; $types = $this->getClassList(); if (!empty($types[$type])) { foreach (array_merge(...array_values($types[$type])) as $name) { $found[$name] = $this->definitions[$name]; } } return $found; } /** * Gets the service objects of the specified tag. * @param string * @return array of [service name => tag attributes] */ public function findByTag($tag) { $found = []; foreach ($this->definitions as $name => $def) { if (($tmp = $def->getTag($tag)) !== null) { $found[$name] = $tmp; } } return $found; } /** * @internal */ public function getClassList() { if ($this->classList !== false && $this->classListNeedsRefresh) { $this->prepareClassList(); $this->classListNeedsRefresh = false; } return $this->classList ?: []; } /** * Generates $dependencies, $classList and normalizes class names. * @return void * @internal */ public function prepareClassList() { unset($this->definitions[self::THIS_CONTAINER]); $this->addDefinition(self::THIS_CONTAINER)->setType(Container::class); $this->classList = false; foreach ($this->definitions as $name => $def) { // prepare generated factories if ($def->getImplement()) { $this->resolveImplement($def, $name); } if ($def->isDynamic()) { if (!$def->getType()) { throw new ServiceCreationException("Type is missing in definition of service '$name'."); } $def->setFactory(null); continue; } // complete class-factory pairs if (!$def->getEntity()) { if (!$def->getType()) { throw new ServiceCreationException("Factory and type are missing in definition of service '$name'."); } $def->setFactory($def->getType(), ($factory = $def->getFactory()) ? $factory->arguments : []); } // auto-disable autowiring for aliases if ( $def->getAutowired() === true && ($alias = $this->getServiceName($def->getFactory()->getEntity())) && (!$def->getImplement() || (!Strings::contains($alias, '\\') && $this->definitions[$alias]->getImplement())) ) { $def->setAutowired(false); } } // resolve and check classes foreach ($this->definitions as $name => $def) { $this->resolveServiceType($name); } // build auto-wiring list $this->classList = $preferred = []; foreach ($this->definitions as $name => $def) { if ($type = $def->getImplement() ?: $def->getType()) { $defAutowired = $def->getAutowired(); if (is_array($defAutowired)) { foreach ($defAutowired as $k => $autowiredType) { if ($autowiredType === self::THIS_SERVICE) { $defAutowired[$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) { $autowired = $defAutowired && empty($this->excludedClasses[$parent]); if ($autowired && is_array($defAutowired)) { $autowired = false; foreach ($defAutowired as $autowiredType) { if (is_a($parent, $autowiredType, true)) { if (empty($preferred[$parent]) && isset($this->classList[$parent][true])) { $this->classList[$parent][false] = array_merge(...$this->classList[$parent]); $this->classList[$parent][true] = []; } $preferred[$parent] = $autowired = true; break; } } } elseif (isset($preferred[$parent])) { $autowired = false; } $this->classList[$parent][$autowired][] = (string) $name; } } } } private function resolveImplement(ServiceDefinition $def, $name) { $interface = $def->getImplement(); if (!interface_exists($interface)) { throw new ServiceCreationException("Interface $interface used in service '$name' not found."); } $interface = Helpers::normalizeClass($interface); $def->setImplement($interface); $rc = new ReflectionClass($interface); $this->addDependency($rc); $method = $rc->hasMethod('create') ? $rc->getMethod('create') : ($rc->hasMethod('get') ? $rc->getMethod('get') : null); if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) { throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get()."); } $def->setImplementMode($rc->hasMethod('create') ? $def::IMPLEMENT_MODE_CREATE : $def::IMPLEMENT_MODE_GET); $methodName = Reflection::toString($method) . '()'; if (!$def->getType() && !$def->getEntity()) { $returnType = Helpers::getReturnType($method); if (!$returnType) { throw new ServiceCreationException("Method $methodName used in service '$name' has not return type hint or annotation @return."); } elseif (!class_exists($returnType)) { throw new ServiceCreationException("Check a type hint or annotation @return of the $methodName method used in service '$name', class '$returnType' cannot be found."); } $def->setType($returnType); } if ($rc->hasMethod('get')) { if ($method->getParameters()) { throw new ServiceCreationException("Method $methodName used in service '$name' must have no arguments."); } elseif ($def->getSetup()) { throw new ServiceCreationException("Service accessor '$name' must have no setup."); } if (!$def->getEntity()) { $def->setFactory('@\\' . ltrim($def->getType(), '\\')); } elseif (!$this->getServiceName($def->getFactory()->getEntity())) { throw new ServiceCreationException("Invalid factory in service '$name' definition."); } } if (!$def->parameters) { $ctorParams = []; if (!$def->getEntity()) { $def->setFactory($def->getType(), $def->getFactory() ? $def->getFactory()->arguments : []); } if ( ($class = $this->resolveEntityType($def->getFactory(), [$name => 1])) && ($ctor = (new ReflectionClass($class))->getConstructor()) ) { foreach ($ctor->getParameters() as $param) { $ctorParams[$param->getName()] = $param; } } foreach ($method->getParameters() as $param) { $hint = Reflection::getParameterType($param); if (isset($ctorParams[$param->getName()])) { $arg = $ctorParams[$param->getName()]; if ($hint !== Reflection::getParameterType($arg)) { throw new ServiceCreationException("Type hint for \${$param->getName()} in $methodName doesn't match type hint in $class constructor."); } $def->getFactory()->arguments[$arg->getPosition()] = self::literal('$' . $arg->getName()); } elseif (!$def->getSetup()) { $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys($ctorParams), $param->getName()); throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $methodName" . ($hint ? ", did you mean \${$hint}?" : '.')); } $nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null); $paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName(); if ($param->isDefaultValueAvailable()) { $def->parameters[$paramDef] = Reflection::getParameterDefaultValue($param); } else { $def->parameters[] = $paramDef; } } } } /** @return string|null */ private function resolveServiceType($name, $recursive = []) { if (isset($recursive[$name])) { throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive)))); } $recursive[$name] = true; $def = $this->definitions[$name]; $factoryClass = $def->getFactory() ? $this->resolveEntityType($def->getFactory()->getEntity(), $recursive) : null; // call always to check entities if ($type = $def->getType() ?: $factoryClass) { if (!class_exists($type) && !interface_exists($type)) { throw new ServiceCreationException("Class or interface '$type' used in service '$name' not found."); } $type = Helpers::normalizeClass($type); $def->setType($type); if (count($recursive) === 1) { $this->addDependency(new ReflectionClass($factoryClass ?: $type)); } } elseif ($def->getAutowired()) { throw new ServiceCreationException("Unknown type of service '$name', declare return type of factory method (for PHP 5 use annotation @return)"); } return $type; } /** @return string|null */ private function resolveEntityType($entity, $recursive = []) { $entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity); $serviceName = current(array_slice(array_keys($recursive), -1)); if (is_array($entity)) { if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) { $entity[0] = $this->resolveEntityType($entity[0], $recursive); if (!$entity[0]) { return; } elseif (isset($this->definitions[$service]) && $this->definitions[$service]->getImplement()) { // @Implement::create return $entity[1] === 'create' ? $this->resolveServiceType($service, $recursive) : null; } } try { $reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity); $refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : null; } catch (\ReflectionException $e) { } if (isset($e) || ($refClass && (!$reflection->isPublic() || ($refClass->isTrait() && !$reflection->isStatic()) ))) { throw new ServiceCreationException(sprintf("Method %s() used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $serviceName)); } $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() used in service '%s' correct?", $type, Nette\Utils\Callback::toString($entity), $serviceName)); } return $type; } elseif ($service = $this->getServiceName($entity)) { // alias or factory if (Strings::contains($service, '\\')) { // @\Class return $service; } return $this->definitions[$service]->getImplement() ?: $this->definitions[$service]->getType() ?: $this->resolveServiceType($service, $recursive); } elseif (is_string($entity)) { // class if (!class_exists($entity)) { throw new ServiceCreationException("Class $entity used in service '$serviceName' not found."); } return $entity; } } /** * @return void */ public function complete() { $this->prepareClassList(); foreach ($this->definitions as $name => $def) { if ($def->isDynamic()) { continue; } $this->currentService = null; $entity = $def->getFactory()->getEntity(); $serviceRef = $this->getServiceName($entity); $factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE ? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef]) : $def->getFactory(); try { $def->setFactory($this->completeStatement($factory)); $this->classListNeedsRefresh = false; $this->currentService = $name; $setups = $def->getSetup(); foreach ($setups as &$setup) { if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === false) { // auto-prepend @self $setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments); } $setup = $this->completeStatement($setup); } $def->setSetup($setups); } catch (\Exception $e) { $message = "Service '$name' (type of {$def->getType()}): " . $e->getMessage(); throw $e instanceof ServiceCreationException ? $e->setMessage($message) : new ServiceCreationException($message, 0, $e); } finally { $this->currentService = null; } } } /** * @return Statement */ public function completeStatement(Statement $statement) { $entity = $this->normalizeEntity($statement->getEntity()); $arguments = $statement->arguments; if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal } elseif ($service = $this->getServiceName($entity)) { // factory calling $params = []; foreach ($this->definitions[$service]->parameters as $k => $v) { $params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v)); } $rm = new \ReflectionFunction(eval('return function(' . implode(', ', $params) . ') {};')); $arguments = Helpers::autowireArguments($rm, $arguments, $this); $entity = '@' . $service; } elseif ($entity === 'not') { // operator } elseif (is_string($entity)) { // class name 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()) { $this->addDependency($constructor); $arguments = Helpers::autowireArguments($constructor, $arguments, $this); } elseif ($arguments) { throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor."); } } elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) { throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity))); } elseif (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?\z#', $entity[1])) { throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given."); } elseif ($entity[0] === '') { // globalFunc 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]); $this->addDependency($rf); $arguments = Helpers::autowireArguments($rf, $arguments, $this); } else { if ($entity[0] instanceof Statement) { $entity[0] = $this->completeStatement($entity[0]); } elseif ($service = $this->getServiceName($entity[0])) { // service method $entity[0] = '@' . $service; } 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 = empty($service) || $entity[1] === 'create' ? $this->resolveEntityType($entity[0]) : $this->definitions[$service]->getType() ) { $arguments = $this->autowireArguments($type, $entity[1], $arguments); } } try { array_walk_recursive($arguments, function (&$val) { if ($val instanceof Statement) { $val = $this->completeStatement($val); } elseif ($val === $this) { trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED); $val = self::literal('$this'); } elseif ($val instanceof ServiceDefinition) { $val = '@' . current(array_keys($this->getDefinitions(), $val, true)); } elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') { $pair = explode('::', $val, 2); $name = $this->getServiceName($pair[0]); if (!isset($pair[1])) { // @service $val = '@' . $name; } elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT $val = self::literal($this->getDefinition($name)->getType() . '::' . $pair[1]); } else { // @service::property $val = new Statement(['@' . $name, '$' . $pair[1]]); } } }); } catch (ServiceCreationException $e) { if ((is_string($entity) || is_array($entity)) && !strpos($e->getMessage(), ' (used in')) { $desc = is_string($entity) ? $entity . '::__construct' : (is_string($entity[0]) ? ($entity[0] . '::') : 'method ') . $entity[1]; $e->setMessage($e->getMessage() . " (used in $desc)"); } throw $e; } return new Statement($entity, $arguments); } /** * Adds item to the list of dependencies. * @param ReflectionClass|\ReflectionFunctionAbstract|string * @return static * @internal */ public function addDependency($dep) { $this->dependencies[] = $dep; return $this; } /** * Returns the list of dependencies. * @return array */ public function getDependencies() { return $this->dependencies; } /** * Expands %placeholders% in strings. * @return mixed * @deprecated */ public function expand($value) { return Helpers::expand($value, $this->parameters); } /** * @return Nette\PhpGenerator\PhpLiteral */ public static function literal($code, array $args = null) { return new Nette\PhpGenerator\PhpLiteral($args === null ? $code : PhpHelpers::formatArgs($code, $args)); } /** * @return string|array Class, @service, [Class, member], [@service, member], [, globalFunc], [Statement, member] * @internal */ public function normalizeEntity($entity) { if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method] $entity = explode('::', $entity); } if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...] $entity[0] = '@' . current(array_keys($this->definitions, $entity[0], true)); } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName $entity = '@' . current(array_keys($this->definitions, $entity, true)); } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...] trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED); $entity[0] = '@' . self::THIS_CONTAINER; } return $entity; } /** * Converts @service or @\Class -> service name and checks its existence. * @return string of false, if argument is not service name * @internal */ public function getServiceName($arg) { if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) { return false; } $service = substr($arg, 1); if ($service === self::THIS_SERVICE) { $service = $this->currentService; } if (Strings::contains($service, '\\')) { if ($this->classList === false) { // may be disabled by prepareClassList return $service; } $res = $this->getByType($service); if (!$res) { throw new ServiceCreationException("Reference to missing service of type $service."); } return $res; } $service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service; if (!isset($this->definitions[$service])) { throw new ServiceCreationException("Reference to missing service '$service'."); } return $service; } /** * Creates a list of arguments using autowiring. * @return array * @internal */ public function autowireArguments($class, $method, array $arguments) { $rc = new ReflectionClass($class); if (!$rc->hasMethod($method)) { if (!Nette\Utils\Arrays::isList($arguments)) { throw new ServiceCreationException("Unable to pass specified arguments to $class::$method()."); } return $arguments; } $rm = $rc->getMethod($method); if (!$rm->isPublic()) { throw new ServiceCreationException("$class::$method() is not callable."); } $this->addDependency($rm); return Helpers::autowireArguments($rm, $arguments, $this); } /** @deprecated */ public function generateClasses($className = 'Container', $parentName = null) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); return (new PhpGenerator($this))->generate($className); } /** @deprecated */ public function formatStatement(Statement $statement) { trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED); return (new PhpGenerator($this))->formatStatement($statement); } /** @deprecated */ public function formatPhp($statement, $args) { array_walk_recursive($args, function (&$val) { if ($val instanceof Statement) { $val = $this->completeStatement($val); } elseif ($val === $this) { trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED); $val = self::literal('$this'); } elseif ($val instanceof ServiceDefinition) { $val = '@' . current(array_keys($this->getDefinitions(), $val, true)); } }); return (new PhpGenerator($this))->formatPhp($statement, $args); } } src/DI/DependencyChecker.php 0000666 00000011436 13436756201 0011723 0 ustar 00 dependencies = array_merge($this->dependencies, $deps); return $this; } /** * Exports dependencies. * @return array */ public function export() { $files = $phpFiles = $classes = $functions = []; foreach ($this->dependencies as $dep) { if (is_string($dep)) { $files[] = $dep; } elseif ($dep instanceof ReflectionClass) { if (empty($classes[$name = $dep->getName()])) { $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? * @return bool */ public static function isExpired($version, $files, &$phpFiles, $classes, $functions, $hash) { $current = @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 !== $current || ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions)); } private static function calculateHash($classes, $functions) { $hash = []; foreach ($classes as $name) { try { $class = new ReflectionClass($name); } catch (\ReflectionException $e) { return; } $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->getName(), $prop->getDocComment()]; } } foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { if ($method->getDeclaringClass() == $class) { // intentionally == $hash[] = [ $name, $method->getName(), $method->getDocComment(), self::hashParameters($method), PHP_VERSION_ID >= 70000 && $method->hasReturnType() ? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()] : null, ]; } } } $flip = array_flip($classes); foreach ($functions as $name) { try { $method = strpos($name, '::') ? new ReflectionMethod($name) : new \ReflectionFunction($name); } catch (\ReflectionException $e) { return; } $class = $method instanceof ReflectionMethod ? $method->getDeclaringClass() : null; if ($class && isset($flip[$class->getName()])) { continue; } $hash[] = [ $name, $class ? Reflection::getUseStatements($method->getDeclaringClass()) : null, $method->getDocComment(), self::hashParameters($method), PHP_VERSION_ID >= 70000 && $method->hasReturnType() ? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()] : null, ]; } return md5(serialize($hash)); } private static function hashParameters(\ReflectionFunctionAbstract $method) { $res = []; if (PHP_VERSION_ID < 70000 && $method->getNumberOfParameters() && $method->getFileName()) { $res[] = file($method->getFileName())[$method->getStartLine() - 1]; } foreach ($method->getParameters() as $param) { $res[] = [ $param->getName(), PHP_VERSION_ID >= 70000 ? [Reflection::getParameterType($param), $param->allowsNull()] : null, $param->isVariadic(), $param->isDefaultValueAvailable() ? [Reflection::getParameterDefaultValue($param)] : null, ]; } return $res; } } src/DI/ServiceDefinition.php 0000666 00000013724 13436756201 0011773 0 ustar 00 notifier); $this->type = $type; if ($args) { $this->setFactory($type, $args); } return $this; } /** * @return string|null * @deprecated */ public function getClass() { return $this->type; } /** * @param string|null * @return static */ public function setType($type) { call_user_func($this->notifier); $this->type = $type; return $this; } /** * @return string|null */ public function getType() { return $this->type; } /** * @return static */ public function setFactory($factory, array $args = []) { call_user_func($this->notifier); $this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args); return $this; } /** * @return Statement|null */ public function getFactory() { return $this->factory; } /** * @return string|array|ServiceDefinition|null */ public function getEntity() { return $this->factory ? $this->factory->getEntity() : null; } /** * @return static */ public function setArguments(array $args = []) { if (!$this->factory) { $this->factory = new Statement($this->type); } $this->factory->arguments = $args; return $this; } /** * @param Statement[] * @return static */ public function setSetup(array $setup) { foreach ($setup as $v) { if (!$v instanceof Statement) { throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Statement[].'); } } $this->setup = $setup; return $this; } /** * @return Statement[] */ public function getSetup() { return $this->setup; } /** * @return static */ public function addSetup($entity, array $args = []) { $this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args); return $this; } /** * @return static */ public function setParameters(array $params) { $this->parameters = $params; return $this; } /** * @return array */ public function getParameters() { return $this->parameters; } /** * @return static */ public function setTags(array $tags) { $this->tags = $tags; return $this; } /** * @return array */ public function getTags() { return $this->tags; } /** * @return static */ public function addTag($tag, $attr = true) { $this->tags[$tag] = $attr; return $this; } /** * @return mixed */ public function getTag($tag) { return isset($this->tags[$tag]) ? $this->tags[$tag] : null; } /** * @param bool|string|string[] * @return static */ public function setAutowired($state = true) { call_user_func($this->notifier); $this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state; return $this; } /** * @return bool|string[] */ public function isAutowired() { return $this->autowired; } /** * @return bool|string[] */ public function getAutowired() { return $this->autowired; } /** * @param bool * @return static */ public function setDynamic($state = true) { $this->dynamic = (bool) $state; return $this; } /** * @return bool */ public function isDynamic() { return $this->dynamic; } /** * @param string * @return static */ public function setImplement($interface) { call_user_func($this->notifier); $this->implement = $interface; return $this; } /** * @return string|null */ public function getImplement() { return $this->implement; } /** * @param string * @return static */ public function setImplementMode($mode) { if (!in_array($mode, [self::IMPLEMENT_MODE_CREATE, self::IMPLEMENT_MODE_GET], true)) { throw new Nette\InvalidArgumentException('Argument must be get|create.'); } $this->implementMode = $mode; return $this; } /** * @return string|null */ public function getImplementMode() { return $this->implementMode; } /** @deprecated */ public function setImplementType($type) { trigger_error(__METHOD__ . '() is deprecated, use setImplementMode()', E_USER_DEPRECATED); return $this->setImplementMode($type); } /** @deprecated */ public function getImplementType() { trigger_error(__METHOD__ . '() is deprecated, use getImplementMode()', E_USER_DEPRECATED); return $this->implementMode; } /** @return static */ public function setInject($state = true) { //trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); return $this->addTag(Extensions\InjectExtension::TAG_INJECT, $state); } /** @return bool|null */ public function getInject() { //trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED); return $this->getTag(Extensions\InjectExtension::TAG_INJECT); } /** * @internal */ public function setNotifier(callable $notifier) { $this->notifier = $notifier; } public function __clone() { $this->factory = unserialize(serialize($this->factory)); $this->setup = unserialize(serialize($this->setup)); $this->notifier = 'pi'; } } src/DI/Container.php 0000666 00000020462 13436756201 0010301 0 ustar 00 parameters = $params + $this->parameters; } /** * @return array */ public function getParameters() { return $this->parameters; } /** * Adds the service to the container. * @param string * @param object * @return static */ public function addService($name, $service) { if (!is_string($name) || !$name) { throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name))); } $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; if (isset($this->registry[$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))); } elseif (isset($this->meta[self::SERVICES][$name]) && !$service instanceof $this->meta[self::SERVICES][$name]) { throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be instance of %s, %s given.", $name, $this->meta[self::SERVICES][$name], get_class($service))); } $this->registry[$name] = $service; return $this; } /** * Removes the service from the container. * @param string * @return void */ public function removeService($name) { $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; unset($this->registry[$name]); } /** * Gets the service object by name. * @param string * @return object * @throws MissingServiceException */ public function getService($name) { if (!isset($this->registry[$name])) { if (isset($this->meta[self::ALIASES][$name])) { return $this->getService($this->meta[self::ALIASES][$name]); } $this->registry[$name] = $this->createService($name); } return $this->registry[$name]; } /** * Gets the service type by name. * @param string * @return string * @throws MissingServiceException */ public function getServiceType($name) { if (isset($this->meta[self::ALIASES][$name])) { return $this->getServiceType($this->meta[self::ALIASES][$name]); } elseif (isset($this->meta[self::SERVICES][$name])) { return $this->meta[self::SERVICES][$name]; } else { throw new MissingServiceException("Service '$name' not found."); } } /** * Does the service exist? * @param string service name * @return bool */ public function hasService($name) { $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; return isset($this->registry[$name]) || (method_exists($this, $method = self::getMethodName($name)) && (new \ReflectionMethod($this, $method))->getName() === $method); } /** * Is the service created? * @param string service name * @return bool */ public function isCreated($name) { if (!$this->hasService($name)) { throw new MissingServiceException("Service '$name' not found."); } $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; return isset($this->registry[$name]); } /** * Creates new instance of the service. * @param string service name * @return object * @throws MissingServiceException */ public function createService($name, array $args = []) { $name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name; $method = self::getMethodName($name); if (isset($this->creating[$name])) { throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating)))); } elseif (!method_exists($this, $method) || (new \ReflectionMethod($this, $method))->getName() !== $method) { throw new MissingServiceException("Service '$name' not found."); } try { $this->creating[$name] = true; $service = $this->$method(...$args); } finally { unset($this->creating[$name]); } if (!is_object($service)) { throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by method $method() is not object."); } return $service; } /** * Resolves service by type. * @param string class or interface * @param bool throw exception if service doesn't exist? * @return object service or null * @throws MissingServiceException */ public function getByType($type, $throw = true) { $type = Helpers::normalizeClass($type); if (!empty($this->meta[self::TYPES][$type][true])) { if (count($names = $this->meta[self::TYPES][$type][true]) === 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."); } } /** * Gets the service names of the specified type. * @param string * @return string[] */ public function findByType($type) { $type = Helpers::normalizeClass($type); return empty($this->meta[self::TYPES][$type]) ? [] : array_merge(...array_values($this->meta[self::TYPES][$type])); } /** * Gets the service names of the specified tag. * @param string * @return array of [service name => tag attributes] */ public function findByTag($tag) { return isset($this->meta[self::TAGS][$tag]) ? $this->meta[self::TAGS][$tag] : []; } /********************* autowiring ****************d*g**/ /** * Creates new instance using autowiring. * @param string class * @param array arguments * @return object * @throws Nette\InvalidArgumentException */ public function createInstance($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(Helpers::autowireArguments($constructor, $args, $this)); } 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 * @return void */ public function callInjects($service) { Extensions\InjectExtension::callInjects($this, $service); } /** * Calls method using autowiring. * @return mixed */ public function callMethod(callable $function, array $args = []) { return $function(...Helpers::autowireArguments(Nette\Utils\Callback::toReflection($function), $args, $this)); } /********************* shortcuts ****************d*g**/ /** * Expands %placeholders%. * @param mixed * @return mixed * @deprecated */ public function expand($s) { return Helpers::expand($s, $this->parameters); } /** @deprecated */ public function &__get($name) { trigger_error(__METHOD__ . ' is deprecated; use getService().', E_USER_DEPRECATED); $tmp = $this->getService($name); return $tmp; } /** @deprecated */ public function __set($name, $service) { trigger_error(__METHOD__ . ' is deprecated; use addService().', E_USER_DEPRECATED); $this->addService($name, $service); } /** @deprecated */ public function __isset($name) { trigger_error(__METHOD__ . ' is deprecated; use hasService().', E_USER_DEPRECATED); return $this->hasService($name); } /** @deprecated */ public function __unset($name) { trigger_error(__METHOD__ . ' is deprecated; use removeService().', E_USER_DEPRECATED); $this->removeService($name); } public static function getMethodName($name) { $uname = ucfirst($name); return 'createService' . ((string) $name === $uname ? '__' : '') . str_replace('.', '__', $uname); } } src/Bridges/DITracy/ContainerPanel.php 0000666 00000003404 13436756201 0013640 0 ustar 00 container = $container; $this->elapsedTime = self::$compilationTime ? microtime(true) - self::$compilationTime : null; } /** * Renders tab. * @return string */ public function getTab() { ob_start(function () {}); $elapsedTime = $this->elapsedTime; require __DIR__ . '/templates/ContainerPanel.tab.phtml'; return ob_get_clean(); } /** * Renders panel. * @return string */ public function getPanel() { $container = $this->container; $registry = $this->getContainerProperty('registry'); $file = (new \ReflectionClass($container))->getFileName(); $tags = []; $meta = $this->getContainerProperty('meta'); $services = $meta[Container::SERVICES]; ksort($services); if (isset($meta[Container::TAGS])) { foreach ($meta[Container::TAGS] as $tag => $tmp) { foreach ($tmp as $service => $val) { $tags[$service][$tag] = $val; } } } ob_start(function () {}); require __DIR__ . '/templates/ContainerPanel.panel.phtml'; return ob_get_clean(); } private function getContainerProperty($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 0000666 00000003416 13436756201 0017274 0 ustar 00
Name | Autowired | Service | Tags |
---|---|---|---|
= htmlspecialchars($name) ?> | = $autowired ? 'yes' : 'no' ?> |
= Dumper::toHtml($registry[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
= get_class($registry[$name]) ?>
= htmlspecialchars($type) ?>
|
true]); } ?> |
Source: = Helpers::editorLink($file) ?>