no message

This commit is contained in:
BarboraFleg
2019-04-04 18:34:40 +02:00
commit ce48ca4a21
589 changed files with 82184 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\Bridges\DITracy;
use Nette;
use Nette\DI\Container;
use Tracy;
/**
* Dependency injection container panel for Debugger Bar.
*/
class ContainerPanel implements Tracy\IBarPanel
{
use Nette\SmartObject;
/** @var int */
public static $compilationTime;
/** @var Nette\DI\Container */
private $container;
/** @var int|null */
private $elapsedTime;
public function __construct(Container $container)
{
$this->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);
}
}
@@ -0,0 +1,70 @@
<?php
namespace Nette\Bridges\DITracy;
use Nette;
use Tracy\Dumper;
use Tracy\Helpers;
?>
<style class="tracy-debug">
#tracy-debug .nette-ContainerPanel table {
width: 100%;
white-space: nowrap;
}
#tracy-debug .nette-ContainerPanel .created {
font-weight: bold;
}
#tracy-debug .nette-ContainerPanel .yes {
color: green;
font-weight: bold;
}
</style>
<h1><?= get_class($container) ?></h1>
<div class="tracy-inner nette-ContainerPanel">
<div class="tracy-inner-container">
<h2>Services</h2>
<table>
<thead>
<tr>
<th>Name</th>
<th>Autowired</th>
<th>Service</th>
<th>Tags</th>
</tr>
</thead>
<tbody>
<?php foreach ($services as $name => $type): ?>
<?php $autowired = !empty($meta[Nette\DI\Container::TYPES][$type][true]) && in_array($name, $meta[Nette\DI\Container::TYPES][$type][true], true) ?>
<tr>
<td class="<?= isset($registry[$name]) ? 'created' : '' ?>"><?= htmlspecialchars($name) ?></td>
<td class="<?= $autowired ? 'yes' : '' ?>"><?= $autowired ? 'yes' : 'no' ?></td>
<td>
<?php if (isset($registry[$name]) && !$registry[$name] instanceof Nette\DI\Container): ?>
<?= Dumper::toHtml($registry[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
<?php elseif (isset($registry[$name])): ?>
<code><?= get_class($registry[$name]) ?></code>
<?php elseif (is_string($type)): ?>
<code><?= htmlspecialchars($type) ?></code>
<?php endif ?>
</td>
<td><?php if (isset($tags[$name])) { echo Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]); } ?></td>
</tr>
<?php endforeach ?>
</tbody>
</table>
<h2>Parameters</h2>
<div class="nette-ContainerPanel-parameters">
<?= Dumper::toHtml($container->parameters); ?>
</div>
<p>Source: <?= Helpers::editorLink($file) ?></p>
</div>
</div>
@@ -0,0 +1,10 @@
<?php
namespace Nette\Bridges\DITracy;
?>
<span title="Dependency Injection Container">
<svg viewBox="0 0 2048 2048"><path fill="#ae7c21" d="m1675 690.55v-125c0-36-4-60-13-71-9-12-26-18-52-18s-43 6-52 17c-8 11-12 36-12 72v910.62c0 37 4 61 12 72 9 11 26 17 52 17s44-6 52-17c9-12 13-36 13-72v-207h243v165c0 86-4 148-12 187-8 38-21 70-39 96-20 28-45 48-77 59-31 11-87 16-170 16-96 0-161-5-196-16-34-11-63-31-86-59-21-26-36-59-45-97-9-39-14-101-14-186v-826.62c0-85 4-147 13-185s25-71 46-97c23-29 54-49 92-60s102-17 189-17c75 0 130 6 164 17s61 30 81 59c19 26 33 59 41 99 8 39 12 101 12 185v84zm-524 1086.6h-254v-1514.6h254zm-1025 0v-1514.6h316c127 0 212 27 257 81 44 53 66 159 66 316v721.62c0 157-22 263-66 317-44 53-130 80-257 80zm388-314v-884.62c0-35-4-58-13-70-9-11-26-17-53-17h-66v1058.6h66c26 0 44-6 53-17 9-12 14-35 14-70z"/>
</svg><span class="tracy-label"><?= $elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '' ?></span>
</span>
+151
View File
@@ -0,0 +1,151 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI;
use Nette;
/**
* Autowiring.
*/
class Autowiring
{
use Nette\SmartObject;
/** @var ContainerBuilder */
private $builder;
/** @var array[] type => services, used by getByType() */
private $highPriority = [];
/** @var array[] type => services, used by findByType() */
private $lowPriority = [];
/** @var string[] of classes excluded from autowiring */
private $excludedClasses = [];
public function __construct(ContainerBuilder $builder)
{
$this->builder = $builder;
}
/**
* Resolves service name by type.
* @param bool $throw exception if service not found?
* @throws MissingServiceException when not found
* @throws ServiceCreationException when multiple found
*/
public function getByType(string $type, bool $throw = false): ?string
{
$type = Helpers::normalizeClass($type);
$types = $this->highPriority;
if (empty($types[$type])) {
if ($throw) {
throw new MissingServiceException("Service of type '$type' not found.");
}
return null;
} elseif (count($types[$type]) === 1) {
return $types[$type][0];
} else {
$list = $types[$type];
natsort($list);
$hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.'))
? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.'
: '';
throw new ServiceCreationException("Multiple services of type $type found: " . implode(', ', $list) . $hint);
}
}
/**
* Gets the service names and definitions of the specified type.
* @return Definitions\Definition[] service name is key
*/
public function findByType(string $type): array
{
$type = Helpers::normalizeClass($type);
$definitions = $this->builder->getDefinitions();
$names = array_merge($this->highPriority[$type] ?? [], $this->lowPriority[$type] ?? []);
$res = [];
foreach ($names as $name) {
$res[$name] = $definitions[$name];
}
return $res;
}
/**
* @param string[] $types
*/
public function addExcludedClasses(array $types): void
{
foreach ($types as $type) {
if (class_exists($type) || interface_exists($type)) {
$type = Helpers::normalizeClass($type);
$this->excludedClasses += class_parents($type) + class_implements($type) + [$type => $type];
}
}
}
public function getClassList(): array
{
return [$this->lowPriority, $this->highPriority];
}
public function rebuild(): void
{
$this->lowPriority = $this->highPriority = $preferred = [];
foreach ($this->builder->getDefinitions() as $name => $def) {
if (!($type = $def->getType())) {
continue;
}
$autowired = $def->getAutowired();
if (is_array($autowired)) {
foreach ($autowired as $k => $autowiredType) {
if ($autowiredType === ContainerBuilder::THIS_SERVICE) {
$autowired[$k] = $type;
} elseif (!is_a($type, $autowiredType, true)) {
throw new ServiceCreationException("Incompatible class $autowiredType in autowiring definition of service '$name'.");
}
}
}
foreach (class_parents($type) + class_implements($type) + [$type] as $parent) {
if (!$autowired || isset($this->excludedClasses[$parent])) {
continue;
} elseif (is_array($autowired)) {
$priority = false;
foreach ($autowired as $autowiredType) {
if (is_a($parent, $autowiredType, true)) {
if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) {
$this->lowPriority[$parent] = array_merge($this->lowPriority[$parent], $this->highPriority[$parent]);
$this->highPriority[$parent] = [];
}
$preferred[$parent] = $priority = true;
break;
}
}
} else {
$priority = empty($preferred[$parent]);
}
$list = $priority ? 'highPriority' : 'lowPriority';
$this->$list[$parent][] = $name;
}
}
}
}
+507
View File
@@ -0,0 +1,507 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
use Nette\Utils\Validators;
/**
* DI container compiler.
*/
class Compiler
{
use Nette\SmartObject;
/** @var CompilerExtension[] */
private $extensions = [];
/** @var ContainerBuilder */
private $builder;
/** @var array */
private $config = [];
/** @var DependencyChecker */
private $dependencies;
/** @var string */
private $className = 'Container';
/** @var string[] */
private $dynamicParams = [];
/** @var array reserved section names */
private static $reserved = ['services' => 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\ObjectHelpers::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 = [];
$sort = false;
foreach ($services as $name => $def) {
$path = [];
while (Config\Helpers::isInheriting($def)) {
$sort = true;
$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);
}
if ($sort) {
@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)) {
$counter = 1;
do {
$name = (string) $counter++;
} while ($builder->hasDefinition($name));
} 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', 'imported', '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\ObjectHelpers::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['imported'])) {
$config['dynamic'] = $config['imported'];
}
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);
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* Configurator compiling extension.
*/
abstract class CompilerExtension
{
use Nette\SmartObject;
/** @var Compiler */
protected $compiler;
/** @var string */
protected $name;
/** @var array */
protected $config = [];
/**
* @return static
*/
public function setCompiler(Compiler $compiler, $name)
{
$this->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\ObjectHelpers::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)
{
}
}
+28
View File
@@ -0,0 +1,28 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Config;
/**
* Adapter for reading and writing configuration files.
*/
interface Adapter
{
/**
* Reads configuration from file.
*/
function load(string $file): array;
/**
* Generates configuration string.
*/
function dump(array $data): string;
}
+151
View File
@@ -0,0 +1,151 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config\Adapters;
use Nette;
use Nette\DI\Config\Helpers;
/**
* Reading and generating INI files.
*/
class IniAdapter implements Nette\DI\Config\IAdapter
{
use Nette\SmartObject;
/** @internal */
const INHERITING_SEPARATOR = '<', // child < parent
KEY_SEPARATOR = '.', // key nesting key1.key2.key3
ESCAPED_KEY_SEPARATOR = '..',
RAW_SECTION = '!';
/**
* Reads configuration from INI file.
* @param string file name
* @return array
* @throws Nette\InvalidStateException
*/
public function load($file)
{
$ini = @parse_ini_file($file, true); // @ escalated to exception
if ($ini === false) {
$error = error_get_last();
throw new Nette\InvalidStateException("parse_ini_file(): $error[message]");
}
return $this->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)));
}
}
}
}
+139
View File
@@ -0,0 +1,139 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config\Adapters;
use Nette;
use Nette\DI\Config\Helpers;
use Nette\DI\Statement;
use Nette\Neon;
/**
* Reading and generating NEON files.
*/
class NeonAdapter implements Nette\DI\Config\IAdapter
{
use Nette\SmartObject;
/** @internal */
const INHERITING_SEPARATOR = '<', // child < parent
PREVENT_MERGING = '!';
/**
* Reads configuration from NEON file.
* @param string file name
* @return array
*/
public function load($file)
{
return $this->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);
}
}
}
+39
View File
@@ -0,0 +1,39 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config\Adapters;
use Nette;
/**
* Reading and generating PHP files.
*/
class PhpAdapter implements Nette\DI\Config\IAdapter
{
use Nette\SmartObject;
/**
* Reads configuration from PHP file.
* @param string file name
* @return array
*/
public function load($file)
{
return require $file;
}
/**
* Generates configuration in PHP format.
* @return string
*/
public function dump(array $data)
{
return "<?php // generated by Nette \nreturn " . Nette\PhpGenerator\Helpers::dump($data) . ';';
}
}
+254
View File
@@ -0,0 +1,254 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Config;
use Nette;
use Nette\DI\Definitions;
use Nette\DI\Definitions\Statement;
use Nette\Schema\Context;
use Nette\Schema\Expect;
use Nette\Schema\Schema;
/**
* Service configuration schema.
*/
class DefinitionSchema implements Schema
{
use Nette\SmartObject;
/** @var Nette\DI\ContainerBuilder */
private $builder;
public function __construct(Nette\DI\ContainerBuilder $builder)
{
$this->builder = $builder;
}
public function complete($def, Context $context)
{
if ($def === [false]) {
return (object) $def;
}
if (Helpers::takeParent($def)) {
$def['reset']['all'] = true;
}
foreach (['arguments', 'setup', 'tags'] as $k) {
if (isset($def[$k]) && Helpers::takeParent($def[$k])) {
$def['reset'][$k] = true;
}
}
$def = $this->expandParameters($def);
$type = $this->sniffType(end($context->path), $def);
$def = $this->getSchema($type)->complete($def, $context);
if ($def) {
$def->defType = $type;
}
return $def;
}
public function merge($def, $base)
{
if (!empty($def['alteration'])) {
unset($def['alteration']);
}
return Nette\Schema\Helpers::merge($def, $base);
}
/**
* Normalizes configuration of service definitions.
*/
public function normalize($def, Context $context)
{
if ($def === null || $def === false) {
return (array) $def;
} elseif (is_string($def) && interface_exists($def)) {
return ['implement' => $def];
} elseif ($def instanceof Statement && is_string($def->getEntity()) && interface_exists($def->getEntity())) {
$res = ['implement' => $def->getEntity()];
if (array_keys($def->arguments) === ['tagged']) {
$res += $def->arguments;
} elseif (count($def->arguments) > 1) {
$res['references'] = $def->arguments;
} elseif ($factory = array_shift($def->arguments)) {
$res['factory'] = $factory;
}
return $res;
} elseif (!is_array($def) || isset($def[0], $def[1])) {
return ['factory' => $def];
} elseif (is_array($def)) {
if (isset($def['class']) && !isset($def['type'])) {
if ($def['class'] instanceof Statement) {
$key = end($context->path);
trigger_error("Service '$key': option 'class' should be changed to 'factory'.", E_USER_DEPRECATED);
$def['factory'] = $def['class'];
unset($def['class']);
} elseif (!isset($def['factory']) && !isset($def['dynamic']) && !isset($def['imported'])) {
$def['factory'] = $def['class'];
unset($def['class']);
}
}
foreach (['class' => 'type', 'dynamic' => 'imported'] as $alias => $original) {
if (array_key_exists($alias, $def)) {
if (array_key_exists($original, $def)) {
throw new Nette\DI\InvalidConfigurationException("Options '$alias' and '$original' are aliases, use only '$original'.");
}
$def[$original] = $def[$alias];
unset($def[$alias]);
}
}
return $def;
} else {
throw new Nette\DI\InvalidConfigurationException('Unexpected format of service definition');
}
}
public function completeDefault(Context $context)
{
}
private function sniffType($key, array $def): string
{
if (is_string($key)) {
$name = preg_match('#^@[\w\\\\]+\z#', $key)
? $this->builder->getByType(substr($key, 1), false)
: $key;
if ($name && $this->builder->hasDefinition($name)) {
return get_class($this->builder->getDefinition($name));
}
}
if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) {
return Definitions\LocatorDefinition::class;
} elseif (isset($def['implement'])) {
return method_exists($def['implement'], 'create')
? Definitions\FactoryDefinition::class
: Definitions\AccessorDefinition::class;
} elseif (isset($def['imported'])) {
return Definitions\ImportedDefinition::class;
} else {
return Definitions\ServiceDefinition::class;
}
}
private function expandParameters(array $config): array
{
$params = $this->builder->parameters;
if (isset($config['parameters'])) {
foreach ((array) $config['parameters'] as $k => $v) {
$v = explode(' ', is_int($k) ? $v : $k);
$params[end($v)] = $this->builder::literal('$' . end($v));
}
}
return Nette\DI\Helpers::expand($config, $params);
}
private static function getSchema(string $type): Schema
{
static $cache;
$cache = $cache ?: [
Definitions\ServiceDefinition::class => self::getServiceSchema(),
Definitions\AccessorDefinition::class => self::getAccessorSchema(),
Definitions\FactoryDefinition::class => self::getFactorySchema(),
Definitions\LocatorDefinition::class => self::getLocatorSchema(),
Definitions\ImportedDefinition::class => self::getImportedSchema(),
];
return $cache[$type];
}
private static function getServiceSchema(): Schema
{
return Expect::structure([
'type' => Expect::type('string'),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
'alteration' => Expect::bool(),
]);
}
private static function getAccessorSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'implement' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getFactorySchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'implement' => Expect::string(),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'parameters' => Expect::array(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
]);
}
private static function getLocatorSchema(): Schema
{
return Expect::structure([
'implement' => Expect::string(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getImportedSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'imported' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
}
+87
View File
@@ -0,0 +1,87 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config;
use Nette;
/**
* Configuration helpers.
*/
class Helpers
{
use Nette\StaticClass;
const
EXTENDS_KEY = '_extends',
OVERWRITE = true;
/**
* Merges configurations. Left has higher priority than right one.
* @return array|string
*/
public static function merge($left, $right)
{
if (is_array($left) && is_array($right)) {
foreach ($left as $key => $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;
}
}
+29
View File
@@ -0,0 +1,29 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config;
/**
* Adapter for reading and writing configuration files.
*/
interface IAdapter
{
/**
* Reads configuration from file.
* @param string file name
* @return array
*/
function load($file);
/**
* Generates configuration string.
* @return string
*/
function dump(array $data);
}
+137
View File
@@ -0,0 +1,137 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Config;
use Nette;
use Nette\Utils\Validators;
/**
* Configuration file loader.
*/
class Loader
{
use Nette\SmartObject;
/** @internal */
const INCLUDES_KEY = 'includes';
private $adapters = [
'php' => 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;
}
}
+345
View File
@@ -0,0 +1,345 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* The dependency injection container default implementation.
*/
class Container
{
use Nette\SmartObject;
const TAGS = 'tags';
const TYPES = 'types';
const SERVICES = 'services';
const ALIASES = 'aliases';
/** @var array user parameters */
public $parameters = [];
/** @var array[] */
protected $meta = [];
/** @var object[] storage for shared objects */
private $registry = [];
/** @var array circular reference detector */
private $creating;
public function __construct(array $params = [])
{
$this->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|null service
* @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.");
}
return null;
}
/**
* 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)
{
return 'createService'
. (preg_match('#^[A-Z]#', $name) ? '__' : '')
. str_replace('.', '__', ucfirst($name));
}
}
+880
View File
@@ -0,0 +1,880 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
use Nette\PhpGenerator\Helpers as PhpHelpers;
use Nette\Utils\Reflection;
use Nette\Utils\Strings;
use Nette\Utils\Validators;
use ReflectionClass;
/**
* Container builder.
*/
class ContainerBuilder
{
use Nette\SmartObject;
const THIS_SERVICE = 'self',
THIS_CONTAINER = 'container';
/** @var array */
public $parameters = [];
/** @var ServiceDefinition[] */
private $definitions = [];
/** @var array of alias => 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()];
$argHint = Reflection::getParameterType($arg);
if ($hint !== $argHint && !is_a($hint, $argHint, true)) {
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\ObjectHelpers::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) {
$type = $def->getType();
if (!$type) {
$message = "Service '$name': " . $e->getMessage();
} elseif (ctype_digit($name)) {
$message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage());
} else {
$message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage());
}
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] . '::') : '')
. $entity[1] . (strpos($entity[1], '$') === false ? '()' : '');
$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);
}
}
+131
View File
@@ -0,0 +1,131 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* DI container loader.
*/
class ContainerLoader
{
use Nette\SmartObject;
/** @var bool */
private $autoRebuild = false;
/** @var string */
private $tempDirectory;
public function __construct($tempDirectory, $autoRebuild = false)
{
$this->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 [
"<?php\n$code",
serialize($compiler->exportDependencies()),
];
}
}
@@ -0,0 +1,122 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\DI\ServiceCreationException;
use Nette\Utils\Reflection;
/**
* Accessor definition.
*/
final class AccessorDefinition extends Definition
{
private const METHOD_GET = 'get';
/** @var Reference|null */
private $reference;
/**
* @return static
*/
public function setImplement(string $type)
{
if (!interface_exists($type)) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
}
$rc = new \ReflectionClass($type);
$method = $rc->getMethods()[0] ?? null;
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_GET || count($rc->getMethods()) > 1) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method get().");
} elseif ($method->getNumberOfParameters()) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Method $type::get() must have no parameters.");
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
/**
* @param string|Reference $reference
* @return static
*/
public function setReference($reference)
{
if ($reference instanceof Reference) {
$this->reference = $reference;
} else {
$this->reference = substr($reference, 0, 1) === '@'
? new Reference(substr($reference, 1))
: Reference::fromType($reference);
}
return $this;
}
public function getReference(): ?Reference
{
return $this->reference;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
if (!$this->reference) {
$interface = $this->getType();
$method = new \ReflectionMethod($interface, self::METHOD_GET);
$returnType = Nette\DI\Helpers::getReturnType($method);
if (!$returnType) {
throw new ServiceCreationException("Method $interface::get() has not return type hint or annotation @return.");
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::get() method, class '$returnType' cannot be found.");
}
$this->setReference($returnType);
}
$this->reference = $resolver->normalizeReference($this->reference);
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setTypeHint($generator->getClassName());
$rm = new \ReflectionMethod($this->getType(), self::METHOD_GET);
$class->addMethod(self::METHOD_GET)
->setBody('return $this->container->getService(?);', [$this->reference->getValue()])
->setReturnType(Reflection::getReturnType($rm));
$method->setBody('return new class ($this) ' . $class . ';');
}
}
+227
View File
@@ -0,0 +1,227 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
/**
* Definition used by ContainerBuilder.
*/
abstract class Definition
{
use Nette\SmartObject;
/** @var string */
private $name;
/** @var string|null class or interface name */
private $type;
/** @var array */
private $tags = [];
/** @var bool|string[] */
private $autowired = true;
/** @var callable|null */
private $notifier;
/**
* @return static
* @internal This is managed by ContainerBuilder and should not be called by user
*/
final public function setName(string $name)
{
if ($this->name) {
throw new Nette\InvalidStateException('Name already has been set.');
}
$this->name = $name;
return $this;
}
final public function getName(): ?string
{
return $this->name;
}
/**
* @return static
*/
protected function setType(?string $type)
{
if ($this->autowired && $this->notifier && $this->type !== $type) {
($this->notifier)();
}
if ($type === null) {
$this->type = null;
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidArgumentException("Service '$this->name': Class or interface '$type' not found.");
} else {
$this->type = Nette\DI\Helpers::normalizeClass($type);
}
return $this;
}
final public function getType(): ?string
{
return $this->type;
}
/**
* @return static
*/
final public function setTags(array $tags)
{
$this->tags = $tags;
return $this;
}
final public function getTags(): array
{
return $this->tags;
}
/**
* @param mixed $attr
* @return static
*/
final public function addTag(string $tag, $attr = true)
{
$this->tags[$tag] = $attr;
return $this;
}
/**
* @return mixed
*/
final public function getTag(string $tag)
{
return $this->tags[$tag] ?? null;
}
/**
* @param bool|string|string[] $state
* @return static
*/
final public function setAutowired($state = true)
{
if ($this->notifier && $this->autowired !== $state) {
($this->notifier)();
}
$this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state;
return $this;
}
/**
* @return bool|string[]
*/
final public function getAutowired()
{
return $this->autowired;
}
/**
* @return static
*/
public function setExported(bool $state = true)
{
return $this->addTag('nette.exported', $state);
}
public function isExported(): bool
{
return (bool) $this->getTag('nette.exported');
}
public function __clone()
{
$this->notifier = $this->name = null;
}
/********************* life cycle ****************d*g**/
abstract public function resolveType(Nette\DI\Resolver $resolver): void;
abstract public function complete(Nette\DI\Resolver $resolver): void;
abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void;
final public function setNotifier(?callable $notifier): void
{
$this->notifier = $notifier;
}
/********************* deprecated stuff from former ServiceDefinition ****************d*g**/
/**
* @deprecated Use setType()
*/
public function setClass(?string $type)
{
return $this->setType($type);
}
/**
* @deprecated Use getType()
*/
public function getClass(): ?string
{
return $this->getType();
}
/**
* @deprecated Use '$def instanceof Nette\DI\Definitions\ImportedDefinition'
*/
public function isDynamic(): bool
{
return false;
}
/**
* @deprecated Use Nette\DI\Definitions\FactoryDefinition or AccessorDefinition
*/
public function getImplement(): ?string
{
return null;
}
/**
* @deprecated Use getAutowired()
*/
public function isAutowired()
{
return $this->autowired;
}
}
+303
View File
@@ -0,0 +1,303 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\DI\ServiceCreationException;
use Nette\Utils\Reflection;
/**
* Definition of standard service.
*/
final class FactoryDefinition extends Definition
{
private const METHOD_CREATE = 'create';
/** @var array */
public $parameters = [];
/** @var Definition */
private $resultDefinition;
public function __construct()
{
$this->resultDefinition = new ServiceDefinition;
}
/**
* @return static
*/
public function setImplement(string $type)
{
if (!interface_exists($type)) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
}
$rc = new \ReflectionClass($type);
$method = $rc->getMethods()[0] ?? null;
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_CREATE || count($rc->getMethods()) > 1) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method create().");
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
final public function getResultType(): ?string
{
return $this->resultDefinition->getType();
}
/**
* @return static
*/
public function setResultDefinition(Definition $definition)
{
$this->resultDefinition = $definition;
return $this;
}
/**
* @return ServiceDefinition
*/
public function getResultDefinition(): Definition
{
return $this->resultDefinition;
}
/**
* @deprecated use ->getResultDefinition()->setFactory()
* @return static
*/
public function setFactory($factory, array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setFactory($factory, $args);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->getFactory()
*/
public function getFactory(): ?Statement
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getFactory();
}
/**
* @deprecated use ->getResultDefinition()->getEntity()
* @return mixed
*/
public function getEntity()
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getEntity()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getEntity();
}
/**
* @deprecated use ->getResultDefinition()->setArguments()
* @return static
*/
public function setArguments(array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setArguments()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setArguments($args);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->setSetup()
* @return static
*/
public function setSetup(array $setup)
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setSetup($setup);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->getSetup()
*/
public function getSetup(): array
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getSetup();
}
/**
* @deprecated use ->getResultDefinition()->addSetup()
* @return static
*/
public function addSetup($entity, array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->addSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->addSetup($entity, $args);
return $this;
}
/**
* @return static
*/
public function setParameters(array $params)
{
$this->parameters = $params;
return $this;
}
public function getParameters(): array
{
return $this->parameters;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
$resultDef = $this->resultDefinition;
try {
$resolver->resolveDefinition($resultDef);
return;
} catch (ServiceCreationException $e) {
}
if (!$resultDef->getType()) {
$interface = $this->getType();
if (!$interface) {
throw new ServiceCreationException('Type is missing in definition of service.');
}
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
$returnType = Nette\DI\Helpers::getReturnType($method);
if (!$returnType) {
throw new ServiceCreationException("Method $interface::create() has not return type hint or annotation @return.");
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::create() method, class '$returnType' cannot be found.");
}
$resultDef->setType($returnType);
}
$resolver->resolveDefinition($resultDef);
}
public function complete(Nette\DI\Resolver $resolver): void
{
$resultDef = $this->resultDefinition;
if ($resultDef instanceof ServiceDefinition) {
if (!$this->parameters) {
$this->completeParameters($resolver);
}
if ($resultDef->getEntity() instanceof Reference && !$resultDef->getFactory()->arguments) {
$resultDef->setFactory([ // render as $container->createMethod()
new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER),
Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
]);
}
}
$resolver->completeDefinition($resultDef);
}
private function completeParameters(Nette\DI\Resolver $resolver): void
{
$interface = $this->getType();
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
$ctorParams = [];
if (
($class = $resolver->resolveEntityType($this->resultDefinition->getFactory()))
&& ($ctor = (new \ReflectionClass($class))->getConstructor())
) {
foreach ($ctor->getParameters() as $param) {
$ctorParams[$param->getName()] = $param;
}
}
foreach ($method->getParameters() as $param) {
$hint = Reflection::getParameterType($param);
if (isset($ctorParams[$param->getName()])) {
$arg = $ctorParams[$param->getName()];
$argHint = Reflection::getParameterType($arg);
if ($hint !== $argHint && !is_a($hint, (string) $argHint, true)) {
throw new ServiceCreationException("Type hint for \${$param->getName()} in $interface::create() doesn't match type hint in $class constructor.");
}
$this->resultDefinition->getFactory()->arguments[$arg->getPosition()] = Nette\DI\ContainerBuilder::literal('$' . $arg->getName());
} elseif (!$this->resultDefinition->getSetup()) {
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($ctorParams), $param->getName());
throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $interface::create()" . ($hint ? ", did you mean \${$hint}?" : '.'));
}
$nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null);
$paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
if ($param->isDefaultValueAvailable()) {
$this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
} else {
$this->parameters[] = $paramDef;
}
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setTypeHint($generator->getClassName());
$methodCreate = $class->addMethod(self::METHOD_CREATE);
$this->resultDefinition->generateMethod($methodCreate, $generator);
$body = $methodCreate->getBody();
$body = str_replace('$this', '$this->container', $body);
$body = str_replace('$this->container->container', '$this->container', $body);
$rm = new \ReflectionMethod($this->getType(), self::METHOD_CREATE);
$methodCreate
->setParameters($generator->convertParameters($this->parameters))
->setReturnType(Reflection::getReturnType($rm) ?: $this->getResultType())
->setBody($body);
$method->setBody('return new class ($this) ' . $class . ';');
}
public function __clone()
{
parent::__clone();
$this->resultDefinition = unserialize(serialize($this->resultDefinition));
}
}
@@ -0,0 +1,57 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\DI\PhpGenerator;
/**
* Imported service injected to the container.
*/
final class ImportedDefinition extends Definition
{
/**
* @return static
*/
public function setType(?string $type)
{
return parent::setType($type);
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
}
public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void
{
$method->setReturnType('void')
->setBody(
'throw new Nette\\DI\\ServiceCreationException(?);',
["Unable to create imported service '{$this->getName()}', it must be added using addService()"]
);
}
/**
* @deprecated use '$def instanceof ImportedDefinition'
*/
public function isDynamic(): bool
{
return true;
}
}
+171
View File
@@ -0,0 +1,171 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\Utils\Reflection;
/**
* Multi accessor/factory definition.
*/
final class LocatorDefinition extends Definition
{
/** @var Reference[] */
private $references = [];
/** @var string|null */
private $tagged;
/**
* @return static
*/
public function setImplement(string $type)
{
if (!interface_exists($type)) {
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $type));
}
$methods = (new \ReflectionClass($type))->getMethods();
if (!$methods) {
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $type));
}
foreach ($methods as $method) {
if ($method->isStatic() || !(
(preg_match('#^(get|create)$#', $method->getName()) && $method->getNumberOfParameters() === 1)
|| (preg_match('#^(get|create)[A-Z]#', $method->getName()) && $method->getNumberOfParameters() === 0)
)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.",
$this->getName(), $type, $method->getName()
));
}
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
/**
* @return static
*/
public function setReferences(array $references)
{
$this->references = [];
foreach ($references as $name => $ref) {
$this->references[$name] = substr($ref, 0, 1) === '@'
? new Reference(substr($ref, 1))
: Reference::fromType($ref);
}
return $this;
}
/**
* @return Reference[]
*/
public function getReferences(): array
{
return $this->references;
}
/**
* @return static
*/
public function setTagged(?string $tagged)
{
$this->tagged = $tagged;
return $this;
}
public function getTagged(): ?string
{
return $this->tagged;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
if ($this->tagged !== null) {
$this->references = [];
foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) {
if (isset($this->references[$tag])) {
trigger_error("Service '{$this->getName()}': duplicated tag '$this->tagged' with value '$tag'.", E_USER_NOTICE);
}
$this->references[$tag] = new Reference($name);
}
}
foreach ($this->references as $name => $ref) {
$this->references[$name] = $resolver->normalizeReference($ref);
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setTypeHint($generator->getClassName());
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) {
preg_match('#^(get|create)(.*)#', $rm->getName(), $m);
$name = lcfirst($m[2]);
$nullable = $rm->getReturnType()->allowsNull();
$methodInner = $class->addMethod($rm->getName())
->setReturnType(Reflection::getReturnType($rm))
->setReturnNullable($nullable);
if (!$name) {
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
->setVisibility('private');
$methodInner->setBody('if (!isset($this->mapping[$name])) {
' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . '
}
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')
->addParameter('name');
} elseif (isset($this->references[$name])) {
$ref = $this->references[$name]->getValue();
if ($m[1] === 'get') {
$methodInner->setBody('return $this->container->getService(?);', [$ref]);
} else {
$methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]);
}
} else {
$methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");');
}
}
$method->setBody('return new class ($this) ' . $class . ';');
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
/**
* Reference to service. Either by name or by type or reference to the 'self' service.
*/
final class Reference
{
use Nette\SmartObject;
public const SELF = 'self';
/** @var string */
private $value;
public static function fromType(string $value): self
{
if (strpos($value, '\\') === false) {
$value = '\\' . $value;
}
return new static($value);
}
public function __construct(string $value)
{
$this->value = $value;
}
public function getValue(): string
{
return $this->value;
}
public function isName(): bool
{
return strpos($this->value, '\\') === false && $this->value !== self::SELF;
}
public function isType(): bool
{
return strpos($this->value, '\\') !== false;
}
public function isSelf(): bool
{
return $this->value === self::SELF;
}
}
+270
View File
@@ -0,0 +1,270 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\DI\Definitions\Reference;
use Nette\DI\ServiceCreationException;
use Nette\PhpGenerator\Helpers as PhpHelpers;
/**
* Definition of standard service.
*
* @property string|null $class
* @property Statement $factory
* @property Statement[] $setup
*/
final class ServiceDefinition extends Definition
{
/** @var Statement */
private $factory;
/** @var Statement[] */
private $setup = [];
public function __construct()
{
$this->factory = new Statement(null);
}
/**
* @deprecated Use setType()
*/
public function setClass(?string $type)
{
$this->setType($type);
if (func_num_args() > 1) {
trigger_error(sprintf('Service %s: %s() second parameter $args is deprecated, use setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
if ($args = func_get_arg(1)) {
$this->setFactory($type, $args);
}
}
return $this;
}
/**
* @return static
*/
public function setType(?string $type)
{
return parent::setType($type);
}
/**
* @param string|array|Definition|Reference|Statement $factory
* @return static
*/
public function setFactory($factory, array $args = [])
{
$this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args);
return $this;
}
public function getFactory(): Statement
{
return $this->factory;
}
/**
* @return string|array|Definition|null
*/
public function getEntity()
{
return $this->factory->getEntity();
}
/**
* @return static
*/
public function setArguments(array $args = [])
{
$this->factory->arguments = $args;
return $this;
}
/**
* @return static
*/
public function setArgument($key, $value)
{
$this->factory->arguments[$key] = $value;
return $this;
}
/**
* @param Statement[] $setup
* @return static
*/
public function setSetup(array $setup)
{
foreach ($setup as $v) {
if (!$v instanceof Statement) {
throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Definitions\Statement[].');
}
}
$this->setup = $setup;
return $this;
}
/**
* @return Statement[]
*/
public function getSetup(): array
{
return $this->setup;
}
/**
* @param string|array|Definition|Reference|Statement $entity
* @return static
*/
public function addSetup($entity, array $args = [])
{
$this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args);
return $this;
}
/**
* @deprecated
*/
public function setParameters(array $params)
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__));
}
/**
* @deprecated
*/
public function getParameters(): array
{
trigger_error(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return [];
}
/**
* @deprecated use $builder->addImportedDefinition(...)
*/
public function setDynamic(): void
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addImportedDefinition(...)', $this->getName(), __METHOD__));
}
/**
* @deprecated use $builder->addFactoryDefinition(...) or addAccessorDefinition(...)
*/
public function setImplement(): void
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addFactoryDefinition(...)', $this->getName(), __METHOD__));
}
/**
* @deprecated use addTag('nette.inject')
*/
public function setInject(bool $state = true)
{
trigger_error(sprintf('Service %s: %s() is deprecated, use addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, $state);
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
if (!$this->getEntity()) {
if (!$this->getType()) {
throw new ServiceCreationException('Factory and type are missing in definition of service.');
}
$this->setFactory($this->getType(), $this->factory->arguments ?? []);
} elseif (!$this->getType()) {
$type = $resolver->resolveEntityType($this->factory);
if (!$type) {
throw new ServiceCreationException('Unknown service type, specify it or declare return type of factory.');
}
$this->setType($type);
$resolver->addDependency(new \ReflectionClass($type));
}
// auto-disable autowiring for aliases
if ($this->getAutowired() === true && $this->getEntity() instanceof Reference) {
$this->setAutowired(false);
}
}
public function complete(Nette\DI\Resolver $resolver): void
{
$entity = $this->factory->getEntity();
if ($entity instanceof Reference && !$this->factory->arguments && !$this->setup) {
$ref = $resolver->normalizeReference($entity);
$this->setFactory([new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER), 'getService'], [$ref->getValue()]);
}
$this->factory = $resolver->completeStatement($this->factory);
foreach ($this->setup as &$setup) {
if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === false) { // auto-prepend @self
$setup = new Statement([new Reference(Reference::SELF), $setup->getEntity()], $setup->arguments);
}
$setup = $resolver->completeStatement($setup, true);
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$entity = $this->factory->getEntity();
$code = '$service = ' . $generator->formatStatement($this->factory) . ";\n";
$type = $this->getType();
if (
$this->setup
&& $type !== $entity
&& !(is_array($entity) && $entity[0] instanceof Reference && $entity[0]->getValue() === Nette\DI\ContainerBuilder::THIS_CONTAINER)
&& !(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 '{$this->getName()}', value returned by factory is not $type type."]
);
}
foreach ($this->setup as $setup) {
$code .= $generator->formatStatement($setup) . ";\n";
}
$code .= 'return $service;';
$method->setBody($code);
}
public function __clone()
{
parent::__clone();
$this->factory = unserialize(serialize($this->factory));
$this->setup = unserialize(serialize($this->setup));
}
}
+72
View File
@@ -0,0 +1,72 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Definitions;
use Nette;
use Nette\Utils\Strings;
/**
* Assignment or calling statement.
*
* @property string|array|Definition|Reference|null $entity
*/
final class Statement
{
use Nette\SmartObject;
/** @var array */
public $arguments;
/** @var string|array|Definition|Reference|null */
private $entity;
/**
* @param string|array|Definition|Reference|null $entity
*/
public function __construct($entity, array $arguments = [])
{
if (
$entity !== null
&& !is_string($entity) // Class, @service, not, tags, types, PHP literal, entity::member
&& !$entity instanceof Definition
&& !$entity instanceof Reference
&& !(is_array($entity)
&& array_keys($entity) === [0, 1]
&& (is_string($entity[0])
|| $entity[0] instanceof self
|| $entity[0] instanceof Reference
|| $entity[0] instanceof Definition)
)) {
throw new Nette\InvalidArgumentException('Argument is not valid Statement entity.');
}
// normalize Class::method to [Class, method]
if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) {
$entity = explode('::', $entity);
}
if (is_string($entity) && substr($entity, 0, 1) === '@') { // normalize @service to Reference
$entity = new Reference(substr($entity, 1));
} elseif (is_array($entity) && is_string($entity[0]) && substr($entity[0], 0, 1) === '@') {
$entity[0] = new Reference(substr($entity[0], 1));
}
$this->entity = $entity;
$this->arguments = $arguments;
}
/** @return string|array|Definition|Reference|null */
public function getEntity()
{
return $this->entity;
}
}
+176
View File
@@ -0,0 +1,176 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
use Nette\Utils\Reflection;
use ReflectionClass;
use ReflectionMethod;
/**
* Cache dependencies checker.
*/
class DependencyChecker
{
use Nette\SmartObject;
const VERSION = 1;
/** @var array of ReflectionClass|\ReflectionFunctionAbstract|string */
private $dependencies = [];
/**
* Adds dependencies to the list.
* @return static
*/
public function add(array $deps)
{
$this->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;
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI;
use Nette;
/**
* @internal
*/
final class DynamicParameter extends Nette\PhpGenerator\PhpLiteral implements Nette\Schema\DynamicParameter
{
}
@@ -0,0 +1,24 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
/**
* Constant definitions.
*/
class ConstantsExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
foreach ($this->getConfig() as $name => $value) {
$class->getMethod('initialize')->addBody('define(?, ?);', [$name, $value]);
}
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
/**
* DI extension.
*/
class DIExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'debugger' => 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]);
}
}
}
@@ -0,0 +1,68 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
/**
* Decorators for services.
*/
class DecoratorExtension extends Nette\DI\CompilerExtension
{
public $defaults = [
'setup' => [],
'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));
});
}
}
@@ -0,0 +1,32 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
/**
* Enables registration of other extensions in $config file
*/
class ExtensionsExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
foreach ($this->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);
}
}
}
}
+144
View File
@@ -0,0 +1,144 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
use Nette\DI;
use Nette\Utils\Reflection;
/**
* Calls inject methods and fills @inject properties.
*/
class InjectExtension extends DI\CompilerExtension
{
const TAG_INJECT = 'inject';
public function beforeCompile()
{
foreach ($this->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?");
}
}
}
@@ -0,0 +1,64 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Extensions;
use Nette;
use Nette\DI\DynamicParameter;
/**
* Parameters.
*/
final class ParametersExtension extends Nette\DI\CompilerExtension
{
/** @var string[] */
public $dynamicParams = [];
/** @var string[][] */
public $dynamicValidators = [];
/** @var array */
private $compilerConfig;
public function __construct(array &$compilerConfig)
{
$this->compilerConfig = &$compilerConfig;
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$params = $this->config;
foreach ($this->dynamicParams as $key) {
$params[$key] = array_key_exists($key, $params)
? new DynamicParameter(Nette\PhpGenerator\Helpers::format('($this->parameters[?] \?\? ?)', $key, $params[$key]))
: new DynamicParameter(Nette\PhpGenerator\Helpers::format('$this->parameters[?]', $key));
}
$builder->parameters = Nette\DI\Helpers::expand($params, $params, true);
// expand all except 'services'
$slice = array_diff_key($this->compilerConfig, ['services' => 1]);
$this->compilerConfig = Nette\DI\Helpers::expand($slice, $builder->parameters) + $this->compilerConfig;
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$builder = $this->getContainerBuilder();
$cnstr = $class->getMethod('__construct');
foreach ($this->dynamicValidators as [$param, $expected]) {
$cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
}
}
}
+48
View File
@@ -0,0 +1,48 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI\Extensions;
use Nette;
/**
* PHP directives definition.
*/
class PhpExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$initialize = $class->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.');
}
}
}
}
+135
View File
@@ -0,0 +1,135 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Extensions;
use Nette;
use Nette\Loaders\RobotLoader;
use Nette\Schema\Expect;
use Nette\Utils\Arrays;
/**
* Services auto-discovery.
*/
final class SearchExtension extends Nette\DI\CompilerExtension
{
/** @var array */
private $classes = [];
/** @var string */
private $tempDir;
public function __construct(string $tempDir)
{
$this->tempDir = $tempDir;
}
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::arrayOf(
Expect::structure([
'in' => Expect::string()->required(),
'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'exclude' => Expect::structure([
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
]),
'tags' => Expect::array(),
])
)->before(function ($val) {
return is_string($val['in'] ?? null)
? ['default' => $val]
: $val;
});
}
public function loadConfiguration()
{
foreach (array_filter($this->config) as $name => $batch) {
if (!is_dir($batch->in)) {
throw new Nette\DI\InvalidConfigurationException("Option '{$this->name} › {$name} › in' must be valid directory name, '{$batch->in}' given.");
}
foreach ($this->findClasses($batch) as $class) {
$this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags);
}
}
}
public function findClasses(\stdClass $config): array
{
$robot = new RobotLoader;
$robot->setTempDirectory($this->tempDir);
$robot->addDirectory($config->in);
$robot->acceptFiles = $config->files ?: ['*.php'];
$robot->reportParseErrors(false);
$robot->refresh();
$classes = array_unique(array_keys($robot->getIndexedClasses()));
$classes = array_filter($classes, 'class_exists');
$exclude = $config->exclude;
$acceptRE = self::buildNameRegexp($config->classes);
$rejectRE = self::buildNameRegexp($exclude->classes);
$acceptParent = array_merge($config->extends, $config->implements);
$rejectParent = array_merge($exclude->extends, $exclude->implements);
$found = [];
foreach ($classes as $class) {
$rc = new \ReflectionClass($class);
if (
$rc->isInstantiable()
&& (!$acceptRE || preg_match($acceptRE, $rc->getName()))
&& (!$rejectRE || !preg_match($rejectRE, $rc->getName()))
&& (!$acceptParent || Arrays::some($acceptParent, function ($nm) use ($rc) { return $rc->isSubclassOf($nm); }))
&& (!$rejectParent || Arrays::every($rejectParent, function ($nm) use ($rc) { return !$rc->isSubclassOf($nm); }))
) {
$found[] = $rc->getName();
}
}
return $found;
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($this->classes as $class => $tags) {
if (!$builder->findByType($class)) {
$builder->addDefinition(null)
->setType($class)
->setTags(Arrays::normalize($tags, true));
}
}
}
private static function buildNameRegexp(array $masks): ?string
{
$res = [];
foreach ((array) $masks as $mask) {
$mask = (strpos($mask, '\\') === false ? '**\\' : '') . $mask;
$mask = preg_quote($mask, '#');
$mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask);
$mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask);
$mask = str_replace('\*', '\w*', $mask);
$res[] = $mask;
}
return $res ? '#^(' . implode('|', $res) . ')$#i' : null;
}
}
+244
View File
@@ -0,0 +1,244 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI\Extensions;
use Nette;
use Nette\DI\Definitions;
use Nette\DI\Definitions\Statement;
use Nette\DI\Helpers;
/**
* Service definitions loader.
*/
final class ServicesExtension extends Nette\DI\CompilerExtension
{
use Nette\SmartObject;
public function getConfigSchema(): Nette\Schema\Schema
{
return Nette\Schema\Expect::arrayOf(new Nette\DI\Config\DefinitionSchema($this->getContainerBuilder()));
}
public function loadConfiguration()
{
$this->loadDefinitions($this->config);
}
/**
* Loads list of service definitions.
*/
public function loadDefinitions(array $config)
{
foreach ($config as $key => $defConfig) {
$this->loadDefinition($this->convertKeyToName($key), $defConfig);
}
}
/**
* Loads service definition from normalized configuration.
*/
private function loadDefinition(?string $name, \stdClass $config): void
{
try {
if ((array) $config === [false]) {
$this->getContainerBuilder()->removeDefinition($name);
return;
} elseif (!empty($config->alteration) && !$this->getContainerBuilder()->hasDefinition($name)) {
throw new Nette\DI\InvalidConfigurationException('missing original definition for alteration.');
}
$def = $this->retrieveDefinition($name, $config);
static $methods = [
Definitions\ServiceDefinition::class => 'updateServiceDefinition',
Definitions\AccessorDefinition::class => 'updateAccessorDefinition',
Definitions\FactoryDefinition::class => 'updateFactoryDefinition',
Definitions\LocatorDefinition::class => 'updateLocatorDefinition',
Definitions\ImportedDefinition::class => 'updateImportedDefinition',
];
$this->{$methods[$config->defType]}($def, $config);
$this->updateDefinition($def, $config);
} catch (\Exception $e) {
throw new Nette\DI\InvalidConfigurationException(($name ? "Service '$name': " : '') . $e->getMessage(), 0, $e);
}
}
/**
* Updates service definition according to normalized configuration.
*/
private function updateServiceDefinition(Definitions\ServiceDefinition $definition, \stdClass $config): void
{
if ($config->factory) {
$definition->setFactory(Helpers::filterArguments([$config->factory])[0]);
$definition->setType(null);
}
if ($config->type) {
$definition->setType($config->type);
}
if ($config->arguments) {
$arguments = Helpers::filterArguments($config->arguments);
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
$arguments += $definition->getFactory()->arguments;
}
$definition->setArguments($arguments);
}
if (isset($config->setup)) {
if (!empty($config->reset['setup'])) {
$definition->setSetup([]);
}
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
if (is_array($setup)) {
$setup = new Statement(key($setup), array_values($setup));
}
$definition->addSetup($setup);
}
}
if (isset($config->inject)) {
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
}
}
private function updateAccessorDefinition(Definitions\AccessorDefinition $definition, \stdClass $config): void
{
if (isset($config->implement)) {
$definition->setImplement($config->implement);
}
if ($ref = $config->factory ?? $config->type ?? null) {
$definition->setReference($ref);
}
}
private function updateFactoryDefinition(Definitions\FactoryDefinition $definition, \stdClass $config): void
{
$resultDef = $definition->getResultDefinition();
if (isset($config->implement)) {
$definition->setImplement($config->implement);
$definition->setAutowired(true);
}
if ($config->factory) {
$resultDef->setFactory(Helpers::filterArguments([$config->factory])[0]);
}
if ($config->type) {
$resultDef->setFactory($config->type);
}
if ($config->arguments) {
$arguments = Helpers::filterArguments($config->arguments);
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
$arguments += $resultDef->getFactory()->arguments;
}
$resultDef->setArguments($arguments);
}
if (isset($config->setup)) {
if (!empty($config->reset['setup'])) {
$resultDef->setSetup([]);
}
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
if (is_array($setup)) {
$setup = new Statement(key($setup), array_values($setup));
}
$resultDef->addSetup($setup);
}
}
if (isset($config->parameters)) {
$definition->setParameters($config->parameters);
}
if (isset($config->inject)) {
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
}
}
private function updateLocatorDefinition(Definitions\LocatorDefinition $definition, \stdClass $config): void
{
if (isset($config->implement)) {
$definition->setImplement($config->implement);
}
if (isset($config->references)) {
$definition->setReferences($config->references);
}
if (isset($config->tagged)) {
$definition->setTagged($config->tagged);
}
}
private function updateImportedDefinition(Definitions\ImportedDefinition $definition, \stdClass $config): void
{
if ($config->type) {
$definition->setType($config->type);
}
}
private function updateDefinition(Definitions\Definition $definition, \stdClass $config): void
{
if (isset($config->autowired)) {
$definition->setAutowired($config->autowired);
}
if (isset($config->tags)) {
if (!empty($config->reset['tags'])) {
$definition->setTags([]);
}
foreach ($config->tags as $tag => $attrs) {
if (is_int($tag) && is_string($attrs)) {
$definition->addTag($attrs);
} else {
$definition->addTag($tag, $attrs);
}
}
}
}
private function convertKeyToName($key): ?string
{
if (is_int($key)) {
return null;
} elseif (preg_match('#^@[\w\\\\]+\z#', $key)) {
return $this->getContainerBuilder()->getByType(substr($key, 1), true);
}
return $key;
}
private function retrieveDefinition(?string $name, \stdClass $config): Definitions\Definition
{
$builder = $this->getContainerBuilder();
if (!empty($config->reset['all'])) {
$builder->removeDefinition($name);
}
return $name && $builder->hasDefinition($name)
? $builder->getDefinition($name)
: $builder->addDefinition($name, new $config->defType);
}
}
+255
View File
@@ -0,0 +1,255 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
use Nette\PhpGenerator\PhpLiteral;
use Nette\Utils\Reflection;
/**
* The DI helpers.
* @internal
*/
class Helpers
{
use Nette\StaticClass;
/**
* Expands %placeholders%.
* @param mixed
* @param array
* @param bool|array
* @return mixed
* @throws Nette\InvalidArgumentException
*/
public static function expand($var, array $params, $recursive = false)
{
if (is_array($var)) {
$res = [];
foreach ($var as $key => $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 {
$val = $params;
foreach (explode('.', $part) as $key) {
if (is_array($val) && array_key_exists($key, $val)) {
$val = $val[$key];
} elseif ($val instanceof PhpLiteral) {
$val = new PhpLiteral($val . '[' . var_export($key, true) . ']');
} else {
throw new Nette\InvalidArgumentException("Missing parameter '$part'.");
}
}
if ($recursive) {
$val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]);
}
if (strlen($part) + 2 === strlen($var)) {
return $val;
}
if ($val instanceof 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;
}
}
+275
View File
@@ -0,0 +1,275 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
use Nette\PhpGenerator\Helpers as PhpHelpers;
use Nette\PhpGenerator\PhpLiteral;
use Nette\Utils\Reflection;
use Nette\Utils\Strings;
/**
* Container PHP code generator.
*/
class PhpGenerator
{
/** @var ContainerBuilder */
private $builder;
/** @var string */
private $className;
/** @var Nette\PhpGenerator\ClassType[] */
private $generatedClasses = [];
/** @var string */
private $currentService;
public function __construct(ContainerBuilder $builder)
{
$this->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->getImplement() ?: $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); // @ deprecated in nette/php-generator 3.1
$param->setDefaultValue($v);
}
if (isset($tmp[1])) {
$param->setTypeHint($tmp[0]);
}
}
return $res;
}
}
+273
View File
@@ -0,0 +1,273 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* PHP reflection helpers.
* @internal
* @deprecated
*/
class PhpReflection
{
use Nette\StaticClass;
/**
* Returns an annotation value.
* @return string|null
*/
public static function parseAnnotation(\Reflector $ref, $name)
{
static $ok;
if (!$ok) {
if (!(new \ReflectionMethod(__METHOD__))->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;
}
}
+533
View File
@@ -0,0 +1,533 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
namespace Nette\DI;
use Nette;
use Nette\DI\Definitions\Definition;
use Nette\DI\Definitions\Reference;
use Nette\DI\Definitions\Statement;
use Nette\PhpGenerator\Helpers as PhpHelpers;
use Nette\Utils\Reflection;
use Nette\Utils\Strings;
use Nette\Utils\Validators;
use ReflectionClass;
/**
* Services resolver
* @internal
*/
class Resolver
{
use Nette\SmartObject;
/** @var ContainerBuilder */
private $builder;
/** @var Definition|null */
private $currentService;
/** @var string|null */
private $currentServiceType;
/** @var bool */
private $currentServiceAllowed;
/** @var \SplObjectStorage circular reference detector */
private $recursive;
public function __construct(ContainerBuilder $builder)
{
$this->builder = $builder;
$this->recursive = new \SplObjectStorage;
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
}
public function resolveDefinition(Definition $def): void
{
if ($this->recursive->contains($def)) {
$names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive));
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
}
try {
$this->recursive->attach($def);
$def->resolveType($this);
if (!$def->getType()) {
throw new ServiceCreationException('Type of service is unknown.');
}
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->recursive->detach($def);
}
}
public function resolveReferenceType(Reference $ref): ?string
{
if ($ref->isSelf()) {
return $this->currentServiceType;
} elseif ($ref->isType()) {
return ltrim($ref->getValue(), '\\');
}
$def = $this->resolveReference($ref);
if (!$def->getType()) {
$this->resolveDefinition($def);
}
return $def->getType();
}
public function resolveEntityType(Statement $statement): ?string
{
$entity = $this->normalizeEntity($statement);
if (is_array($entity)) {
if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
$entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
if (!$entity[0]) {
return null;
}
}
try {
$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() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null);
}
$this->addDependency($reflection);
$type = Helpers::getReturnType($reflection);
if ($type && !class_exists($type) && !interface_exists($type)) {
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() correct?", $type, Nette\Utils\Callback::toString($entity)));
}
return $type;
} elseif ($entity instanceof Reference) { // alias or factory
return $this->resolveReferenceType($entity);
} elseif (is_string($entity)) { // class
if (!class_exists($entity)) {
throw new ServiceCreationException("Class $entity not found.");
}
return $entity;
}
return null;
}
public function completeDefinition(Definition $def): void
{
$this->currentService = in_array($def, $this->builder->getDefinitions(), true) ? $def : null;
$this->currentServiceType = $def->getType();
$this->currentServiceAllowed = false;
try {
$def->complete($this);
$this->addDependency(new \ReflectionClass($def->getType()));
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->currentService = $this->currentServiceType = null;
}
}
public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
{
$this->currentServiceAllowed = $currentServiceAllowed;
$entity = $this->normalizeEntity($statement);
$arguments = $this->convertReferences($statement->arguments);
switch (true) {
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
break;
case $entity === 'not':
$entity = ['', '!'];
break;
case is_string($entity): // create class
if (!class_exists($entity)) {
throw new ServiceCreationException("Class $entity not found.");
} elseif ((new ReflectionClass($entity))->isAbstract()) {
throw new ServiceCreationException("Class $entity is abstract.");
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
$visibility = $rm->isProtected() ? 'protected' : 'private';
throw new ServiceCreationException("Class $entity has $visibility constructor.");
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
$arguments = self::autowireArguments($constructor, $arguments, $this, $this->currentService);
$this->addDependency($constructor);
} elseif ($arguments) {
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
}
break;
case $entity instanceof Reference:
$entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())];
break;
case is_array($entity):
if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?\z#', $entity[1])) {
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
}
switch (true) {
case $entity[0] === '': // function call
if (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
} elseif (!function_exists($entity[1])) {
throw new ServiceCreationException("Function $entity[1] doesn't exist.");
}
$rf = new \ReflectionFunction($entity[1]);
$arguments = self::autowireArguments($rf, $arguments, $this, $this->currentService);
$this->addDependency($rf);
break;
case $entity[0] instanceof Statement:
$entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
// break omitted
case is_string($entity[0]): // static method call
case $entity[0] instanceof Reference:
if ($entity[1][0] === '$') { // property getter, setter or appender
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
if (!$arguments && substr($entity[1], -2) === '[]') {
throw new ServiceCreationException("Missing argument for $entity[1].");
}
} elseif (
$type = $entity[0] instanceof Reference
? $this->resolveReferenceType($entity[0])
: $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
) {
$rc = new ReflectionClass($type);
if ($rc->hasMethod($entity[1])) {
$rm = $rc->getMethod($entity[1]);
if (!$rm->isPublic()) {
throw new ServiceCreationException("$type::$entity[1]() is not callable.");
}
$arguments = self::autowireArguments($rm, $arguments, $this, $this->currentService);
$this->addDependency($rm);
} elseif (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException("Unable to pass specified arguments to $type::$entity[1]().");
}
}
}
}
try {
$arguments = $this->completeArguments($arguments);
} catch (ServiceCreationException $e) {
if (!strpos($e->getMessage(), ' (used in')) {
$e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
}
throw $e;
}
return new Statement($entity, $arguments);
}
private function completeArguments(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if ($val instanceof Statement) {
$entity = $val->getEntity();
if ($entity === 'typed' || $entity === 'tagged') {
$services = [];
$current = $this->currentService ? $this->currentService->getName() : null;
foreach ($val->arguments as $argument) {
foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
if ($name !== $current) {
$services[] = new Reference($name);
}
}
}
$val = $this->completeArguments($services);
} else {
$val = $this->completeStatement($val, $this->currentServiceAllowed);
}
} elseif ($val instanceof Definition || $val instanceof Reference) {
$val = $this->normalizeEntity(new Statement($val));
}
});
return $arguments;
}
/**
* @return string|array|Reference literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member]
*/
private function normalizeEntity(Statement $statement)
{
$entity = $statement->getEntity();
if (is_array($entity)) {
$item = &$entity[0];
} else {
$item = &$entity;
}
if ($item instanceof Definition) {
$name = current(array_keys($this->builder->getDefinitions(), $item, true));
if ($name == false) {
throw new ServiceCreationException("Service '{$item->getName()}' not found in definitions.");
}
$item = new Reference($name);
}
if ($item instanceof Reference) {
$item = $this->normalizeReference($item);
}
return $entity;
}
/**
* Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
*/
public function normalizeReference(Reference $ref): Reference
{
$service = $ref->getValue();
if ($ref->isSelf()) {
return $ref;
} elseif ($ref->isName()) {
if (!$this->builder->hasDefinition($service)) {
throw new ServiceCreationException("Reference to missing service '$service'.");
}
return $this->currentService && $service === $this->currentService->getName()
? new Reference(Reference::SELF)
: $ref;
} else {
try {
$res = $this->getByType($service);
} catch (NotAllowedDuringResolvingException $e) {
return new Reference($service);
}
if (!$res) {
throw new ServiceCreationException("Reference to missing service of type $service.");
}
return $res;
}
}
public function resolveReference(Reference $ref): Definition
{
return $ref->isSelf()
? $this->currentService
: $this->builder->getDefinition($ref->getValue());
}
/**
* Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
* @throws ServiceCreationException when multiple found
* @throws MissingServiceException when not found
*/
public function getByType(string $type): Reference
{
if (
$this->currentService
&& $this->currentServiceAllowed
&& is_a($this->currentServiceType, $type, true)
) {
return new Reference(Reference::SELF);
}
return new Reference($this->builder->getByType($type, true));
}
/**
* Adds item to the list of dependencies.
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
* @return static
*/
public function addDependency($dep)
{
$this->builder->addDependency($dep);
return $this;
}
private function completeException(\Exception $e, Definition $def): ServiceCreationException
{
if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) {
return $e;
} else {
$name = $def->getName();
$type = $def->getType();
if (!$type) {
$message = "Service '$name': " . $e->getMessage();
} elseif (!$name || ctype_digit($name)) {
$message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage());
} else {
$message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage());
}
return $e instanceof ServiceCreationException
? $e->setMessage($message)
: new ServiceCreationException($message, 0, $e);
}
}
private function entityToString($entity): string
{
$referenceToText = function (Reference $ref): string {
return $ref->isSelf() && $this->currentService
? '@' . $this->currentService->getName()
: '@' . $ref->getValue();
};
if (is_string($entity)) {
return $entity . '::__construct()';
} elseif ($entity instanceof Reference) {
$entity = $referenceToText($entity);
} elseif (is_array($entity)) {
if (strpos($entity[1], '$') === false) {
$entity[1] .= '()';
}
if ($entity[0] instanceof Reference) {
$entity[0] = $referenceToText($entity[0]);
} elseif (!is_string($entity[0])) {
return $entity[1];
}
return implode('::', $entity);
}
return (string) $entity;
}
private function convertReferences(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
$pair = explode('::', substr($val, 1), 2);
if (!isset($pair[1])) { // @service
$val = new Reference($pair[0]);
} elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
$val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
} else { // @service::property
$val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
}
} elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
$val = substr($val, 1);
}
});
return $arguments;
}
/**
* Add missing arguments using autowiring.
* @param Resolver|Container $resolver
* @throws ServiceCreationException
*/
public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, $resolver, Definitions\Definition $current = null): array
{
$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] = $resolver->getByType($type);
} catch (MissingServiceException $e) {
$res[$num] = null;
} 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 {
$optCount = 0;
}
} elseif (
$method instanceof \ReflectionMethod
&& $parameter->isArray()
&& preg_match('#@param[ \t]+([\w\\\\]+)\[\][ \t]+\$' . $paramName . '#', (string) $method->getDocComment(), $m)
&& ($type = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
&& (class_exists($type) || interface_exists($type))
) {
$list = $resolver instanceof self
? $resolver->getContainerBuilder()->findAutowired($type)
: array_map([$resolver, 'getService'], $resolver->findAutowired($type));
$res[$num] = [];
foreach ($list as $item) {
if ($item !== $current) {
$res[$num][] = $item;
}
}
} 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;
}
}
+381
View File
@@ -0,0 +1,381 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* Definition used by ContainerBuilder.
*
* @property string|null $class
* @property Statement|null $factory
* @property Statement[] $setup
*/
class ServiceDefinition
{
use Nette\SmartObject;
const
IMPLEMENT_MODE_CREATE = 'create',
IMPLEMENT_MODE_GET = 'get';
/** @var array */
public $parameters = [];
/** @var string|null class or interface name */
private $type;
/** @var Statement|null */
private $factory;
/** @var Statement[] */
private $setup = [];
/** @var array */
private $tags = [];
/** @var bool|string[] */
private $autowired = true;
/** @var bool */
private $dynamic = false;
/** @var string|null interface name */
private $implement;
/** @var string|null create | get */
private $implementMode;
/** @var callable 'pi' is noop */
private $notifier = 'pi';
/**
* @param string|null
* @return static
* @deprecated Use setType() instead.
*/
public function setClass($type, array $args = [])
{
call_user_func($this->notifier);
$this->type = $type;
if ($args) {
$this->setFactory($type, $args);
}
return $this;
}
/**
* @return string|null
* @deprecated Use getType() instead.
*/
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';
}
}
+60
View File
@@ -0,0 +1,60 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* Assignment or calling statement.
*
* @property string|array|ServiceDefinition|null $entity
*/
class Statement
{
use Nette\SmartObject;
/** @var array */
public $arguments;
/** @var string|array|ServiceDefinition|null */
private $entity;
/**
* @param string|array|ServiceDefinition|null
*/
public function __construct($entity, array $arguments = [])
{
if (
!is_string($entity) // Class, @service, not, PHP literal, entity::member
&& !(is_array($entity) && isset($entity[0], $entity[1])) // [Class | @service | '' | Statement | ServiceDefinition, method | $property | $appender]
&& !$entity instanceof ServiceDefinition
&& $entity !== null
) {
throw new Nette\InvalidArgumentException('Argument is not valid Statement entity.');
}
$this->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;
}
}
+31
View File
@@ -0,0 +1,31 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
namespace Nette\DI;
use Nette;
/**
* Service not found exception.
*/
class MissingServiceException extends Nette\InvalidStateException
{
}
/**
* Service creation exception.
*/
class ServiceCreationException extends Nette\InvalidStateException
{
public function setMessage($message)
{
$this->message = $message;
return $this;
}
}
+12
View File
@@ -0,0 +1,12 @@
<?php
/**
* This file is part of the Nette Framework (https://nette.org)
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
*/
declare(strict_types=1);
class_alias(Nette\DI\Config\Adapter::class, Nette\DI\Config\IAdapter::class);
class_alias(Nette\DI\Definitions\Statement::class, Nette\DI\Statement::class);
class_alias(Nette\DI\Definitions\ServiceDefinition::class, Nette\DI\ServiceDefinition::class);