.phpstorm.meta.php 0000666 00000000273 13601315514 0010141 0 ustar 00 '@']));
override(\Nette\DI\ContainerBuilder::addDefinition(1), type(1));
composer.json 0000666 00000002212 13601315514 0007266 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 7.1 features.",
"keywords": ["nette", "di", "dic", "ioc", "factory", "compiled", "static"],
"homepage": "https://nette.org",
"license": ["BSD-3-Clause", "GPL-2.0", "GPL-3.0"],
"authors": [
{
"name": "David Grudl",
"homepage": "https://davidgrudl.com"
},
{
"name": "Nette Community",
"homepage": "https://nette.org/contributors"
}
],
"require": {
"php": ">=7.1",
"ext-tokenizer": "*",
"nette/neon": "^3.0",
"nette/php-generator": "^3.3",
"nette/robot-loader": "^3.2",
"nette/schema": "^1.0",
"nette/utils": "^3.0"
},
"require-dev": {
"nette/tester": "^2.2",
"tracy/tracy": "^2.3",
"phpstan/phpstan": "^0.12"
},
"conflict": {
"nette/bootstrap": "<3.0"
},
"autoload": {
"classmap": ["src/"],
"files": ["src/compatibility.php"]
},
"minimum-stability": "dev",
"scripts": {
"phpstan": "phpstan analyse --level 5 src",
"tester": "tester tests -s"
},
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
}
}
}
contributing.md 0000666 00000002504 13601315514 0007601 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 0000666 00000005244 13601315514 0006520 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 0000666 00000021152 13601315514 0006327 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)
[![Build Status](https://travis-ci.org/nette/di.svg?branch=master)](https://travis-ci.org/nette/di)
[![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).
If you like Nette, **[please make a donation now](https://nette.org/donate)**. 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 7.4.
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 0000666 00000004021 13601315514 0013624 0 ustar 00 container = $container;
$this->elapsedTime = self::$compilationTime ? microtime(true) - self::$compilationTime : null;
}
/**
* Renders tab.
*/
public function getTab(): string
{
ob_start(function () {});
$elapsedTime = $this->elapsedTime;
require __DIR__ . '/templates/ContainerPanel.tab.phtml';
return ob_get_clean();
}
/**
* Renders panel.
*/
public function getPanel(): string
{
$container = $this->container;
$rc = new \ReflectionClass($container);
$file = $rc->getFileName();
$tags = [];
$instances = $this->getContainerProperty('instances');
$wiring = $this->getContainerProperty('wiring');
$types = [];
foreach ($rc->getMethods() as $method) {
if (preg_match('#^createService(.+)#', $method->getName(), $m) && $method->getReturnType()) {
$types[lcfirst(str_replace('__', '.', $m[1]))] = $method->getReturnType()->getName();
}
}
$types = $this->getContainerProperty('types') + $types;
ksort($types);
foreach ($this->getContainerProperty('tags') as $tag => $tmp) {
foreach ($tmp as $service => $val) {
$tags[$service][$tag] = $val;
}
}
ob_start(function () {});
require __DIR__ . '/templates/ContainerPanel.panel.phtml';
return ob_get_clean();
}
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 0000666 00000004235 13601315514 0017264 0 ustar 00
= get_class($container) ?>
Services
Name
Autowired
Service
Tags
$type): ?>
= is_numeric($name) ? "– " : Helpers::escapeHtml($name) ?>
= $autowired ? 'yes' : (isset($wiring[$type]) ? 'no' : '?') ?>
= Dumper::toHtml($instances[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
= get_class($instances[$name]) ?>
= Helpers::escapeHtml($type) ?>
true])
: Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]);
} ?>
Parameters
= Dumper::toHtml($container->parameters); ?>
Source: = Helpers::editorLink($file) ?>
src/Bridges/DITracy/templates/ContainerPanel.tab.phtml 0000666 00000001714 13601315514 0016732 0 ustar 00
= $elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '' ?>
src/DI/Autowiring.php 0000666 00000007620 13601315514 0010500 0 ustar 00 services, used by getByType() */
private $highPriority = [];
/** @var array[] type => services, used by findByType() */
private $lowPriority = [];
/** @var string[] of classes excluded from autowiring */
private $excludedClasses = [];
public function __construct(ContainerBuilder $builder)
{
$this->builder = $builder;
}
/**
* Resolves service name by type.
* @param bool $throw exception if service not found?
* @throws MissingServiceException when not found
* @throws ServiceCreationException when multiple found
*/
public function getByType(string $type, bool $throw = false): ?string
{
$type = Helpers::normalizeClass($type);
$types = $this->highPriority;
if (empty($types[$type])) {
if ($throw) {
throw new MissingServiceException("Service of type '$type' not found.");
}
return null;
} elseif (count($types[$type]) === 1) {
return $types[$type][0];
} else {
$list = $types[$type];
natsort($list);
$hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.'))
? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.'
: '';
throw new ServiceCreationException("Multiple services of type $type found: " . implode(', ', $list) . $hint);
}
}
/**
* Gets the service names and definitions of the specified type.
* @return Definitions\Definition[] service name is key
*/
public function findByType(string $type): array
{
$type = Helpers::normalizeClass($type);
$definitions = $this->builder->getDefinitions();
$names = array_merge($this->highPriority[$type] ?? [], $this->lowPriority[$type] ?? []);
$res = [];
foreach ($names as $name) {
$res[$name] = $definitions[$name];
}
return $res;
}
/**
* @param string[] $types
*/
public function addExcludedClasses(array $types): void
{
foreach ($types as $type) {
if (class_exists($type) || interface_exists($type)) {
$type = Helpers::normalizeClass($type);
$this->excludedClasses += class_parents($type) + class_implements($type) + [$type => $type];
}
}
}
public function getClassList(): array
{
return [$this->lowPriority, $this->highPriority];
}
public function rebuild(): void
{
$this->lowPriority = $this->highPriority = $preferred = [];
foreach ($this->builder->getDefinitions() as $name => $def) {
if (!($type = $def->getType())) {
continue;
}
$autowired = $def->getAutowired();
if (is_array($autowired)) {
foreach ($autowired as $k => $autowiredType) {
if ($autowiredType === ContainerBuilder::THIS_SERVICE) {
$autowired[$k] = $type;
} elseif (!is_a($type, $autowiredType, true)) {
throw new ServiceCreationException("Incompatible class $autowiredType in autowiring definition of service '$name'.");
}
}
}
foreach (class_parents($type) + class_implements($type) + [$type] as $parent) {
if (!$autowired || isset($this->excludedClasses[$parent])) {
continue;
} elseif (is_array($autowired)) {
$priority = false;
foreach ($autowired as $autowiredType) {
if (is_a($parent, $autowiredType, true)) {
if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) {
$this->lowPriority[$parent] = array_merge($this->lowPriority[$parent] ?? [], $this->highPriority[$parent]);
$this->highPriority[$parent] = [];
}
$preferred[$parent] = $priority = true;
break;
}
}
} else {
$priority = empty($preferred[$parent]);
}
$list = $priority ? 'highPriority' : 'lowPriority';
$this->$list[$parent][] = $name;
}
}
}
}
src/DI/Compiler.php 0000666 00000020407 13601315514 0010120 0 ustar 00 array[]] */
private $configs = [];
/** @var string */
private $sources = '';
/** @var DependencyChecker */
private $dependencies;
/** @var string */
private $className = 'Container';
public function __construct(ContainerBuilder $builder = null)
{
$this->builder = $builder ?: new ContainerBuilder;
$this->dependencies = new DependencyChecker;
$this->addExtension(self::SERVICES, new Extensions\ServicesExtension);
$this->addExtension(self::PARAMETERS, new Extensions\ParametersExtension($this->configs));
}
/**
* Add custom configurator extension.
* @return static
*/
public function addExtension(?string $name, CompilerExtension $extension)
{
if ($name === null) {
$name = '_' . count($this->extensions);
} elseif (isset($this->extensions[$name])) {
throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved.");
}
$lname = strtolower($name);
foreach (array_keys($this->extensions) as $nm) {
if ($lname === strtolower((string) $nm)) {
throw new Nette\InvalidArgumentException("Name of extension '$name' has the same name as '$nm' in a case-insensitive manner.");
}
}
$this->extensions[$name] = $extension->setCompiler($this, $name);
return $this;
}
public function getExtensions(string $type = null): array
{
return $type
? array_filter($this->extensions, function ($item) use ($type): bool { return $item instanceof $type; })
: $this->extensions;
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
}
/**
* @return static
*/
public function setClassName(string $className)
{
$this->className = $className;
return $this;
}
/**
* Adds new configuration.
* @return static
*/
public function addConfig(array $config)
{
foreach ($config as $section => $data) {
$this->configs[$section][] = $data;
}
$this->sources .= "// source: array\n";
return $this;
}
/**
* Adds new configuration from file.
* @return static
*/
public function loadConfig(string $file, Config\Loader $loader = null)
{
$sources = $this->sources . "// source: $file\n";
$loader = $loader ?: new Config\Loader;
foreach ($loader->load($file, false) as $data) {
$this->addConfig($data);
}
$this->dependencies->add($loader->getDependencies());
$this->sources = $sources;
return $this;
}
/**
* Returns configuration.
* @deprecated
*/
public function getConfig(): array
{
return $this->config;
}
/**
* Sets the names of dynamic parameters.
* @return static
*/
public function setDynamicParameterNames(array $names)
{
assert($this->extensions[self::PARAMETERS] instanceof Extensions\ParametersExtension);
$this->extensions[self::PARAMETERS]->dynamicParams = $names;
return $this;
}
/**
* Adds dependencies to the list.
* @param array $deps of ReflectionClass|\ReflectionFunctionAbstract|string
* @return static
*/
public function addDependencies(array $deps)
{
$this->dependencies->add(array_filter($deps));
return $this;
}
/**
* Exports dependencies.
*/
public function exportDependencies(): array
{
return $this->dependencies->export();
}
/**
* @return static
*/
public function addExportedTag(string $tag)
{
if (isset($this->extensions[self::DI])) {
assert($this->extensions[self::DI] instanceof Extensions\DIExtension);
$this->extensions[self::DI]->exportedTags[$tag] = true;
}
return $this;
}
/**
* @return static
*/
public function addExportedType(string $type)
{
if (isset($this->extensions[self::DI])) {
assert($this->extensions[self::DI] instanceof Extensions\DIExtension);
$this->extensions[self::DI]->exportedTypes[$type] = true;
}
return $this;
}
public function compile(): string
{
$this->processExtensions();
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);
$extensions = array_diff_key($this->extensions, $first, [self::SERVICES => 1]);
foreach ($extensions as $name => $extension) {
$config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name);
$extension->setConfig($this->config[$name] = $config);
}
foreach ($extensions as $extension) {
$extension->loadConfiguration();
}
foreach ($this->getExtensions(Extensions\ServicesExtension::class) as $name => $extension) {
$config = $this->processSchema($extension->getConfigSchema(), $this->configs[$name] ?? [], $name);
$extension->setConfig($this->config[$name] = $config);
$extension->loadConfiguration();
}
if ($extra = array_diff_key($this->extensions, $extensions, $first, [self::SERVICES => 1])) {
$extra = implode("', '", array_keys($extra));
throw new Nette\DeprecatedException("Extensions '$extra' were added while container was being compiled.");
} elseif ($extra = key(array_diff_key($this->configs, $this->extensions))) {
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($this->extensions), $extra);
throw new InvalidConfigurationException(
"Found section '$extra' in configuration, but corresponding extension is missing"
. ($hint ? ", did you mean '$hint'?" : '.')
);
}
}
/**
* Merges and validates configurations against scheme.
* @return array|object
*/
private function processSchema(Schema\Schema $schema, array $configs, $name = null)
{
$processor = new Schema\Processor;
$processor->onNewContext[] = function (Schema\Context $context) use ($name) {
$context->path = $name ? [$name] : [];
$context->dynamics = &$this->extensions[self::PARAMETERS]->dynamicValidators;
};
try {
return $processor->processMultiple($schema, $configs);
} catch (Schema\ValidationException $e) {
throw new Nette\DI\InvalidConfigurationException($e->getMessage());
}
}
/** @internal */
public function generateCode(): string
{
$this->builder->resolve();
foreach ($this->extensions as $extension) {
$extension->beforeCompile();
$this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]);
}
$this->builder->complete();
$generator = new PhpGenerator($this->builder);
$class = $generator->generate($this->className);
$class->addMethod('initialize');
$this->dependencies->add($this->builder->getDependencies());
foreach ($this->extensions as $extension) {
$extension->afterCompile($class);
}
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]));
}
/**
* @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 0000666 00000007262 13601315514 0012021 0 ustar 00 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\ObjectHelpers::getSuggestion(array_keys($expected), key($extra));
$extra = $hint ? key($extra) : implode("', '{$name} › ", array_keys($extra));
throw new Nette\DI\InvalidConfigurationException("Unknown configuration option '{$name} › {$extra}'" . ($hint ? ", did you mean '{$name} › {$hint}'?" : '.'));
}
return Nette\Schema\Helpers::merge($config, $expected);
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->compiler->getContainerBuilder();
}
/**
* Reads configuration from file.
*/
public function loadFromFile(string $file): array
{
$loader = $this->createLoader();
$res = $loader->load($file);
$this->compiler->addDependencies($loader->getDependencies());
return $res;
}
/**
* Loads list of service definitions from configuration.
* Prefixes its names and replaces @extension with name in definition.
*/
public function loadDefinitionsFromConfig(array $configList): void
{
$res = [];
foreach ($configList as $key => $config) {
$key = is_string($key) ? $this->name . '.' . $key : $key;
$res[$key] = Helpers::prefixServiceName($config, $this->name);
}
$this->compiler->loadDefinitionsFromConfig($res);
}
protected function createLoader(): Config\Loader
{
return new Config\Loader;
}
/**
* 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 0000666 00000000677 13601315514 0011142 0 ustar 00 process((array) Neon\Neon::decode(file_get_contents($file)));
}
/**
* @throws Nette\InvalidStateException
*/
public function process(array $arr): array
{
$res = [];
foreach ($arr as $key => $val) {
if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING_SUFFIX) {
if (!is_array($val) && $val !== null) {
throw new Nette\DI\InvalidConfigurationException("Replacing operator is available only for arrays, item '$key' is not array.");
}
$key = substr($key, 0, -1);
$val[Helpers::PREVENT_MERGING] = true;
}
if (is_array($val)) {
$val = $this->process($val);
} elseif ($val instanceof Neon\Entity) {
if ($val->value === Neon\Neon::CHAIN) {
$tmp = null;
foreach ($this->process($val->attributes) as $st) {
$tmp = new Statement(
$tmp === null ? $st->getEntity() : [$tmp, ltrim(implode('::', (array) $st->getEntity()), ':')],
$st->arguments
);
}
$val = $tmp;
} else {
$tmp = $this->process([$val->value]);
if (is_string($tmp[0]) && strpos($tmp[0], '?') !== false) {
trigger_error('Operator ? is deprecated in config files.', E_USER_DEPRECATED);
}
$val = new Statement($tmp[0], $this->process($val->attributes));
}
}
$res[$key] = $val;
}
return $res;
}
/**
* Generates configuration in NEON format.
*/
public function dump(array $data): string
{
array_walk_recursive(
$data,
function (&$val): void {
if ($val instanceof Statement) {
$val = self::statementToEntity($val);
}
}
);
return "# generated by Nette\n\n" . Neon\Neon::encode($data, Neon\Neon::BLOCK);
}
private static function statementToEntity(Statement $val): Neon\Entity
{
array_walk_recursive(
$val->arguments,
function (&$val): void {
if ($val instanceof Statement) {
$val = self::statementToEntity($val);
} elseif ($val instanceof Reference) {
$val = '@' . $val->getValue();
}
}
);
$entity = $val->getEntity();
if ($entity instanceof Reference) {
$entity = '@' . $entity->getValue();
} elseif (is_array($entity)) {
if ($entity[0] instanceof Statement) {
return new Neon\Entity(
Neon\Neon::CHAIN,
[
self::statementToEntity($entity[0]),
new Neon\Entity('::' . $entity[1], $val->arguments),
]
);
} elseif ($entity[0] instanceof Reference) {
$entity = '@' . $entity[0]->getValue() . '::' . $entity[1];
} elseif (is_string($entity[0])) {
$entity = $entity[0] . '::' . $entity[1];
}
}
return new Neon\Entity($entity, $val->arguments);
}
}
src/DI/Config/Adapters/PhpAdapter.php 0000666 00000001237 13601315514 0013346 0 ustar 00 builder = $builder;
}
public function complete($def, Context $context)
{
if ($def === [false]) {
return (object) $def;
}
if (Helpers::takeParent($def)) {
$def['reset']['all'] = true;
}
foreach (['arguments', 'setup', 'tags'] as $k) {
if (isset($def[$k]) && Helpers::takeParent($def[$k])) {
$def['reset'][$k] = true;
}
}
$def = $this->expandParameters($def);
$type = $this->sniffType(end($context->path), $def);
$def = $this->getSchema($type)->complete($def, $context);
if ($def) {
$def->defType = $type;
}
return $def;
}
public function merge($def, $base)
{
if (!empty($def['alteration'])) {
unset($def['alteration']);
}
return Nette\Schema\Helpers::merge($def, $base);
}
/**
* Normalizes configuration of service definitions.
*/
public function normalize($def, Context $context)
{
if ($def === null || $def === false) {
return (array) $def;
} elseif (is_string($def) && interface_exists($def)) {
return ['implement' => $def];
} elseif ($def instanceof Statement && is_string($def->getEntity()) && interface_exists($def->getEntity())) {
$res = ['implement' => $def->getEntity()];
if (array_keys($def->arguments) === ['tagged']) {
$res += $def->arguments;
} elseif (count($def->arguments) > 1) {
$res['references'] = $def->arguments;
} elseif ($factory = array_shift($def->arguments)) {
$res['factory'] = $factory;
}
return $res;
} elseif (!is_array($def) || isset($def[0], $def[1])) {
return ['factory' => $def];
} elseif (is_array($def)) {
if (isset($def['class']) && !isset($def['type'])) {
if ($def['class'] instanceof Statement) {
$key = end($context->path);
trigger_error("Service '$key': option 'class' should be changed to 'factory'.", E_USER_DEPRECATED);
$def['factory'] = $def['class'];
unset($def['class']);
} elseif (!isset($def['factory']) && !isset($def['dynamic']) && !isset($def['imported'])) {
$def['factory'] = $def['class'];
unset($def['class']);
}
}
foreach (['class' => 'type', 'dynamic' => 'imported'] as $alias => $original) {
if (array_key_exists($alias, $def)) {
if (array_key_exists($original, $def)) {
throw new Nette\DI\InvalidConfigurationException("Options '$alias' and '$original' are aliases, use only '$original'.");
}
$def[$original] = $def[$alias];
unset($def[$alias]);
}
}
return $def;
} else {
throw new Nette\DI\InvalidConfigurationException('Unexpected format of service definition');
}
}
public function completeDefault(Context $context)
{
}
private function sniffType($key, array $def): string
{
if (is_string($key)) {
$name = preg_match('#^@[\w\\\\]+$#D', $key)
? $this->builder->getByType(substr($key, 1), false)
: $key;
if ($name && $this->builder->hasDefinition($name)) {
return get_class($this->builder->getDefinition($name));
}
}
if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) {
return Definitions\LocatorDefinition::class;
} elseif (isset($def['implement'])) {
return method_exists($def['implement'], 'create')
? Definitions\FactoryDefinition::class
: Definitions\AccessorDefinition::class;
} elseif (isset($def['imported'])) {
return Definitions\ImportedDefinition::class;
} else {
return Definitions\ServiceDefinition::class;
}
}
private function expandParameters(array $config): array
{
$params = $this->builder->parameters;
if (isset($config['parameters'])) {
foreach ((array) $config['parameters'] as $k => $v) {
$v = explode(' ', is_int($k) ? $v : $k);
$params[end($v)] = $this->builder::literal('$' . end($v));
}
}
return Nette\DI\Helpers::expand($config, $params);
}
private static function getSchema(string $type): Schema
{
static $cache;
$cache = $cache ?: [
Definitions\ServiceDefinition::class => self::getServiceSchema(),
Definitions\AccessorDefinition::class => self::getAccessorSchema(),
Definitions\FactoryDefinition::class => self::getFactorySchema(),
Definitions\LocatorDefinition::class => self::getLocatorSchema(),
Definitions\ImportedDefinition::class => self::getImportedSchema(),
];
return $cache[$type];
}
private static function getServiceSchema(): Schema
{
return Expect::structure([
'type' => Expect::type('string'),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
'alteration' => Expect::bool(),
]);
}
private static function getAccessorSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'implement' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getFactorySchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
'implement' => Expect::string(),
'arguments' => Expect::array(),
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
'parameters' => Expect::array(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'inject' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
'reset' => Expect::array(),
]);
}
private static function getLocatorSchema(): Schema
{
return Expect::structure([
'implement' => Expect::string(),
'references' => Expect::array(),
'tagged' => Expect::string(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
private static function getImportedSchema(): Schema
{
return Expect::structure([
'type' => Expect::string(),
'imported' => Expect::bool(),
'autowired' => Expect::type('bool|string|array'),
'tags' => Expect::array(),
]);
}
}
src/DI/Config/Helpers.php 0000666 00000001514 13601315514 0011153 0 ustar 00 Adapters\PhpAdapter::class,
'neon' => Adapters\NeonAdapter::class,
];
private $dependencies = [];
private $loadedFiles = [];
private $parameters = [];
/**
* Reads configuration from file.
*/
public function load(string $file, ?bool $merge = true): array
{
if (!is_file($file) || !is_readable($file)) {
throw new Nette\FileNotFoundException("File '$file' is missing or is not readable.");
}
if (isset($this->loadedFiles[$file])) {
throw new Nette\InvalidStateException("Recursive included file '$file'");
}
$this->loadedFiles[$file] = true;
$this->dependencies[] = $file;
$data = $this->getAdapter($file)->load($file);
$res = [];
if (isset($data[self::INCLUDES_KEY])) {
Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'");
$includes = Nette\DI\Helpers::expand($data[self::INCLUDES_KEY], $this->parameters);
foreach ($includes as $include) {
$include = $this->expandIncludedFile($include, $file);
$res = Nette\Schema\Helpers::merge($this->load($include, $merge), $res);
}
}
unset($data[self::INCLUDES_KEY], $this->loadedFiles[$file]);
if ($merge === false) {
$res[] = $data;
} else {
$res = Nette\Schema\Helpers::merge($data, $res);
}
return $res;
}
/**
* Save configuration to file.
*/
public function save(array $data, string $file): void
{
if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === false) {
throw new Nette\IOException("Cannot write file '$file'.");
}
}
/**
* Returns configuration files.
*/
public function getDependencies(): array
{
return array_unique($this->dependencies);
}
/**
* Expands included file name.
*/
public function expandIncludedFile(string $includedFile, string $mainFile): string
{
return preg_match('#([a-z]+:)?[/\\\\]#Ai', $includedFile) // is absolute
? $includedFile
: dirname($mainFile) . '/' . $includedFile;
}
/**
* Registers adapter for given file extension.
* @param string|Adapter $adapter
* @return static
*/
public function addAdapter(string $extension, $adapter)
{
$this->adapters[strtolower($extension)] = $adapter;
return $this;
}
private function getAdapter(string $file): Adapter
{
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!isset($this->adapters[$extension])) {
throw new Nette\InvalidArgumentException("Unknown file extension '$file'.");
}
return is_object($this->adapters[$extension]) ? $this->adapters[$extension] : new $this->adapters[$extension];
}
/**
* @return static
*/
public function setParameters(array $params)
{
$this->parameters = $params;
return $this;
}
}
src/DI/Container.php 0000666 00000017751 13601315514 0010300 0 ustar 00 type (complete list of available services) */
protected $types = [];
/** @var string[] alias => service name */
protected $aliases = [];
/** @var array[] tag name => service name => tag value */
protected $tags = [];
/** @var array[] type => level => services */
protected $wiring = [];
/** @var object[] service name => instance */
private $instances = [];
/** @var array circular reference detector */
private $creating;
/** @var array */
private $methods;
public function __construct(array $params = [])
{
$this->parameters = $params;
$this->methods = array_flip(get_class_methods($this));
}
public function getParameters(): array
{
return $this->parameters;
}
/**
* Adds the service to the container.
* @param object $service service or its factory
* @return static
*/
public function addService(string $name, $service)
{
$name = $this->aliases[$name] ?? $name;
if (isset($this->instances[$name])) {
throw new Nette\InvalidStateException("Service '$name' already exists.");
} elseif (!is_object($service)) {
throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service)));
}
$type = $service instanceof \Closure
? (($tmp = (new \ReflectionFunction($service))->getReturnType()) ? $tmp->getName() : '')
: get_class($service);
if (!isset($this->methods[self::getMethodName($name)])) {
$this->types[$name] = $type;
} elseif (($expectedType = $this->getServiceType($name)) && !is_a($type, $expectedType, true)) {
throw new Nette\InvalidArgumentException("Service '$name' must be instance of $expectedType, " . ($type ? "$type given." : 'add typehint to closure.'));
}
if ($service instanceof \Closure) {
$this->methods[self::getMethodName($name)] = $service;
$this->types[$name] = $type;
} else {
$this->instances[$name] = $service;
}
return $this;
}
/**
* Removes the service from the container.
*/
public function removeService(string $name): void
{
$name = $this->aliases[$name] ?? $name;
unset($this->instances[$name]);
}
/**
* Gets the service object by name.
* @return object
* @throws MissingServiceException
*/
public function getService(string $name)
{
if (!isset($this->instances[$name])) {
if (isset($this->aliases[$name])) {
return $this->getService($this->aliases[$name]);
}
$this->instances[$name] = $this->createService($name);
}
return $this->instances[$name];
}
/**
* Gets the service type by name.
* @throws MissingServiceException
*/
public function getServiceType(string $name): string
{
$method = self::getMethodName($name);
if (isset($this->aliases[$name])) {
return $this->getServiceType($this->aliases[$name]);
} elseif (isset($this->types[$name])) {
return $this->types[$name];
} elseif (isset($this->methods[$method])) {
$type = (new \ReflectionMethod($this, $method))->getReturnType();
return $type ? $type->getName() : '';
} else {
throw new MissingServiceException("Service '$name' not found.");
}
}
/**
* Does the service exist?
*/
public function hasService(string $name): bool
{
$name = $this->aliases[$name] ?? $name;
return isset($this->methods[self::getMethodName($name)]) || isset($this->instances[$name]);
}
/**
* Is the service created?
*/
public function isCreated(string $name): bool
{
if (!$this->hasService($name)) {
throw new MissingServiceException("Service '$name' not found.");
}
$name = $this->aliases[$name] ?? $name;
return isset($this->instances[$name]);
}
/**
* Creates new instance of the service.
* @return object
* @throws MissingServiceException
*/
public function createService(string $name, array $args = [])
{
$name = $this->aliases[$name] ?? $name;
$method = self::getMethodName($name);
$cb = $this->methods[$method] ?? null;
if (isset($this->creating[$name])) {
throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));
} elseif ($cb === null) {
throw new MissingServiceException("Service '$name' not found.");
}
try {
$this->creating[$name] = true;
$service = $cb instanceof \Closure ? $cb(...$args) : $this->$method(...$args);
} finally {
unset($this->creating[$name]);
}
if (!is_object($service)) {
throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by " . ($cb instanceof \Closure ? 'closure' : "method $method()") . ' is not object.');
}
return $service;
}
/**
* Resolves service by type.
* @param bool $throw exception if service doesn't exist?
* @return object|null service
* @throws MissingServiceException
*/
public function getByType(string $type, bool $throw = true)
{
$type = Helpers::normalizeClass($type);
if (!empty($this->wiring[$type][0])) {
if (count($names = $this->wiring[$type][0]) === 1) {
return $this->getService($names[0]);
}
natsort($names);
throw new MissingServiceException("Multiple services of type $type found: " . implode(', ', $names) . '.');
} elseif ($throw) {
throw new MissingServiceException("Service of type $type not found.");
}
return null;
}
/**
* Gets the autowired service names of the specified type.
* @return string[]
* @internal
*/
public function findAutowired(string $type): array
{
$type = Helpers::normalizeClass($type);
return array_merge($this->wiring[$type][0] ?? [], $this->wiring[$type][1] ?? []);
}
/**
* Gets the service names of the specified type.
* @return string[]
*/
public function findByType(string $type): array
{
$type = Helpers::normalizeClass($type);
return empty($this->wiring[$type])
? []
: array_merge(...array_values($this->wiring[$type]));
}
/**
* Gets the service names of the specified tag.
* @return array of [service name => tag attributes]
*/
public function findByTag(string $tag): array
{
return $this->tags[$tag] ?? [];
}
/********************* autowiring ****************d*g**/
/**
* Creates new instance using autowiring.
* @return object
* @throws Nette\InvalidArgumentException
*/
public function createInstance(string $class, array $args = [])
{
$rc = new \ReflectionClass($class);
if (!$rc->isInstantiable()) {
throw new ServiceCreationException("Class $class is not instantiable.");
} elseif ($constructor = $rc->getConstructor()) {
return $rc->newInstanceArgs($this->autowireArguments($constructor, $args));
} elseif ($args) {
throw new ServiceCreationException("Unable to pass arguments, class $class has no constructor.");
}
return new $class;
}
/**
* Calls all methods starting with with "inject" using autowiring.
* @param object $service
*/
public function callInjects($service): void
{
Extensions\InjectExtension::callInjects($this, $service);
}
/**
* Calls method using autowiring.
* @return mixed
*/
public function callMethod(callable $function, array $args = [])
{
return $function(...$this->autowireArguments(Nette\Utils\Callback::toReflection($function), $args));
}
private function autowireArguments(\ReflectionFunctionAbstract $function, array $args = []): array
{
return Resolver::autowireArguments($function, $args, function (string $type, bool $single) {
return $single
? $this->getByType($type)
: array_map([$this, 'getService'], $this->findAutowired($type));
});
}
public static function getMethodName(string $name): string
{
if ($name === '') {
throw new Nette\InvalidArgumentException('Service name must be a non-empty string.');
}
return 'createService' . str_replace('.', '__', ucfirst($name));
}
}
src/DI/ContainerBuilder.php 0000666 00000022633 13601315514 0011602 0 ustar 00 service */
private $aliases = [];
/** @var Autowiring */
private $autowiring;
/** @var bool */
private $needsResolve = true;
/** @var bool */
private $resolving = false;
/** @var array */
private $dependencies = [];
public function __construct()
{
$this->autowiring = new Autowiring($this);
$this->addImportedDefinition(self::THIS_CONTAINER)->setType(Container::class);
}
/**
* Adds new service definition.
* @return Definitions\ServiceDefinition
*/
public function addDefinition(?string $name, Definition $definition = null): Definition
{
$this->needsResolve = true;
if ($name === null) {
for ($i = 1; isset($this->definitions['0' . $i]) || isset($this->aliases['0' . $i]); $i++);
$name = '0' . $i; // prevents converting to integer in array key
} elseif (is_int(key([$name => 1])) || !preg_match('#^\w+(\.\w+)*$#D', $name)) {
throw new Nette\InvalidArgumentException(sprintf('Service name must be a alpha-numeric string and not a number, %s given.', gettype($name)));
} else {
$name = $this->aliases[$name] ?? $name;
if (isset($this->definitions[$name])) {
throw new Nette\InvalidStateException("Service '$name' has already been added.");
}
$lname = strtolower($name);
foreach ($this->definitions as $nm => $foo) {
if ($lname === strtolower($nm)) {
throw new Nette\InvalidStateException("Service '$name' has the same name as '$nm' in a case-insensitive manner.");
}
}
}
$definition = $definition ?: new Definitions\ServiceDefinition;
$definition->setName($name);
$definition->setNotifier(function (): void {
$this->needsResolve = true;
});
return $this->definitions[$name] = $definition;
}
public function addAccessorDefinition(?string $name): Definitions\AccessorDefinition
{
return $this->addDefinition($name, new Definitions\AccessorDefinition);
}
public function addFactoryDefinition(?string $name): Definitions\FactoryDefinition
{
return $this->addDefinition($name, new Definitions\FactoryDefinition);
}
public function addLocatorDefinition(?string $name): Definitions\LocatorDefinition
{
return $this->addDefinition($name, new Definitions\LocatorDefinition);
}
public function addImportedDefinition(?string $name): Definitions\ImportedDefinition
{
return $this->addDefinition($name, new Definitions\ImportedDefinition);
}
/**
* Removes the specified service definition.
*/
public function removeDefinition(string $name): void
{
$this->needsResolve = true;
$name = $this->aliases[$name] ?? $name;
unset($this->definitions[$name]);
}
/**
* Gets the service definition.
*/
public function getDefinition(string $name): Definition
{
$service = $this->aliases[$name] ?? $name;
if (!isset($this->definitions[$service])) {
throw new MissingServiceException("Service '$name' not found.");
}
return $this->definitions[$service];
}
/**
* Gets all service definitions.
* @return Definition[]
*/
public function getDefinitions(): array
{
return $this->definitions;
}
/**
* Does the service definition or alias exist?
*/
public function hasDefinition(string $name): bool
{
$name = $this->aliases[$name] ?? $name;
return isset($this->definitions[$name]);
}
public function addAlias(string $alias, string $service): void
{
if (!$alias) { // builder is not ready for falsy names such as '0'
throw new Nette\InvalidArgumentException(sprintf('Alias name must be a non-empty string, %s given.', gettype($alias)));
} elseif (!$service) { // builder is not ready for falsy names such as '0'
throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($service)));
} elseif (isset($this->aliases[$alias])) {
throw new Nette\InvalidStateException("Alias '$alias' has already been added.");
} elseif (isset($this->definitions[$alias])) {
throw new Nette\InvalidStateException("Service '$alias' has already been added.");
}
$this->aliases[$alias] = $service;
}
/**
* Removes the specified alias.
*/
public function removeAlias(string $alias): void
{
unset($this->aliases[$alias]);
}
/**
* Gets all service aliases.
*/
public function getAliases(): array
{
return $this->aliases;
}
/**
* @param string[] $types
* @return static
*/
public function addExcludedClasses(array $types)
{
$this->needsResolve = true;
$this->autowiring->addExcludedClasses($types);
return $this;
}
/**
* Resolves autowired service name by type.
* @param bool $throw exception if service doesn't exist?
* @throws MissingServiceException
*/
public function getByType(string $type, bool $throw = false): ?string
{
$this->needResolved();
return $this->autowiring->getByType($type, $throw);
}
/**
* Gets autowired service definition of the specified type.
* @throws MissingServiceException
*/
public function getDefinitionByType(string $type): Definition
{
return $this->getDefinition($this->getByType($type, true));
}
/**
* Gets the autowired service names and definitions of the specified type.
* @return Definition[] service name is key
* @internal
*/
public function findAutowired(string $type): array
{
$this->needResolved();
return $this->autowiring->findByType($type);
}
/**
* Gets the service names and definitions of the specified type.
* @return Definition[] service name is key
*/
public function findByType(string $type): array
{
$this->needResolved();
$found = [];
foreach ($this->definitions as $name => $def) {
if (is_a($def->getType(), $type, true)) {
$found[$name] = $def;
}
}
return $found;
}
/**
* Gets the service names and tag values.
* @return array of [service name => tag attributes]
*/
public function findByTag(string $tag): array
{
$found = [];
foreach ($this->definitions as $name => $def) {
if (($tmp = $def->getTag($tag)) !== null) {
$found[$name] = $tmp;
}
}
return $found;
}
/********************* building ****************d*g**/
/**
* Checks services, resolves types and rebuilts autowiring classlist.
*/
public function resolve(): void
{
if ($this->resolving) {
return;
}
$this->resolving = true;
$resolver = new Resolver($this);
foreach ($this->definitions as $def) {
$resolver->resolveDefinition($def);
}
$this->autowiring->rebuild();
$this->resolving = $this->needsResolve = false;
}
private function needResolved(): void
{
if ($this->resolving) {
throw new NotAllowedDuringResolvingException;
} elseif ($this->needsResolve) {
$this->resolve();
}
}
public function complete(): void
{
$this->resolve();
foreach ($this->definitions as $def) {
$def->setNotifier(null);
}
$resolver = new Resolver($this);
foreach ($this->definitions as $def) {
$resolver->completeDefinition($def);
}
$this->parameters = $resolver->completeArguments($this->parameters);
}
/**
* Adds item to the list of dependencies.
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
* @return static
* @internal
*/
public function addDependency($dep)
{
$this->dependencies[] = $dep;
return $this;
}
/**
* Returns the list of dependencies.
*/
public function getDependencies(): array
{
return $this->dependencies;
}
/**
* @internal
*/
public function exportMeta(): array
{
$defs = $this->definitions;
ksort($defs);
foreach ($defs as $name => $def) {
if ($def instanceof Definitions\ImportedDefinition) {
$meta['types'][$name] = $def->getType();
}
foreach ($def->getTags() as $tag => $value) {
$meta['tags'][$tag][$name] = $value;
}
}
$meta['aliases'] = $this->aliases;
ksort($meta['aliases']);
$all = [];
foreach ($this->definitions as $name => $def) {
if ($type = $def->getType()) {
foreach (class_parents($type) + class_implements($type) + [$type] as $class) {
$all[$class][] = $name;
}
}
}
[$low, $high] = $this->autowiring->getClassList();
foreach ($all as $class => $names) {
$meta['wiring'][$class] = array_filter([
$high[$class] ?? [],
$low[$class] ?? [],
array_diff($names, $low[$class] ?? [], $high[$class] ?? []),
]);
}
return $meta;
}
public static function literal(string $code, array $args = null): Nette\PhpGenerator\PhpLiteral
{
return new Nette\PhpGenerator\PhpLiteral(
$args === null ? $code : Nette\PhpGenerator\Helpers::formatArgs($code, $args)
);
}
/** @deprecated */
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Statement) {
$val = (new Resolver($this))->completeStatement($val);
} elseif ($val instanceof Definition) {
$val = new Definitions\Reference($val->getName());
}
});
return (new PhpGenerator($this))->formatPhp($statement, $args);
}
/** @deprecated use resolve() */
public function prepareClassList(): void
{
trigger_error(__METHOD__ . '() is deprecated, use resolve()', E_USER_DEPRECATED);
$this->resolve();
}
}
src/DI/ContainerLoader.php 0000666 00000006227 13601315514 0011423 0 ustar 00 tempDirectory = $tempDirectory;
$this->autoRebuild = $autoRebuild;
}
/**
* @param callable $generator function (Nette\DI\Compiler $compiler): string|null
* @param mixed $key
*/
public function load(callable $generator, $key = null): string
{
$class = $this->getClassName($key);
if (!class_exists($class, false)) {
$this->loadFile($class, $generator);
}
return $class;
}
/**
* @param mixed $key
*/
public function getClassName($key): string
{
return 'Container_' . substr(md5(serialize($key)), 0, 10);
}
private function loadFile(string $class, callable $generator): void
{
$file = "$this->tempDirectory/$class.php";
if (!$this->isExpired($file) && (@include $file) !== false) { // @ file may not exist
return;
}
Nette\Utils\FileSystem::createDir($this->tempDirectory);
$handle = @fopen("$file.lock", 'c+'); // @ is escalated to exception
if (!$handle) {
throw new Nette\IOException("Unable to create file '$file.lock'. " . error_get_last()['message']);
} elseif (!@flock($handle, LOCK_EX)) { // @ is escalated to exception
throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'. " . error_get_last()['message']);
}
if (!is_file($file) || $this->isExpired($file, $updatedMeta)) {
if (isset($updatedMeta)) {
$toWrite["$file.meta"] = $updatedMeta;
} else {
[$toWrite[$file], $toWrite["$file.meta"]] = $this->generate($class, $generator);
}
foreach ($toWrite as $name => $content) {
if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) {
@unlink("$name.tmp"); // @ - file may not exist
throw new Nette\IOException("Unable to create file '$name'.");
} elseif (function_exists('opcache_invalidate')) {
@opcache_invalidate($name, true); // @ can be restricted
}
}
}
if ((@include $file) === false) { // @ - error escalated to exception
throw new Nette\IOException("Unable to include '$file'.");
}
flock($handle, LOCK_UN);
}
private function isExpired(string $file, string &$updatedMeta = null): bool
{
if ($this->autoRebuild) {
$meta = @unserialize((string) file_get_contents("$file.meta")); // @ - file may not exist
$orig = $meta[2] ?? null;
return empty($meta[0])
|| DependencyChecker::isExpired(...$meta)
|| ($orig !== $meta[2] && $updatedMeta = serialize($meta));
}
return false;
}
/**
* @return array of (code, file[])
*/
protected function generate(string $class, callable $generator): array
{
$compiler = new Compiler;
$compiler->setClassName($class);
$code = $generator(...[&$compiler]) ?: $compiler->compile();
return [
"exportDependencies()),
];
}
}
src/DI/Definitions/AccessorDefinition.php 0000666 00000006322 13601315514 0014374 0 ustar 00 getName()}': Interface '$type' not found.");
}
$rc = new \ReflectionClass($type);
$method = $rc->getMethods()[0] ?? null;
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_GET || count($rc->getMethods()) > 1) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method get().");
} elseif ($method->getNumberOfParameters()) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Method $type::get() must have no parameters.");
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
/**
* @param string|Reference $reference
* @return static
*/
public function setReference($reference)
{
if ($reference instanceof Reference) {
$this->reference = $reference;
} else {
$this->reference = substr($reference, 0, 1) === '@'
? new Reference(substr($reference, 1))
: Reference::fromType($reference);
}
return $this;
}
public function getReference(): ?Reference
{
return $this->reference;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
if (!$this->reference) {
$interface = $this->getType();
$method = new \ReflectionMethod($interface, self::METHOD_GET);
$returnType = Nette\DI\Helpers::getReturnType($method);
if (!$returnType) {
throw new ServiceCreationException("Method $interface::get() has not return type hint or annotation @return.");
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::get() method, class '$returnType' cannot be found.");
}
$this->setReference($returnType);
}
$this->reference = $resolver->normalizeReference($this->reference);
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$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 0000666 00000007443 13601315514 0012716 0 ustar 00 name) {
throw new Nette\InvalidStateException('Name already has been set.');
}
$this->name = $name;
return $this;
}
final public function getName(): ?string
{
return $this->name;
}
/**
* @return static
*/
protected function setType(?string $type)
{
if ($this->autowired && $this->notifier && $this->type !== $type) {
($this->notifier)();
}
if ($type === null) {
$this->type = null;
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidArgumentException("Service '$this->name': Class or interface '$type' not found.");
} else {
$this->type = Nette\DI\Helpers::normalizeClass($type);
}
return $this;
}
final public function getType(): ?string
{
return $this->type;
}
/**
* @return static
*/
final public function setTags(array $tags)
{
$this->tags = $tags;
return $this;
}
final public function getTags(): array
{
return $this->tags;
}
/**
* @param mixed $attr
* @return static
*/
final public function addTag(string $tag, $attr = true)
{
$this->tags[$tag] = $attr;
return $this;
}
/**
* @return mixed
*/
final public function getTag(string $tag)
{
return $this->tags[$tag] ?? null;
}
/**
* @param bool|string|string[] $state
* @return static
*/
final public function setAutowired($state = true)
{
if ($this->notifier && $this->autowired !== $state) {
($this->notifier)();
}
$this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state;
return $this;
}
/**
* @return bool|string[]
*/
final public function getAutowired()
{
return $this->autowired;
}
/**
* @return static
*/
public function setExported(bool $state = true)
{
return $this->addTag('nette.exported', $state);
}
public function isExported(): bool
{
return (bool) $this->getTag('nette.exported');
}
public function __clone()
{
$this->notifier = $this->name = null;
}
/********************* life cycle ****************d*g**/
abstract public function resolveType(Nette\DI\Resolver $resolver): void;
abstract public function complete(Nette\DI\Resolver $resolver): void;
abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void;
final public function setNotifier(?callable $notifier): void
{
$this->notifier = $notifier;
}
/********************* deprecated stuff from former ServiceDefinition ****************d*g**/
/**
* @deprecated Use setType()
*/
public function setClass(?string $type)
{
return $this->setType($type);
}
/**
* @deprecated Use getType()
*/
public function getClass(): ?string
{
return $this->getType();
}
/**
* @deprecated Use '$def instanceof Nette\DI\Definitions\ImportedDefinition'
*/
public function isDynamic(): bool
{
return false;
}
/**
* @deprecated Use Nette\DI\Definitions\FactoryDefinition or AccessorDefinition
*/
public function getImplement(): ?string
{
return null;
}
/**
* @deprecated Use getAutowired()
*/
public function isAutowired()
{
return $this->autowired;
}
}
src/DI/Definitions/FactoryDefinition.php 0000666 00000021166 13601315514 0014244 0 ustar 00 resultDefinition = new ServiceDefinition;
}
/**
* @return static
*/
public function setImplement(string $type)
{
if (!interface_exists($type)) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
}
$rc = new \ReflectionClass($type);
$method = $rc->getMethods()[0] ?? null;
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_CREATE || count($rc->getMethods()) > 1) {
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method create().");
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
final public function getResultType(): ?string
{
return $this->resultDefinition->getType();
}
/**
* @return static
*/
public function setResultDefinition(Definition $definition)
{
$this->resultDefinition = $definition;
return $this;
}
/**
* @return ServiceDefinition
*/
public function getResultDefinition(): Definition
{
return $this->resultDefinition;
}
/**
* @deprecated use ->getResultDefinition()->setFactory()
* @return static
*/
public function setFactory($factory, array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setFactory($factory, $args);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->getFactory()
*/
public function getFactory(): ?Statement
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getFactory();
}
/**
* @deprecated use ->getResultDefinition()->getEntity()
* @return mixed
*/
public function getEntity()
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getEntity()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getEntity();
}
/**
* @deprecated use ->getResultDefinition()->setArguments()
* @return static
*/
public function setArguments(array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setArguments()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setArguments($args);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->setSetup()
* @return static
*/
public function setSetup(array $setup)
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->setSetup($setup);
return $this;
}
/**
* @deprecated use ->getResultDefinition()->getSetup()
*/
public function getSetup(): array
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
return $this->resultDefinition->getSetup();
}
/**
* @deprecated use ->getResultDefinition()->addSetup()
* @return static
*/
public function addSetup($entity, array $args = [])
{
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->addSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
$this->resultDefinition->addSetup($entity, $args);
return $this;
}
/**
* @return static
*/
public function setParameters(array $params)
{
$this->parameters = $params;
return $this;
}
public function getParameters(): array
{
return $this->parameters;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
$resultDef = $this->resultDefinition;
try {
$resolver->resolveDefinition($resultDef);
return;
} catch (ServiceCreationException $e) {
}
if (!$resultDef->getType()) {
$interface = $this->getType();
if (!$interface) {
throw new ServiceCreationException('Type is missing in definition of service.');
}
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
$returnType = Nette\DI\Helpers::getReturnType($method);
if (!$returnType) {
throw new ServiceCreationException("Method $interface::create() has not return type hint or annotation @return.");
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::create() method, class '$returnType' cannot be found.");
}
$resultDef->setType($returnType);
}
$resolver->resolveDefinition($resultDef);
}
public function complete(Nette\DI\Resolver $resolver): void
{
$resultDef = $this->resultDefinition;
if ($resultDef instanceof ServiceDefinition) {
if (!$this->parameters) {
$this->completeParameters($resolver);
}
if ($resultDef->getEntity() instanceof Reference && !$resultDef->getFactory()->arguments) {
$resultDef->setFactory([ // render as $container->createMethod()
new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER),
Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
]);
}
}
$resolver->completeDefinition($resultDef);
}
private function completeParameters(Nette\DI\Resolver $resolver): void
{
$interface = $this->getType();
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
$ctorParams = [];
if (
($class = $resolver->resolveEntityType($this->resultDefinition->getFactory()))
&& ($ctor = (new \ReflectionClass($class))->getConstructor())
) {
foreach ($ctor->getParameters() as $param) {
$ctorParams[$param->getName()] = $param;
}
}
foreach ($method->getParameters() as $param) {
$hint = Reflection::getParameterType($param);
if (isset($ctorParams[$param->getName()])) {
$arg = $ctorParams[$param->getName()];
$argHint = Reflection::getParameterType($arg);
if ($hint !== $argHint && !is_a($hint, (string) $argHint, true)) {
throw new ServiceCreationException("Type hint for \${$param->getName()} in $interface::create() doesn't match type hint in $class constructor.");
}
$this->resultDefinition->getFactory()->arguments[$arg->getPosition()] = Nette\DI\ContainerBuilder::literal('$' . $arg->getName());
} elseif (!$this->resultDefinition->getSetup()) {
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($ctorParams), $param->getName());
throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $interface::create()" . ($hint ? ", did you mean \${$hint}?" : '.'));
}
$nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null);
$paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
if ($param->isDefaultValueAvailable()) {
$this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
} else {
$this->parameters[] = $paramDef;
}
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->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 0000666 00000002015 13601315514 0014410 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 0000666 00000010726 13601315514 0014240 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->getName()) && $method->getNumberOfParameters() === 1)
|| (preg_match('#^(get|create)[A-Z]#', $method->getName()) && $method->getNumberOfParameters() === 0)
)) {
throw new Nette\InvalidArgumentException(sprintf(
"Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.",
$this->getName(), $type, $method->getName()
));
}
}
return parent::setType($type);
}
public function getImplement(): ?string
{
return $this->getType();
}
/**
* @return static
*/
public function setReferences(array $references)
{
$this->references = [];
foreach ($references as $name => $ref) {
$this->references[$name] = substr($ref, 0, 1) === '@'
? new Reference(substr($ref, 1))
: Reference::fromType($ref);
}
return $this;
}
/**
* @return Reference[]
*/
public function getReferences(): array
{
return $this->references;
}
/**
* @return static
*/
public function setTagged(?string $tagged)
{
$this->tagged = $tagged;
return $this;
}
public function getTagged(): ?string
{
return $this->tagged;
}
public function resolveType(Nette\DI\Resolver $resolver): void
{
}
public function complete(Nette\DI\Resolver $resolver): void
{
if ($this->tagged !== null) {
$this->references = [];
foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) {
if (isset($this->references[$tag])) {
trigger_error("Service '{$this->getName()}': duplicated tag '$this->tagged' with value '$tag'.", E_USER_NOTICE);
}
$this->references[$tag] = new Reference($name);
}
}
foreach ($this->references as $name => $ref) {
$this->references[$name] = $resolver->normalizeReference($ref);
}
}
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
{
$class = (new Nette\PhpGenerator\ClassType)
->addImplement($this->getType());
$class->addProperty('container')
->setVisibility('private');
$class->addMethod('__construct')
->addBody('$this->container = $container;')
->addParameter('container')
->setType($generator->getClassName());
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) {
preg_match('#^(get|create)(.*)#', $rm->getName(), $m);
$name = lcfirst($m[2]);
$nullable = $rm->getReturnType()->allowsNull();
$methodInner = $class->addMethod($rm->getName())
->setReturnType(Reflection::getReturnType($rm))
->setReturnNullable($nullable);
if (!$name) {
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
->setVisibility('private');
$methodInner->setBody('if (!isset($this->mapping[$name])) {
' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . '
}
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')
->addParameter('name');
} elseif (isset($this->references[$name])) {
$ref = $this->references[$name]->getValue();
if ($m[1] === 'get') {
$methodInner->setBody('return $this->container->getService(?);', [$ref]);
} else {
$methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]);
}
} else {
$methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");');
}
}
$method->setBody('return new class ($this) ' . $class . ';');
}
}
src/DI/Definitions/Reference.php 0000666 00000001765 13601315514 0012525 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 0000666 00000015025 13601315514 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 .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n"
. "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
["Unable to create service '{$this->getName()}', value returned by factory is not $type type."]
);
}
foreach ($this->setup as $setup) {
$code .= $generator->formatStatement($setup) . ";\n";
}
$code .= 'return $service;';
$method->setBody($code);
}
public function __clone()
{
parent::__clone();
$this->factory = unserialize(serialize($this->factory));
$this->setup = unserialize(serialize($this->setup));
}
}
src/DI/Definitions/Statement.php 0000666 00000003420 13601315514 0012561 0 ustar 00 entity = $entity;
$this->arguments = $arguments;
}
/** @return string|array|Definition|Reference|null */
public function getEntity()
{
return $this->entity;
}
}
src/DI/DependencyChecker.php 0000666 00000011056 13601315514 0011711 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->getName()])) {
$all = [$name] + class_parents($name) + class_implements($name);
foreach ($all as &$item) {
$all += class_uses($item);
$phpFiles[] = (new ReflectionClass($item))->getFileName();
$classes[$item] = true;
}
}
} elseif ($dep instanceof \ReflectionFunctionAbstract) {
$phpFiles[] = $dep->getFileName();
$functions[] = Reflection::toString($dep);
} else {
throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep));
}
}
$classes = array_keys($classes);
$functions = array_unique($functions, SORT_REGULAR);
$hash = self::calculateHash($classes, $functions);
$files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist
$phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist
return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash];
}
/**
* Are dependencies expired?
*/
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->getName(), $prop->getDocComment()];
}
}
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
if ($method->getDeclaringClass() == $class) { // intentionally ==
$hash[] = [
$name,
$method->getName(),
$method->getDocComment(),
self::hashParameters($method),
$method->hasReturnType()
? [$method->getReturnType()->getName(), $method->getReturnType()->allowsNull()]
: null,
];
}
}
}
$flip = array_flip($classes);
foreach ($functions as $name) {
if (strpos($name, '::')) {
$method = new ReflectionMethod($name);
$class = $method->getDeclaringClass();
if (isset($flip[$class->getName()])) {
continue;
}
$uses = Reflection::getUseStatements($class);
} else {
$method = new \ReflectionFunction($name);
$uses = null;
}
$hash[] = [
$name,
$uses,
$method->getDocComment(),
self::hashParameters($method),
$method->hasReturnType()
? [$method->getReturnType()->getName(), $method->getReturnType()->allowsNull()]
: null,
];
}
return md5(serialize($hash));
}
private static function hashParameters(\ReflectionFunctionAbstract $method): array
{
$res = [];
foreach ($method->getParameters() as $param) {
$res[] = [
$param->getName(),
Reflection::getParameterType($param),
$param->allowsNull(),
$param->isVariadic(),
$param->isDefaultValueAvailable()
? [Reflection::getParameterDefaultValue($param)]
: null,
];
}
return $res;
}
}
src/DI/DynamicParameter.php 0000666 00000000514 13601315514 0011570 0 ustar 00 getConfig() as $name => $value) {
$class->getMethod('initialize')->addBody('define(?, ?);', [$name, $value]);
}
}
}
src/DI/Extensions/DIExtension.php 0000666 00000006270 13601315514 0012700 0 ustar 00 debugMode = $debugMode;
$this->time = microtime(true);
$this->config = new class {
/** @var bool */
public $debugger;
/** @var string[] */
public $excluded = [];
/** @var ?string */
public $parentClass;
/** @var object */
public $export;
};
$this->config->export = new class {
/** @var bool */
public $parameters = true;
/** @var string[]|bool|null */
public $tags = true;
/** @var string[]|bool|null */
public $types = true;
};
$this->config->debugger = interface_exists(\Tracy\IBarPanel::class);
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addExcludedClasses($this->config->excluded);
}
public function beforeCompile()
{
if (!$this->config->export->parameters) {
$this->getContainerBuilder()->parameters = [];
}
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
if ($this->config->parentClass) {
$class->setExtends($this->config->parentClass);
}
$this->restrictTags($class);
$this->restrictTypes($class);
if ($this->debugMode && $this->config->debugger) {
$this->enableTracyIntegration($class);
}
$this->initializeTaggedServices($class);
}
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(Nette\PhpGenerator\ClassType $class): 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);
$class->getMethod('initialize')->addBody('$this->getService(?);', [$name]);
}
}
private function enableTracyIntegration(Nette\PhpGenerator\ClassType $class): void
{
Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time;
$class->getMethod('initialize')->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 0000666 00000003522 13601315514 0014323 0 ustar 00 Expect::list(),
'tags' => Expect::array(),
'inject' => Expect::bool(),
])
);
}
public function beforeCompile()
{
foreach ($this->config as $type => $info) {
if ($info->inject !== null) {
$info->tags[InjectExtension::TAG_INJECT] = $info->inject;
}
$this->addSetups($type, Nette\DI\Helpers::filterArguments($info->setup));
$this->addTags($type, Nette\DI\Helpers::filterArguments($info->tags));
}
}
public function addSetups(string $type, array $setups): void
{
foreach ($this->findByType($type) as $def) {
if ($def instanceof Definitions\FactoryDefinition) {
$def = $def->getResultDefinition();
}
foreach ($setups as $setup) {
if (is_array($setup)) {
$setup = new Definitions\Statement(key($setup), array_values($setup));
}
$def->addSetup($setup);
}
}
}
public function addTags(string $type, array $tags): void
{
$tags = Nette\Utils\Arrays::normalize($tags, true);
foreach ($this->findByType($type) as $def) {
$def->setTags($def->getTags() + $tags);
}
}
private function findByType(string $type): array
{
return array_filter($this->getContainerBuilder()->getDefinitions(), function (Definitions\Definition $def) use ($type): bool {
return is_a($def->getType(), $type, true)
|| ($def instanceof Definitions\FactoryDefinition && is_a($def->getResultType(), $type, true));
});
}
}
src/DI/Extensions/ExtensionsExtension.php 0000666 00000002076 13601315514 0014543 0 ustar 00 getConfig() as $name => $class) {
if (is_int($name)) {
$name = null;
}
$args = [];
if ($class instanceof Nette\DI\Definitions\Statement) {
[$class, $args] = [$class->getEntity(), $class->arguments];
}
if (!is_a($class, Nette\DI\CompilerExtension::class, true)) {
throw new Nette\DI\InvalidConfigurationException("Extension '$class' not found or is not Nette\\DI\\CompilerExtension descendant.");
}
$this->compiler->addExtension($name, (new \ReflectionClass($class))->newInstanceArgs($args));
}
}
}
src/DI/Extensions/InjectExtension.php 0000666 00000011065 13601315514 0013616 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
{
$resolver = new DI\Resolver($this->getContainerBuilder());
$class = $resolver->resolveEntityType($def->getFactory()) ?: $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
{
$res = [];
foreach (get_class_methods($class) as $name) {
if (substr($name, 0, 6) === 'inject') {
$res[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->getName();
}
}
uksort($res, function (string $a, string $b) use ($res): int {
return $res[$a] === $res[$b]
? strcmp($a, $b)
: (is_a($res[$a], $res[$b], true) ? 1 : -1);
});
return array_keys($res);
}
/**
* Generates list of properties with annotation @inject.
* @internal
*/
public static function getInjectProperties(string $class): array
{
$res = [];
foreach (get_class_vars($class) as $name => $foo) {
$rp = new \ReflectionProperty($class, $name);
if (DI\Helpers::parseAnnotation($rp, 'inject') !== null) {
if ($type = Reflection::getPropertyType($rp)) {
} elseif ($type = DI\Helpers::parseAnnotation($rp, 'var')) {
$type = Reflection::expandClassName($type, Reflection::getPropertyDeclaringClass($rp));
}
$res[$name] = $type;
}
}
ksort($res);
return $res;
}
/**
* Calls all methods starting with with "inject" using autowiring.
* @param object $service
*/
public static function callInjects(DI\Container $container, $service): void
{
if (!is_object($service)) {
throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service)));
}
foreach (self::getInjectMethods(get_class($service)) as $method) {
$container->callMethod([$service, $method]);
}
foreach (self::getInjectProperties(get_class($service)) as $property => $type) {
self::checkType($service, $property, $type, $container);
$service->$property = $container->getByType($type);
}
}
/**
* @param object|string $class
* @param DI\Container|DI\ContainerBuilder|null $container
*/
private static function checkType($class, string $name, ?string $type, $container): void
{
$propName = Reflection::toString(new \ReflectionProperty($class, $name));
if (!$type) {
throw new Nette\InvalidStateException("Property $propName has no @var annotation.");
} elseif (!class_exists($type) && !interface_exists($type)) {
throw new Nette\InvalidStateException("Class or interface '$type' used in @var annotation at $propName not found. Check annotation and 'use' statements.");
} elseif ($container && !$container->getByType($type, false)) {
throw new Nette\DI\MissingServiceException("Service of type $type used in @var annotation at $propName not found. Did you register it in configuration file?");
}
}
}
src/DI/Extensions/ParametersExtension.php 0000666 00000003151 13601315514 0014502 0 ustar 00 compilerConfig = &$compilerConfig;
}
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$params = $this->config;
foreach ($this->dynamicParams as $key) {
$params[$key] = array_key_exists($key, $params)
? new DynamicParameter(Nette\PhpGenerator\Helpers::format('($this->parameters[?] \?\? ?)', $key, $params[$key]))
: new DynamicParameter(Nette\PhpGenerator\Helpers::format('$this->parameters[?]', $key));
}
$builder->parameters = Nette\DI\Helpers::expand($params, $params, true);
// expand all except 'services'
$slice = array_diff_key($this->compilerConfig, ['services' => 1]);
$this->compilerConfig = Nette\DI\Helpers::expand($slice, $builder->parameters) + $this->compilerConfig;
}
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$builder = $this->getContainerBuilder();
$cnstr = $class->getMethod('__construct');
foreach ($this->dynamicValidators as [$param, $expected]) {
$cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
}
}
}
src/DI/Extensions/PhpExtension.php 0000666 00000002566 13601315514 0013137 0 ustar 00 getMethod('initialize');
foreach ($this->getConfig() as $name => $value) {
if ($value === null) {
continue;
} elseif ($name === 'include_path') {
$initialize->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]);
} elseif ($name === 'ignore_user_abort') {
$initialize->addBody('ignore_user_abort(?);', [$value]);
} elseif ($name === 'max_execution_time') {
$initialize->addBody('set_time_limit(?);', [$value]);
} elseif ($name === 'date.timezone') {
$initialize->addBody('date_default_timezone_set(?);', [$value]);
} elseif (function_exists('ini_set')) {
$initialize->addBody('ini_set(?, ?);', [$name, $value === false ? '0' : (string) $value]);
} elseif (ini_get($name) != $value) { // intentionally ==
throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
}
}
}
}
src/DI/Extensions/SearchExtension.php 0000666 00000010443 13601315514 0013606 0 ustar 00 tempDir = $tempDir;
}
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::arrayOf(
Expect::structure([
'in' => Expect::string()->required(),
'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'exclude' => Expect::structure([
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
]),
'tags' => Expect::array(),
])
)->before(function ($val) {
return is_string($val['in'] ?? null)
? ['default' => $val]
: $val;
});
}
public function loadConfiguration()
{
foreach (array_filter($this->config) as $name => $batch) {
if (!is_dir($batch->in)) {
throw new Nette\DI\InvalidConfigurationException("Option '{$this->name} › {$name} › in' must be valid directory name, '{$batch->in}' given.");
}
foreach ($this->findClasses($batch) as $class) {
$this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags);
}
}
}
public function findClasses(\stdClass $config): array
{
$robot = new RobotLoader;
$robot->setTempDirectory($this->tempDir);
$robot->addDirectory($config->in);
$robot->acceptFiles = $config->files ?: ['*.php'];
$robot->reportParseErrors(false);
$robot->refresh();
$classes = array_unique(array_keys($robot->getIndexedClasses()));
$exclude = $config->exclude;
$acceptRE = self::buildNameRegexp($config->classes);
$rejectRE = self::buildNameRegexp($exclude->classes);
$acceptParent = array_merge($config->extends, $config->implements);
$rejectParent = array_merge($exclude->extends, $exclude->implements);
$found = [];
foreach ($classes as $class) {
$rc = new \ReflectionClass($class);
if (
($rc->isInstantiable()
||
($rc->isInterface()
&& count($methods = $rc->getMethods()) === 1
&& $methods[0]->getName() === 'create')
)
&& (!$acceptRE || preg_match($acceptRE, $rc->getName()))
&& (!$rejectRE || !preg_match($rejectRE, $rc->getName()))
&& (!$acceptParent || Arrays::some($acceptParent, function ($nm) use ($rc) { return $rc->isSubclassOf($nm); }))
&& (!$rejectParent || Arrays::every($rejectParent, function ($nm) use ($rc) { return !$rc->isSubclassOf($nm); }))
) {
$found[] = $rc->getName();
}
}
return $found;
}
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($this->classes as $class => $foo) {
if ($builder->findByType($class)) {
unset($this->classes[$class]);
}
}
foreach ($this->classes as $class => $tags) {
if (class_exists($class)) {
$def = $builder->addDefinition(null)
->setType($class);
} else {
$def = $builder->addFactoryDefinition(null)
->setImplement($class);
}
$def->setTags(Arrays::normalize($tags, true));
}
}
private static function buildNameRegexp(array $masks): ?string
{
$res = [];
foreach ((array) $masks as $mask) {
$mask = (strpos($mask, '\\') === false ? '**\\' : '') . $mask;
$mask = preg_quote($mask, '#');
$mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask);
$mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask);
$mask = str_replace('\*', '\w*', $mask);
$res[] = $mask;
}
return $res ? '#^(' . implode('|', $res) . ')$#i' : null;
}
}
src/DI/Extensions/ServicesExtension.php 0000666 00000014460 13601315514 0014167 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 0000666 00000012676 13601315514 0007761 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 (!is_string($var)) {
return $var;
}
$parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = [];
$php = false;
foreach ($parts as $n => $part) {
if ($n % 2 === 0) {
$res[] = $part;
} elseif ($part === '') {
$res[] = '%';
} elseif (isset($recursive[$part])) {
throw new Nette\InvalidArgumentException(sprintf('Circular reference detected for variables: %s.', implode(', ', array_keys($recursive))));
} else {
$val = $params;
foreach (explode('.', $part) as $key) {
if (is_array($val) && array_key_exists($key, $val)) {
$val = $val[$key];
} elseif ($val instanceof DynamicParameter) {
$val = new DynamicParameter($val . '[' . var_export($key, true) . ']');
} else {
throw new Nette\InvalidArgumentException("Missing parameter '$part'.");
}
}
if ($recursive) {
$val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]);
}
if (strlen($part) + 2 === strlen($var)) {
return $val;
}
if ($val instanceof DynamicParameter) {
$php = true;
} elseif (!is_scalar($val)) {
throw new Nette\InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$var'.");
}
$res[] = $val;
}
}
if ($php) {
$res = array_filter($res, function ($val): bool { return $val !== ''; });
$res = array_map(function ($val): string { return $val instanceof DynamicParameter ? "($val)" : var_export((string) $val, true); }, $res);
return new DynamicParameter(implode(' . ', $res));
}
return implode('', $res);
}
/**
* Removes ... and process constants recursively.
*/
public static function filterArguments(array $args): array
{
foreach ($args as $k => $v) {
if ($v === '...') {
unset($args[$k]);
} elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*$#D', $v, $m)) {
$args[$k] = constant(ltrim($v, ':'));
} elseif (is_string($v) && preg_match('#^@[\w\\\\]+$#D', $v)) {
$args[$k] = new Reference(substr($v, 1));
} elseif (is_array($v)) {
$args[$k] = self::filterArguments($v);
} elseif ($v instanceof Statement) {
$tmp = self::filterArguments([$v->getEntity()]);
$args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments));
}
}
return $args;
}
/**
* Replaces @extension with real extension name in service definition.
* @param mixed $config
* @return mixed
*/
public static function prefixServiceName($config, string $namespace)
{
if (is_string($config)) {
if (strncmp($config, '@extension.', 10) === 0) {
$config = '@' . $namespace . '.' . substr($config, 11);
}
} elseif ($config instanceof Reference) {
if (strncmp($config->getValue(), 'extension.', 9) === 0) {
$config = new Reference($namespace . '.' . substr($config->getValue(), 10));
}
} elseif ($config instanceof Statement) {
return new Statement(
self::prefixServiceName($config->getEntity(), $namespace),
self::prefixServiceName($config->arguments, $namespace)
);
} elseif (is_array($config)) {
foreach ($config as &$val) {
$val = self::prefixServiceName($val, $namespace);
}
}
return $config;
}
/**
* Returns an annotation value.
* @param \ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionClass $ref
*/
public static function parseAnnotation(\Reflector $ref, string $name): ?string
{
if (!Reflection::areCommentsAvailable()) {
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
}
$re = '#[\s*]@' . preg_quote($name, '#') . '(?=\s|$)(?:[ \t]+([^@\s]\S*))?#';
if ($ref->getDocComment() && preg_match($re, trim($ref->getDocComment(), '/*'), $m)) {
return $m[1] ?? '';
}
return null;
}
public static function getReturnType(\ReflectionFunctionAbstract $func): ?string
{
if ($type = Reflection::getReturnType($func)) {
return $type;
} elseif ($type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'))) {
if ($type === 'object' || $type === 'mixed') {
return null;
} elseif ($func instanceof \ReflectionMethod) {
return $type === 'static' || $type === '$this'
? $func->getDeclaringClass()->getName()
: 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))->getName()
: $type;
}
}
src/DI/PhpGenerator.php 0000666 00000012330 13601315514 0010740 0 ustar 00 builder = $builder;
}
/**
* Generates PHP classes. First class is the container.
*/
public function generate(string $className): Nette\PhpGenerator\ClassType
{
$this->className = $className;
$class = new Nette\PhpGenerator\ClassType($this->className);
$class->setExtends(Container::class);
$class->addMethod('__construct')
->addBody('parent::__construct($params);')
->addBody($this->formatPhp('$this->parameters += ?;', [$this->builder->parameters]))
->addParameter('params', [])
->setType('array');
foreach ($this->builder->exportMeta() as $key => $value) {
$class->addProperty($key)
->setVisibility('protected')
->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;');
return $class;
}
public function toString(Nette\PhpGenerator\ClassType $class): string
{
return '/** @noinspection PhpParamsInspection,PhpMethodMayBeStaticInspection */
declare(strict_types=1);
' . $class->__toString();
}
public function generateMethod(Definitions\Definition $def): Nette\PhpGenerator\Method
{
$name = $def->getName();
try {
$method = new Nette\PhpGenerator\Method(Container::getMethodName($name));
$method->setVisibility('public');
$method->setReturnType($def->getType());
$def->generateMethod($method, $this);
return $method;
} catch (\Exception $e) {
throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
}
}
/**
* Formats PHP code for class instantiating, function calling or property setting in PHP.
*/
public function formatStatement(Statement $statement): string
{
$entity = $statement->getEntity();
$arguments = $statement->arguments;
switch (true) {
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
return $this->formatPhp($entity, $arguments);
case is_string($entity): // create class
return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []);
case is_array($entity):
switch (true) {
case $entity[1][0] === '$': // property getter, setter or appender
$name = substr($entity[1], 1);
if ($append = (substr($name, -2) === '[]')) {
$name = substr($name, 0, -2);
}
if ($entity[0] instanceof Reference) {
$prop = $this->formatPhp('?->?', [$entity[0], $name]);
} else {
$prop = $this->formatPhp($entity[0] . '::$?', [$name]);
}
return $arguments
? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]])
: $prop;
case $entity[0] instanceof Statement:
$inner = $this->formatPhp('?', [$entity[0]]);
if (substr($inner, 0, 4) === 'new ') {
$inner = "($inner)";
}
return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]);
case $entity[0] instanceof Reference:
return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]);
case $entity[0] === '': // function call
return $this->formatPhp("$entity[1](...?)", [$arguments]);
case is_string($entity[0]): // static method call
return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]);
}
}
throw new Nette\InvalidStateException;
}
/**
* Formats PHP statement.
* @internal
*/
public function formatPhp(string $statement, array $args): string
{
array_walk_recursive($args, function (&$val): void {
if ($val instanceof Statement) {
$val = new PhpLiteral($this->formatStatement($val));
} elseif ($val instanceof Reference) {
$name = $val->getValue();
if ($val->isSelf()) {
$val = new PhpLiteral('$service');
} elseif ($name === ContainerBuilder::THIS_CONTAINER) {
$val = new PhpLiteral('$this');
} else {
$val = ContainerBuilder::literal('$this->getService(?)', [$name]);
}
}
});
return PhpHelpers::formatArgs($statement, $args);
}
/**
* Converts parameters from Definition to PhpGenerator.
* @return Nette\PhpGenerator\Parameter[]
*/
public function convertParameters(array $parameters): array
{
$res = [];
foreach ($parameters as $k => $v) {
$tmp = explode(' ', is_int($k) ? $v : $k);
$param = $res[] = new Nette\PhpGenerator\Parameter(end($tmp));
if (!is_int($k)) {
$param->setDefaultValue($v);
}
if (isset($tmp[1])) {
$param->setType($tmp[0]);
}
}
return $res;
}
public function getClassName(): ?string
{
return $this->className;
}
}
src/DI/Resolver.php 0000666 00000042060 13601315514 0010146 0 ustar 00 builder = $builder;
$this->recursive = new \SplObjectStorage;
}
public function getContainerBuilder(): ContainerBuilder
{
return $this->builder;
}
public function resolveDefinition(Definition $def): void
{
if ($this->recursive->contains($def)) {
$names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive));
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
}
try {
$this->recursive->attach($def);
$def->resolveType($this);
if (!$def->getType()) {
throw new ServiceCreationException('Type of service is unknown.');
}
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->recursive->detach($def);
}
}
public function resolveReferenceType(Reference $ref): ?string
{
if ($ref->isSelf()) {
return $this->currentServiceType;
} elseif ($ref->isType()) {
return ltrim($ref->getValue(), '\\');
}
$def = $this->resolveReference($ref);
if (!$def->getType()) {
$this->resolveDefinition($def);
}
return $def->getType();
}
public function resolveEntityType(Statement $statement): ?string
{
$entity = $this->normalizeEntity($statement);
if (is_array($entity)) {
if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
$entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
if (!$entity[0]) {
return null;
}
}
try {
/** @var \ReflectionMethod|\ReflectionFunction $reflection */
$reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
$refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : null;
} catch (\ReflectionException $e) {
$refClass = $reflection = null;
}
if (isset($e) || ($refClass && (!$reflection->isPublic()
|| ($refClass->isTrait() && !$reflection->isStatic())
))) {
throw new ServiceCreationException(sprintf('Method %s() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null);
}
$this->addDependency($reflection);
$type = Helpers::getReturnType($reflection);
if ($type && !class_exists($type) && !interface_exists($type)) {
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() correct?", $type, Nette\Utils\Callback::toString($entity)));
}
return $type;
} elseif ($entity instanceof Reference) { // alias or factory
return $this->resolveReferenceType($entity);
} elseif (is_string($entity)) { // class
if (!class_exists($entity)) {
throw new ServiceCreationException("Class $entity not found.");
}
return $entity;
}
return null;
}
public function completeDefinition(Definition $def): void
{
$this->currentService = in_array($def, $this->builder->getDefinitions(), true) ? $def : null;
$this->currentServiceType = $def->getType();
$this->currentServiceAllowed = false;
try {
$def->complete($this);
$this->addDependency(new \ReflectionClass($def->getType()));
} catch (\Exception $e) {
throw $this->completeException($e, $def);
} finally {
$this->currentService = $this->currentServiceType = null;
}
}
public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
{
$this->currentServiceAllowed = $currentServiceAllowed;
$entity = $this->normalizeEntity($statement);
$arguments = $this->convertReferences($statement->arguments);
$getter = function (string $type, bool $single) {
return $single
? $this->getByType($type)
: array_values(array_filter($this->builder->findAutowired($type), function ($obj) { return $obj !== $this->currentService; }));
};
switch (true) {
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
break;
case $entity === 'not':
$entity = ['', '!'];
break;
case is_string($entity): // create class
if (!class_exists($entity)) {
throw new ServiceCreationException("Class $entity not found.");
} elseif ((new ReflectionClass($entity))->isAbstract()) {
throw new ServiceCreationException("Class $entity is abstract.");
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
$visibility = $rm->isProtected() ? 'protected' : 'private';
throw new ServiceCreationException("Class $entity has $visibility constructor.");
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
$arguments = self::autowireArguments($constructor, $arguments, $getter);
$this->addDependency($constructor);
} elseif ($arguments) {
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
}
break;
case $entity instanceof Reference:
$entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())];
break;
case is_array($entity):
if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?$#D', $entity[1])) {
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
}
switch (true) {
case $entity[0] === '': // function call
if (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
} elseif (!function_exists($entity[1])) {
throw new ServiceCreationException("Function $entity[1] doesn't exist.");
}
$rf = new \ReflectionFunction($entity[1]);
$arguments = self::autowireArguments($rf, $arguments, $getter);
$this->addDependency($rf);
break;
case $entity[0] instanceof Statement:
$entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
// break omitted
case is_string($entity[0]): // static method call
case $entity[0] instanceof Reference:
if ($entity[1][0] === '$') { // property getter, setter or appender
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
if (!$arguments && substr($entity[1], -2) === '[]') {
throw new ServiceCreationException("Missing argument for $entity[1].");
}
} elseif (
$type = $entity[0] instanceof Reference
? $this->resolveReferenceType($entity[0])
: $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
) {
$rc = new ReflectionClass($type);
if ($rc->hasMethod($entity[1])) {
$rm = $rc->getMethod($entity[1]);
if (!$rm->isPublic()) {
throw new ServiceCreationException("$type::$entity[1]() is not callable.");
}
$arguments = self::autowireArguments($rm, $arguments, $getter);
$this->addDependency($rm);
} elseif (!Nette\Utils\Arrays::isList($arguments)) {
throw new ServiceCreationException("Unable to pass specified arguments to $type::$entity[1]().");
}
}
}
}
try {
$arguments = $this->completeArguments($arguments);
} catch (ServiceCreationException $e) {
if (!strpos($e->getMessage(), ' (used in')) {
$e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
}
throw $e;
}
return new Statement($entity, $arguments);
}
public function completeArguments(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if ($val instanceof Statement) {
$entity = $val->getEntity();
if ($entity === 'typed' || $entity === 'tagged') {
$services = [];
$current = $this->currentService ? $this->currentService->getName() : null;
foreach ($val->arguments as $argument) {
foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
if ($name !== $current) {
$services[] = new Reference($name);
}
}
}
$val = $this->completeArguments($services);
} else {
$val = $this->completeStatement($val, $this->currentServiceAllowed);
}
} elseif ($val instanceof Definition || $val instanceof Reference) {
$val = $this->normalizeEntity(new Statement($val));
}
});
return $arguments;
}
/**
* @return string|array|Reference literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member]
*/
private function normalizeEntity(Statement $statement)
{
$entity = $statement->getEntity();
if (is_array($entity)) {
$item = &$entity[0];
} else {
$item = &$entity;
}
if ($item instanceof Definition) {
$name = current(array_keys($this->builder->getDefinitions(), $item, true));
if ($name == false) {
throw new ServiceCreationException("Service '{$item->getName()}' not found in definitions.");
}
$item = new Reference($name);
}
if ($item instanceof Reference) {
$item = $this->normalizeReference($item);
}
return $entity;
}
/**
* Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
*/
public function normalizeReference(Reference $ref): Reference
{
$service = $ref->getValue();
if ($ref->isSelf()) {
return $ref;
} elseif ($ref->isName()) {
if (!$this->builder->hasDefinition($service)) {
throw new ServiceCreationException("Reference to missing service '$service'.");
}
return $this->currentService && $service === $this->currentService->getName()
? new Reference(Reference::SELF)
: $ref;
}
try {
return $this->getByType($service);
} catch (NotAllowedDuringResolvingException $e) {
return new Reference($service);
}
}
public function resolveReference(Reference $ref): Definition
{
return $ref->isSelf()
? $this->currentService
: $this->builder->getDefinition($ref->getValue());
}
/**
* Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
* @throws ServiceCreationException when multiple found
* @throws MissingServiceException when not found
*/
public function getByType(string $type): Reference
{
if (
$this->currentService
&& $this->currentServiceAllowed
&& is_a($this->currentServiceType, $type, true)
) {
return new Reference(Reference::SELF);
}
return new Reference($this->builder->getByType($type, true));
}
/**
* Adds item to the list of dependencies.
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
* @return static
*/
public function addDependency($dep)
{
$this->builder->addDependency($dep);
return $this;
}
private function completeException(\Exception $e, Definition $def): ServiceCreationException
{
if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) {
return $e;
} else {
$name = $def->getName();
$type = $def->getType();
if (!$type) {
$message = "Service '$name': " . $e->getMessage();
} elseif (!$name || ctype_digit($name)) {
$message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage());
} else {
$message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage());
}
return $e instanceof ServiceCreationException
? $e->setMessage($message)
: new ServiceCreationException($message, 0, $e);
}
}
private function entityToString($entity): string
{
$referenceToText = function (Reference $ref): string {
return $ref->isSelf() && $this->currentService
? '@' . $this->currentService->getName()
: '@' . $ref->getValue();
};
if (is_string($entity)) {
return $entity . '::__construct()';
} elseif ($entity instanceof Reference) {
$entity = $referenceToText($entity);
} elseif (is_array($entity)) {
if (strpos($entity[1], '$') === false) {
$entity[1] .= '()';
}
if ($entity[0] instanceof Reference) {
$entity[0] = $referenceToText($entity[0]);
} elseif (!is_string($entity[0])) {
return $entity[1];
}
return implode('::', $entity);
}
return (string) $entity;
}
private function convertReferences(array $arguments): array
{
array_walk_recursive($arguments, function (&$val): void {
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
$pair = explode('::', substr($val, 1), 2);
if (!isset($pair[1])) { // @service
$val = new Reference($pair[0]);
} elseif (preg_match('#^[A-Z][A-Z0-9_]*$#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->getName();
if (!$param->isVariadic() && array_key_exists($paramName, $arguments)) {
$res[$num] = $arguments[$paramName];
unset($arguments[$paramName], $arguments[$num]);
} elseif (array_key_exists($num, $arguments)) {
$res[$num] = $arguments[$num];
unset($arguments[$num]);
} else {
$res[$num] = self::autowireArgument($param, $getter);
}
$optCount = $param->isOptional() && $res[$num] === ($param->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($param) : null)
? $optCount + 1
: 0;
}
// extra parameters
while (array_key_exists(++$num, $arguments)) {
$res[$num] = $arguments[$num];
unset($arguments[$num]);
$optCount = 0;
}
if ($arguments) {
throw new ServiceCreationException('Unable to pass specified arguments to ' . Reflection::toString($method) . '().');
} elseif ($optCount) {
$res = array_slice($res, 0, -$optCount);
}
return $res;
}
/**
* Resolves missing argument using autowiring.
* @param (callable(string $type, bool $single): object|object[]|null) $getter
* @throws ServiceCreationException
* @return mixed
*/
private static function autowireArgument(\ReflectionParameter $parameter, callable $getter)
{
$type = Reflection::getParameterType($parameter);
$method = $parameter->getDeclaringFunction();
$desc = '$' . $parameter->getName() . ' in ' . Reflection::toString($method) . '()';
if ($type && !Reflection::isBuiltinType($type)) {
try {
$res = $getter($type, true);
} catch (MissingServiceException $e) {
$res = null;
} catch (ServiceCreationException $e) {
throw new ServiceCreationException("{$e->getMessage()} (needed by $desc)", 0, $e);
}
if ($res !== null || $parameter->allowsNull()) {
return $res;
} elseif (class_exists($type) || interface_exists($type)) {
throw new ServiceCreationException("Service of type $type needed by $desc not found. Did you register it in configuration file?");
} else {
throw new ServiceCreationException("Class $type needed by $desc not found. Check type hint and 'use' statements.");
}
} elseif (
$method instanceof \ReflectionMethod
&& $parameter->isArray()
&& preg_match('#@param[ \t]+([\w\\\\]+)\[\][ \t]+\$' . $parameter->getName() . '#', (string) $method->getDocComment(), $m)
&& ($itemType = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
&& (class_exists($itemType) || interface_exists($itemType))
) {
return $getter($itemType, false);
} elseif (($type && $parameter->allowsNull()) || $parameter->isOptional() || $parameter->isDefaultValueAvailable()) {
// !optional + defaultAvailable = func($a = null, $b) since 5.4.7
// optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ...
return $parameter->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($parameter) : null;
} else {
throw new ServiceCreationException("Parameter $desc has no class type hint or default value, so its value must be specified.");
}
}
}
src/DI/exceptions.php 0000666 00000001403 13601315514 0010522 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 0000666 00000000641 13601315514 0010721 0 ustar 00