.phpstorm.meta.php 0000644 00000000273 14113404363 0010135 0 ustar 00 '@']));
override(\Nette\DI\ContainerBuilder::addDefinition(1), type(1));
composer.json 0000644 00000002145 14113404363 0007267 0 ustar 00 {
"name": "nette/di",
"description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP features.",
"keywords": ["nette", "di", "dic", "ioc", "factory", "compiled", "static"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.1 <8.2",
"ext-tokenizer": "*",
"nette/neon": "^3.0",
"nette/php-generator": "^3.3.3",
"nette/robot-loader": "^3.2",
"nette/schema": "^1.1",
"nette/utils": "^3.1.4"
},
"require-dev": {
"nette/tester": "^2.2",
"tracy/tracy": "^2.3",
"phpstan/phpstan": "^0.12"
},
"conflict": {
"nette/bootstrap": "<3.0"
},
"autoload": {
"classmap": ["src/"]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}
contributing.md 0000644 00000002504 14113404363 0007575 0 ustar 00 How to contribute & use the issue tracker
=========================================
Nette welcomes your contributions. There are several ways to help out:
* Create an issue on GitHub, if you have found a bug
* Write test cases for open bug issues
* Write fixes for open bug/feature issues, preferably with test cases included
* Contribute to the [documentation](https://nette.org/en/writing)
Issues
------
Please **do not use the issue tracker to ask questions**. We will be happy to help you
on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette).
A good bug report shouldn't leave others needing to chase you up for more
information. Please try to be as detailed as possible in your report.
**Feature requests** are welcome. But take a moment to find out whether your idea
fits with the scope and aims of the project. It's up to *you* to make a strong
case to convince the project's developers of the merits of this feature.
Contributing
------------
If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing).
The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them.
Please do not fix whitespace, format code, or make a purely cosmetic patch.
Thanks! :heart:
license.md 0000644 00000005244 14113404363 0006514 0 ustar 00 Licenses
========
Good news! You may use Nette Framework under the terms of either
the New BSD License or the GNU General Public License (GPL) version 2 or 3.
The BSD License is recommended for most projects. It is easy to understand and it
places almost no restrictions on what you can do with the framework. If the GPL
fits better to your project, you can use the framework under this license.
You don't have to notify anyone which license you are using. You can freely
use Nette Framework in commercial projects as long as the copyright header
remains intact.
Please be advised that the name "Nette Framework" is a protected trademark and its
usage has some limitations. So please do not use word "Nette" in the name of your
project or top-level domain, and choose a name that stands on its own merits.
If your stuff is good, it will not take long to establish a reputation for yourselves.
New BSD License
---------------
Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of "Nette Framework" nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
This software is provided by the copyright holders and contributors "as is" and
any express or implied warranties, including, but not limited to, the implied
warranties of merchantability and fitness for a particular purpose are
disclaimed. In no event shall the copyright owner or contributors be liable for
any direct, indirect, incidental, special, exemplary, or consequential damages
(including, but not limited to, procurement of substitute goods or services;
loss of use, data, or profits; or business interruption) however caused and on
any theory of liability, whether in contract, strict liability, or tort
(including negligence or otherwise) arising in any way out of the use of this
software, even if advised of the possibility of such damage.
GNU General Public License
--------------------------
GPL licenses are very very long, so instead of including them here we offer
you URLs with full text:
- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html)
- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html)
readme.md 0000644 00000021460 14113404363 0006325 0 ustar 00 Nette Dependency Injection (DI)
===============================
[![Downloads this Month](https://img.shields.io/packagist/dm/nette/di.svg)](https://packagist.org/packages/nette/di)
[![Tests](https://github.com/nette/di/workflows/Tests/badge.svg?branch=master)](https://github.com/nette/di/actions)
[![Coverage Status](https://coveralls.io/repos/github/nette/di/badge.svg?branch=master)](https://coveralls.io/github/nette/di?branch=master)
[![Latest Stable Version](https://poser.pugx.org/nette/di/v/stable)](https://github.com/nette/di/releases)
[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/di/blob/master/license.md)
Introduction
------------
Purpose of the Dependecy Injection (DI) is to free classes from the responsibility for obtaining objects that they need for its operation (these objects are called **services**). To pass them these services on their instantiation instead.
Nette DI is one of the most interesting part of framework. It is compiled DI container, extremely fast and easy to configure.
Documentation can be found on the [website](https://doc.nette.org/dependency-injection).
[Support Me](https://github.com/sponsors/dg)
--------------------------------------------
Do you like Nette DI? Are you looking forward to the new features?
[![Buy me a coffee](https://files.nette.org/icons/donation-3.svg)](https://github.com/sponsors/dg)
Thank you!
Installation
------------
The recommended way to install is via Composer:
```
composer require nette/di
```
It requires PHP version 7.1 and supports PHP up to 8.1.
Usage
-----
Let's have an application for sending newsletters. The code is maximally simplified and is available on the [GitHub](https://github.com/dg/di-example).
We have the object representing email:
```php
class Mail
{
public $subject;
public $message;
}
```
An object which can send emails:
```php
interface Mailer
{
function send(Mail $mail, $to);
}
```
A support for logging:
```php
interface Logger
{
function log($message);
}
```
And finally, a class that provides sending newsletters:
```php
class NewsletterManager
{
private $mailer;
private $logger;
function __construct(Mailer $mailer, Logger $logger)
{
$this->mailer = $mailer;
$this->logger = $logger;
}
function distribute(array $recipients)
{
$mail = new Mail;
...
foreach ($recipients as $recipient) {
$this->mailer->send($mail, $recipient);
}
$this->logger->log(...);
}
}
```
The code respects Dependency Injection, ie. **each object uses only variables which we had passed into it.**
Also, we have a ability to implement own `Logger` or `Mailer`, like this:
```php
class SendMailMailer implements Mailer
{
function send(Mail $mail, $to)
{
mail($to, $mail->subject, $mail->message);
}
}
class FileLogger implements Logger
{
private $file;
function __construct($file)
{
$this->file = $file;
}
function log($message)
{
file_put_contents($this->file, $message . "\n", FILE_APPEND);
}
}
```
**DI container is the supreme architect** which can create individual objects (in the terminology DI called services) and assemble and configure them exactly according to our needs.
Container for our application might look like this:
```php
class Container
{
private $logger;
private $mailer;
function getLogger()
{
if (!$this->logger) {
$this->logger = new FileLogger('log.txt');
}
return $this->logger;
}
function getMailer()
{
if (!$this->mailer) {
$this->mailer = new SendMailMailer;
}
return $this->mailer;
}
function createNewsletterManager()
{
return new NewsletterManager($this->getMailer(), $this->getLogger());
}
}
```
The implementation looks like this because:
- the individual services are created only on demand (lazy loading)
- doubly called `createNewsletterManager` will use the same logger and mailer instances
Let's instantiate `Container`, let it create manager and we can start spamming users with newsletters :-)
```php
$container = new Container;
$manager = $container->createNewsletterManager();
$manager->distribute(...);
```
Significant to Dependency Injection is that no class depends on the container. Thus it can be easily replaced with another one. For example with the container generated by Nette DI.
Nette DI
----------
Nette DI is the generator of containers. We instruct it (usually) with configuration files. This is configuration that leads to generate nearly the same class as the class `Container` above:
```neon
services:
- FileLogger( log.txt )
- SendMailMailer
- NewsletterManager
```
The big advantage is the shortness of configuration.
Nette DI actually generates PHP code of container. Therefore it is extremely fast. Developer can see the code, so he knows exactly what it is doing. He can even trace it.
Usage of Nette DI is very easy. Save the (above) configuration to the file `config.neon` and let's create a container:
```php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp');
$class = $loader->load(function($compiler) {
$compiler->loadConfig(__DIR__ . '/config.neon');
});
$container = new $class;
```
and then use container to create object `NewsletterManager` and we can send e-mails:
```php
$manager = $container->getByType(NewsletterManager::class);
$manager->distribute(['john@example.com', ...]);
```
The container will be generated only once and the code is stored in cache (in directory `__DIR__ . '/temp'`). Therefore the loading of configuration file is placed in the closure in `$loader->load()`, so it is called only once.
During development it is useful to activate auto-refresh mode which automatically regenerate the container when any class or configuration file is changed. Just in the constructor `ContainerLoader` append `true` as the second argument:
```php
$loader = new Nette\DI\ContainerLoader(__DIR__ . '/temp', true);
```
Services
--------
Services are registered in the DI container and their dependencies are automatically passed.
```neon
services:
manager: NewsletterManager
```
All dependencies declared in the constructor of this service will be automatically passed. Constructor passing is the preferred way of dependency injection for services.
If we want to pass dependencies by the setter, we can add the `setup` section to the service definition:
```neon
services:
manager:
factory: NewsletterManager
setup:
- setAnotherService
```
Class of the service:
```php
class NewsletterManager
{
private $anotherService;
public function setAnotherService(AnotherService $service)
{
$this->anotherService = $service;
}
...
```
We can also add the `inject: yes` directive. This directive will enable automatic call of `inject*` methods and passing dependencies to public variables with `@inject` annotations:
```neon
services:
foo:
factory: FooClass
inject: yes
```
Dependency `Service1` will be passed by calling the `inject*` method, dependency `Service2` will be assigned to the `$service2` variable:
```php
class FooClass
{
private $service1;
// 1) inject* method:
public function injectService1(Service1 $service)
{
$this->service1 = $service1;
}
// 2) Assign to the variable with the @inject annotation:
/** @inject @var Service2 */
public $service2;
}
```
However, this method is not ideal, because the variable must be declared as public and there is no way how you can ensure that the passed object will be of the given type. We also lose the ability to handle the assigned dependency in our code and we violate the principles of encapsulation.
Factories
---------
We can use factories generated from an interface. The interface must declare the returning type in the `@return` annotation of the method. Nette will generate a proper implementation of the interface.
The interface must have exactly one method named `create`. Our factory interface could be declared in the following way:
```php
interface IBarFactory
{
/**
* @return Bar
*/
public function create();
}
```
The `create` method will instantiate an `Bar` with the following definition:
```php
class Bar
{
private $logger;
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
}
```
The factory will be registered in the `config.neon` file:
```neon
services:
- IBarFactory
```
Nette will check if the declared service is an interface. If yes, it will also generate the corresponding implementation of the factory. The definition can be also written in a more verbose form:
```neon
services:
barFactory:
implement: IBarFactory
```
This full definition allows us to declare additional configuration of the object using the `arguments` and `setup` sections, similarly as for all other services.
In our code, we only have to obtain the factory instance and call the `create` method:
```php
class Foo
{
private $barFactory;
function __construct(IBarFactory $barFactory)
{
$this->barFactory = $barFactory;
}
function bar()
{
$bar = $this->barFactory->create();
}
}
```
src/Bridges/DITracy/ContainerPanel.php 0000644 00000004120 14113404363 0013620 0 ustar 00 container = $container;
$this->elapsedTime = self::$compilationTime
? microtime(true) - self::$compilationTime
: null;
}
/**
* Renders tab.
*/
public function getTab(): string
{
return Nette\Utils\Helpers::capture(function () {
$elapsedTime = $this->elapsedTime;
require __DIR__ . '/templates/ContainerPanel.tab.phtml';
});
}
/**
* Renders panel.
*/
public function getPanel(): string
{
$rc = new \ReflectionClass($this->container);
$tags = [];
$types = [];
foreach ($rc->getMethods() as $method) {
if (preg_match('#^createService(.+)#', $method->name, $m) && $method->getReturnType()) {
$types[lcfirst(str_replace('__', '.', $m[1]))] = $method->getReturnType()->getName();
}
}
$types = $this->getContainerProperty('types') + $types;
ksort($types, SORT_NATURAL);
foreach ($this->getContainerProperty('tags') as $tag => $tmp) {
foreach ($tmp as $service => $val) {
$tags[$service][$tag] = $val;
}
}
return Nette\Utils\Helpers::capture(function () use ($tags, $types, $rc) {
$container = $this->container;
$file = $rc->getFileName();
$instances = $this->getContainerProperty('instances');
$wiring = $this->getContainerProperty('wiring');
require __DIR__ . '/templates/ContainerPanel.panel.phtml';
});
}
private function getContainerProperty(string $name)
{
$prop = (new \ReflectionClass(Nette\DI\Container::class))->getProperty($name);
$prop->setAccessible(true);
return $prop->getValue($this->container);
}
}
src/Bridges/DITracy/templates/ContainerPanel.panel.phtml 0000644 00000004223 14113404363 0017255 0 ustar 00
= get_class($container) ?>
Services
Name
Autowired
Service
Tags
$type): ?>
= is_numeric($name) ? "$name " : Helpers::escapeHtml($name) ?>
= $autowired ? 'yes' : (isset($wiring[$type]) ? 'no' : '?') ?>
= Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
= get_class($instances[$name]) ?>
= Helpers::escapeHtml($type) ?>
true])
: Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]);
} ?>
Parameters
= Dumper::toHtml($container->parameters); ?>
Source: = Helpers::editorLink($file) ?>
src/Bridges/DITracy/templates/ContainerPanel.tab.phtml 0000644 00000001714 14113404363 0016726 0 ustar 00
= $elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '' ?>
src/DI/Attributes/Inject.php 0000644 00000000420 14113404363 0011675 0 ustar 00 services, used by getByType() */
private $highPriority = [];
/** @var array[] type => services, used by findByType() */
private $lowPriority = [];
/** @var string[] of classes excluded from autowiring */
private $excludedClasses = [];
public function __construct(ContainerBuilder $builder)
{
$this->builder = $builder;
}
/**
* Resolves service name by type.
* @param bool $throw exception if service not found?
* @throws MissingServiceException when not found
* @throws ServiceCreationException when multiple found
*/
public function getByType(string $type, bool $throw = false): ?string
{
$type = Helpers::normalizeClass($type);
$types = $this->highPriority;
if (empty($types[$type])) {
if ($throw) {
if (!class_exists($type) && !interface_exists($type)) {
throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
}
throw new MissingServiceException(sprintf('Service of type %s not found. Did you add it to configuration file?', $type));
}
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(sprintf(
"Multiple services of type $type found: %s%s",
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(sprintf(
"Incompatible class %s in autowiring definition of service '%s'.",
$autowiredType,
$name
));
}
}
}
foreach (class_parents($type) + class_implements($type) + [$type] as $parent) {
if (!$autowired || isset($this->excludedClasses[$parent])) {
continue;
} elseif (is_array($autowired)) {
$priority = false;
foreach ($autowired as $autowiredType) {
if (is_a($parent, $autowiredType, true)) {
if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) {
$this->lowPriority[$parent] = array_merge($this->lowPriority[$parent] ?? [], $this->highPriority[$parent]);
$this->highPriority[$parent] = [];
}
$preferred[$parent] = $priority = true;
break;
}
}
} else {
$priority = empty($preferred[$parent]);
}
$list = $priority ? 'highPriority' : 'lowPriority';
$this->$list[$parent][] = $name;
}
}
}
}
src/DI/Compiler.php 0000644 00000021506 14113404363 0010115 0 ustar 00 array[]] */
private $configs = [];
/** @var string */
private $sources = '';
/** @var DependencyChecker */
private $dependencies;
/** @var string */
private $className = 'Container';
public function __construct(ContainerBuilder $builder = null)
{
$this->builder = $builder ?: new ContainerBuilder;
$this->dependencies = new DependencyChecker;
$this->addExtension(self::SERVICES, new Extensions\ServicesExtension);
$this->addExtension(self::PARAMETERS, new Extensions\ParametersExtension($this->configs));
}
/**
* Add custom configurator extension.
* @return static
*/
public function addExtension(?string $name, CompilerExtension $extension)
{
if ($name === null) {
$name = '_' . count($this->extensions);
} elseif (isset($this->extensions[$name])) {
throw new Nette\InvalidArgumentException(sprintf("Name '%s' is already used or reserved.", $name));
}
$lname = strtolower($name);
foreach (array_keys($this->extensions) as $nm) {
if ($lname === strtolower((string) $nm)) {
throw new Nette\InvalidArgumentException(sprintf(
"Name of extension '%s' has the same name as '%s' in a case-insensitive manner.",
$name,
$nm
));
}
}
$this->extensions[$name] = $extension->setCompiler($this, $name);
return $this;
}
public function getExtensions(string $type = null): array
{
return $type
? array_filter($this->extensions, function ($item) use ($type): bool { return $item instanceof $type; })
: $this->extensions;
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
}
/** @return static */
public function setClassName(string $className)
{
$this->className = $className;
return $this;
}
/**
* Adds new configuration.
* @return static
*/
public function addConfig(array $config)
{
foreach ($config as $section => $data) {
$this->configs[$section][] = $data;
}
$this->sources .= "// source: array\n";
return $this;
}
/**
* Adds new configuration from file.
* @return static
*/
public function loadConfig(string $file, Config\Loader $loader = null)
{
$sources = $this->sources . "// source: $file\n";
$loader = $loader ?: new Config\Loader;
foreach ($loader->load($file, false) as $data) {
$this->addConfig($data);
}
$this->dependencies->add($loader->getDependencies());
$this->sources = $sources;
return $this;
}
/**
* Returns configuration.
* @deprecated
*/
public function getConfig(): array
{
return $this->config;
}
/**
* Sets the names of dynamic parameters.
* @return static
*/
public function setDynamicParameterNames(array $names)
{
assert($this->extensions[self::PARAMETERS] instanceof Extensions\ParametersExtension);
$this->extensions[self::PARAMETERS]->dynamicParams = $names;
return $this;
}
/**
* Adds dependencies to the list.
* @param array $deps of ReflectionClass|\ReflectionFunctionAbstract|string
* @return static
*/
public function addDependencies(array $deps)
{
$this->dependencies->add(array_filter($deps));
return $this;
}
/**
* Exports dependencies.
*/
public function exportDependencies(): array
{
return $this->dependencies->export();
}
/** @return static */
public function addExportedTag(string $tag)
{
if (isset($this->extensions[self::DI])) {
assert($this->extensions[self::DI] instanceof Extensions\DIExtension);
$this->extensions[self::DI]->exportedTags[$tag] = true;
}
return $this;
}
/** @return static */
public function addExportedType(string $type)
{
if (isset($this->extensions[self::DI])) {
assert($this->extensions[self::DI] instanceof Extensions\DIExtension);
$this->extensions[self::DI]->exportedTypes[$type] = true;
}
return $this;
}
public function compile(): string
{
$this->processExtensions();
$this->processBeforeCompile();
return $this->generateCode();
}
/** @internal */
public function processExtensions(): void
{
$first = $this->getExtensions(Extensions\ParametersExtension::class) + $this->getExtensions(Extensions\ExtensionsExtension::class);
foreach ($first as $name => $extension) {
$config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name);
$extension->setConfig($this->config[$name] = $config);
$extension->loadConfiguration();
}
$last = $this->getExtensions(Extensions\InjectExtension::class);
$this->extensions = array_merge(array_diff_key($this->extensions, $last), $last);
if ($decorator = $this->getExtensions(Extensions\DecoratorExtension::class)) {
Nette\Utils\Arrays::insertBefore($this->extensions, key($decorator), $this->getExtensions(Extensions\SearchExtension::class));
}
$extensions = array_diff_key($this->extensions, $first, [self::SERVICES => 1]);
foreach ($extensions as $name => $extension) {
$config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name);
$extension->setConfig($this->config[$name] = $config);
}
foreach ($extensions as $extension) {
$extension->loadConfiguration();
}
foreach ($this->getExtensions(Extensions\ServicesExtension::class) as $name => $extension) {
$config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name);
$extension->setConfig($this->config[$name] = $config);
$extension->loadConfiguration();
}
if ($extra = array_diff_key($this->extensions, $extensions, $first, [self::SERVICES => 1])) {
throw new Nette\DeprecatedException(sprintf(
"Extensions '%s' were added while container was being compiled.",
implode("', '", array_keys($extra))
));
} elseif ($extra = key(array_diff_key($this->configs, $this->extensions))) {
$hint = Nette\Utils\Helpers::getSuggestion(array_keys($this->extensions), $extra);
throw new InvalidConfigurationException(
sprintf("Found section '%s' in configuration, but corresponding extension is missing", $extra)
. ($hint ? ", did you mean '$hint'?" : '.')
);
}
}
private function processBeforeCompile(): void
{
$this->builder->resolve();
foreach ($this->extensions as $extension) {
$extension->beforeCompile();
$this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]);
}
$this->builder->complete();
}
/**
* Merges and validates configurations against scheme.
* @return array|object
*/
private function processSchema(Schema\Schema $schema, array $configs, $name = null)
{
$processor = new Schema\Processor;
$processor->onNewContext[] = function (Schema\Context $context) use ($name) {
$context->path = $name ? [$name] : [];
$context->dynamics = &$this->extensions[self::PARAMETERS]->dynamicValidators;
};
try {
$res = $processor->processMultiple($schema, $configs);
} catch (Schema\ValidationException $e) {
throw new Nette\DI\InvalidConfigurationException($e->getMessage());
}
foreach ($processor->getWarnings() as $warning) {
trigger_error($warning, E_USER_DEPRECATED);
}
return $res;
}
/** @internal */
public function generateCode(): string
{
$generator = $this->createPhpGenerator();
$class = $generator->generate($this->className);
$this->dependencies->add($this->builder->getDependencies());
foreach ($this->extensions as $extension) {
$extension->afterCompile($class);
$generator->addInitialization($class, $extension);
}
return $this->sources . "\n" . $generator->toString($class);
}
/**
* Loads list of service definitions from configuration.
*/
public function loadDefinitionsFromConfig(array $configList): void
{
$extension = $this->extensions[self::SERVICES];
assert($extension instanceof Extensions\ServicesExtension);
$extension->loadDefinitions($this->processSchema($extension->getConfigSchema(), [$configList]));
}
protected function createPhpGenerator(): PhpGenerator
{
return new PhpGenerator($this->builder);
}
/** @deprecated use non-static Compiler::loadDefinitionsFromConfig() */
public static function loadDefinitions(): void
{
throw new Nette\DeprecatedException(__METHOD__ . '() is deprecated, use non-static Compiler::loadDefinitionsFromConfig(array $configList).');
}
/** @deprecated use non-static Compiler::loadDefinitionsFromConfig() */
public static function loadDefinition(): void
{
throw new Nette\DeprecatedException(__METHOD__ . '() is deprecated, use non-static Compiler::loadDefinitionsFromConfig(array $configList).');
}
}
src/DI/CompilerExtension.php 0000644 00000007630 14113404363 0012014 0 ustar 00 initialization = new Nette\PhpGenerator\Closure;
$this->compiler = $compiler;
$this->name = $name;
return $this;
}
/**
* @param array|object $config
* @return static
*/
public function setConfig($config)
{
if (!is_array($config) && !is_object($config)) {
throw new Nette\InvalidArgumentException;
}
$this->config = $config;
return $this;
}
/**
* Returns extension configuration.
* @return array|object
*/
public function getConfig()
{
return $this->config;
}
/**
* Returns configuration schema.
*/
public function getConfigSchema(): Nette\Schema\Schema
{
return is_object($this->config)
? Nette\Schema\Expect::from($this->config)
: Nette\Schema\Expect::array();
}
/**
* Checks whether $config contains only $expected items and returns combined array.
* @throws Nette\InvalidStateException
* @deprecated use getConfigSchema()
*/
public function validateConfig(array $expected, array $config = null, string $name = null): array
{
if (func_num_args() === 1) {
return $this->config = $this->validateConfig($expected, $this->config);
}
if ($extra = array_diff_key((array) $config, $expected)) {
$name = $name ? str_replace('.', ' › ', $name) : $this->name;
$hint = Nette\Utils\Helpers::getSuggestion(array_keys($expected), key($extra));
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Unknown configuration option '%s › %s'",
$name,
$hint ? key($extra) : implode("', '{$name} › ", array_keys($extra))
) . ($hint ? ", did you mean '{$name} › {$hint}'?" : '.'));
}
return Nette\Schema\Helpers::merge($config, $expected);
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->compiler->getContainerBuilder();
}
/**
* Reads configuration from file.
*/
public function loadFromFile(string $file): array
{
$loader = $this->createLoader();
$res = $loader->load($file);
$this->compiler->addDependencies($loader->getDependencies());
return $res;
}
/**
* Loads list of service definitions from configuration.
* Prefixes its names and replaces @extension with name in definition.
*/
public function loadDefinitionsFromConfig(array $configList): void
{
$res = [];
foreach ($configList as $key => $config) {
$key = is_string($key) ? $this->name . '.' . $key : $key;
$res[$key] = Helpers::prefixServiceName($config, $this->name);
}
$this->compiler->loadDefinitionsFromConfig($res);
}
protected function createLoader(): Config\Loader
{
return new Config\Loader;
}
public function getInitialization(): Nette\PhpGenerator\Closure
{
return $this->initialization;
}
/**
* Prepend extension name to identifier or service name.
*/
public function prefix(string $id): string
{
return substr_replace($id, $this->name . '.', substr($id, 0, 1) === '@' ? 1 : 0, 0);
}
/**
* Processes configuration data. Intended to be overridden by descendant.
* @return void
*/
public function loadConfiguration()
{
}
/**
* Adjusts DI container before is compiled to PHP class. Intended to be overridden by descendant.
* @return void
*/
public function beforeCompile()
{
}
/**
* Adjusts DI container compiled to PHP class. Intended to be overridden by descendant.
* @return void
*/
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
}
}
src/DI/Config/Adapter.php 0000644 00000000737 14113404363 0011133 0 ustar 00 process((array) Neon\Neon::decode(Nette\Utils\FileSystem::read($file)));
}
/** @throws Nette\InvalidStateException */
public function process(array $arr): array
{
$res = [];
foreach ($arr as $key => $val) {
if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING_SUFFIX) {
if (!is_array($val) && $val !== null) {
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Replacing operator is available only for arrays, item '%s' is not array.",
$key
));
}
$key = substr($key, 0, -1);
$val[Helpers::PREVENT_MERGING] = true;
}
if (is_array($val)) {
$val = $this->process($val);
} elseif ($val instanceof Neon\Entity) {
if ($val->value === Neon\Neon::CHAIN) {
$tmp = null;
foreach ($this->process($val->attributes) as $st) {
$tmp = new Statement(
$tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')],
$st->arguments
);
}
$val = $tmp;
} else {
$tmp = $this->process([$val->value]);
if (is_string($tmp[0]) && strpos($tmp[0], '?') !== false) {
trigger_error('Operator ? is deprecated in config files.', E_USER_DEPRECATED);
}
$val = new Statement($tmp[0], $this->process($val->attributes));
}
}
$res[$key] = $val;
}
return $res;
}
/**
* Generates configuration in NEON format.
*/
public function dump(array $data): string
{
array_walk_recursive(
$data,
function (&$val): void {
if ($val instanceof Statement) {
$val = self::statementToEntity($val);
}
}
);
return "# generated by Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK);
}
private static function statementToEntity(Statement $val): Neon\Entity
{
array_walk_recursive(
$val->arguments,
function (&$val): void {
if ($val instanceof Statement) {
$val = self::statementToEntity($val);
} elseif ($val instanceof Reference) {
$val = '@' . $val->getValue();
}
}
);
$entity = $val->getEntity();
if ($entity instanceof Reference) {
$entity = '@' . $entity->getValue();
} elseif (is_array($entity)) {
if ($entity[0] instanceof Statement) {
return new Neon\Entity(
Neon\Neon::CHAIN,
[
self::statementToEntity($entity[0]),
new Neon\Entity('::' . $entity[1], $val->arguments),
]
);
} elseif ($entity[0] instanceof Reference) {
$entity = '@' . $entity[0]->getValue() . '::' . $entity[1];
} elseif (is_string($entity[0])) {
$entity = $entity[0] . '::' . $entity[1];
}
}
return new Neon\Entity($entity, $val->arguments);
}
}
src/DI/Config/Adapters/PhpAdapter.php 0000644 00000001244 14113404363 0013340 0 ustar 00 dump($data) . ';';
}
}
src/DI/Config/DefinitionSchema.php 0000644 00000015345 14113404363 0012765 0 ustar 00 builder = $builder;
}
public function complete($def, Context $context)
{
if ($def === [false]) {
return (object) $def;
}
if (Helpers::takeParent($def)) {
$def['reset']['all'] = true;
}
foreach (['arguments', 'setup', 'tags'] as $k) {
if (isset($def[$k]) && Helpers::takeParent($def[$k])) {
$def['reset'][$k] = true;
}
}
$def = $this->expandParameters($def);
$type = $this->sniffType(end($context->path), $def);
$def = $this->getSchema($type)->complete($def, $context);
if ($def) {
$def->defType = $type;
}
return $def;
}
public function merge($def, $base)
{
if (!empty($def['alteration'])) {
unset($def['alteration']);
}
return Nette\Schema\Helpers::merge($def, $base);
}
/**
* Normalizes configuration of service definitions.
*/
public function normalize($def, Context $context)
{
if ($def === null || $def === false) {
return (array) $def;
} elseif (is_string($def) && interface_exists($def)) {
return ['implement' => $def];
} elseif ($def instanceof Statement && is_string($def->getEntity()) && interface_exists($def->getEntity())) {
$res = ['implement' => $def->getEntity()];
if (array_keys($def->arguments) === ['tagged']) {
$res += $def->arguments;
} elseif (count($def->arguments) > 1) {
$res['references'] = $def->arguments;
} elseif ($factory = array_shift($def->arguments)) {
$res['factory'] = $factory;
}
return $res;
} elseif (!is_array($def) || isset($def[0], $def[1])) {
return ['factory' => $def];
} elseif (is_array($def)) {
if (isset($def['class']) && !isset($def['type'])) {
if ($def['class'] instanceof Statement) {
$key = end($context->path);
trigger_error(sprintf("Service '%s': option 'class' should be changed to 'factory'.", $key), 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(sprintf(
"Options '%s' and '%s' are aliases, use only '%s'.",
$alias,
$original,
$original
));
}
$def[$original] = $def[$alias];
unset($def[$alias]);
}
}
return $def;
} else {
throw new Nette\DI\InvalidConfigurationException('Unexpected format of service definition');
}
}
public function completeDefault(Context $context)
{
}
private function sniffType($key, array $def): string
{
if (is_string($key)) {
$name = preg_match('#^@[\w\\\\]+$#D', $key)
? $this->builder->getByType(substr($key, 1), false)
: $key;
if ($name && $this->builder->hasDefinition($name)) {
return get_class($this->builder->getDefinition($name));
}
}
if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) {
return Definitions\LocatorDefinition::class;
} elseif (isset($def['implement'])) {
return method_exists($def['implement'], 'create')
? Definitions\FactoryDefinition::class
: Definitions\AccessorDefinition::class;
} elseif (isset($def['imported'])) {
return Definitions\ImportedDefinition::class;
} else {
return Definitions\ServiceDefinition::class;
}
}
private function expandParameters(array $config): array
{
$params = $this->builder->parameters;
if (isset($config['parameters'])) {
foreach ((array) $config['parameters'] as $k => $v) {
$v = explode(' ', is_int($k) ? $v : $k);
$params[end($v)] = $this->builder::literal('$' . end($v));
}
}
return Nette\DI\Helpers::expand($config, $params);
}
private static function getSchema(string $type): Schema
{
static $cache;
$cache = $cache ?: [
Definitions\ServiceDefinition::class => self::getServiceSchema(),
Definitions\AccessorDefinition::class => self::getAccessorSchema(),
Definitions\FactoryDefinition::class => self::getFactorySchema(),
Definitions\LocatorDefinition::class => self::getLocatorSchema(),
Definitions\ImportedDefinition::class => self::getImportedSchema(),
];
return $cache[$type];
}
private static function getServiceSchema(): Schema
{
return Expect::structure([
'type' => Expect::type('string'),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
'alteration' => Expect::bool(),
]);
}
private static function getAccessorSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'implement' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getFactorySchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'implement' => Expect::string(),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'parameters' => Expect::array(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
]);
}
private static function getLocatorSchema(): Schema
{
return Expect::structure([
'implement' => Expect::string(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getImportedSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'imported' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
}
src/DI/Config/Helpers.php 0000644 00000001514 14113404363 0011147 0 ustar 00 Adapters\PhpAdapter::class,
'neon' => Adapters\NeonAdapter::class,
];
private $dependencies = [];
private $loadedFiles = [];
private $parameters = [];
/**
* Reads configuration from file.
*/
public function load(string $file, ?bool $merge = true): array
{
if (!is_file($file) || !is_readable($file)) {
throw new Nette\FileNotFoundException(sprintf("File '%s' is missing or is not readable.", $file));
}
if (isset($this->loadedFiles[$file])) {
throw new Nette\InvalidStateException(sprintf("Recursive included file '%s'", $file));
}
$this->loadedFiles[$file] = true;
$this->dependencies[] = $file;
$data = $this->getAdapter($file)->load($file);
$res = [];
if (isset($data[self::INCLUDES_KEY])) {
Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'");
$includes = Nette\DI\Helpers::expand($data[self::INCLUDES_KEY], $this->parameters);
foreach ($includes as $include) {
$include = $this->expandIncludedFile($include, $file);
$res = Nette\Schema\Helpers::merge($this->load($include, $merge), $res);
}
}
unset($data[self::INCLUDES_KEY], $this->loadedFiles[$file]);
if ($merge === false) {
$res[] = $data;
} else {
$res = Nette\Schema\Helpers::merge($data, $res);
}
return $res;
}
/**
* Save configuration to file.
*/
public function save(array $data, string $file): void
{
if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === false) {
throw new Nette\IOException(sprintf("Cannot write file '%s'.", $file));
}
}
/**
* Returns configuration files.
*/
public function getDependencies(): array
{
return array_unique($this->dependencies);
}
/**
* Expands included file name.
*/
public function expandIncludedFile(string $includedFile, string $mainFile): string
{
return preg_match('#([a-z]+:)?[/\\\\]#Ai', $includedFile) // is absolute
? $includedFile
: dirname($mainFile) . '/' . $includedFile;
}
/**
* Registers adapter for given file extension.
* @param string|Adapter $adapter
* @return static
*/
public function addAdapter(string $extension, $adapter)
{
$this->adapters[strtolower($extension)] = $adapter;
return $this;
}
private function getAdapter(string $file): Adapter
{
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!isset($this->adapters[$extension])) {
throw new Nette\InvalidArgumentException(sprintf("Unknown file extension '%s'.", $file));
}
return is_object($this->adapters[$extension])
? $this->adapters[$extension]
: new $this->adapters[$extension];
}
/** @return static */
public function setParameters(array $params)
{
$this->parameters = $params;
return $this;
}
}
src/DI/Container.php 0000644 00000021764 14113404363 0010273 0 ustar 00 type (complete list of available services) */
protected $types = [];
/** @var string[] alias => service name */
protected $aliases = [];
/** @var array[] tag name => service name => tag value */
protected $tags = [];
/** @var array[] type => level => services */
protected $wiring = [];
/** @var object[] service name => instance */
private $instances = [];
/** @var array circular reference detector */
private $creating;
/** @var array */
private $methods;
public function __construct(array $params = [])
{
$this->parameters = $params;
$this->methods = array_flip(array_filter(
get_class_methods($this),
function ($s) { return preg_match('#^createService.#', $s); }
));
}
public function getParameters(): array
{
return $this->parameters;
}
/**
* Adds the service to the container.
* @param object $service service or its factory
* @return static
*/
public function addService(string $name, $service)
{
$name = $this->aliases[$name] ?? $name;
if (isset($this->instances[$name])) {
throw new Nette\InvalidStateException(sprintf("Service '%s' already exists.", $name));
} elseif (!is_object($service)) {
throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service)));
}
$type = $service instanceof \Closure
? (string) Nette\Utils\Reflection::getReturnType(new \ReflectionFunction($service))
: get_class($service);
if (!isset($this->methods[self::getMethodName($name)])) {
$this->types[$name] = $type;
} elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s' must be instance of %s, %s.",
$name,
$expectedType,
$type ? "$type given" : 'add typehint to closure'
));
}
if ($service instanceof \Closure) {
$this->methods[self::getMethodName($name)] = $service;
$this->types[$name] = $type;
} else {
$this->instances[$name] = $service;
}
return $this;
}
/**
* Removes the service from the container.
*/
public function removeService(string $name): void
{
$name = $this->aliases[$name] ?? $name;
unset($this->instances[$name]);
}
/**
* Gets the service object by name.
* @return object
* @throws MissingServiceException
*/
public function getService(string $name)
{
if (!isset($this->instances[$name])) {
if (isset($this->aliases[$name])) {
return $this->getService($this->aliases[$name]);
}
$this->instances[$name] = $this->createService($name);
}
return $this->instances[$name];
}
/**
* Gets the service object by name.
* @return object
* @throws MissingServiceException
*/
public function getByName(string $name)
{
return $this->getService($name);
}
/**
* Gets the service type by name.
* @throws MissingServiceException
*/
public function getServiceType(string $name): string
{
$method = self::getMethodName($name);
if (isset($this->aliases[$name])) {
return $this->getServiceType($this->aliases[$name]);
} elseif (isset($this->types[$name])) {
return $this->types[$name];
} elseif (isset($this->methods[$method])) {
$type = (new \ReflectionMethod($this, $method))->getReturnType();
return $type ? $type->getName() : '';
} else {
throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
}
}
/**
* Does the service exist?
*/
public function hasService(string $name): bool
{
$name = $this->aliases[$name] ?? $name;
return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
}
/**
* Is the service created?
*/
public function isCreated(string $name): bool
{
if (!$this->hasService($name)) {
throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
}
$name = $this->aliases[$name] ?? $name;
return isset($this->instances[$name]);
}
/**
* Creates new instance of the service.
* @return object
* @throws MissingServiceException
*/
public function createService(string $name, array $args = [])
{
$name = $this->aliases[$name] ?? $name;
$method = self::getMethodName($name);
$cb = $this->methods[$method] ?? null;
if (isset($this->creating[$name])) {
throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));
} elseif ($cb === null) {
throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
}
try {
$this->creating[$name] = true;
$service = $cb instanceof \Closure
? $cb(...$args)
: $this->$method(...$args);
} finally {
unset($this->creating[$name]);
}
if (!is_object($service)) {
throw new Nette\UnexpectedValueException(sprintf(
"Unable to create service '$name', value returned by %s is not object.",
$cb instanceof \Closure ? 'closure' : "method $method()"
));
}
return $service;
}
/**
* Resolves service by type.
* @param bool $throw exception if service doesn't exist?
* @return object|null service
* @throws MissingServiceException
*/
public function getByType(string $type, bool $throw = true)
{
$type = Helpers::normalizeClass($type);
if (!empty($this->wiring[$type][0])) {
if (count($names = $this->wiring[$type][0]) === 1) {
return $this->getService($names[0]);
}
natsort($names);
throw new MissingServiceException(sprintf("Multiple services of type $type found: %s.", implode(', ', $names)));
} elseif ($throw) {
if (!class_exists($type) && !interface_exists($type)) {
throw new MissingServiceException(sprintf("Service of type '%s' not found. Check the class name because it cannot be found.", $type));
}
foreach ($this->methods as $method => $foo) {
$methodType = (new \ReflectionMethod(static::class, $method))->getReturnType()->getName();
if (is_a($methodType, $type, true)) {
throw new MissingServiceException(sprintf(
'Service of type %s is not autowired or is missing in di › export › types.',
$type
));
}
}
throw new MissingServiceException(sprintf(
'Service of type %s not found. Did you add it to configuration file?',
$type
));
}
return null;
}
/**
* Gets the autowired service names of the specified type.
* @return string[]
* @internal
*/
public function findAutowired(string $type): array
{
$type = Helpers::normalizeClass($type);
return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
}
/**
* Gets the service names of the specified type.
* @return string[]
*/
public function findByType(string $type): array
{
$type = Helpers::normalizeClass($type);
return empty($this->wiring[$type])
? []
: array_merge(...array_values($this->wiring[$type]));
}
/**
* Gets the service names of the specified tag.
* @return array of [service name => tag attributes]
*/
public function findByTag(string $tag): array
{
return $this->tags[$tag] ?? [];
}
/********************* autowiring ****************d*g**/
/**
* Creates new instance using autowiring.
* @return object
* @throws Nette\InvalidArgumentException
*/
public function createInstance(string $class, array $args = [])
{
$rc = new \ReflectionClass($class);
if (!$rc->isInstantiable()) {
throw new ServiceCreationException(sprintf('Class %s is not instantiable.', $class));
} elseif ($constructor = $rc->getConstructor()) {
return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
} elseif ($args) {
throw new ServiceCreationException(sprintf('Unable to pass arguments, class %s has no constructor.', $class));
}
return new $class;
}
/**
* Calls all methods starting with with "inject" using autowiring.
* @param object $service
*/
public function callInjects($service): void
{
Extensions\InjectExtension::callInjects($this, $service);
}
/**
* Calls method using autowiring.
* @return mixed
*/
public function callMethod(callable $function, array $args = [])
{
return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
}
private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
{
return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
return $single
? $this->getByType($type)
: array_map([$this, 'getService'], $this->findAutowired($type));
});
}
public static function getMethodName(string $name): string
{
if ($name === '') {
throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
}
return 'createService' . str_replace('.', '__', ucfirst($name));
}
}
src/DI/ContainerBuilder.php 0000644 00000022700 14113404363 0011571 0 ustar 00 service */
private $aliases = [];
/** @var Autowiring */
private $autowiring;
/** @var bool */
private $needsResolve = true;
/** @var bool */
private $resolving = false;
/** @var array */
private $dependencies = [];
public function __construct()
{
$this->autowiring = new Autowiring($this);
$this->addImportedDefinition(self::THIS_CONTAINER)->setType(Container::class);
}
/**
* Adds new service definition.
* @return Definitions\ServiceDefinition
*/
public function addDefinition(?string $name, Definition $definition = null): Definition
{
$this->needsResolve = true;
if ($name === null) {
for (
$i = 1;
isset($this->definitions['0' . $i]) || isset($this->aliases['0' . $i]);
$i++
);
$name = '0' . $i; // prevents converting to integer in array key
} elseif (is_int(key([$name => 1])) || !preg_match('#^\w+(\.\w+)*$#D', $name)) {
throw new Nette\InvalidArgumentException(sprintf('Service name must be a alpha-numeric string and not a number, %s given.', gettype($name)));
} else {
$name = $this->aliases[$name] ?? $name;
if (isset($this->definitions[$name])) {
throw new Nette\InvalidStateException(sprintf("Service '%s' has already been added.", $name));
}
$lname = strtolower($name);
foreach ($this->definitions as $nm => $foo) {
if ($lname === strtolower($nm)) {
throw new Nette\InvalidStateException(sprintf(
"Service '%s' has the same name as '%s' in a case-insensitive manner.",
$name,
$nm
));
}
}
}
$definition = $definition ?: new Definitions\ServiceDefinition;
$definition->setName($name);
$definition->setNotifier(function (): void {
$this->needsResolve = true;
});
return $this->definitions[$name] = $definition;
}
public function addAccessorDefinition(?string $name): Definitions\AccessorDefinition
{
return $this->addDefinition($name, new Definitions\AccessorDefinition);
}
public function addFactoryDefinition(?string $name): Definitions\FactoryDefinition
{
return $this->addDefinition($name, new Definitions\FactoryDefinition);
}
public function addLocatorDefinition(?string $name): Definitions\LocatorDefinition
{
return $this->addDefinition($name, new Definitions\LocatorDefinition);
}
public function addImportedDefinition(?string $name): Definitions\ImportedDefinition
{
return $this->addDefinition($name, new Definitions\ImportedDefinition);
}
/**
* Removes the specified service definition.
*/
public function removeDefinition(string $name): void
{
$this->needsResolve = true;
$name = $this->aliases[$name] ?? $name;
unset($this->definitions[$name]);
}
/**
* Gets the service definition.
*/
public function getDefinition(string $name): Definition
{
$service = $this->aliases[$name] ?? $name;
if (!isset($this->definitions[$service])) {
throw new MissingServiceException(sprintf("Service '%s' not found.", $name));
}
return $this->definitions[$service];
}
/**
* Gets all service definitions.
* @return Definition[]
*/
public function getDefinitions(): array
{
return $this->definitions;
}
/**
* Does the service definition or alias exist?
*/
public function hasDefinition(string $name): bool
{
$name = $this->aliases[$name] ?? $name;
return isset($this->definitions[$name]);
}
public function addAlias(string $alias, string $service): void
{
if (!$alias) { // builder is not ready for falsy names such as '0'
throw new Nette\InvalidArgumentException(sprintf('Alias name must be a non-empty string, %s given.', gettype($alias)));
} elseif (!$service) { // builder is not ready for falsy names such as '0'
throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($service)));
} elseif (isset($this->aliases[$alias])) {
throw new Nette\InvalidStateException(sprintf("Alias '%s' has already been added.", $alias));
} elseif (isset($this->definitions[$alias])) {
throw new Nette\InvalidStateException(sprintf("Service '%s' has already been added.", $alias));
}
$this->aliases[$alias] = $service;
}
/**
* Removes the specified alias.
*/
public function removeAlias(string $alias): void
{
unset($this->aliases[$alias]);
}
/**
* Gets all service aliases.
*/
public function getAliases(): array
{
return $this->aliases;
}
/**
* @param string[] $types
* @return static
*/
public function addExcludedClasses(array $types)
{
$this->needsResolve = true;
$this->autowiring->addExcludedClasses($types);
return $this;
}
/**
* Resolves autowired service name by type.
* @param bool $throw exception if service doesn't exist?
* @throws MissingServiceException
*/
public function getByType(string $type, bool $throw = false): ?string
{
$this->needResolved();
return $this->autowiring->getByType($type, $throw);
}
/**
* Gets autowired service definition of the specified type.
* @throws MissingServiceException
*/
public function getDefinitionByType(string $type): Definition
{
return $this->getDefinition($this->getByType($type, true));
}
/**
* Gets the autowired service names and definitions of the specified type.
* @return Definition[] service name is key
* @internal
*/
public function findAutowired(string $type): array
{
$this->needResolved();
return $this->autowiring->findByType($type);
}
/**
* Gets the service names and definitions of the specified type.
* @return Definition[] service name is key
*/
public function findByType(string $type): array
{
$this->needResolved();
$found = [];
foreach ($this->definitions as $name => $def) {
if (is_a($def->getType(), $type, true)) {
$found[$name] = $def;
}
}
return $found;
}
/**
* Gets the service names and tag values.
* @return array of [service name => tag attributes]
*/
public function findByTag(string $tag): array
{
$found = [];
foreach ($this->definitions as $name => $def) {
if (($tmp = $def->getTag($tag)) !== null) {
$found[$name] = $tmp;
}
}
return $found;
}
/********************* building ****************d*g**/
/**
* Checks services, resolves types and rebuilts autowiring classlist.
*/
public function resolve(): void
{
if ($this->resolving) {
return;
}
$this->resolving = true;
$resolver = new Resolver($this);
foreach ($this->definitions as $def) {
$resolver->resolveDefinition($def);
}
$this->autowiring->rebuild();
$this->resolving = $this->needsResolve = false;
}
private function needResolved(): void
{
if ($this->resolving) {
throw new NotAllowedDuringResolvingException;
} elseif ($this->needsResolve) {
$this->resolve();
}
}
public function complete(): void
{
$this->resolve();
foreach ($this->definitions as $def) {
$def->setNotifier(null);
}
$resolver = new Resolver($this);
foreach ($this->definitions as $def) {
$resolver->completeDefinition($def);
}
}
/**
* Adds item to the list of dependencies.
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
* @return static
* @internal
*/
public function addDependency($dep)
{
$this->dependencies[] = $dep;
return $this;
}
/**
* Returns the list of dependencies.
*/
public function getDependencies(): array
{
return $this->dependencies;
}
/** @internal */
public function exportMeta(): array
{
$defs = $this->definitions;
ksort($defs);
foreach ($defs as $name => $def) {
if ($def instanceof Definitions\ImportedDefinition) {
$meta['types'][$name] = $def->getType();
}
foreach ($def->getTags() as $tag => $value) {
$meta['tags'][$tag][$name] = $value;
}
}
$meta['aliases'] = $this->aliases;
ksort($meta['aliases']);
$all = [];
foreach ($this->definitions as $name => $def) {
if ($type = $def->getType()) {
foreach (class_parents($type) + class_implements($type) + [$type] as $class) {
$all[$class][] = $name;
}
}
}
[$low, $high] = $this->autowiring->getClassList();
foreach ($all as $class => $names) {
$meta['wiring'][$class] = array_filter([
$high[$class] ?? [],
$low[$class] ?? [],
array_diff($names, $low[$class] ?? [], $high[$class] ?? []),
]);
}
return $meta;
}
public static function literal(string $code, array $args = null): Nette\PhpGenerator\PhpLiteral
{
return new Nette\PhpGenerator\PhpLiteral(
$args === null ? $code : (new Nette\PhpGenerator\Dumper)->format($code, ...$args)
);
}
/** @deprecated */
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Statement) {
$val = (new Resolver($this))->completeStatement($val);
} elseif ($val instanceof Definition) {
$val = new Definitions\Reference($val->getName());
}
});
return (new PhpGenerator($this))->formatPhp($statement, $args);
}
/** @deprecated use resolve() */
public function prepareClassList(): void
{
trigger_error(__METHOD__ . '() is deprecated, use resolve()', E_USER_DEPRECATED);
$this->resolve();
}
}
src/DI/ContainerLoader.php 0000644 00000006327 14113404363 0011420 0 ustar 00 tempDirectory = $tempDirectory;
$this->autoRebuild = $autoRebuild;
}
/**
* @param callable $generator function (Nette\DI\Compiler $compiler): string|null
* @param mixed $key
*/
public function load(callable $generator, $key = null): string
{
$class = $this->getClassName($key);
if (!class_exists($class, false)) {
$this->loadFile($class, $generator);
}
return $class;
}
/**
* @param mixed $key
*/
public function getClassName($key): string
{
return 'Container_' . substr(md5(serialize($key)), 0, 10);
}
private function loadFile(string $class, callable $generator): void
{
$file = "$this->tempDirectory/$class.php";
if (!$this->isExpired($file) && (@include $file) !== false) { // @ file may not exist
return;
}
Nette\Utils\FileSystem::createDir($this->tempDirectory);
$handle = @fopen("$file.lock", 'c+'); // @ is escalated to exception
if (!$handle) {
throw new Nette\IOException(sprintf("Unable to create file '%s.lock'. %s", $file, Nette\Utils\Helpers::getLastError()));
} elseif (!@flock($handle, LOCK_EX)) { // @ is escalated to exception
throw new Nette\IOException(sprintf("Unable to acquire exclusive lock on '%s.lock'. %s", $file, Nette\Utils\Helpers::getLastError()));
}
if (!is_file($file) || $this->isExpired($file, $updatedMeta)) {
if (isset($updatedMeta)) {
$toWrite["$file.meta"] = $updatedMeta;
} else {
[$toWrite[$file], $toWrite["$file.meta"]] = $this->generate($class, $generator);
}
foreach ($toWrite as $name => $content) {
if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) {
@unlink("$name.tmp"); // @ - file may not exist
throw new Nette\IOException(sprintf("Unable to create file '%s'.", $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(sprintf("Unable to include '%s'.", $file));
}
flock($handle, LOCK_UN);
}
private function isExpired(string $file, string &$updatedMeta = null): bool
{
if ($this->autoRebuild) {
$meta = @unserialize((string) file_get_contents("$file.meta")); // @ - file may not exist
$orig = $meta[2] ?? null;
return empty($meta[0])
|| DependencyChecker::isExpired(...$meta)
|| ($orig !== $meta[2] && $updatedMeta = serialize($meta));
}
return false;
}
/** @return array of (code, file[]) */
protected function generate(string $class, callable $generator): array
{
$compiler = new Compiler;
$compiler->setClassName($class);
$code = $generator(...[&$compiler]) ?: $compiler->compile();
return [
"exportDependencies()),
];
}
}
src/DI/Definitions/AccessorDefinition.php 0000644 00000006534 14113404363 0014375 0 ustar 00 getName(),
$type
));
}
$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(sprintf(
"Service '%s': Interface %s must have just one non-static method get().",
$this->getName(),
$type
));
} elseif ($method->getNumberOfParameters()) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::get() must have no parameters.",
$this->getName(),
$type
));
}
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(sprintf('Method %s::get() has no return type or annotation @return.', $interface));
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException(sprintf(
"Class '%s' not found.\nCheck the return type or annotation @return of the %s::get() method.",
$returnType,
$interface
));
}
$this->setReference($returnType);
}
$this->reference = $resolver->normalizeReference($this->reference);
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setPrivate();
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setType($generator->getClassName());
$rm = new \ReflectionMethod($this->getType(), self::METHOD_GET);
$class->addMethod(self::METHOD_GET)
->setBody('return $this->container->getService(?);', [$this->reference->getValue()])
->setReturnType(Reflection::getReturnType($rm));
$method->setBody('return new class ($this) ' . $class . ';');
}
}
src/DI/Definitions/Definition.php 0000644 00000007417 14113404363 0012713 0 ustar 00 name) {
throw new Nette\InvalidStateException('Name already has been set.');
}
$this->name = $name;
return $this;
}
final public function getName(): ?string
{
return $this->name;
}
/** @return static */
protected function setType(?string $type)
{
if ($this->autowired && $this->notifier && $this->type !== $type) {
($this->notifier)();
}
if ($type === null) {
$this->type = null;
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Class or interface '%s' not found.",
$this->name,
$type
));
} else {
$this->type = Nette\DI\Helpers::normalizeClass($type);
}
return $this;
}
final public function getType(): ?string
{
return $this->type;
}
/** @return static */
final public function setTags(array $tags)
{
$this->tags = $tags;
return $this;
}
final public function getTags(): array
{
return $this->tags;
}
/**
* @param mixed $attr
* @return static
*/
final public function addTag(string $tag, $attr = true)
{
$this->tags[$tag] = $attr;
return $this;
}
/** @return mixed */
final public function getTag(string $tag)
{
return $this->tags[$tag] ?? null;
}
/**
* @param bool|string|string[] $state
* @return static
*/
final public function setAutowired($state = true)
{
if ($this->notifier && $this->autowired !== $state) {
($this->notifier)();
}
$this->autowired = is_string($state) || is_array($state)
? (array) $state
: (bool) $state;
return $this;
}
/** @return bool|string[] */
final public function getAutowired()
{
return $this->autowired;
}
/** @return static */
public function setExported(bool $state = true)
{
return $this->addTag('nette.exported', $state);
}
public function isExported(): bool
{
return (bool) $this->getTag('nette.exported');
}
public function __clone()
{
$this->notifier = $this->name = null;
}
/********************* life cycle ****************d*g**/
abstract public function resolveType(Nette\DI\Resolver $resolver): void;
abstract public function complete(Nette\DI\Resolver $resolver): void;
abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void;
final public function setNotifier(?callable $notifier): void
{
$this->notifier = $notifier;
}
/********************* deprecated stuff from former ServiceDefinition ****************d*g**/
/** @deprecated Use setType() */
public function setClass(?string $type)
{
return $this->setType($type);
}
/** @deprecated Use getType() */
public function getClass(): ?string
{
return $this->getType();
}
/** @deprecated Use '$def instanceof Nette\DI\Definitions\ImportedDefinition' */
public function isDynamic(): bool
{
return false;
}
/** @deprecated Use Nette\DI\Definitions\FactoryDefinition or AccessorDefinition */
public function getImplement(): ?string
{
return null;
}
/** @deprecated Use getAutowired() */
public function isAutowired()
{
return $this->autowired;
}
}
src/DI/Definitions/FactoryDefinition.php 0000644 00000021421 14113404363 0014232 0 ustar 00 resultDefinition = new ServiceDefinition;
}
/** @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
));
}
$rc = new \ReflectionClass($type);
$method = $rc->getMethods()[0] ?? null;
if (!$method || $method->isStatic() || $method->name !== self::METHOD_CREATE || count($rc->getMethods()) > 1) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Interface %s must have just one non-static method create().",
$this->getName(),
$type
));
}
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(sprintf('Method %s::create() has no return type or annotation @return.', $interface));
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException(sprintf(
"Class '%s' not found.\nCheck the return type or annotation @return of the %s::create() method.",
$returnType,
$interface
));
}
$resultDef->setType($returnType);
}
$resolver->resolveDefinition($resultDef);
}
public function complete(Nette\DI\Resolver $resolver): void
{
$resultDef = $this->resultDefinition;
if ($resultDef instanceof ServiceDefinition) {
if (!$this->parameters) {
$this->completeParameters($resolver);
}
if ($resultDef->getEntity() instanceof Reference && !$resultDef->getFactory()->arguments) {
$resultDef->setFactory([ // render as $container->createMethod()
new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER),
Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
]);
}
}
$resolver->completeDefinition($resultDef);
}
private function completeParameters(Nette\DI\Resolver $resolver): void
{
$interface = $this->getType();
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
$ctorParams = [];
if (
($class = $resolver->resolveEntityType($this->resultDefinition->getFactory()))
&& ($ctor = (new \ReflectionClass($class))->getConstructor())
) {
foreach ($ctor->getParameters() as $param) {
$ctorParams[$param->name] = $param;
}
}
foreach ($method->getParameters() as $param) {
$methodHint = Reflection::getParameterTypes($param);
if (isset($ctorParams[$param->name])) {
$ctorParam = $ctorParams[$param->name];
$ctorHint = Reflection::getParameterTypes($ctorParam);
if ($methodHint !== $ctorHint
&& !is_a((string) reset($methodHint), (string) reset($ctorHint), true)
) {
throw new ServiceCreationException(sprintf(
"Type of \$%s in %s::create() doesn't match type in %s constructor.",
$param->name,
$interface,
$class
));
}
$this->resultDefinition->getFactory()->arguments[$ctorParam->getPosition()] = Nette\DI\ContainerBuilder::literal('$' . $ctorParam->name);
} elseif (!$this->resultDefinition->getSetup()) {
$hint = Nette\Utils\Helpers::getSuggestion(array_keys($ctorParams), $param->name);
throw new ServiceCreationException(sprintf(
'Unused parameter $%s when implementing method %s::create()',
$param->name,
$interface
) . ($hint ? ", did you mean \${$hint}?" : '.'));
}
$paramDef = PHP_VERSION_ID < 80000
? ($methodHint && $param->allowsNull() ? '?' : '') . reset($methodHint)
: implode('|', $methodHint);
$paramDef .= ' ' . $param->name;
if ($param->isDefaultValueAvailable()) {
$this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
} else {
$this->parameters[] = $paramDef;
}
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setPrivate();
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setType($generator->getClassName());
$methodCreate = $class->addMethod(self::METHOD_CREATE);
$this->resultDefinition->generateMethod($methodCreate, $generator);
$body = $methodCreate->getBody();
$body = str_replace('$this', '$this->container', $body);
$body = str_replace('$this->container->container', '$this->container', $body);
$rm = new \ReflectionMethod($this->getType(), self::METHOD_CREATE);
$methodCreate
->setParameters($generator->convertParameters($this->parameters))
->setReturnType(Reflection::getReturnType($rm) ?: $this->getResultType())
->setBody($body);
$method->setBody('return new class ($this) ' . $class . ';');
}
public function __clone()
{
parent::__clone();
$this->resultDefinition = unserialize(serialize($this->resultDefinition));
}
}
src/DI/Definitions/ImportedDefinition.php 0000644 00000002001 14113404363 0014377 0 ustar 00 setReturnType('void')
->setBody(
'throw new Nette\\DI\\ServiceCreationException(?);',
["Unable to create imported service '{$this->getName()}', it must be added using addService()"]
);
}
/** @deprecated use '$def instanceof ImportedDefinition' */
public function isDynamic(): bool
{
return true;
}
}
src/DI/Definitions/LocatorDefinition.php 0000644 00000010711 14113404363 0014226 0 ustar 00 getName(), $type));
}
$methods = (new \ReflectionClass($type))->getMethods();
if (!$methods) {
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $type));
}
foreach ($methods as $method) {
if ($method->isStatic() || !(
(preg_match('#^(get|create)$#', $method->name) && $method->getNumberOfParameters() === 1)
|| (preg_match('#^(get|create)[A-Z]#', $method->name) && $method->getNumberOfParameters() === 0)
)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.",
$this->getName(),
$type,
$method->name
));
}
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
/** @return static */
public function setReferences(array $references)
{
$this->references = [];
foreach ($references as $name => $ref) {
$this->references[$name] = substr($ref, 0, 1) === '@'
? new Reference(substr($ref, 1))
: Reference::fromType($ref);
}
return $this;
}
/** @return Reference[] */
public function getReferences(): array
{
return $this->references;
}
/** @return static */
public function setTagged(?string $tagged)
{
$this->tagged = $tagged;
return $this;
}
public function getTagged(): ?string
{
return $this->tagged;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
if ($this->tagged !== null) {
$this->references = [];
foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) {
if (isset($this->references[$tag])) {
trigger_error(sprintf(
"Service '%s': duplicated tag '%s' with value '%s'.",
$this->getName(),
$this->tagged,
$tag
), E_USER_NOTICE);
}
$this->references[$tag] = new Reference($name);
}
}
foreach ($this->references as $name => $ref) {
$this->references[$name] = $resolver->normalizeReference($ref);
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setPrivate();
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setType($generator->getClassName());
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) {
preg_match('#^(get|create)(.*)#', $rm->name, $m);
$name = lcfirst($m[2]);
$nullable = $rm->getReturnType()->allowsNull();
$methodInner = $class->addMethod($rm->name)
->setReturnType(Reflection::getReturnType($rm))
->setReturnNullable($nullable);
if (!$name) {
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
->setPrivate();
$methodInner->setBody('if (!isset($this->mapping[$name])) {
' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . '
}
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')
->addParameter('name');
} elseif (isset($this->references[$name])) {
$ref = $this->references[$name]->getValue();
if ($m[1] === 'get') {
$methodInner->setBody('return $this->container->getService(?);', [$ref]);
} else {
$methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]);
}
} else {
$methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");');
}
}
$method->setBody('return new class ($this) ' . $class . ';');
}
}
src/DI/Definitions/Reference.php 0000644 00000001765 14113404363 0012521 0 ustar 00 value = $value;
}
public function getValue(): string
{
return $this->value;
}
public function isName(): bool
{
return strpos($this->value, '\\') === false && $this->value !== self::SELF;
}
public function isType(): bool
{
return strpos($this->value, '\\') !== false;
}
public function isSelf(): bool
{
return $this->value === self::SELF;
}
}
src/DI/Definitions/ServiceDefinition.php 0000644 00000014741 14113404363 0014232 0 ustar 00 factory = new Statement(null);
}
/** @deprecated Use setType() */
public function setClass(?string $type)
{
$this->setType($type);
if (func_num_args() > 1) {
trigger_error(sprintf('Service %s: %s() second parameter $args is deprecated, use setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
if ($args = func_get_arg(1)) {
$this->setFactory($type, $args);
}
}
return $this;
}
/** @return static */
public function setType(?string $type)
{
return parent::setType($type);
}
/**
* @param string|array|Definition|Reference|Statement $factory
* @return static
*/
public function setFactory($factory, array $args = [])
{
$this->factory = $factory instanceof Statement
? $factory
: new Statement($factory, $args);
return $this;
}
public function getFactory(): Statement
{
return $this->factory;
}
/** @return string|array|Definition|Reference|null */
public function getEntity()
{
return $this->factory->getEntity();
}
/** @return static */
public function setArguments(array $args = [])
{
$this->factory->arguments = $args;
return $this;
}
/** @return static */
public function setArgument($key, $value)
{
$this->factory->arguments[$key] = $value;
return $this;
}
/**
* @param Statement[] $setup
* @return static
*/
public function setSetup(array $setup)
{
foreach ($setup as $v) {
if (!$v instanceof Statement) {
throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Definitions\Statement[].');
}
}
$this->setup = $setup;
return $this;
}
/** @return Statement[] */
public function getSetup(): array
{
return $this->setup;
}
/**
* @param string|array|Definition|Reference|Statement $entity
* @return static
*/
public function addSetup($entity, array $args = [])
{
$this->setup[] = $entity instanceof Statement
? $entity
: new Statement($entity, $args);
return $this;
}
/** @deprecated */
public function setParameters(array $params)
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__));
}
/** @deprecated */
public function getParameters(): array
{
trigger_error(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return [];
}
/** @deprecated use $builder->addImportedDefinition(...) */
public function setDynamic(): void
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addImportedDefinition(...)', $this->getName(), __METHOD__));
}
/** @deprecated use $builder->addFactoryDefinition(...) or addAccessorDefinition(...) */
public function setImplement(): void
{
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addFactoryDefinition(...)', $this->getName(), __METHOD__));
}
/** @deprecated use addTag('nette.inject') */
public function setInject(bool $state = true)
{
trigger_error(sprintf('Service %s: %s() is deprecated, use addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, $state);
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
if (!$this->getEntity()) {
if (!$this->getType()) {
throw new ServiceCreationException('Factory and type are missing in definition of service.');
}
$this->setFactory($this->getType(), $this->factory->arguments ?? []);
} elseif (!$this->getType()) {
$type = $resolver->resolveEntityType($this->factory);
if (!$type) {
throw new ServiceCreationException('Unknown service type, specify it or declare return type of factory.');
}
$this->setType($type);
$resolver->addDependency(new \ReflectionClass($type));
}
// auto-disable autowiring for aliases
if ($this->getAutowired() === true && $this->getEntity() instanceof Reference) {
$this->setAutowired(false);
}
}
public function complete(Nette\DI\Resolver $resolver): void
{
$entity = $this->factory->getEntity();
if ($entity instanceof Reference && !$this->factory->arguments && !$this->setup) {
$ref = $resolver->normalizeReference($entity);
$this->setFactory([new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER), 'getService'], [$ref->getValue()]);
}
$this->factory = $resolver->completeStatement($this->factory);
foreach ($this->setup as &$setup) {
if (
is_string($setup->getEntity())
&& strpbrk($setup->getEntity(), ':@?\\') === false
) { // auto-prepend @self
$setup = new Statement([new Reference(Reference::SELF), $setup->getEntity()], $setup->arguments);
}
$setup = $resolver->completeStatement($setup, true);
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$entity = $this->factory->getEntity();
$code = $generator->formatStatement($this->factory) . ";\n";
if (!$this->setup) {
$method->setBody('return ' . $code);
return;
}
$code = '$service = ' . $code;
$type = $this->getType();
if (
$type !== $entity
&& !(is_array($entity) && $entity[0] instanceof Reference && $entity[0]->getValue() === Nette\DI\ContainerBuilder::THIS_CONTAINER)
&& !(is_string($entity) && preg_match('#^[\w\\\\]+$#D', $entity) && is_subclass_of($entity, $type))
) {
$code .= (new Nette\PhpGenerator\Dumper)->format(
"if (!\$service instanceof $type) {\n"
. "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
"Unable to create service '{$this->getName()}', value returned by factory is not $type type."
);
}
foreach ($this->setup as $setup) {
$code .= $generator->formatStatement($setup) . ";\n";
}
$code .= 'return $service;';
$method->setBody($code);
}
public function __clone()
{
parent::__clone();
$this->factory = unserialize(serialize($this->factory));
$this->setup = unserialize(serialize($this->setup));
}
}
class_exists(Nette\DI\ServiceDefinition::class);
src/DI/Definitions/Statement.php 0000644 00000003547 14113404363 0012567 0 ustar 00 entity = $entity;
$this->arguments = $arguments;
}
/** @return string|array|Definition|Reference|null */
public function getEntity()
{
return $this->entity;
}
}
class_exists(Nette\DI\Statement::class);
src/DI/DependencyChecker.php 0000644 00000011024 14113404363 0011700 0 ustar 00 dependencies = array_merge($this->dependencies, $deps);
return $this;
}
/**
* Exports dependencies.
*/
public function export(): array
{
$files = $phpFiles = $classes = $functions = [];
foreach ($this->dependencies as $dep) {
if (is_string($dep)) {
$files[] = $dep;
} elseif ($dep instanceof ReflectionClass) {
if (empty($classes[$name = $dep->name])) {
$all = [$name] + class_parents($name) + class_implements($name);
foreach ($all as &$item) {
$all += class_uses($item);
$phpFiles[] = (new ReflectionClass($item))->getFileName();
$classes[$item] = true;
}
}
} elseif ($dep instanceof \ReflectionFunctionAbstract) {
$phpFiles[] = $dep->getFileName();
$functions[] = rtrim(Reflection::toString($dep), '()');
} else {
throw new Nette\InvalidStateException(sprintf('Unexpected dependency %s', gettype($dep)));
}
}
$classes = array_keys($classes);
$functions = array_unique($functions, SORT_REGULAR);
$hash = self::calculateHash($classes, $functions);
$files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist
$phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist
return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash];
}
/**
* Are dependencies expired?
*/
public static function isExpired(
int $version,
array $files,
array &$phpFiles,
array $classes,
array $functions,
string $hash
): bool {
try {
$currentFiles = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist
$origPhpFiles = $phpFiles;
$phpFiles = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist
return $version !== self::VERSION
|| $files !== $currentFiles
|| ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions));
} catch (\ReflectionException $e) {
return true;
}
}
private static function calculateHash(array $classes, array $functions): string
{
$hash = [];
foreach ($classes as $name) {
$class = new ReflectionClass($name);
$hash[] = [
$name,
Reflection::getUseStatements($class),
$class->isAbstract(),
get_parent_class($name),
class_implements($name),
class_uses($name),
];
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
if ($prop->getDeclaringClass() == $class) { // intentionally ==
$hash[] = [
$name,
$prop->name,
$prop->getDocComment(),
Reflection::getPropertyTypes($prop),
PHP_VERSION_ID >= 80000 ? count($prop->getAttributes(Attributes\Inject::class)) : null,
];
}
}
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->getDeclaringClass() == $class) { // intentionally ==
$hash[] = [
$name,
$method->name,
$method->getDocComment(),
self::hashParameters($method),
Reflection::getReturnTypes($method),
];
}
}
}
$flip = array_flip($classes);
foreach ($functions as $name) {
if (strpos($name, '::')) {
$method = new ReflectionMethod($name);
$class = $method->getDeclaringClass();
if (isset($flip[$class->name])) {
continue;
}
$uses = Reflection::getUseStatements($class);
} else {
$method = new \ReflectionFunction($name);
$uses = null;
}
$hash[] = [
$name,
$uses,
$method->getDocComment(),
self::hashParameters($method),
Reflection::getReturnTypes($method),
];
}
return md5(serialize($hash));
}
private static function hashParameters(\ReflectionFunctionAbstract $method): array
{
$res = [];
foreach ($method->getParameters() as $param) {
$res[] = [
$param->name,
Reflection::getParameterTypes($param),
$param->isVariadic(),
$param->isDefaultValueAvailable()
? [Reflection::getParameterDefaultValue($param)]
: null,
];
}
return $res;
}
}
src/DI/DynamicParameter.php 0000644 00000000514 14113404363 0011564 0 ustar 00 getConfig() as $name => $value) {
$this->initialization->addBody('define(?, ?);', [$name, $value]);
}
}
}
src/DI/Extensions/DIExtension.php 0000644 00000006133 14113404363 0012672 0 ustar 00 debugMode = $debugMode;
$this->time = microtime(true);
$this->config = new class {
/** @var ?bool */
public $debugger;
/** @var string[] */
public $excluded = [];
/** @var ?string */
public $parentClass;
/** @var object */
public $export;
};
$this->config->export = new class {
/** @var bool */
public $parameters = true;
/** @var string[]|bool|null */
public $tags = true;
/** @var string[]|bool|null */
public $types = true;
};
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addExcludedClasses($this->config->excluded);
}
public function beforeCompile()
{
if (!$this->config->export->parameters) {
$this->getContainerBuilder()->parameters = [];
}
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if ($this->config->parentClass) {
$class->setExtends($this->config->parentClass);
}
$this->restrictTags($class);
$this->restrictTypes($class);
if (
$this->debugMode &&
($this->config->debugger ?? $this->getContainerBuilder()->getByType(\Tracy\Bar::class))
) {
$this->enableTracyIntegration();
}
$this->initializeTaggedServices();
}
private function restrictTags(Nette\PhpGenerator\ClassType $class): void
{
$option = $this->config->export->tags;
if ($option === true) {
} elseif ($option === false) {
$class->removeProperty('tags');
} elseif ($prop = $class->getProperties()['tags'] ?? null) {
$prop->value = array_intersect_key($prop->value, $this->exportedTags + array_flip((array) $option));
}
}
private function restrictTypes(Nette\PhpGenerator\ClassType $class): void
{
$option = $this->config->export->types;
if ($option === true) {
return;
}
$prop = $class->getProperty('wiring');
$prop->value = array_intersect_key(
$prop->value,
$this->exportedTypes + (is_array($option) ? array_flip($option) : [])
);
}
private function initializeTaggedServices(): void
{
foreach (array_filter($this->getContainerBuilder()->findByTag('run')) as $name => $on) {
trigger_error("Tag 'run' used in service '$name' definition is deprecated.", E_USER_DEPRECATED);
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
private function enableTracyIntegration(): void
{
Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time;
$this->initialization->addBody($this->getContainerBuilder()->formatPhp('?;', [
new Nette\DI\Definitions\Statement('@Tracy\Bar::addPanel', [new Nette\DI\Definitions\Statement(Nette\Bridges\DITracy\ContainerPanel::class)]),
]));
}
}
src/DI/Extensions/DecoratorExtension.php 0000644 00000004046 14113404363 0014321 0 ustar 00 Expect::list(),
'tags' => Expect::array(),
'inject' => Expect::bool(),
])
);
}
public function beforeCompile()
{
$this->getContainerBuilder()->resolve();
foreach ($this->config as $type => $info) {
if (!class_exists($type) && !interface_exists($type)) {
throw new Nette\DI\InvalidConfigurationException(sprintf("Decorated class '%s' not found.", $type));
}
if ($info->inject !== null) {
$info->tags[InjectExtension::TAG_INJECT] = $info->inject;
}
$this->addSetups($type, Nette\DI\Helpers::filterArguments($info->setup));
$this->addTags($type, Nette\DI\Helpers::filterArguments($info->tags));
}
}
public function addSetups(string $type, array $setups): void
{
foreach ($this->findByType($type) as $def) {
if ($def instanceof Definitions\FactoryDefinition) {
$def = $def->getResultDefinition();
}
foreach ($setups as $setup) {
if (is_array($setup)) {
$setup = new Definitions\Statement(key($setup), array_values($setup));
}
$def->addSetup($setup);
}
}
}
public function addTags(string $type, array $tags): void
{
$tags = Nette\Utils\Arrays::normalize($tags, true);
foreach ($this->findByType($type) as $def) {
$def->setTags($def->getTags() + $tags);
}
}
private function findByType(string $type): array
{
return array_filter($this->getContainerBuilder()->getDefinitions(), function (Definitions\Definition $def) use ($type): bool {
return is_a($def->getType(), $type, true)
|| ($def instanceof Definitions\FactoryDefinition && is_a($def->getResultType(), $type, true));
});
}
}
src/DI/Extensions/ExtensionsExtension.php 0000644 00000002133 14113404363 0014531 0 ustar 00 getConfig() as $name => $class) {
if (is_int($name)) {
$name = null;
}
$args = [];
if ($class instanceof Nette\DI\Definitions\Statement) {
[$class, $args] = [$class->getEntity(), $class->arguments];
}
if (!is_a($class, Nette\DI\CompilerExtension::class, true)) {
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Extension '%s' not found or is not Nette\\DI\\CompilerExtension descendant.",
$class
));
}
$this->compiler->addExtension($name, (new \ReflectionClass($class))->newInstanceArgs($args));
}
}
}
src/DI/Extensions/InjectExtension.php 0000644 00000012135 14113404363 0013611 0 ustar 00 getContainerBuilder()->getDefinitions() as $def) {
if ($def->getTag(self::TAG_INJECT)) {
$def = $def instanceof Definitions\FactoryDefinition
? $def->getResultDefinition()
: $def;
if ($def instanceof Definitions\ServiceDefinition) {
$this->updateDefinition($def);
}
}
}
}
private function updateDefinition(Definitions\ServiceDefinition $def): void
{
$resolvedType = (new DI\Resolver($this->getContainerBuilder()))->resolveEntityType($def->getFactory());
$class = is_subclass_of($resolvedType, $def->getType())
? $resolvedType
: $def->getType();
$setups = $def->getSetup();
foreach (self::getInjectProperties($class) as $property => $type) {
$builder = $this->getContainerBuilder();
$inject = new Definitions\Statement('$' . $property, [Definitions\Reference::fromType((string) $type)]);
foreach ($setups as $key => $setup) {
if ($setup->getEntity() === $inject->getEntity()) {
$inject = $setup;
$builder = null;
unset($setups[$key]);
}
}
self::checkType($class, $property, $type, $builder);
array_unshift($setups, $inject);
}
foreach (array_reverse(self::getInjectMethods($class)) as $method) {
$inject = new Definitions\Statement($method);
foreach ($setups as $key => $setup) {
if ($setup->getEntity() === $inject->getEntity()) {
$inject = $setup;
unset($setups[$key]);
}
}
array_unshift($setups, $inject);
}
$def->setSetup($setups);
}
/**
* Generates list of inject methods.
* @internal
*/
public static function getInjectMethods(string $class): array
{
$classes = [];
foreach (get_class_methods($class) as $name) {
if (substr($name, 0, 6) === 'inject') {
$classes[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->name;
}
}
$methods = array_keys($classes);
uksort($classes, function (string $a, string $b) use ($classes, $methods): int {
return $classes[$a] === $classes[$b]
? array_search($a, $methods, true) <=> array_search($b, $methods, true)
: (is_a($classes[$a], $classes[$b], true) ? 1 : -1);
});
return array_keys($classes);
}
/**
* Generates list of properties with annotation @inject.
* @internal
*/
public static function getInjectProperties(string $class): array
{
$res = [];
foreach (get_class_vars($class) as $name => $foo) {
$rp = new \ReflectionProperty($class, $name);
$hasAttr = PHP_VERSION_ID >= 80000 && $rp->getAttributes(DI\Attributes\Inject::class);
if ($hasAttr || DI\Helpers::parseAnnotation($rp, 'inject') !== null) {
if ($type = Reflection::getPropertyType($rp)) {
} elseif (!$hasAttr && ($type = DI\Helpers::parseAnnotation($rp, 'var'))) {
if (strpos($type, '|') !== false) {
throw new Nette\InvalidStateException(sprintf(
'The %s is not expected to have a union type.',
Reflection::toString($rp)
));
}
$type = Reflection::expandClassName($type, Reflection::getPropertyDeclaringClass($rp));
}
$res[$name] = $type;
}
}
ksort($res);
return $res;
}
/**
* Calls all methods starting with with "inject" using autowiring.
* @param object $service
*/
public static function callInjects(DI\Container $container, $service): void
{
if (!is_object($service)) {
throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service)));
}
foreach (self::getInjectMethods(get_class($service)) as $method) {
$container->callMethod([$service, $method]);
}
foreach (self::getInjectProperties(get_class($service)) as $property => $type) {
self::checkType($service, $property, $type, $container);
$service->$property = $container->getByType($type);
}
}
/**
* @param object|string $class
* @param DI\Container|DI\ContainerBuilder|null $container
*/
private static function checkType($class, string $name, ?string $type, $container): void
{
$propName = Reflection::toString(new \ReflectionProperty($class, $name));
if (!$type) {
throw new Nette\InvalidStateException(sprintf('Property %s has no type.', $propName));
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidStateException(sprintf(
"Class '%s' required by %s not found. Check the property type and 'use' statements.",
$type,
$propName
));
} elseif ($container && !$container->getByType($type, false)) {
throw new Nette\DI\MissingServiceException(sprintf(
'Service of type %s required by %s not found. Did you add it to configuration file?',
$type,
$propName
));
}
}
}
src/DI/Extensions/ParametersExtension.php 0000644 00000004153 14113404363 0014501 0 ustar 00 compilerConfig = &$compilerConfig;
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$params = $this->config;
$resolver = new Nette\DI\Resolver($builder);
$generator = new Nette\DI\PhpGenerator($builder);
foreach ($this->dynamicParams as $key) {
$params[$key] = array_key_exists($key, $params)
? new DynamicParameter($generator->formatPhp('($this->parameters[?] \?\? ?)', $resolver->completeArguments(Nette\DI\Helpers::filterArguments([$key, $params[$key]]))))
: new DynamicParameter((new Nette\PhpGenerator\Dumper)->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]);
$slice = Nette\DI\Helpers::expand($slice, $builder->parameters);
$this->compilerConfig = $slice + $this->compilerConfig;
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$parameters = $this->getContainerBuilder()->parameters;
array_walk_recursive($parameters, function (&$val): void {
if ($val instanceof Nette\DI\Definitions\Statement || $val instanceof DynamicParameter) {
$val = null;
}
});
$cnstr = $class->getMethod('__construct');
$cnstr->addBody('$this->parameters += ?;', [$parameters]);
foreach ($this->dynamicValidators as [$param, $expected]) {
if ($param instanceof Nette\DI\Definitions\Statement) {
continue;
}
$cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
}
}
}
src/DI/Extensions/PhpExtension.php 0000644 00000002517 14113404363 0013127 0 ustar 00 getConfig() as $name => $value) {
if ($value === null) {
continue;
} elseif ($name === 'include_path') {
$this->initialization->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]);
} elseif ($name === 'ignore_user_abort') {
$this->initialization->addBody('ignore_user_abort(?);', [$value]);
} elseif ($name === 'max_execution_time') {
$this->initialization->addBody('set_time_limit(?);', [$value]);
} elseif ($name === 'date.timezone') {
$this->initialization->addBody('date_default_timezone_set(?);', [$value]);
} elseif (function_exists('ini_set')) {
$this->initialization->addBody('ini_set(?, ?);', [$name, $value === false ? '0' : (string) $value]);
} elseif (ini_get($name) !== (string) $value) {
throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
}
}
}
}
src/DI/Extensions/SearchExtension.php 0000644 00000010766 14113404363 0013612 0 ustar 00 tempDir = $tempDir;
}
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::arrayOf(
Expect::structure([
'in' => Expect::string()->required(),
'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'exclude' => Expect::structure([
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
]),
'tags' => Expect::array(),
])
)->before(function ($val) {
return is_string($val['in'] ?? null)
? ['default' => $val]
: $val;
});
}
public function loadConfiguration()
{
foreach (array_filter($this->config) as $name => $batch) {
if (!is_dir($batch->in)) {
throw new Nette\DI\InvalidConfigurationException(sprintf(
"Option '%s › %s › in' must be valid directory name, '%s' given.",
$this->name,
$name,
$batch->in
));
}
foreach ($this->findClasses($batch) as $class) {
$this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags);
}
}
}
public function findClasses(\stdClass $config): array
{
$robot = new RobotLoader;
$robot->setTempDirectory($this->tempDir);
$robot->addDirectory($config->in);
$robot->acceptFiles = $config->files ?: ['*.php'];
$robot->reportParseErrors(false);
$robot->refresh();
$classes = array_unique(array_keys($robot->getIndexedClasses()));
$exclude = $config->exclude;
$acceptRE = self::buildNameRegexp($config->classes);
$rejectRE = self::buildNameRegexp($exclude->classes);
$acceptParent = array_merge($config->extends, $config->implements);
$rejectParent = array_merge($exclude->extends, $exclude->implements);
$found = [];
foreach ($classes as $class) {
if (!class_exists($class) && !interface_exists($class) && !trait_exists($class)) {
throw new Nette\InvalidStateException(sprintf(
'Class %s was found, but it cannot be loaded by autoloading.',
$class
));
}
$rc = new \ReflectionClass($class);
if (
($rc->isInstantiable()
||
($rc->isInterface()
&& count($methods = $rc->getMethods()) === 1
&& $methods[0]->name === 'create')
)
&& (!$acceptRE || preg_match($acceptRE, $rc->name))
&& (!$rejectRE || !preg_match($rejectRE, $rc->name))
&& (!$acceptParent || Arrays::some($acceptParent, function ($nm) use ($rc) { return $rc->isSubclassOf($nm); }))
&& (!$rejectParent || Arrays::every($rejectParent, function ($nm) use ($rc) { return !$rc->isSubclassOf($nm); }))
) {
$found[] = $rc->name;
}
}
return $found;
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($this->classes as $class => $foo) {
if ($builder->findByType($class)) {
unset($this->classes[$class]);
}
}
foreach ($this->classes as $class => $tags) {
$def = class_exists($class)
? $builder->addDefinition(null)->setType($class)
: $builder->addFactoryDefinition(null)->setImplement($class);
$def->setTags(Arrays::normalize($tags, true));
}
}
private static function buildNameRegexp(array $masks): ?string
{
$res = [];
foreach ((array) $masks as $mask) {
$mask = (strpos($mask, '\\') === false ? '**\\' : '') . $mask;
$mask = preg_quote($mask, '#');
$mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask);
$mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask);
$mask = str_replace('\*', '\w*', $mask);
$res[] = $mask;
}
return $res ? '#^(' . implode('|', $res) . ')$#i' : null;
}
}
src/DI/Extensions/ServicesExtension.php 0000644 00000014460 14113404363 0014163 0 ustar 00 getContainerBuilder()));
}
public function loadConfiguration()
{
$this->loadDefinitions($this->config);
}
/**
* Loads list of service definitions.
*/
public function loadDefinitions(array $config)
{
foreach ($config as $key => $defConfig) {
$this->loadDefinition($this->convertKeyToName($key), $defConfig);
}
}
/**
* Loads service definition from normalized configuration.
*/
private function loadDefinition(?string $name, \stdClass $config): void
{
try {
if ((array) $config === [false]) {
$this->getContainerBuilder()->removeDefinition($name);
return;
} elseif (!empty($config->alteration) && !$this->getContainerBuilder()->hasDefinition($name)) {
throw new Nette\DI\InvalidConfigurationException('missing original definition for alteration.');
}
$def = $this->retrieveDefinition($name, $config);
static $methods = [
Definitions\ServiceDefinition::class => 'updateServiceDefinition',
Definitions\AccessorDefinition::class => 'updateAccessorDefinition',
Definitions\FactoryDefinition::class => 'updateFactoryDefinition',
Definitions\LocatorDefinition::class => 'updateLocatorDefinition',
Definitions\ImportedDefinition::class => 'updateImportedDefinition',
];
$this->{$methods[$config->defType]}($def, $config);
$this->updateDefinition($def, $config);
} catch (\Exception $e) {
throw new Nette\DI\InvalidConfigurationException(($name ? "Service '$name': " : '') . $e->getMessage(), 0, $e);
}
}
/**
* Updates service definition according to normalized configuration.
*/
private function updateServiceDefinition(Definitions\ServiceDefinition $definition, \stdClass $config): void
{
if ($config->factory) {
$definition->setFactory(Helpers::filterArguments([$config->factory])[0]);
$definition->setType(null);
}
if ($config->type) {
$definition->setType($config->type);
}
if ($config->arguments) {
$arguments = Helpers::filterArguments($config->arguments);
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
$arguments += $definition->getFactory()->arguments;
}
$definition->setArguments($arguments);
}
if (isset($config->setup)) {
if (!empty($config->reset['setup'])) {
$definition->setSetup([]);
}
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
if (is_array($setup)) {
$setup = new Statement(key($setup), array_values($setup));
}
$definition->addSetup($setup);
}
}
if (isset($config->inject)) {
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
}
}
private function updateAccessorDefinition(Definitions\AccessorDefinition $definition, \stdClass $config): void
{
if (isset($config->implement)) {
$definition->setImplement($config->implement);
}
if ($ref = $config->factory ?? $config->type ?? null) {
$definition->setReference($ref);
}
}
private function updateFactoryDefinition(Definitions\FactoryDefinition $definition, \stdClass $config): void
{
$resultDef = $definition->getResultDefinition();
if (isset($config->implement)) {
$definition->setImplement($config->implement);
$definition->setAutowired(true);
}
if ($config->factory) {
$resultDef->setFactory(Helpers::filterArguments([$config->factory])[0]);
}
if ($config->type) {
$resultDef->setFactory($config->type);
}
if ($config->arguments) {
$arguments = Helpers::filterArguments($config->arguments);
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
$arguments += $resultDef->getFactory()->arguments;
}
$resultDef->setArguments($arguments);
}
if (isset($config->setup)) {
if (!empty($config->reset['setup'])) {
$resultDef->setSetup([]);
}
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
if (is_array($setup)) {
$setup = new Statement(key($setup), array_values($setup));
}
$resultDef->addSetup($setup);
}
}
if (isset($config->parameters)) {
$definition->setParameters($config->parameters);
}
if (isset($config->inject)) {
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
}
}
private function updateLocatorDefinition(Definitions\LocatorDefinition $definition, \stdClass $config): void
{
if (isset($config->implement)) {
$definition->setImplement($config->implement);
}
if (isset($config->references)) {
$definition->setReferences($config->references);
}
if (isset($config->tagged)) {
$definition->setTagged($config->tagged);
}
}
private function updateImportedDefinition(Definitions\ImportedDefinition $definition, \stdClass $config): void
{
if ($config->type) {
$definition->setType($config->type);
}
}
private function updateDefinition(Definitions\Definition $definition, \stdClass $config): void
{
if (isset($config->autowired)) {
$definition->setAutowired($config->autowired);
}
if (isset($config->tags)) {
if (!empty($config->reset['tags'])) {
$definition->setTags([]);
}
foreach ($config->tags as $tag => $attrs) {
if (is_int($tag) && is_string($attrs)) {
$definition->addTag($attrs);
} else {
$definition->addTag($tag, $attrs);
}
}
}
}
private function convertKeyToName($key): ?string
{
if (is_int($key)) {
return null;
} elseif (preg_match('#^@[\w\\\\]+$#D', $key)) {
return $this->getContainerBuilder()->getByType(substr($key, 1), true);
}
return $key;
}
private function retrieveDefinition(?string $name, \stdClass $config): Definitions\Definition
{
$builder = $this->getContainerBuilder();
if (!empty($config->reset['all'])) {
$builder->removeDefinition($name);
}
return $name && $builder->hasDefinition($name)
? $builder->getDefinition($name)
: $builder->addDefinition($name, new $config->defType);
}
}
src/DI/Helpers.php 0000644 00000015655 14113404363 0007755 0 ustar 00 $val) {
$res[self::expand($key, $params, $recursive)] = self::expand($val, $params, $recursive);
}
return $res;
} elseif ($var instanceof Statement) {
return new Statement(self::expand($var->getEntity(), $params, $recursive), self::expand($var->arguments, $params, $recursive));
} elseif ($var === '%parameters%' && !array_key_exists('parameters', $params)) {
return $recursive
? self::expand($params, $params, (is_array($recursive) ? $recursive : []))
: $params;
} elseif (!is_string($var)) {
return $var;
}
$parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = [];
$php = false;
foreach ($parts as $n => $part) {
if ($n % 2 === 0) {
$res[] = $part;
} elseif ($part === '') {
$res[] = '%';
} elseif (isset($recursive[$part])) {
throw new Nette\InvalidArgumentException(sprintf(
'Circular reference detected for variables: %s.',
implode(', ', array_keys($recursive))
));
} else {
$val = $params;
foreach (explode('.', $part) as $key) {
if (is_array($val) && array_key_exists($key, $val)) {
$val = $val[$key];
} elseif ($val instanceof DynamicParameter) {
$val = new DynamicParameter($val . '[' . var_export($key, true) . ']');
} else {
throw new Nette\InvalidArgumentException(sprintf("Missing parameter '%s'.", $part));
}
}
if ($recursive) {
$val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]);
}
if (strlen($part) + 2 === strlen($var)) {
return $val;
}
if ($val instanceof DynamicParameter) {
$php = true;
} elseif (!is_scalar($val)) {
throw new Nette\InvalidArgumentException(sprintf("Unable to concatenate non-scalar parameter '%s' into '%s'.", $part, $var));
}
$res[] = $val;
}
}
if ($php) {
$res = array_filter($res, function ($val): bool { return $val !== ''; });
$res = array_map(function ($val): string {
return $val instanceof DynamicParameter
? "($val)"
: var_export((string) $val, true);
}, $res);
return new DynamicParameter(implode(' . ', $res));
}
return implode('', $res);
}
/**
* Escapes '%' and '@'
* @param mixed $value
* @return mixed
*/
public static function escape($value)
{
if (is_array($value)) {
$res = [];
foreach ($value as $key => $val) {
$key = is_string($key) ? str_replace('%', '%%', $key) : $key;
$res[$key] = self::escape($val);
}
return $res;
} elseif (is_string($value)) {
return preg_replace('#^@|%#', '$0$0', $value);
}
return $value;
}
/**
* Removes ... and process constants recursively.
*/
public static function filterArguments(array $args): array
{
foreach ($args as $k => $v) {
if ($v === '...') {
unset($args[$k]);
} elseif (
PHP_VERSION_ID >= 80100
&& is_string($v)
&& preg_match('#^([\w\\\\]+)::\w+$#D', $v, $m)
&& enum_exists($m[1])
) {
$args[$k] = new Nette\PhpGenerator\PhpLiteral($v);
} elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*$#D', $v)) {
$args[$k] = constant(ltrim($v, ':'));
} elseif (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) {
$args[$k] = new Reference(substr($v, 1));
} elseif (is_array($v)) {
$args[$k] = self::filterArguments($v);
} elseif ($v instanceof Statement) {
[$tmp] = self::filterArguments([$v->getEntity()]);
$args[$k] = new Statement($tmp, self::filterArguments($v->arguments));
}
}
return $args;
}
/**
* Replaces @extension with real extension name in service definition.
* @param mixed $config
* @return mixed
*/
public static function prefixServiceName($config, string $namespace)
{
if (is_string($config)) {
if (strncmp($config, '@extension.', 10) === 0) {
$config = '@' . $namespace . '.' . substr($config, 11);
}
} elseif ($config instanceof Reference) {
if (strncmp($config->getValue(), 'extension.', 9) === 0) {
$config = new Reference($namespace . '.' . substr($config->getValue(), 10));
}
} elseif ($config instanceof Statement) {
return new Statement(
self::prefixServiceName($config->getEntity(), $namespace),
self::prefixServiceName($config->arguments, $namespace)
);
} elseif (is_array($config)) {
foreach ($config as &$val) {
$val = self::prefixServiceName($val, $namespace);
}
}
return $config;
}
/**
* Returns an annotation value.
* @param \ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionClass $ref
*/
public static function parseAnnotation(\Reflector $ref, string $name): ?string
{
if (!Reflection::areCommentsAvailable()) {
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
}
$re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
return $m[1] ?? '';
}
return null;
}
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
{
if ($type = Reflection::getReturnType($func)) {
return $type;
} elseif ($type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'))) {
if ($type === 'object' || $type === 'mixed') {
return null;
} elseif ($func instanceof \ReflectionMethod) {
return $type === 'static' || $type === '$this'
? $func->getDeclaringClass()->name
: Reflection::expandClassName($type, $func->getDeclaringClass());
} else {
return $type;
}
}
return null;
}
public static function normalizeClass(string $type): string
{
return class_exists($type) || interface_exists($type)
? (new \ReflectionClass($type))->name
: $type;
}
/**
* Non data-loss type conversion.
* @param mixed $value
* @return mixed
* @throws Nette\InvalidStateException
*/
public static function convertType($value, string $type)
{
if (is_scalar($value)) {
$norm = ($value === false ? '0' : (string) $value);
if ($type === 'float') {
$norm = preg_replace('#\.0*$#D', '', $norm);
}
$orig = $norm;
settype($norm, $type);
if ($orig === ($norm === false ? '0' : (string) $norm)) {
return $norm;
}
}
throw new Nette\InvalidStateException(sprintf(
'Cannot convert %s to %s.',
is_scalar($value) ? "'$value'" : gettype($value),
$type
));
}
}
src/DI/PhpGenerator.php 0000644 00000012373 14113404363 0010743 0 ustar 00 builder = $builder;
}
/**
* Generates PHP classes. First class is the container.
*/
public function generate(string $className): Php\ClassType
{
$this->className = $className;
$class = new Php\ClassType($this->className);
$class->setExtends(Container::class);
$class->addMethod('__construct')
->addBody('parent::__construct($params);')
->addParameter('params', [])
->setType('array');
foreach ($this->builder->exportMeta() as $key => $value) {
$class->addProperty($key)
->setProtected()
->setValue($value);
}
$definitions = $this->builder->getDefinitions();
ksort($definitions);
foreach ($definitions as $def) {
$class->addMember($this->generateMethod($def));
}
$class->getMethod(Container::getMethodName(ContainerBuilder::THIS_CONTAINER))
->setReturnType($className)
->setBody('return $this;');
$class->addMethod('initialize');
return $class;
}
public function toString(Php\ClassType $class): string
{
return '/** @noinspection PhpParamsInspection,PhpMethodMayBeStaticInspection */
declare(strict_types=1);
' . $class->__toString();
}
public function addInitialization(Php\ClassType $class, CompilerExtension $extension): void
{
$closure = $extension->getInitialization();
if ($closure->getBody()) {
$class->getMethod('initialize')
->addBody('// ' . $extension->prefix(''))
->addBody("($closure)();");
}
}
public function generateMethod(Definitions\Definition $def): Php\Method
{
$name = $def->getName();
try {
$method = new Php\Method(Container::getMethodName($name));
$method->setPublic();
$method->setReturnType($def->getType());
$def->generateMethod($method, $this);
return $method;
} catch (\Exception $e) {
throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
}
}
/**
* Formats PHP code for class instantiating, function calling or property setting in PHP.
*/
public function formatStatement(Statement $statement): string
{
$entity = $statement->getEntity();
$arguments = $statement->arguments;
switch (true) {
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
return $this->formatPhp($entity, $arguments);
case is_string($entity): // create class
return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []);
case is_array($entity):
switch (true) {
case $entity[1][0] === '$': // property getter, setter or appender
$name = substr($entity[1], 1);
if ($append = (substr($name, -2) === '[]')) {
$name = substr($name, 0, -2);
}
$prop = $entity[0] instanceof Reference
? $this->formatPhp('?->?', [$entity[0], $name])
: $this->formatPhp($entity[0] . '::$?', [$name]);
return $arguments
? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]])
: $prop;
case $entity[0] instanceof Statement:
$inner = $this->formatPhp('?', [$entity[0]]);
if (substr($inner, 0, 4) === 'new ') {
$inner = "($inner)";
}
return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]);
case $entity[0] instanceof Reference:
return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]);
case $entity[0] === '': // function call
return $this->formatPhp("$entity[1](...?)", [$arguments]);
case is_string($entity[0]): // static method call
return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]);
}
}
throw new Nette\InvalidStateException;
}
/**
* Formats PHP statement.
* @internal
*/
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Statement) {
$val = new Php\Literal($this->formatStatement($val));
} elseif ($val instanceof Reference) {
$name = $val->getValue();
if ($val->isSelf()) {
$val = new Php\Literal('$service');
} elseif ($name === ContainerBuilder::THIS_CONTAINER) {
$val = new Php\Literal('$this');
} else {
$val = ContainerBuilder::literal('$this->getService(?)', [$name]);
}
}
});
return (new Php\Dumper)->format($statement, ...$args);
}
/**
* Converts parameters from Definition to PhpGenerator.
* @return Php\Parameter[]
*/
public function convertParameters(array $parameters): array
{
$res = [];
foreach ($parameters as $k => $v) {
$tmp = explode(' ', is_int($k) ? $v : $k);
$param = $res[] = new Php\Parameter(end($tmp));
if (!is_int($k)) {
$param->setDefaultValue($v);
}
if (isset($tmp[1])) {
$param->setType($tmp[0]);
}
}
return $res;
}
public function getClassName(): ?string
{
return $this->className;
}
}
src/DI/Resolver.php 0000644 00000045405 14113404363 0010150 0 ustar 00 builder = $builder;
$this->recursive = new \SplObjectStorage;
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
}
public function resolveDefinition(Definition $def): void
{
if ($this->recursive->contains($def)) {
$names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive));
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
}
try {
$this->recursive->attach($def);
$def->resolveType($this);
if (!$def->getType()) {
throw new ServiceCreationException('Type of service is unknown.');
}
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->recursive->detach($def);
}
}
public function resolveReferenceType(Reference $ref): ?string
{
if ($ref->isSelf()) {
return $this->currentServiceType;
} elseif ($ref->isType()) {
return ltrim($ref->getValue(), '\\');
}
$def = $this->resolveReference($ref);
if (!$def->getType()) {
$this->resolveDefinition($def);
}
return $def->getType();
}
public function resolveEntityType(Statement $statement): ?string
{
$entity = $this->normalizeEntity($statement);
if (is_array($entity)) {
if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
$entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
if (!$entity[0]) {
return null;
}
}
try {
/** @var \ReflectionMethod|\ReflectionFunction $reflection */
$reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
$refClass = $reflection instanceof \ReflectionMethod
? $reflection->getDeclaringClass()
: null;
} catch (\ReflectionException $e) {
$refClass = $reflection = null;
}
if (isset($e) || ($refClass && (!$reflection->isPublic()
|| ($refClass->isTrait() && !$reflection->isStatic())
))) {
throw new ServiceCreationException(sprintf('Method %s() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null);
}
$this->addDependency($reflection);
$type = Helpers::getReturnType($reflection);
if ($type && !class_exists($type) && !interface_exists($type)) {
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Check the return type of %s() method.", $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(sprintf(
interface_exists($entity)
? "Interface %s can not be used as 'factory', did you mean 'implement'?"
: "Class '%s' not found.",
$entity
));
}
return $entity;
}
return null;
}
public function completeDefinition(Definition $def): void
{
$this->currentService = in_array($def, $this->builder->getDefinitions(), true)
? $def
: null;
$this->currentServiceType = $def->getType();
$this->currentServiceAllowed = false;
try {
$def->complete($this);
$this->addDependency(new \ReflectionClass($def->getType()));
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->currentService = $this->currentServiceType = null;
}
}
public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
{
$this->currentServiceAllowed = $currentServiceAllowed;
$entity = $this->normalizeEntity($statement);
$arguments = $this->convertReferences($statement->arguments);
$getter = function (string $type, bool $single) {
return $single
? $this->getByType($type)
: array_values(array_filter($this->builder->findAutowired($type), function ($obj) { return $obj !== $this->currentService; }));
};
switch (true) {
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
break;
case $entity === 'not':
if (count($arguments) > 1) {
throw new ServiceCreationException(sprintf(
'Function %s() expects at most 1 parameter, %s given.',
$entity,
count($arguments)
));
}
$entity = ['', '!'];
break;
case $entity === 'bool':
case $entity === 'int':
case $entity === 'float':
case $entity === 'string':
if (count($arguments) > 1) {
throw new ServiceCreationException(sprintf(
'Function %s() expects at most 1 parameter, %s given.',
$entity,
count($arguments)
));
}
$arguments = [$arguments[0], $entity];
$entity = [Helpers::class, 'convertType'];
break;
case is_string($entity): // create class
if (!class_exists($entity)) {
throw new ServiceCreationException(sprintf("Class '%s' not found.", $entity));
} elseif ((new ReflectionClass($entity))->isAbstract()) {
throw new ServiceCreationException(sprintf('Class %s is abstract.', $entity));
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
throw new ServiceCreationException(sprintf('Class %s has %s constructor.', $entity, $rm->isProtected() ? 'protected' : 'private'));
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
$arguments = self::autowireArguments($constructor, $arguments, $getter);
$this->addDependency($constructor);
} elseif ($arguments) {
throw new ServiceCreationException(sprintf(
'Unable to pass arguments, class %s has no constructor.',
$entity
));
}
break;
case $entity instanceof Reference:
$entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())];
break;
case is_array($entity):
if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) {
throw new ServiceCreationException(sprintf(
"Expected function, method or property name, '%s' given.",
$entity[1]
));
}
switch (true) {
case $entity[0] === '': // function call
if (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException(sprintf(
'Unable to pass specified arguments to %s.',
$entity[0]
));
} elseif (!function_exists($entity[1])) {
throw new ServiceCreationException(sprintf("Function %s doesn't exist.", $entity[1]));
}
$rf = new \ReflectionFunction($entity[1]);
$arguments = self::autowireArguments($rf, $arguments, $getter);
$this->addDependency($rf);
break;
case $entity[0] instanceof Statement:
$entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
// break omitted
case is_string($entity[0]): // static method call
case $entity[0] instanceof Reference:
if ($entity[1][0] === '$') { // property getter, setter or appender
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
if (!$arguments && substr($entity[1], -2) === '[]') {
throw new ServiceCreationException(sprintf('Missing argument for %s.', $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(sprintf('%s::%s() is not callable.', $type, $entity[1]));
}
$arguments = self::autowireArguments($rm, $arguments, $getter);
$this->addDependency($rm);
} elseif (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException(sprintf('Unable to pass specified arguments to %s::%s().', $type, $entity[1]));
}
}
}
}
try {
$arguments = $this->completeArguments($arguments);
} catch (ServiceCreationException $e) {
if (!strpos($e->getMessage(), ' (used in')) {
$e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
}
throw $e;
}
return new Statement($entity, $arguments);
}
public function completeArguments(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if ($val instanceof Statement) {
$entity = $val->getEntity();
if ($entity === 'typed' || $entity === 'tagged') {
$services = [];
$current = $this->currentService
? $this->currentService->getName()
: null;
foreach ($val->arguments as $argument) {
foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
if ($name !== $current) {
$services[] = new Reference($name);
}
}
}
$val = $this->completeArguments($services);
} else {
$val = $this->completeStatement($val, $this->currentServiceAllowed);
}
} elseif ($val instanceof Definition || $val instanceof Reference) {
$val = $this->normalizeEntity(new Statement($val));
}
});
return $arguments;
}
/** @return string|array|Reference literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member] */
private function normalizeEntity(Statement $statement)
{
$entity = $statement->getEntity();
if (is_array($entity)) {
$item = &$entity[0];
} else {
$item = &$entity;
}
if ($item instanceof Definition) {
$name = current(array_keys($this->builder->getDefinitions(), $item, true));
if ($name === false) {
throw new ServiceCreationException(sprintf("Service '%s' not found in definitions.", $item->getName()));
}
$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(sprintf("Reference to missing service '%s'.", $service));
}
return $this->currentService && $service === $this->currentService->getName()
? new Reference(Reference::SELF)
: $ref;
}
try {
return $this->getByType($service);
} catch (NotAllowedDuringResolvingException $e) {
return new Reference($service);
}
}
public function resolveReference(Reference $ref): Definition
{
return $ref->isSelf()
? $this->currentService
: $this->builder->getDefinition($ref->getValue());
}
/**
* Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
* @throws ServiceCreationException when multiple found
* @throws MissingServiceException when not found
*/
public function getByType(string $type): Reference
{
if (
$this->currentService
&& $this->currentServiceAllowed
&& is_a($this->currentServiceType, $type, true)
) {
return new Reference(Reference::SELF);
}
$name = $this->builder->getByType($type, true);
if (
!$this->currentServiceAllowed
&& $this->currentService === $this->builder->getDefinition($name)
) {
throw new MissingServiceException;
}
return new Reference($name);
}
/**
* Adds item to the list of dependencies.
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
* @return static
*/
public function addDependency($dep)
{
$this->builder->addDependency($dep);
return $this;
}
private function completeException(\Exception $e, Definition $def): ServiceCreationException
{
if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) {
return $e;
}
$name = $def->getName();
$type = $def->getType();
if ($name && !ctype_digit($name)) {
$message = "Service '$name'" . ($type ? " (type of $type)" : '') . ': ';
} elseif ($type) {
$message = "Service of type $type: ";
} elseif ($def instanceof Definitions\ServiceDefinition && $def->getEntity()) {
$message = 'Service (' . $this->entityToString($def->getEntity()) . '): ';
} else {
$message = '';
}
$message .= $type
? str_replace("$type::", '', $e->getMessage())
: $e->getMessage();
return $e instanceof ServiceCreationException
? $e->setMessage($message)
: new ServiceCreationException($message, 0, $e);
}
private function entityToString($entity): string
{
$referenceToText = function (Reference $ref): string {
return $ref->isSelf() && $this->currentService
? '@' . $this->currentService->getName()
: '@' . $ref->getValue();
};
if (is_string($entity)) {
return $entity . '::__construct()';
} elseif ($entity instanceof Reference) {
$entity = $referenceToText($entity);
} elseif (is_array($entity)) {
if (strpos($entity[1], '$') === false) {
$entity[1] .= '()';
}
if ($entity[0] instanceof Reference) {
$entity[0] = $referenceToText($entity[0]);
} elseif (!is_string($entity[0])) {
return $entity[1];
}
return implode('::', $entity);
}
return (string) $entity;
}
private function convertReferences(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
$pair = explode('::', substr($val, 1), 2);
if (!isset($pair[1])) { // @service
$val = new Reference($pair[0]);
} elseif (preg_match('#^[A-Z][A-Z0-9_]*$#D', $pair[1], $m)) { // @service::CONSTANT
$val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
} else { // @service::property
$val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
}
} elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
$val = substr($val, 1);
}
});
return $arguments;
}
/**
* Add missing arguments using autowiring.
* @param (callable(string $type, bool $single): object|object[]|null) $getter
* @throws ServiceCreationException
*/
public static function autowireArguments(
\ReflectionFunctionAbstract $method,
array $arguments,
callable $getter
): array {
$optCount = 0;
$num = -1;
$res = [];
foreach ($method->getParameters() as $num => $param) {
$paramName = $param->name;
if (!$param->isVariadic() && array_key_exists($paramName, $arguments)) {
$res[$num] = $arguments[$paramName];
unset($arguments[$paramName], $arguments[$num]);
} elseif (array_key_exists($num, $arguments)) {
$res[$num] = $arguments[$num];
unset($arguments[$num]);
} else {
$res[$num] = self::autowireArgument($param, $getter);
}
$optCount = $param->isOptional() && $res[$num] === ($param->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($param) : null)
? $optCount + 1
: 0;
}
// extra parameters
while (array_key_exists(++$num, $arguments)) {
$res[$num] = $arguments[$num];
unset($arguments[$num]);
$optCount = 0;
}
if ($arguments) {
throw new ServiceCreationException(sprintf(
'Unable to pass specified arguments to %s.',
Reflection::toString($method)
));
} elseif ($optCount) {
$res = array_slice($res, 0, -$optCount);
}
return $res;
}
/**
* Resolves missing argument using autowiring.
* @param (callable(string $type, bool $single): object|object[]|null) $getter
* @throws ServiceCreationException
* @return mixed
*/
private static function autowireArgument(\ReflectionParameter $parameter, callable $getter)
{
$desc = Reflection::toString($parameter);
if ($parameter->getType() instanceof \ReflectionIntersectionType) {
throw new ServiceCreationException(sprintf(
'Parameter %s has intersection type, so its value must be specified.',
$desc
));
}
$types = array_diff(Reflection::getParameterTypes($parameter), ['null']);
$type = count($types) === 1 ? reset($types) : null;
$method = $parameter->getDeclaringFunction();
if ($type && !Reflection::isBuiltinType($type)) {
try {
$res = $getter($type, true);
} catch (MissingServiceException $e) {
$res = null;
} catch (ServiceCreationException $e) {
throw new ServiceCreationException("{$e->getMessage()} (required by $desc)", 0, $e);
}
if ($res !== null || $parameter->allowsNull()) {
return $res;
} elseif (class_exists($type) || interface_exists($type)) {
throw new ServiceCreationException(sprintf(
'Service of type %s required by %s not found. Did you add it to configuration file?',
$type,
$desc
));
} else {
throw new ServiceCreationException(sprintf(
"Class '%s' required by %s not found. Check the parameter type and 'use' statements.",
$type,
$desc
));
}
} elseif (
$method instanceof \ReflectionMethod
&& $type === 'array'
&& preg_match('#@param[ \t]+([\w\\\\]+)\[\][ \t]+\$' . $parameter->name . '#', (string) $method->getDocComment(), $m)
&& ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
&& (class_exists($itemType) || interface_exists($itemType))
) {
return $getter($itemType, false);
} elseif (
($types && $parameter->allowsNull())
|| $parameter->isOptional()
|| $parameter->isDefaultValueAvailable()
) {
// !optional + defaultAvailable = func($a = null, $b) since 5.4.7
// optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ...
return $parameter->isDefaultValueAvailable()
? Reflection::getParameterDefaultValue($parameter)
: null;
} else {
throw new ServiceCreationException(sprintf(
'Parameter %s has %s, so its value must be specified.',
$desc,
count($types) > 1 ? 'union type and no default value' : 'no class type or default value'
));
}
}
}
src/DI/exceptions.php 0000644 00000001403 14113404363 0010516 0 ustar 00 message = $message;
return $this;
}
}
/**
* Not allowed when container is resolving.
*/
class NotAllowedDuringResolvingException extends Nette\InvalidStateException
{
}
/**
* Error in configuration.
*/
class InvalidConfigurationException extends Nette\InvalidStateException
{
}
src/compatibility.php 0000644 00000001544 14113404363 0010720 0 ustar 00