no message
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+507
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+345
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 . ';');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Vendored
+255
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
Vendored
+533
@@ -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
@@ -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';
|
||||
}
|
||||
}
|
||||
Vendored
+60
@@ -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
@@ -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
@@ -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);
|
||||
Reference in New Issue
Block a user