no message
This commit is contained in:
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace PHPSTORM_META;
|
||||
|
||||
override(\Nette\DI\Container::getByType(0), map(['' => '@']));
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"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": ">=5.6.0",
|
||||
"ext-tokenizer": "*",
|
||||
"nette/neon": "^2.3.3 || ~3.0.0",
|
||||
"nette/php-generator": "^2.6.1 || ^3.0.0",
|
||||
"nette/utils": "^2.5.0 || ~3.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.0",
|
||||
"tracy/tracy": "^2.3"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/nette": "<2.2",
|
||||
"nette/bootstrap": "<2.4"
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": ["src/"]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.4-dev"
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+33
@@ -0,0 +1,33 @@
|
||||
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:
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
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)
|
||||
Vendored
+330
@@ -0,0 +1,330 @@
|
||||
Nette Dependency Injection (DI)
|
||||
===============================
|
||||
|
||||
[](https://packagist.org/packages/nette/di)
|
||||
[](https://travis-ci.org/nette/di)
|
||||
[](https://coveralls.io/github/nette/di?branch=master)
|
||||
[](https://github.com/nette/di/releases)
|
||||
[](https://github.com/nette/di/blob/master/license.md)
|
||||
|
||||
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.
|
||||
|
||||
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. In first, install it using Composer:
|
||||
|
||||
```
|
||||
composer require nette/di
|
||||
```
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\Bridges\DITracy;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Container;
|
||||
use Tracy;
|
||||
|
||||
|
||||
/**
|
||||
* Dependency injection container panel for Debugger Bar.
|
||||
*/
|
||||
class ContainerPanel implements Tracy\IBarPanel
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var int */
|
||||
public static $compilationTime;
|
||||
|
||||
/** @var Nette\DI\Container */
|
||||
private $container;
|
||||
|
||||
/** @var int|null */
|
||||
private $elapsedTime;
|
||||
|
||||
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
$this->container = $container;
|
||||
$this->elapsedTime = self::$compilationTime ? microtime(true) - self::$compilationTime : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders tab.
|
||||
* @return string
|
||||
*/
|
||||
public function getTab()
|
||||
{
|
||||
ob_start(function () {});
|
||||
$elapsedTime = $this->elapsedTime;
|
||||
require __DIR__ . '/templates/ContainerPanel.tab.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Renders panel.
|
||||
* @return string
|
||||
*/
|
||||
public function getPanel()
|
||||
{
|
||||
$container = $this->container;
|
||||
$registry = $this->getContainerProperty('registry');
|
||||
$file = (new \ReflectionClass($container))->getFileName();
|
||||
$tags = [];
|
||||
$meta = $this->getContainerProperty('meta');
|
||||
$services = $meta[Container::SERVICES];
|
||||
ksort($services);
|
||||
if (isset($meta[Container::TAGS])) {
|
||||
foreach ($meta[Container::TAGS] as $tag => $tmp) {
|
||||
foreach ($tmp as $service => $val) {
|
||||
$tags[$service][$tag] = $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ob_start(function () {});
|
||||
require __DIR__ . '/templates/ContainerPanel.panel.phtml';
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
|
||||
private function getContainerProperty($name)
|
||||
{
|
||||
$prop = (new \ReflectionClass(Nette\DI\Container::class))->getProperty($name);
|
||||
$prop->setAccessible(true);
|
||||
return $prop->getValue($this->container);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace Nette\Bridges\DITracy;
|
||||
|
||||
use Nette;
|
||||
use Tracy\Dumper;
|
||||
use Tracy\Helpers;
|
||||
|
||||
?>
|
||||
<style class="tracy-debug">
|
||||
#tracy-debug .nette-ContainerPanel table {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-ContainerPanel .created {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#tracy-debug .nette-ContainerPanel .yes {
|
||||
color: green;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
<h1><?= get_class($container) ?></h1>
|
||||
|
||||
<div class="tracy-inner nette-ContainerPanel">
|
||||
<div class="tracy-inner-container">
|
||||
<h2>Services</h2>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Autowired</th>
|
||||
<th>Service</th>
|
||||
<th>Tags</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($services as $name => $type): ?>
|
||||
<?php $autowired = !empty($meta[Nette\DI\Container::TYPES][$type][true]) && in_array($name, $meta[Nette\DI\Container::TYPES][$type][true], true) ?>
|
||||
<tr>
|
||||
<td class="<?= isset($registry[$name]) ? 'created' : '' ?>"><?= htmlspecialchars($name) ?></td>
|
||||
<td class="<?= $autowired ? 'yes' : '' ?>"><?= $autowired ? 'yes' : 'no' ?></td>
|
||||
<td>
|
||||
<?php if (isset($registry[$name]) && !$registry[$name] instanceof Nette\DI\Container): ?>
|
||||
<?= Dumper::toHtml($registry[$name], [Dumper::COLLAPSE => true, Dumper::LIVE => true]); ?>
|
||||
<?php elseif (isset($registry[$name])): ?>
|
||||
<code><?= get_class($registry[$name]) ?></code>
|
||||
<?php elseif (is_string($type)): ?>
|
||||
<code><?= htmlspecialchars($type) ?></code>
|
||||
<?php endif ?>
|
||||
</td>
|
||||
<td><?php if (isset($tags[$name])) { echo Dumper::toHtml($tags[$name], [Dumper::COLLAPSE => true]); } ?></td>
|
||||
</tr>
|
||||
<?php endforeach ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>Parameters</h2>
|
||||
|
||||
<div class="nette-ContainerPanel-parameters">
|
||||
<?= Dumper::toHtml($container->parameters); ?>
|
||||
</div>
|
||||
|
||||
<p>Source: <?= Helpers::editorLink($file) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace Nette\Bridges\DITracy;
|
||||
|
||||
|
||||
?>
|
||||
<span title="Dependency Injection Container">
|
||||
<svg viewBox="0 0 2048 2048"><path fill="#ae7c21" d="m1675 690.55v-125c0-36-4-60-13-71-9-12-26-18-52-18s-43 6-52 17c-8 11-12 36-12 72v910.62c0 37 4 61 12 72 9 11 26 17 52 17s44-6 52-17c9-12 13-36 13-72v-207h243v165c0 86-4 148-12 187-8 38-21 70-39 96-20 28-45 48-77 59-31 11-87 16-170 16-96 0-161-5-196-16-34-11-63-31-86-59-21-26-36-59-45-97-9-39-14-101-14-186v-826.62c0-85 4-147 13-185s25-71 46-97c23-29 54-49 92-60s102-17 189-17c75 0 130 6 164 17s61 30 81 59c19 26 33 59 41 99 8 39 12 101 12 185v84zm-524 1086.6h-254v-1514.6h254zm-1025 0v-1514.6h316c127 0 212 27 257 81 44 53 66 159 66 316v721.62c0 157-22 263-66 317-44 53-130 80-257 80zm388-314v-884.62c0-35-4-58-13-70-9-11-26-17-53-17h-66v1058.6h66c26 0 44-6 53-17 9-12 14-35 14-70z"/>
|
||||
</svg><span class="tracy-label"><?= $elapsedTime ? sprintf('%0.1f ms', $elapsedTime * 1000) : '' ?></span>
|
||||
</span>
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Autowiring.
|
||||
*/
|
||||
class Autowiring
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var ContainerBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var array[] type => services, used by getByType() */
|
||||
private $highPriority = [];
|
||||
|
||||
/** @var array[] type => services, used by findByType() */
|
||||
private $lowPriority = [];
|
||||
|
||||
/** @var string[] of classes excluded from autowiring */
|
||||
private $excludedClasses = [];
|
||||
|
||||
|
||||
public function __construct(ContainerBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves service name by type.
|
||||
* @param bool $throw exception if service not found?
|
||||
* @throws MissingServiceException when not found
|
||||
* @throws ServiceCreationException when multiple found
|
||||
*/
|
||||
public function getByType(string $type, bool $throw = false): ?string
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$types = $this->highPriority;
|
||||
if (empty($types[$type])) {
|
||||
if ($throw) {
|
||||
throw new MissingServiceException("Service of type '$type' not found.");
|
||||
}
|
||||
return null;
|
||||
|
||||
} elseif (count($types[$type]) === 1) {
|
||||
return $types[$type][0];
|
||||
|
||||
} else {
|
||||
$list = $types[$type];
|
||||
natsort($list);
|
||||
$hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.'))
|
||||
? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.'
|
||||
: '';
|
||||
throw new ServiceCreationException("Multiple services of type $type found: " . implode(', ', $list) . $hint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service names and definitions of the specified type.
|
||||
* @return Definitions\Definition[] service name is key
|
||||
*/
|
||||
public function findByType(string $type): array
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$definitions = $this->builder->getDefinitions();
|
||||
$names = array_merge($this->highPriority[$type] ?? [], $this->lowPriority[$type] ?? []);
|
||||
$res = [];
|
||||
foreach ($names as $name) {
|
||||
$res[$name] = $definitions[$name];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[] $types
|
||||
*/
|
||||
public function addExcludedClasses(array $types): void
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
if (class_exists($type) || interface_exists($type)) {
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$this->excludedClasses += class_parents($type) + class_implements($type) + [$type => $type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function getClassList(): array
|
||||
{
|
||||
return [$this->lowPriority, $this->highPriority];
|
||||
}
|
||||
|
||||
|
||||
public function rebuild(): void
|
||||
{
|
||||
$this->lowPriority = $this->highPriority = $preferred = [];
|
||||
|
||||
foreach ($this->builder->getDefinitions() as $name => $def) {
|
||||
if (!($type = $def->getType())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$autowired = $def->getAutowired();
|
||||
if (is_array($autowired)) {
|
||||
foreach ($autowired as $k => $autowiredType) {
|
||||
if ($autowiredType === ContainerBuilder::THIS_SERVICE) {
|
||||
$autowired[$k] = $type;
|
||||
} elseif (!is_a($type, $autowiredType, true)) {
|
||||
throw new ServiceCreationException("Incompatible class $autowiredType in autowiring definition of service '$name'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (class_parents($type) + class_implements($type) + [$type] as $parent) {
|
||||
if (!$autowired || isset($this->excludedClasses[$parent])) {
|
||||
continue;
|
||||
} elseif (is_array($autowired)) {
|
||||
$priority = false;
|
||||
foreach ($autowired as $autowiredType) {
|
||||
if (is_a($parent, $autowiredType, true)) {
|
||||
if (empty($preferred[$parent]) && isset($this->highPriority[$parent])) {
|
||||
$this->lowPriority[$parent] = array_merge($this->lowPriority[$parent], $this->highPriority[$parent]);
|
||||
$this->highPriority[$parent] = [];
|
||||
}
|
||||
$preferred[$parent] = $priority = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$priority = empty($preferred[$parent]);
|
||||
}
|
||||
$list = $priority ? 'highPriority' : 'lowPriority';
|
||||
$this->$list[$parent][] = $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+507
@@ -0,0 +1,507 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Validators;
|
||||
|
||||
|
||||
/**
|
||||
* DI container compiler.
|
||||
*/
|
||||
class Compiler
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var CompilerExtension[] */
|
||||
private $extensions = [];
|
||||
|
||||
/** @var ContainerBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var array */
|
||||
private $config = [];
|
||||
|
||||
/** @var DependencyChecker */
|
||||
private $dependencies;
|
||||
|
||||
/** @var string */
|
||||
private $className = 'Container';
|
||||
|
||||
/** @var string[] */
|
||||
private $dynamicParams = [];
|
||||
|
||||
/** @var array reserved section names */
|
||||
private static $reserved = ['services' => 1, 'parameters' => 1];
|
||||
|
||||
|
||||
public function __construct(ContainerBuilder $builder = null)
|
||||
{
|
||||
$this->builder = $builder ?: new ContainerBuilder;
|
||||
$this->dependencies = new DependencyChecker;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add custom configurator extension.
|
||||
* @param string|null
|
||||
* @return static
|
||||
*/
|
||||
public function addExtension($name, CompilerExtension $extension)
|
||||
{
|
||||
if ($name === null) {
|
||||
$name = '_' . count($this->extensions);
|
||||
} elseif (isset($this->extensions[$name]) || isset(self::$reserved[$name])) {
|
||||
throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved.");
|
||||
}
|
||||
$this->extensions[$name] = $extension->setCompiler($this, $name);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getExtensions($type = null)
|
||||
{
|
||||
return $type
|
||||
? array_filter($this->extensions, function ($item) use ($type) { return $item instanceof $type; })
|
||||
: $this->extensions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ContainerBuilder
|
||||
*/
|
||||
public function getContainerBuilder()
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setClassName($className)
|
||||
{
|
||||
$this->className = $className;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new configuration.
|
||||
* @return static
|
||||
*/
|
||||
public function addConfig(array $config)
|
||||
{
|
||||
$this->config = Config\Helpers::merge($config, $this->config);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds new configuration from file.
|
||||
* @return static
|
||||
*/
|
||||
public function loadConfig($file)
|
||||
{
|
||||
$loader = new Config\Loader;
|
||||
$this->addConfig($loader->load($file));
|
||||
$this->dependencies->add($loader->getDependencies());
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns configuration.
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the names of dynamic parameters.
|
||||
* @return static
|
||||
*/
|
||||
public function setDynamicParameterNames(array $names)
|
||||
{
|
||||
$this->dynamicParams = $names;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds dependencies to the list.
|
||||
* @param array of ReflectionClass|\ReflectionFunctionAbstract|string
|
||||
* @return static
|
||||
*/
|
||||
public function addDependencies(array $deps)
|
||||
{
|
||||
$this->dependencies->add(array_filter($deps));
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exports dependencies.
|
||||
* @return array
|
||||
*/
|
||||
public function exportDependencies()
|
||||
{
|
||||
return $this->dependencies->export();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function compile()
|
||||
{
|
||||
if (func_num_args()) {
|
||||
trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::addConfig() and Compiler::setClassName().', E_USER_DEPRECATED);
|
||||
$this->config = func_get_arg(0) ?: $this->config;
|
||||
$this->className = @func_get_arg(1) ?: $this->className;
|
||||
}
|
||||
$this->processParameters();
|
||||
$this->processExtensions();
|
||||
$this->processServices();
|
||||
$classes = $this->generateCode();
|
||||
return implode("\n\n\n", $classes);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function processParameters()
|
||||
{
|
||||
$params = isset($this->config['parameters']) ? $this->config['parameters'] : [];
|
||||
foreach ($this->dynamicParams as $key) {
|
||||
$params[$key] = array_key_exists($key, $params)
|
||||
? ContainerBuilder::literal('isset($this->parameters[?]) \? $this->parameters[?] : ?', [$key, $key, $params[$key]])
|
||||
: ContainerBuilder::literal('$this->parameters[?]', [$key]);
|
||||
}
|
||||
$this->builder->parameters = Helpers::expand($params, $params, true);
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function processExtensions()
|
||||
{
|
||||
$this->config = Helpers::expand(array_diff_key($this->config, self::$reserved), $this->builder->parameters)
|
||||
+ array_intersect_key($this->config, self::$reserved);
|
||||
|
||||
foreach ($first = $this->getExtensions(Extensions\ExtensionsExtension::class) as $name => $extension) {
|
||||
$extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : []);
|
||||
$extension->loadConfiguration();
|
||||
}
|
||||
|
||||
$last = $this->getExtensions(Extensions\InjectExtension::class);
|
||||
$this->extensions = array_merge(array_diff_key($this->extensions, $last), $last);
|
||||
|
||||
$extensions = array_diff_key($this->extensions, $first);
|
||||
foreach (array_intersect_key($extensions, $this->config) as $name => $extension) {
|
||||
$extension->setConfig($this->config[$name] ?: []);
|
||||
}
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->loadConfiguration();
|
||||
}
|
||||
|
||||
if ($extra = array_diff_key($this->extensions, $extensions, $first)) {
|
||||
$extra = implode("', '", array_keys($extra));
|
||||
throw new Nette\DeprecatedException("Extensions '$extra' were added while container was being compiled.");
|
||||
|
||||
} elseif ($extra = key(array_diff_key($this->config, self::$reserved, $this->extensions))) {
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys(self::$reserved + $this->extensions), $extra);
|
||||
throw new Nette\InvalidStateException(
|
||||
"Found section '$extra' in configuration, but corresponding extension is missing"
|
||||
. ($hint ? ", did you mean '$hint'?" : '.')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function processServices()
|
||||
{
|
||||
if (isset($this->config['services'])) {
|
||||
self::loadDefinitions($this->builder, $this->config['services']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
public function generateCode()
|
||||
{
|
||||
if (func_num_args()) {
|
||||
trigger_error(__METHOD__ . ' arguments are deprecated, use Compiler::setClassName().', E_USER_DEPRECATED);
|
||||
$this->className = func_get_arg(0) ?: $this->className;
|
||||
}
|
||||
|
||||
$this->builder->prepareClassList();
|
||||
|
||||
foreach ($this->extensions as $extension) {
|
||||
$extension->beforeCompile();
|
||||
$this->dependencies->add([(new \ReflectionClass($extension))->getFileName()]);
|
||||
}
|
||||
|
||||
$generator = new PhpGenerator($this->builder);
|
||||
$classes = $generator->generate($this->className);
|
||||
$classes[0]->addMethod('initialize');
|
||||
$this->dependencies->add($this->builder->getDependencies());
|
||||
|
||||
foreach ($this->extensions as $extension) {
|
||||
$extension->afterCompile($classes[0]);
|
||||
}
|
||||
return $classes;
|
||||
}
|
||||
|
||||
|
||||
/********************* tools ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Adds service definitions from configuration.
|
||||
* @return void
|
||||
*/
|
||||
public static function loadDefinitions(ContainerBuilder $builder, array $services, $namespace = null)
|
||||
{
|
||||
$depths = [];
|
||||
$sort = false;
|
||||
foreach ($services as $name => $def) {
|
||||
$path = [];
|
||||
while (Config\Helpers::isInheriting($def)) {
|
||||
$sort = true;
|
||||
$path[] = $def;
|
||||
$def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : [];
|
||||
if (in_array($def, $path, true)) {
|
||||
throw new ServiceCreationException("Circular reference detected for service '$name'.");
|
||||
}
|
||||
}
|
||||
$depths[$name] = count($path) + preg_match('#^@[\w\\\\]+\z#', $name);
|
||||
}
|
||||
if ($sort) {
|
||||
@array_multisort($depths, $services); // @ may trigger E_NOTICE: Object of class Nette\DI\Statement could not be converted to int
|
||||
}
|
||||
|
||||
foreach ($services as $name => $def) {
|
||||
if (is_int($name)) {
|
||||
$counter = 1;
|
||||
do {
|
||||
$name = (string) $counter++;
|
||||
} while ($builder->hasDefinition($name));
|
||||
} elseif (preg_match('#^@[\w\\\\]+\z#', $name)) {
|
||||
$name = $builder->getByType(substr($name, 1), true);
|
||||
} elseif ($namespace) {
|
||||
$name = $namespace . '.' . $name;
|
||||
}
|
||||
|
||||
if ($def === false) {
|
||||
$builder->removeDefinition($name);
|
||||
continue;
|
||||
}
|
||||
if ($namespace) {
|
||||
$def = Helpers::prefixServiceName($def, $namespace);
|
||||
}
|
||||
|
||||
$params = $builder->parameters;
|
||||
if (is_array($def) && isset($def['parameters'])) {
|
||||
foreach ((array) $def['parameters'] as $k => $v) {
|
||||
$v = explode(' ', is_int($k) ? $v : $k);
|
||||
$params[end($v)] = $builder::literal('$' . end($v));
|
||||
}
|
||||
}
|
||||
$def = Helpers::expand($def, $params);
|
||||
|
||||
if (is_array($def) && !empty($def['alteration']) && !$builder->hasDefinition($name)) {
|
||||
throw new ServiceCreationException("Service '$name': missing original definition for alteration.");
|
||||
}
|
||||
|
||||
if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
|
||||
if ($parent !== Config\Helpers::OVERWRITE) {
|
||||
trigger_error("Section inheritance $name < $parent is deprecated.", E_USER_DEPRECATED);
|
||||
}
|
||||
$builder->removeDefinition($name);
|
||||
$definition = $builder->addDefinition(
|
||||
$name,
|
||||
$parent === Config\Helpers::OVERWRITE ? null : clone $builder->getDefinition($parent)
|
||||
);
|
||||
} elseif ($builder->hasDefinition($name)) {
|
||||
$definition = $builder->getDefinition($name);
|
||||
} else {
|
||||
$definition = $builder->addDefinition($name);
|
||||
}
|
||||
|
||||
try {
|
||||
static::loadDefinition($definition, $def);
|
||||
} catch (\Exception $e) {
|
||||
throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses single service definition from configuration.
|
||||
* @return void
|
||||
*/
|
||||
public static function loadDefinition(ServiceDefinition $definition, $config)
|
||||
{
|
||||
if ($config === null) {
|
||||
return;
|
||||
|
||||
} elseif (is_string($config) && interface_exists($config)) {
|
||||
$config = ['class' => null, 'implement' => $config];
|
||||
|
||||
} elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) {
|
||||
$config = ['class' => null, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments)];
|
||||
|
||||
} elseif (!is_array($config) || isset($config[0], $config[1])) {
|
||||
$config = ['class' => null, 'factory' => $config];
|
||||
}
|
||||
|
||||
if (array_key_exists('create', $config)) {
|
||||
trigger_error("Key 'create' is deprecated, use 'factory' or 'type' in configuration.", E_USER_DEPRECATED);
|
||||
$config['factory'] = $config['create'];
|
||||
unset($config['create']);
|
||||
}
|
||||
|
||||
$known = ['type', 'class', 'factory', 'arguments', 'setup', 'autowired', 'dynamic', 'imported', 'inject', 'parameters', 'implement', 'run', 'tags', 'alteration'];
|
||||
if ($error = array_diff(array_keys($config), $known)) {
|
||||
$hints = array_filter(array_map(function ($error) use ($known) {
|
||||
return Nette\Utils\ObjectHelpers::getSuggestion($known, $error);
|
||||
}, $error));
|
||||
$hint = $hints ? ", did you mean '" . implode("', '", $hints) . "'?" : '.';
|
||||
throw new Nette\InvalidStateException(sprintf("Unknown key '%s' in definition of service$hint", implode("', '", $error)));
|
||||
}
|
||||
|
||||
$config = Helpers::filterArguments($config);
|
||||
|
||||
if (array_key_exists('class', $config) || array_key_exists('factory', $config)) {
|
||||
$definition->setType(null);
|
||||
$definition->setFactory(null);
|
||||
}
|
||||
|
||||
if (array_key_exists('type', $config)) {
|
||||
Validators::assertField($config, 'type', 'string|null');
|
||||
$definition->setType($config['type']);
|
||||
if (array_key_exists('class', $config)) {
|
||||
throw new Nette\InvalidStateException("Unexpected 'class' when 'type' is used.");
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('class', $config)) {
|
||||
Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null');
|
||||
if (!$config['class'] instanceof Statement) {
|
||||
$definition->setType($config['class']);
|
||||
}
|
||||
$definition->setFactory($config['class']);
|
||||
}
|
||||
|
||||
if (array_key_exists('factory', $config)) {
|
||||
Validators::assertField($config, 'factory', 'callable|Nette\DI\Statement|null');
|
||||
$definition->setFactory($config['factory']);
|
||||
}
|
||||
|
||||
if (array_key_exists('arguments', $config)) {
|
||||
Validators::assertField($config, 'arguments', 'array');
|
||||
$arguments = $config['arguments'];
|
||||
if (!Config\Helpers::takeParent($arguments) && !Nette\Utils\Arrays::isList($arguments) && $definition->getFactory()) {
|
||||
$arguments += $definition->getFactory()->arguments;
|
||||
}
|
||||
$definition->setArguments($arguments);
|
||||
}
|
||||
|
||||
if (isset($config['setup'])) {
|
||||
if (Config\Helpers::takeParent($config['setup'])) {
|
||||
$definition->setSetup([]);
|
||||
}
|
||||
Validators::assertField($config, 'setup', 'list');
|
||||
foreach ($config['setup'] as $id => $setup) {
|
||||
Validators::assert($setup, 'callable|Nette\DI\Statement|array:1', "setup item #$id");
|
||||
if (is_array($setup)) {
|
||||
$setup = new Statement(key($setup), array_values($setup));
|
||||
}
|
||||
$definition->addSetup($setup);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config['parameters'])) {
|
||||
Validators::assertField($config, 'parameters', 'array');
|
||||
$definition->setParameters($config['parameters']);
|
||||
}
|
||||
|
||||
if (isset($config['implement'])) {
|
||||
Validators::assertField($config, 'implement', 'string');
|
||||
$definition->setImplement($config['implement']);
|
||||
$definition->setAutowired(true);
|
||||
}
|
||||
|
||||
if (isset($config['autowired'])) {
|
||||
Validators::assertField($config, 'autowired', 'bool|string|array');
|
||||
$definition->setAutowired($config['autowired']);
|
||||
}
|
||||
|
||||
if (isset($config['imported'])) {
|
||||
$config['dynamic'] = $config['imported'];
|
||||
}
|
||||
|
||||
if (isset($config['dynamic'])) {
|
||||
Validators::assertField($config, 'dynamic', 'bool');
|
||||
$definition->setDynamic($config['dynamic']);
|
||||
}
|
||||
|
||||
if (isset($config['inject'])) {
|
||||
Validators::assertField($config, 'inject', 'bool');
|
||||
$definition->addTag(Extensions\InjectExtension::TAG_INJECT, $config['inject']);
|
||||
}
|
||||
|
||||
if (isset($config['run'])) {
|
||||
trigger_error("Option 'run' is deprecated, use 'run' as tag.", E_USER_DEPRECATED);
|
||||
$config['tags']['run'] = (bool) $config['run'];
|
||||
}
|
||||
|
||||
if (isset($config['tags'])) {
|
||||
Validators::assertField($config, 'tags', 'array');
|
||||
if (Config\Helpers::takeParent($config['tags'])) {
|
||||
$definition->setTags([]);
|
||||
}
|
||||
foreach ($config['tags'] as $tag => $attrs) {
|
||||
if (is_int($tag) && is_string($attrs)) {
|
||||
$definition->addTag($attrs);
|
||||
} else {
|
||||
$definition->addTag($tag, $attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public static function filterArguments(array $args)
|
||||
{
|
||||
return Helpers::filterArguments($args);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public static function parseServices(ContainerBuilder $builder, array $config, $namespace = null)
|
||||
{
|
||||
self::loadDefinitions($builder, isset($config['services']) ? $config['services'] : [], $namespace);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public static function parseService(ServiceDefinition $definition, $config)
|
||||
{
|
||||
self::loadDefinition($definition, $config);
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Configurator compiling extension.
|
||||
*/
|
||||
abstract class CompilerExtension
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Compiler */
|
||||
protected $compiler;
|
||||
|
||||
/** @var string */
|
||||
protected $name;
|
||||
|
||||
/** @var array */
|
||||
protected $config = [];
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setCompiler(Compiler $compiler, $name)
|
||||
{
|
||||
$this->compiler = $compiler;
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setConfig(array $config)
|
||||
{
|
||||
$this->config = $config;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns extension configuration.
|
||||
* @return array
|
||||
*/
|
||||
public function getConfig()
|
||||
{
|
||||
if (func_num_args()) { // deprecated
|
||||
return Config\Helpers::merge($this->config, $this->getContainerBuilder()->expand(func_get_arg(0)));
|
||||
}
|
||||
return $this->config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether $config contains only $expected items and returns combined array.
|
||||
* @return array
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
public function validateConfig(array $expected, array $config = null, $name = null)
|
||||
{
|
||||
if (func_num_args() === 1) {
|
||||
return $this->config = $this->validateConfig($expected, $this->config);
|
||||
}
|
||||
if ($extra = array_diff_key((array) $config, $expected)) {
|
||||
$name = $name ?: $this->name;
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($expected), key($extra));
|
||||
$extra = $hint ? key($extra) : implode("', '{$name} › ", array_keys($extra));
|
||||
throw new Nette\InvalidStateException("Unknown configuration option '{$name} › {$extra}'" . ($hint ? ", did you mean '{$name} › {$hint}'?" : '.'));
|
||||
}
|
||||
return Config\Helpers::merge($config, $expected);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ContainerBuilder
|
||||
*/
|
||||
public function getContainerBuilder()
|
||||
{
|
||||
return $this->compiler->getContainerBuilder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads configuration from file.
|
||||
* @param string file name
|
||||
* @return array
|
||||
*/
|
||||
public function loadFromFile($file)
|
||||
{
|
||||
$loader = new Config\Loader;
|
||||
$res = $loader->load($file);
|
||||
$this->compiler->addDependencies($loader->getDependencies());
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepend extension name to identifier or service name.
|
||||
* @param string
|
||||
* @return string
|
||||
*/
|
||||
public function prefix($id)
|
||||
{
|
||||
return substr_replace($id, $this->name . '.', substr($id, 0, 1) === '@' ? 1 : 0, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Processes configuration data. Intended to be overridden by descendant.
|
||||
* @return void
|
||||
*/
|
||||
public function loadConfiguration()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts DI container before is compiled to PHP class. Intended to be overridden by descendant.
|
||||
* @return void
|
||||
*/
|
||||
public function beforeCompile()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adjusts DI container compiled to PHP class. Intended to be overridden by descendant.
|
||||
* @return void
|
||||
*/
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Config;
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for reading and writing configuration files.
|
||||
*/
|
||||
interface Adapter
|
||||
{
|
||||
|
||||
/**
|
||||
* Reads configuration from file.
|
||||
*/
|
||||
function load(string $file): array;
|
||||
|
||||
/**
|
||||
* Generates configuration string.
|
||||
*/
|
||||
function dump(array $data): string;
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config\Adapters;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Config\Helpers;
|
||||
|
||||
|
||||
/**
|
||||
* Reading and generating INI files.
|
||||
*/
|
||||
class IniAdapter implements Nette\DI\Config\IAdapter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @internal */
|
||||
const INHERITING_SEPARATOR = '<', // child < parent
|
||||
KEY_SEPARATOR = '.', // key nesting key1.key2.key3
|
||||
ESCAPED_KEY_SEPARATOR = '..',
|
||||
RAW_SECTION = '!';
|
||||
|
||||
|
||||
/**
|
||||
* Reads configuration from INI file.
|
||||
* @param string file name
|
||||
* @return array
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
public function load($file)
|
||||
{
|
||||
$ini = @parse_ini_file($file, true); // @ escalated to exception
|
||||
if ($ini === false) {
|
||||
$error = error_get_last();
|
||||
throw new Nette\InvalidStateException("parse_ini_file(): $error[message]");
|
||||
}
|
||||
return $this->process($ini);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
public function process(array $arr)
|
||||
{
|
||||
$data = [];
|
||||
foreach ($arr as $secName => $secData) {
|
||||
if (is_array($secData)) { // is section?
|
||||
if (substr($secName, -1) === self::RAW_SECTION) {
|
||||
$secName = substr($secName, 0, -1);
|
||||
} else { // process key nesting separator (key1.key2.key3)
|
||||
$tmp = [];
|
||||
foreach ($secData as $key => $val) {
|
||||
$cursor = &$tmp;
|
||||
$key = str_replace(self::ESCAPED_KEY_SEPARATOR, "\xFF", $key);
|
||||
foreach (explode(self::KEY_SEPARATOR, $key) as $part) {
|
||||
$part = str_replace("\xFF", self::KEY_SEPARATOR, $part);
|
||||
if (!isset($cursor[$part]) || is_array($cursor[$part])) {
|
||||
$cursor = &$cursor[$part];
|
||||
} else {
|
||||
throw new Nette\InvalidStateException("Invalid key '$key' in section [$secName].");
|
||||
}
|
||||
}
|
||||
$cursor = $val;
|
||||
}
|
||||
$secData = $tmp;
|
||||
}
|
||||
|
||||
$parts = explode(self::INHERITING_SEPARATOR, $secName);
|
||||
if (count($parts) > 1) {
|
||||
$secName = trim($parts[0]);
|
||||
$secData[Helpers::EXTENDS_KEY] = trim($parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
$cursor = &$data; // nesting separator in section name
|
||||
foreach (explode(self::KEY_SEPARATOR, $secName) as $part) {
|
||||
if (!isset($cursor[$part]) || is_array($cursor[$part])) {
|
||||
$cursor = &$cursor[$part];
|
||||
} else {
|
||||
throw new Nette\InvalidStateException("Invalid section [$secName].");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($secData) && is_array($cursor)) {
|
||||
$secData = Helpers::merge($secData, $cursor);
|
||||
}
|
||||
|
||||
$cursor = $secData;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates configuration in INI format.
|
||||
* @return string
|
||||
*/
|
||||
public function dump(array $data)
|
||||
{
|
||||
$output = [];
|
||||
foreach ($data as $name => $secData) {
|
||||
if (!is_array($secData)) {
|
||||
$output = [];
|
||||
self::build($data, $output, '');
|
||||
break;
|
||||
}
|
||||
if ($parent = Helpers::takeParent($secData)) {
|
||||
$output[] = "[$name " . self::INHERITING_SEPARATOR . " $parent]";
|
||||
} else {
|
||||
$output[] = "[$name]";
|
||||
}
|
||||
self::build($secData, $output, '');
|
||||
$output[] = '';
|
||||
}
|
||||
return "; generated by Nette\n\n" . implode(PHP_EOL, $output);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursive builds INI list.
|
||||
* @return void
|
||||
*/
|
||||
private static function build($input, &$output, $prefix)
|
||||
{
|
||||
foreach ($input as $key => $val) {
|
||||
$key = str_replace(self::KEY_SEPARATOR, self::ESCAPED_KEY_SEPARATOR, $key);
|
||||
if (is_array($val)) {
|
||||
self::build($val, $output, $prefix . $key . self::KEY_SEPARATOR);
|
||||
|
||||
} elseif (is_bool($val)) {
|
||||
$output[] = "$prefix$key = " . ($val ? 'true' : 'false');
|
||||
|
||||
} elseif (is_numeric($val)) {
|
||||
$output[] = "$prefix$key = $val";
|
||||
|
||||
} elseif (is_string($val)) {
|
||||
$output[] = "$prefix$key = \"$val\"";
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException(sprintf("The '%s' item must be scalar or array, %s given.", $prefix . $key, gettype($val)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config\Adapters;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Config\Helpers;
|
||||
use Nette\DI\Statement;
|
||||
use Nette\Neon;
|
||||
|
||||
|
||||
/**
|
||||
* Reading and generating NEON files.
|
||||
*/
|
||||
class NeonAdapter implements Nette\DI\Config\IAdapter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @internal */
|
||||
const INHERITING_SEPARATOR = '<', // child < parent
|
||||
PREVENT_MERGING = '!';
|
||||
|
||||
|
||||
/**
|
||||
* Reads configuration from NEON file.
|
||||
* @param string file name
|
||||
* @return array
|
||||
*/
|
||||
public function load($file)
|
||||
{
|
||||
return $this->process((array) Neon\Neon::decode(file_get_contents($file)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws Nette\InvalidStateException
|
||||
*/
|
||||
public function process(array $arr)
|
||||
{
|
||||
$res = [];
|
||||
foreach ($arr as $key => $val) {
|
||||
if (is_string($key) && substr($key, -1) === self::PREVENT_MERGING) {
|
||||
if (!is_array($val) && $val !== null) {
|
||||
throw new Nette\InvalidStateException("Replacing operator is available only for arrays, item '$key' is not array.");
|
||||
}
|
||||
$key = substr($key, 0, -1);
|
||||
$val[Helpers::EXTENDS_KEY] = Helpers::OVERWRITE;
|
||||
|
||||
} elseif (is_string($key) && preg_match('#^(\S+)\s+' . self::INHERITING_SEPARATOR . '\s+(\S+)\z#', $key, $matches)) {
|
||||
if (!is_array($val) && $val !== null) {
|
||||
throw new Nette\InvalidStateException("Inheritance operator is available only for arrays, item '$key' is not array.");
|
||||
}
|
||||
list(, $key, $val[Helpers::EXTENDS_KEY]) = $matches;
|
||||
if (isset($res[$key])) {
|
||||
throw new Nette\InvalidStateException("Duplicated key '$key'.");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_array($val)) {
|
||||
$val = $this->process($val);
|
||||
|
||||
} elseif ($val instanceof Neon\Entity) {
|
||||
if ($val->value === Neon\Neon::CHAIN) {
|
||||
$tmp = null;
|
||||
foreach ($this->process($val->attributes) as $st) {
|
||||
$tmp = new Statement(
|
||||
$tmp === null ? $st->getEntity() : [$tmp, ltrim($st->getEntity(), ':')],
|
||||
$st->arguments
|
||||
);
|
||||
}
|
||||
$val = $tmp;
|
||||
} else {
|
||||
$tmp = $this->process([$val->value]);
|
||||
$val = new Statement($tmp[0], $this->process($val->attributes));
|
||||
}
|
||||
}
|
||||
$res[$key] = $val;
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates configuration in NEON format.
|
||||
* @return string
|
||||
*/
|
||||
public function dump(array $data)
|
||||
{
|
||||
$tmp = [];
|
||||
foreach ($data as $name => $secData) {
|
||||
if ($parent = Helpers::takeParent($secData)) {
|
||||
$name .= ' ' . self::INHERITING_SEPARATOR . ' ' . $parent;
|
||||
}
|
||||
$tmp[$name] = $secData;
|
||||
}
|
||||
array_walk_recursive(
|
||||
$tmp,
|
||||
function (&$val) {
|
||||
if ($val instanceof Statement) {
|
||||
$val = self::statementToEntity($val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return "# generated by Nette\n\n" . Neon\Neon::encode($tmp, Neon\Neon::BLOCK);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Neon\Entity
|
||||
*/
|
||||
private static function statementToEntity(Statement $val)
|
||||
{
|
||||
array_walk_recursive(
|
||||
$val->arguments,
|
||||
function (&$val) {
|
||||
if ($val instanceof Statement) {
|
||||
$val = self::statementToEntity($val);
|
||||
}
|
||||
}
|
||||
);
|
||||
if (is_array($val->getEntity()) && $val->getEntity()[0] instanceof Statement) {
|
||||
return new Neon\Entity(
|
||||
Neon\Neon::CHAIN,
|
||||
[
|
||||
self::statementToEntity($val->getEntity()[0]),
|
||||
new Neon\Entity('::' . $val->getEntity()[1], $val->arguments),
|
||||
]
|
||||
);
|
||||
} else {
|
||||
return new Neon\Entity($val->getEntity(), $val->arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config\Adapters;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Reading and generating PHP files.
|
||||
*/
|
||||
class PhpAdapter implements Nette\DI\Config\IAdapter
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/**
|
||||
* Reads configuration from PHP file.
|
||||
* @param string file name
|
||||
* @return array
|
||||
*/
|
||||
public function load($file)
|
||||
{
|
||||
return require $file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates configuration in PHP format.
|
||||
* @return string
|
||||
*/
|
||||
public function dump(array $data)
|
||||
{
|
||||
return "<?php // generated by Nette \nreturn " . Nette\PhpGenerator\Helpers::dump($data) . ';';
|
||||
}
|
||||
}
|
||||
+254
@@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Config;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Definitions;
|
||||
use Nette\DI\Definitions\Statement;
|
||||
use Nette\Schema\Context;
|
||||
use Nette\Schema\Expect;
|
||||
use Nette\Schema\Schema;
|
||||
|
||||
|
||||
/**
|
||||
* Service configuration schema.
|
||||
*/
|
||||
class DefinitionSchema implements Schema
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var Nette\DI\ContainerBuilder */
|
||||
private $builder;
|
||||
|
||||
|
||||
public function __construct(Nette\DI\ContainerBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
|
||||
public function complete($def, Context $context)
|
||||
{
|
||||
if ($def === [false]) {
|
||||
return (object) $def;
|
||||
}
|
||||
if (Helpers::takeParent($def)) {
|
||||
$def['reset']['all'] = true;
|
||||
}
|
||||
foreach (['arguments', 'setup', 'tags'] as $k) {
|
||||
if (isset($def[$k]) && Helpers::takeParent($def[$k])) {
|
||||
$def['reset'][$k] = true;
|
||||
}
|
||||
}
|
||||
$def = $this->expandParameters($def);
|
||||
$type = $this->sniffType(end($context->path), $def);
|
||||
$def = $this->getSchema($type)->complete($def, $context);
|
||||
if ($def) {
|
||||
$def->defType = $type;
|
||||
}
|
||||
return $def;
|
||||
}
|
||||
|
||||
|
||||
public function merge($def, $base)
|
||||
{
|
||||
if (!empty($def['alteration'])) {
|
||||
unset($def['alteration']);
|
||||
}
|
||||
return Nette\Schema\Helpers::merge($def, $base);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes configuration of service definitions.
|
||||
*/
|
||||
public function normalize($def, Context $context)
|
||||
{
|
||||
if ($def === null || $def === false) {
|
||||
return (array) $def;
|
||||
|
||||
} elseif (is_string($def) && interface_exists($def)) {
|
||||
return ['implement' => $def];
|
||||
|
||||
} elseif ($def instanceof Statement && is_string($def->getEntity()) && interface_exists($def->getEntity())) {
|
||||
$res = ['implement' => $def->getEntity()];
|
||||
if (array_keys($def->arguments) === ['tagged']) {
|
||||
$res += $def->arguments;
|
||||
} elseif (count($def->arguments) > 1) {
|
||||
$res['references'] = $def->arguments;
|
||||
} elseif ($factory = array_shift($def->arguments)) {
|
||||
$res['factory'] = $factory;
|
||||
}
|
||||
return $res;
|
||||
|
||||
} elseif (!is_array($def) || isset($def[0], $def[1])) {
|
||||
return ['factory' => $def];
|
||||
|
||||
} elseif (is_array($def)) {
|
||||
if (isset($def['class']) && !isset($def['type'])) {
|
||||
if ($def['class'] instanceof Statement) {
|
||||
$key = end($context->path);
|
||||
trigger_error("Service '$key': option 'class' should be changed to 'factory'.", E_USER_DEPRECATED);
|
||||
$def['factory'] = $def['class'];
|
||||
unset($def['class']);
|
||||
} elseif (!isset($def['factory']) && !isset($def['dynamic']) && !isset($def['imported'])) {
|
||||
$def['factory'] = $def['class'];
|
||||
unset($def['class']);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (['class' => 'type', 'dynamic' => 'imported'] as $alias => $original) {
|
||||
if (array_key_exists($alias, $def)) {
|
||||
if (array_key_exists($original, $def)) {
|
||||
throw new Nette\DI\InvalidConfigurationException("Options '$alias' and '$original' are aliases, use only '$original'.");
|
||||
}
|
||||
$def[$original] = $def[$alias];
|
||||
unset($def[$alias]);
|
||||
}
|
||||
}
|
||||
|
||||
return $def;
|
||||
|
||||
} else {
|
||||
throw new Nette\DI\InvalidConfigurationException('Unexpected format of service definition');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function completeDefault(Context $context)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
private function sniffType($key, array $def): string
|
||||
{
|
||||
if (is_string($key)) {
|
||||
$name = preg_match('#^@[\w\\\\]+\z#', $key)
|
||||
? $this->builder->getByType(substr($key, 1), false)
|
||||
: $key;
|
||||
|
||||
if ($name && $this->builder->hasDefinition($name)) {
|
||||
return get_class($this->builder->getDefinition($name));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($def['implement'], $def['references']) || isset($def['implement'], $def['tagged'])) {
|
||||
return Definitions\LocatorDefinition::class;
|
||||
|
||||
} elseif (isset($def['implement'])) {
|
||||
return method_exists($def['implement'], 'create')
|
||||
? Definitions\FactoryDefinition::class
|
||||
: Definitions\AccessorDefinition::class;
|
||||
|
||||
} elseif (isset($def['imported'])) {
|
||||
return Definitions\ImportedDefinition::class;
|
||||
|
||||
} else {
|
||||
return Definitions\ServiceDefinition::class;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function expandParameters(array $config): array
|
||||
{
|
||||
$params = $this->builder->parameters;
|
||||
if (isset($config['parameters'])) {
|
||||
foreach ((array) $config['parameters'] as $k => $v) {
|
||||
$v = explode(' ', is_int($k) ? $v : $k);
|
||||
$params[end($v)] = $this->builder::literal('$' . end($v));
|
||||
}
|
||||
}
|
||||
return Nette\DI\Helpers::expand($config, $params);
|
||||
}
|
||||
|
||||
|
||||
private static function getSchema(string $type): Schema
|
||||
{
|
||||
static $cache;
|
||||
$cache = $cache ?: [
|
||||
Definitions\ServiceDefinition::class => self::getServiceSchema(),
|
||||
Definitions\AccessorDefinition::class => self::getAccessorSchema(),
|
||||
Definitions\FactoryDefinition::class => self::getFactorySchema(),
|
||||
Definitions\LocatorDefinition::class => self::getLocatorSchema(),
|
||||
Definitions\ImportedDefinition::class => self::getImportedSchema(),
|
||||
];
|
||||
return $cache[$type];
|
||||
}
|
||||
|
||||
|
||||
private static function getServiceSchema(): Schema
|
||||
{
|
||||
return Expect::structure([
|
||||
'type' => Expect::type('string'),
|
||||
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
|
||||
'arguments' => Expect::array(),
|
||||
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
|
||||
'inject' => Expect::bool(),
|
||||
'autowired' => Expect::type('bool|string|array'),
|
||||
'tags' => Expect::array(),
|
||||
'reset' => Expect::array(),
|
||||
'alteration' => Expect::bool(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private static function getAccessorSchema(): Schema
|
||||
{
|
||||
return Expect::structure([
|
||||
'type' => Expect::string(),
|
||||
'implement' => Expect::string(),
|
||||
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
|
||||
'autowired' => Expect::type('bool|string|array'),
|
||||
'tags' => Expect::array(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private static function getFactorySchema(): Schema
|
||||
{
|
||||
return Expect::structure([
|
||||
'type' => Expect::string(),
|
||||
'factory' => Expect::type('callable|Nette\DI\Definitions\Statement'),
|
||||
'implement' => Expect::string(),
|
||||
'arguments' => Expect::array(),
|
||||
'setup' => Expect::listOf('callable|Nette\DI\Definitions\Statement|array:1'),
|
||||
'parameters' => Expect::array(),
|
||||
'references' => Expect::array(),
|
||||
'tagged' => Expect::string(),
|
||||
'inject' => Expect::bool(),
|
||||
'autowired' => Expect::type('bool|string|array'),
|
||||
'tags' => Expect::array(),
|
||||
'reset' => Expect::array(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private static function getLocatorSchema(): Schema
|
||||
{
|
||||
return Expect::structure([
|
||||
'implement' => Expect::string(),
|
||||
'references' => Expect::array(),
|
||||
'tagged' => Expect::string(),
|
||||
'autowired' => Expect::type('bool|string|array'),
|
||||
'tags' => Expect::array(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
private static function getImportedSchema(): Schema
|
||||
{
|
||||
return Expect::structure([
|
||||
'type' => Expect::string(),
|
||||
'imported' => Expect::bool(),
|
||||
'autowired' => Expect::type('bool|string|array'),
|
||||
'tags' => Expect::array(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration helpers.
|
||||
*/
|
||||
class Helpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
const
|
||||
EXTENDS_KEY = '_extends',
|
||||
OVERWRITE = true;
|
||||
|
||||
|
||||
/**
|
||||
* Merges configurations. Left has higher priority than right one.
|
||||
* @return array|string
|
||||
*/
|
||||
public static function merge($left, $right)
|
||||
{
|
||||
if (is_array($left) && is_array($right)) {
|
||||
foreach ($left as $key => $val) {
|
||||
if (is_int($key)) {
|
||||
$right[] = $val;
|
||||
} else {
|
||||
if (is_array($val) && isset($val[self::EXTENDS_KEY])) {
|
||||
if ($val[self::EXTENDS_KEY] === self::OVERWRITE) {
|
||||
unset($val[self::EXTENDS_KEY]);
|
||||
}
|
||||
} elseif (isset($right[$key])) {
|
||||
$val = static::merge($val, $right[$key]);
|
||||
}
|
||||
$right[$key] = $val;
|
||||
}
|
||||
}
|
||||
return $right;
|
||||
|
||||
} elseif ($left === null && is_array($right)) {
|
||||
return $right;
|
||||
|
||||
} else {
|
||||
return $left;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Finds out and removes information about the parent.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function takeParent(&$data)
|
||||
{
|
||||
if (is_array($data) && isset($data[self::EXTENDS_KEY])) {
|
||||
$parent = $data[self::EXTENDS_KEY];
|
||||
unset($data[self::EXTENDS_KEY]);
|
||||
return $parent;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isOverwriting(&$data)
|
||||
{
|
||||
return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] === self::OVERWRITE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public static function isInheriting(&$data)
|
||||
{
|
||||
return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] !== self::OVERWRITE;
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config;
|
||||
|
||||
|
||||
/**
|
||||
* Adapter for reading and writing configuration files.
|
||||
*/
|
||||
interface IAdapter
|
||||
{
|
||||
|
||||
/**
|
||||
* Reads configuration from file.
|
||||
* @param string file name
|
||||
* @return array
|
||||
*/
|
||||
function load($file);
|
||||
|
||||
/**
|
||||
* Generates configuration string.
|
||||
* @return string
|
||||
*/
|
||||
function dump(array $data);
|
||||
}
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Config;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Validators;
|
||||
|
||||
|
||||
/**
|
||||
* Configuration file loader.
|
||||
*/
|
||||
class Loader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @internal */
|
||||
const INCLUDES_KEY = 'includes';
|
||||
|
||||
private $adapters = [
|
||||
'php' => Adapters\PhpAdapter::class,
|
||||
'ini' => Adapters\IniAdapter::class,
|
||||
'neon' => Adapters\NeonAdapter::class,
|
||||
];
|
||||
|
||||
private $dependencies = [];
|
||||
|
||||
private $loadedFiles = [];
|
||||
|
||||
|
||||
/**
|
||||
* Reads configuration from file.
|
||||
* @param string file name
|
||||
* @param string optional section to load
|
||||
* @return array
|
||||
*/
|
||||
public function load($file, $section = null)
|
||||
{
|
||||
if (!is_file($file) || !is_readable($file)) {
|
||||
throw new Nette\FileNotFoundException("File '$file' is missing or is not readable.");
|
||||
}
|
||||
|
||||
if (isset($this->loadedFiles[$file])) {
|
||||
throw new Nette\InvalidStateException("Recursive included file '$file'");
|
||||
}
|
||||
$this->loadedFiles[$file] = true;
|
||||
|
||||
$this->dependencies[] = $file;
|
||||
$data = $this->getAdapter($file)->load($file);
|
||||
|
||||
if ($section) {
|
||||
if (isset($data[self::INCLUDES_KEY])) {
|
||||
throw new Nette\InvalidStateException("Section 'includes' must be placed under some top section in file '$file'.");
|
||||
}
|
||||
$data = $this->getSection($data, $section, $file);
|
||||
}
|
||||
|
||||
// include child files
|
||||
$merged = [];
|
||||
if (isset($data[self::INCLUDES_KEY])) {
|
||||
Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'");
|
||||
foreach ($data[self::INCLUDES_KEY] as $include) {
|
||||
if (!preg_match('#([a-z]+:)?[/\\\\]#Ai', $include)) {
|
||||
$include = dirname($file) . '/' . $include;
|
||||
}
|
||||
$merged = Helpers::merge($this->load($include), $merged);
|
||||
}
|
||||
}
|
||||
unset($data[self::INCLUDES_KEY], $this->loadedFiles[$file]);
|
||||
|
||||
|
||||
return Helpers::merge($data, $merged);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Save configuration to file.
|
||||
* @param array
|
||||
* @param string file
|
||||
* @return void
|
||||
*/
|
||||
public function save($data, $file)
|
||||
{
|
||||
if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === false) {
|
||||
throw new Nette\IOException("Cannot write file '$file'.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns configuration files.
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
return array_unique($this->dependencies);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Registers adapter for given file extension.
|
||||
* @param string file extension
|
||||
* @param string|IAdapter
|
||||
* @return static
|
||||
*/
|
||||
public function addAdapter($extension, $adapter)
|
||||
{
|
||||
$this->adapters[strtolower($extension)] = $adapter;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/** @return IAdapter */
|
||||
private function getAdapter($file)
|
||||
{
|
||||
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
|
||||
if (!isset($this->adapters[$extension])) {
|
||||
throw new Nette\InvalidArgumentException("Unknown file extension '$file'.");
|
||||
}
|
||||
return is_object($this->adapters[$extension]) ? $this->adapters[$extension] : new $this->adapters[$extension];
|
||||
}
|
||||
|
||||
|
||||
private function getSection(array $data, $key, $file)
|
||||
{
|
||||
Validators::assertField($data, $key, 'array|null', "section '%' in file '$file'");
|
||||
$item = $data[$key];
|
||||
if ($parent = Helpers::takeParent($item)) {
|
||||
$item = Helpers::merge($item, $this->getSection($data, $parent, $file));
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
Vendored
+345
@@ -0,0 +1,345 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* The dependency injection container default implementation.
|
||||
*/
|
||||
class Container
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const TAGS = 'tags';
|
||||
|
||||
const TYPES = 'types';
|
||||
|
||||
const SERVICES = 'services';
|
||||
|
||||
const ALIASES = 'aliases';
|
||||
|
||||
/** @var array user parameters */
|
||||
public $parameters = [];
|
||||
|
||||
/** @var array[] */
|
||||
protected $meta = [];
|
||||
|
||||
/** @var object[] storage for shared objects */
|
||||
private $registry = [];
|
||||
|
||||
/** @var array circular reference detector */
|
||||
private $creating;
|
||||
|
||||
|
||||
public function __construct(array $params = [])
|
||||
{
|
||||
$this->parameters = $params + $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the service to the container.
|
||||
* @param string
|
||||
* @param object
|
||||
* @return static
|
||||
*/
|
||||
public function addService($name, $service)
|
||||
{
|
||||
if (!is_string($name) || !$name) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name)));
|
||||
}
|
||||
$name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name;
|
||||
if (isset($this->registry[$name])) {
|
||||
throw new Nette\InvalidStateException("Service '$name' already exists.");
|
||||
|
||||
} elseif (!is_object($service)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be a object, %s given.", $name, gettype($service)));
|
||||
|
||||
} elseif (isset($this->meta[self::SERVICES][$name]) && !$service instanceof $this->meta[self::SERVICES][$name]) {
|
||||
throw new Nette\InvalidArgumentException(sprintf("Service '%s' must be instance of %s, %s given.", $name, $this->meta[self::SERVICES][$name], get_class($service)));
|
||||
}
|
||||
|
||||
$this->registry[$name] = $service;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the service from the container.
|
||||
* @param string
|
||||
* @return void
|
||||
*/
|
||||
public function removeService($name)
|
||||
{
|
||||
$name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name;
|
||||
unset($this->registry[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service object by name.
|
||||
* @param string
|
||||
* @return object
|
||||
* @throws MissingServiceException
|
||||
*/
|
||||
public function getService($name)
|
||||
{
|
||||
if (!isset($this->registry[$name])) {
|
||||
if (isset($this->meta[self::ALIASES][$name])) {
|
||||
return $this->getService($this->meta[self::ALIASES][$name]);
|
||||
}
|
||||
$this->registry[$name] = $this->createService($name);
|
||||
}
|
||||
return $this->registry[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service type by name.
|
||||
* @param string
|
||||
* @return string
|
||||
* @throws MissingServiceException
|
||||
*/
|
||||
public function getServiceType($name)
|
||||
{
|
||||
if (isset($this->meta[self::ALIASES][$name])) {
|
||||
return $this->getServiceType($this->meta[self::ALIASES][$name]);
|
||||
|
||||
} elseif (isset($this->meta[self::SERVICES][$name])) {
|
||||
return $this->meta[self::SERVICES][$name];
|
||||
|
||||
} else {
|
||||
throw new MissingServiceException("Service '$name' not found.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does the service exist?
|
||||
* @param string service name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasService($name)
|
||||
{
|
||||
$name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name;
|
||||
return isset($this->registry[$name])
|
||||
|| (method_exists($this, $method = self::getMethodName($name))
|
||||
&& (new \ReflectionMethod($this, $method))->getName() === $method);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is the service created?
|
||||
* @param string service name
|
||||
* @return bool
|
||||
*/
|
||||
public function isCreated($name)
|
||||
{
|
||||
if (!$this->hasService($name)) {
|
||||
throw new MissingServiceException("Service '$name' not found.");
|
||||
}
|
||||
$name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name;
|
||||
return isset($this->registry[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of the service.
|
||||
* @param string service name
|
||||
* @return object
|
||||
* @throws MissingServiceException
|
||||
*/
|
||||
public function createService($name, array $args = [])
|
||||
{
|
||||
$name = isset($this->meta[self::ALIASES][$name]) ? $this->meta[self::ALIASES][$name] : $name;
|
||||
$method = self::getMethodName($name);
|
||||
if (isset($this->creating[$name])) {
|
||||
throw new Nette\InvalidStateException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($this->creating))));
|
||||
|
||||
} elseif (!method_exists($this, $method) || (new \ReflectionMethod($this, $method))->getName() !== $method) {
|
||||
throw new MissingServiceException("Service '$name' not found.");
|
||||
}
|
||||
|
||||
try {
|
||||
$this->creating[$name] = true;
|
||||
$service = $this->$method(...$args);
|
||||
|
||||
} finally {
|
||||
unset($this->creating[$name]);
|
||||
}
|
||||
|
||||
if (!is_object($service)) {
|
||||
throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by method $method() is not object.");
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Resolves service by type.
|
||||
* @param string class or interface
|
||||
* @param bool throw exception if service doesn't exist?
|
||||
* @return object|null service
|
||||
* @throws MissingServiceException
|
||||
*/
|
||||
public function getByType($type, $throw = true)
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
if (!empty($this->meta[self::TYPES][$type][true])) {
|
||||
if (count($names = $this->meta[self::TYPES][$type][true]) === 1) {
|
||||
return $this->getService($names[0]);
|
||||
}
|
||||
natsort($names);
|
||||
throw new MissingServiceException("Multiple services of type $type found: " . implode(', ', $names) . '.');
|
||||
|
||||
} elseif ($throw) {
|
||||
throw new MissingServiceException("Service of type $type not found.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service names of the specified type.
|
||||
* @param string
|
||||
* @return string[]
|
||||
*/
|
||||
public function findByType($type)
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
return empty($this->meta[self::TYPES][$type])
|
||||
? []
|
||||
: array_merge(...array_values($this->meta[self::TYPES][$type]));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service names of the specified tag.
|
||||
* @param string
|
||||
* @return array of [service name => tag attributes]
|
||||
*/
|
||||
public function findByTag($tag)
|
||||
{
|
||||
return isset($this->meta[self::TAGS][$tag]) ? $this->meta[self::TAGS][$tag] : [];
|
||||
}
|
||||
|
||||
|
||||
/********************* autowiring ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance using autowiring.
|
||||
* @param string class
|
||||
* @param array arguments
|
||||
* @return object
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public function createInstance($class, array $args = [])
|
||||
{
|
||||
$rc = new \ReflectionClass($class);
|
||||
if (!$rc->isInstantiable()) {
|
||||
throw new ServiceCreationException("Class $class is not instantiable.");
|
||||
|
||||
} elseif ($constructor = $rc->getConstructor()) {
|
||||
return $rc->newInstanceArgs(Helpers::autowireArguments($constructor, $args, $this));
|
||||
|
||||
} elseif ($args) {
|
||||
throw new ServiceCreationException("Unable to pass arguments, class $class has no constructor.");
|
||||
}
|
||||
return new $class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls all methods starting with with "inject" using autowiring.
|
||||
* @param object
|
||||
* @return void
|
||||
*/
|
||||
public function callInjects($service)
|
||||
{
|
||||
Extensions\InjectExtension::callInjects($this, $service);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls method using autowiring.
|
||||
* @return mixed
|
||||
*/
|
||||
public function callMethod(callable $function, array $args = [])
|
||||
{
|
||||
return $function(...Helpers::autowireArguments(Nette\Utils\Callback::toReflection($function), $args, $this));
|
||||
}
|
||||
|
||||
|
||||
/********************* shortcuts ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Expands %placeholders%.
|
||||
* @param mixed
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public function expand($s)
|
||||
{
|
||||
return Helpers::expand($s, $this->parameters);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function &__get($name)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated; use getService().', E_USER_DEPRECATED);
|
||||
$tmp = $this->getService($name);
|
||||
return $tmp;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function __set($name, $service)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated; use addService().', E_USER_DEPRECATED);
|
||||
$this->addService($name, $service);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function __isset($name)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated; use hasService().', E_USER_DEPRECATED);
|
||||
return $this->hasService($name);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function __unset($name)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated; use removeService().', E_USER_DEPRECATED);
|
||||
$this->removeService($name);
|
||||
}
|
||||
|
||||
|
||||
public static function getMethodName($name)
|
||||
{
|
||||
return 'createService'
|
||||
. (preg_match('#^[A-Z]#', $name) ? '__' : '')
|
||||
. str_replace('.', '__', ucfirst($name));
|
||||
}
|
||||
}
|
||||
+880
@@ -0,0 +1,880 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\PhpGenerator\Helpers as PhpHelpers;
|
||||
use Nette\Utils\Reflection;
|
||||
use Nette\Utils\Strings;
|
||||
use Nette\Utils\Validators;
|
||||
use ReflectionClass;
|
||||
|
||||
|
||||
/**
|
||||
* Container builder.
|
||||
*/
|
||||
class ContainerBuilder
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const THIS_SERVICE = 'self',
|
||||
THIS_CONTAINER = 'container';
|
||||
|
||||
/** @var array */
|
||||
public $parameters = [];
|
||||
|
||||
/** @var ServiceDefinition[] */
|
||||
private $definitions = [];
|
||||
|
||||
/** @var array of alias => service */
|
||||
private $aliases = [];
|
||||
|
||||
/** @var array for auto-wiring */
|
||||
private $classList = [];
|
||||
|
||||
/** @var bool */
|
||||
private $classListNeedsRefresh = true;
|
||||
|
||||
/** @var string[] of classes excluded from auto-wiring */
|
||||
private $excludedClasses = [];
|
||||
|
||||
/** @var array */
|
||||
private $dependencies = [];
|
||||
|
||||
/** @var string */
|
||||
private $currentService;
|
||||
|
||||
|
||||
/**
|
||||
* Adds new service definition.
|
||||
* @param string
|
||||
* @return ServiceDefinition
|
||||
*/
|
||||
public function addDefinition($name, ServiceDefinition $definition = null)
|
||||
{
|
||||
$this->classListNeedsRefresh = true;
|
||||
if (!is_string($name) || !$name) { // builder is not ready for falsy names such as '0'
|
||||
throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($name)));
|
||||
}
|
||||
$name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
|
||||
if (isset($this->definitions[$name])) {
|
||||
throw new Nette\InvalidStateException("Service '$name' has already been added.");
|
||||
}
|
||||
if (!$definition) {
|
||||
$definition = new ServiceDefinition;
|
||||
}
|
||||
$definition->setNotifier(function () {
|
||||
$this->classListNeedsRefresh = true;
|
||||
});
|
||||
return $this->definitions[$name] = $definition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the specified service definition.
|
||||
* @param string
|
||||
* @return void
|
||||
*/
|
||||
public function removeDefinition($name)
|
||||
{
|
||||
$this->classListNeedsRefresh = true;
|
||||
$name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
|
||||
unset($this->definitions[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service definition.
|
||||
* @param string
|
||||
* @return ServiceDefinition
|
||||
*/
|
||||
public function getDefinition($name)
|
||||
{
|
||||
$service = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
|
||||
if (!isset($this->definitions[$service])) {
|
||||
throw new MissingServiceException("Service '$name' not found.");
|
||||
}
|
||||
return $this->definitions[$service];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all service definitions.
|
||||
* @return ServiceDefinition[]
|
||||
*/
|
||||
public function getDefinitions()
|
||||
{
|
||||
return $this->definitions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does the service definition or alias exist?
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public function hasDefinition($name)
|
||||
{
|
||||
$name = isset($this->aliases[$name]) ? $this->aliases[$name] : $name;
|
||||
return isset($this->definitions[$name]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @param string
|
||||
*/
|
||||
public function addAlias($alias, $service)
|
||||
{
|
||||
if (!is_string($alias) || !$alias) { // builder is not ready for falsy names such as '0'
|
||||
throw new Nette\InvalidArgumentException(sprintf('Alias name must be a non-empty string, %s given.', gettype($alias)));
|
||||
|
||||
} elseif (!is_string($service) || !$service) { // builder is not ready for falsy names such as '0'
|
||||
throw new Nette\InvalidArgumentException(sprintf('Service name must be a non-empty string, %s given.', gettype($service)));
|
||||
|
||||
} elseif (isset($this->aliases[$alias])) {
|
||||
throw new Nette\InvalidStateException("Alias '$alias' has already been added.");
|
||||
|
||||
} elseif (isset($this->definitions[$alias])) {
|
||||
throw new Nette\InvalidStateException("Service '$alias' has already been added.");
|
||||
}
|
||||
$this->aliases[$alias] = $service;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the specified alias.
|
||||
* @return void
|
||||
*/
|
||||
public function removeAlias($alias)
|
||||
{
|
||||
unset($this->aliases[$alias]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all service aliases.
|
||||
* @return array
|
||||
*/
|
||||
public function getAliases()
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string[]
|
||||
* @return static
|
||||
*/
|
||||
public function addExcludedClasses(array $types)
|
||||
{
|
||||
foreach ($types as $type) {
|
||||
if (class_exists($type) || interface_exists($type)) {
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$this->excludedClasses += class_parents($type) + class_implements($type) + [$type => $type];
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function setClassName($name)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getClassName()
|
||||
{
|
||||
trigger_error(__METHOD__ . ' has been deprecated', E_USER_DEPRECATED);
|
||||
}
|
||||
|
||||
|
||||
/********************* class resolving ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* Resolves service name by type.
|
||||
* @param string class or interface
|
||||
* @param bool throw exception if service doesn't exist?
|
||||
* @return string|null service name or null
|
||||
* @throws ServiceCreationException
|
||||
*/
|
||||
public function getByType($type, $throw = false)
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
|
||||
if (
|
||||
$this->currentService !== null
|
||||
&& is_a($this->definitions[$this->currentService]->getType(), $type, true)
|
||||
) {
|
||||
return $this->currentService;
|
||||
}
|
||||
|
||||
$types = $this->getClassList();
|
||||
if (empty($types[$type][true])) {
|
||||
if ($throw) {
|
||||
throw new MissingServiceException("Service of type '$type' not found.");
|
||||
}
|
||||
return;
|
||||
|
||||
} elseif (count($types[$type][true]) === 1) {
|
||||
return $types[$type][true][0];
|
||||
|
||||
} else {
|
||||
$list = $types[$type][true];
|
||||
natsort($list);
|
||||
$hint = count($list) === 2 && ($tmp = strpos($list[0], '.') xor strpos($list[1], '.'))
|
||||
? '. If you want to overwrite service ' . $list[$tmp ? 0 : 1] . ', give it proper name.'
|
||||
: '';
|
||||
throw new ServiceCreationException("Multiple services of type $type found: " . implode(', ', $list) . $hint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service definition of the specified type.
|
||||
* @param string
|
||||
* @return ServiceDefinition
|
||||
*/
|
||||
public function getDefinitionByType($type)
|
||||
{
|
||||
return $this->getDefinition($this->getByType($type, true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service names and definitions of the specified type.
|
||||
* @param string
|
||||
* @return ServiceDefinition[]
|
||||
*/
|
||||
public function findByType($type)
|
||||
{
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$found = [];
|
||||
$types = $this->getClassList();
|
||||
if (!empty($types[$type])) {
|
||||
foreach (array_merge(...array_values($types[$type])) as $name) {
|
||||
$found[$name] = $this->definitions[$name];
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets the service objects of the specified tag.
|
||||
* @param string
|
||||
* @return array of [service name => tag attributes]
|
||||
*/
|
||||
public function findByTag($tag)
|
||||
{
|
||||
$found = [];
|
||||
foreach ($this->definitions as $name => $def) {
|
||||
if (($tmp = $def->getTag($tag)) !== null) {
|
||||
$found[$name] = $tmp;
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function getClassList()
|
||||
{
|
||||
if ($this->classList !== false && $this->classListNeedsRefresh) {
|
||||
$this->prepareClassList();
|
||||
$this->classListNeedsRefresh = false;
|
||||
}
|
||||
return $this->classList ?: [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates $dependencies, $classList and normalizes class names.
|
||||
* @return void
|
||||
* @internal
|
||||
*/
|
||||
public function prepareClassList()
|
||||
{
|
||||
unset($this->definitions[self::THIS_CONTAINER]);
|
||||
$this->addDefinition(self::THIS_CONTAINER)->setType(Container::class);
|
||||
|
||||
$this->classList = false;
|
||||
|
||||
foreach ($this->definitions as $name => $def) {
|
||||
// prepare generated factories
|
||||
if ($def->getImplement()) {
|
||||
$this->resolveImplement($def, $name);
|
||||
}
|
||||
|
||||
if ($def->isDynamic()) {
|
||||
if (!$def->getType()) {
|
||||
throw new ServiceCreationException("Type is missing in definition of service '$name'.");
|
||||
}
|
||||
$def->setFactory(null);
|
||||
continue;
|
||||
}
|
||||
|
||||
// complete class-factory pairs
|
||||
if (!$def->getEntity()) {
|
||||
if (!$def->getType()) {
|
||||
throw new ServiceCreationException("Factory and type are missing in definition of service '$name'.");
|
||||
}
|
||||
$def->setFactory($def->getType(), ($factory = $def->getFactory()) ? $factory->arguments : []);
|
||||
}
|
||||
|
||||
// auto-disable autowiring for aliases
|
||||
if (
|
||||
$def->getAutowired() === true
|
||||
&& ($alias = $this->getServiceName($def->getFactory()->getEntity()))
|
||||
&& (!$def->getImplement() || (!Strings::contains($alias, '\\') && $this->definitions[$alias]->getImplement()))
|
||||
) {
|
||||
$def->setAutowired(false);
|
||||
}
|
||||
}
|
||||
|
||||
// resolve and check classes
|
||||
foreach ($this->definitions as $name => $def) {
|
||||
$this->resolveServiceType($name);
|
||||
}
|
||||
|
||||
// build auto-wiring list
|
||||
$this->classList = $preferred = [];
|
||||
foreach ($this->definitions as $name => $def) {
|
||||
if ($type = $def->getImplement() ?: $def->getType()) {
|
||||
$defAutowired = $def->getAutowired();
|
||||
if (is_array($defAutowired)) {
|
||||
foreach ($defAutowired as $k => $autowiredType) {
|
||||
if ($autowiredType === self::THIS_SERVICE) {
|
||||
$defAutowired[$k] = $type;
|
||||
} elseif (!is_a($type, $autowiredType, true)) {
|
||||
throw new ServiceCreationException("Incompatible class $autowiredType in autowiring definition of service '$name'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (class_parents($type) + class_implements($type) + [$type] as $parent) {
|
||||
$autowired = $defAutowired && empty($this->excludedClasses[$parent]);
|
||||
if ($autowired && is_array($defAutowired)) {
|
||||
$autowired = false;
|
||||
foreach ($defAutowired as $autowiredType) {
|
||||
if (is_a($parent, $autowiredType, true)) {
|
||||
if (empty($preferred[$parent]) && isset($this->classList[$parent][true])) {
|
||||
$this->classList[$parent][false] = array_merge(...$this->classList[$parent]);
|
||||
$this->classList[$parent][true] = [];
|
||||
}
|
||||
$preferred[$parent] = $autowired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} elseif (isset($preferred[$parent])) {
|
||||
$autowired = false;
|
||||
}
|
||||
$this->classList[$parent][$autowired][] = (string) $name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function resolveImplement(ServiceDefinition $def, $name)
|
||||
{
|
||||
$interface = $def->getImplement();
|
||||
if (!interface_exists($interface)) {
|
||||
throw new ServiceCreationException("Interface $interface used in service '$name' not found.");
|
||||
}
|
||||
$interface = Helpers::normalizeClass($interface);
|
||||
$def->setImplement($interface);
|
||||
|
||||
$rc = new ReflectionClass($interface);
|
||||
$this->addDependency($rc);
|
||||
$method = $rc->hasMethod('create')
|
||||
? $rc->getMethod('create')
|
||||
: ($rc->hasMethod('get') ? $rc->getMethod('get') : null);
|
||||
|
||||
if (count($rc->getMethods()) !== 1 || !$method || $method->isStatic()) {
|
||||
throw new ServiceCreationException("Interface $interface used in service '$name' must have just one non-static method create() or get().");
|
||||
}
|
||||
$def->setImplementMode($rc->hasMethod('create') ? $def::IMPLEMENT_MODE_CREATE : $def::IMPLEMENT_MODE_GET);
|
||||
$methodName = Reflection::toString($method) . '()';
|
||||
|
||||
if (!$def->getType() && !$def->getEntity()) {
|
||||
$returnType = Helpers::getReturnType($method);
|
||||
if (!$returnType) {
|
||||
throw new ServiceCreationException("Method $methodName used in service '$name' has not return type hint or annotation @return.");
|
||||
} elseif (!class_exists($returnType)) {
|
||||
throw new ServiceCreationException("Check a type hint or annotation @return of the $methodName method used in service '$name', class '$returnType' cannot be found.");
|
||||
}
|
||||
$def->setType($returnType);
|
||||
}
|
||||
|
||||
if ($rc->hasMethod('get')) {
|
||||
if ($method->getParameters()) {
|
||||
throw new ServiceCreationException("Method $methodName used in service '$name' must have no arguments.");
|
||||
} elseif ($def->getSetup()) {
|
||||
throw new ServiceCreationException("Service accessor '$name' must have no setup.");
|
||||
}
|
||||
if (!$def->getEntity()) {
|
||||
$def->setFactory('@\\' . ltrim($def->getType(), '\\'));
|
||||
} elseif (!$this->getServiceName($def->getFactory()->getEntity())) {
|
||||
throw new ServiceCreationException("Invalid factory in service '$name' definition.");
|
||||
}
|
||||
}
|
||||
|
||||
if (!$def->parameters) {
|
||||
$ctorParams = [];
|
||||
if (!$def->getEntity()) {
|
||||
$def->setFactory($def->getType(), $def->getFactory() ? $def->getFactory()->arguments : []);
|
||||
}
|
||||
if (
|
||||
($class = $this->resolveEntityType($def->getFactory(), [$name => 1]))
|
||||
&& ($ctor = (new ReflectionClass($class))->getConstructor())
|
||||
) {
|
||||
foreach ($ctor->getParameters() as $param) {
|
||||
$ctorParams[$param->getName()] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$hint = Reflection::getParameterType($param);
|
||||
if (isset($ctorParams[$param->getName()])) {
|
||||
$arg = $ctorParams[$param->getName()];
|
||||
$argHint = Reflection::getParameterType($arg);
|
||||
if ($hint !== $argHint && !is_a($hint, $argHint, true)) {
|
||||
throw new ServiceCreationException("Type hint for \${$param->getName()} in $methodName doesn't match type hint in $class constructor.");
|
||||
}
|
||||
$def->getFactory()->arguments[$arg->getPosition()] = self::literal('$' . $arg->getName());
|
||||
} elseif (!$def->getSetup()) {
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($ctorParams), $param->getName());
|
||||
throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $methodName" . ($hint ? ", did you mean \${$hint}?" : '.'));
|
||||
}
|
||||
$nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null);
|
||||
$paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$def->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
|
||||
} else {
|
||||
$def->parameters[] = $paramDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @return string|null */
|
||||
private function resolveServiceType($name, $recursive = [])
|
||||
{
|
||||
if (isset($recursive[$name])) {
|
||||
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', array_keys($recursive))));
|
||||
}
|
||||
$recursive[$name] = true;
|
||||
|
||||
$def = $this->definitions[$name];
|
||||
$factoryClass = $def->getFactory() ? $this->resolveEntityType($def->getFactory()->getEntity(), $recursive) : null; // call always to check entities
|
||||
if ($type = $def->getType() ?: $factoryClass) {
|
||||
if (!class_exists($type) && !interface_exists($type)) {
|
||||
throw new ServiceCreationException("Class or interface '$type' used in service '$name' not found.");
|
||||
}
|
||||
$type = Helpers::normalizeClass($type);
|
||||
$def->setType($type);
|
||||
if (count($recursive) === 1) {
|
||||
$this->addDependency(new ReflectionClass($factoryClass ?: $type));
|
||||
}
|
||||
|
||||
} elseif ($def->getAutowired()) {
|
||||
throw new ServiceCreationException("Unknown type of service '$name', declare return type of factory method (for PHP 5 use annotation @return)");
|
||||
}
|
||||
return $type;
|
||||
}
|
||||
|
||||
|
||||
/** @return string|null */
|
||||
private function resolveEntityType($entity, $recursive = [])
|
||||
{
|
||||
$entity = $this->normalizeEntity($entity instanceof Statement ? $entity->getEntity() : $entity);
|
||||
$serviceName = current(array_slice(array_keys($recursive), -1));
|
||||
|
||||
if (is_array($entity)) {
|
||||
if (($service = $this->getServiceName($entity[0])) || $entity[0] instanceof Statement) {
|
||||
$entity[0] = $this->resolveEntityType($entity[0], $recursive);
|
||||
if (!$entity[0]) {
|
||||
return;
|
||||
} elseif (isset($this->definitions[$service]) && $this->definitions[$service]->getImplement()) { // @Implement::create
|
||||
return $entity[1] === 'create' ? $this->resolveServiceType($service, $recursive) : null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
|
||||
$refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : null;
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
|
||||
if (isset($e) || ($refClass && (!$reflection->isPublic()
|
||||
|| ($refClass->isTrait() && !$reflection->isStatic())
|
||||
))) {
|
||||
throw new ServiceCreationException(sprintf("Method %s() used in service '%s' is not callable.", Nette\Utils\Callback::toString($entity), $serviceName));
|
||||
}
|
||||
$this->addDependency($reflection);
|
||||
|
||||
$type = Helpers::getReturnType($reflection);
|
||||
if ($type && !class_exists($type) && !interface_exists($type)) {
|
||||
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() used in service '%s' correct?", $type, Nette\Utils\Callback::toString($entity), $serviceName));
|
||||
}
|
||||
return $type;
|
||||
|
||||
} elseif ($service = $this->getServiceName($entity)) { // alias or factory
|
||||
if (Strings::contains($service, '\\')) { // @\Class
|
||||
return $service;
|
||||
}
|
||||
return $this->definitions[$service]->getImplement()
|
||||
?: $this->definitions[$service]->getType()
|
||||
?: $this->resolveServiceType($service, $recursive);
|
||||
|
||||
} elseif (is_string($entity)) { // class
|
||||
if (!class_exists($entity)) {
|
||||
throw new ServiceCreationException("Class $entity used in service '$serviceName' not found.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function complete()
|
||||
{
|
||||
$this->prepareClassList();
|
||||
|
||||
foreach ($this->definitions as $name => $def) {
|
||||
if ($def->isDynamic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->currentService = null;
|
||||
$entity = $def->getFactory()->getEntity();
|
||||
$serviceRef = $this->getServiceName($entity);
|
||||
$factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
|
||||
? new Statement(['@' . self::THIS_CONTAINER, 'getService'], [$serviceRef])
|
||||
: $def->getFactory();
|
||||
|
||||
try {
|
||||
$def->setFactory($this->completeStatement($factory));
|
||||
$this->classListNeedsRefresh = false;
|
||||
|
||||
$this->currentService = $name;
|
||||
$setups = $def->getSetup();
|
||||
foreach ($setups as &$setup) {
|
||||
if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === false) { // auto-prepend @self
|
||||
$setup = new Statement(['@' . $name, $setup->getEntity()], $setup->arguments);
|
||||
}
|
||||
$setup = $this->completeStatement($setup);
|
||||
}
|
||||
$def->setSetup($setups);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$type = $def->getType();
|
||||
if (!$type) {
|
||||
$message = "Service '$name': " . $e->getMessage();
|
||||
} elseif (ctype_digit($name)) {
|
||||
$message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage());
|
||||
} else {
|
||||
$message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage());
|
||||
}
|
||||
throw $e instanceof ServiceCreationException
|
||||
? $e->setMessage($message)
|
||||
: new ServiceCreationException($message, 0, $e);
|
||||
|
||||
} finally {
|
||||
$this->currentService = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Statement
|
||||
*/
|
||||
public function completeStatement(Statement $statement)
|
||||
{
|
||||
$entity = $this->normalizeEntity($statement->getEntity());
|
||||
$arguments = $statement->arguments;
|
||||
|
||||
if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
|
||||
|
||||
} elseif ($service = $this->getServiceName($entity)) { // factory calling
|
||||
$params = [];
|
||||
foreach ($this->definitions[$service]->parameters as $k => $v) {
|
||||
$params[] = preg_replace('#\w+\z#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
|
||||
}
|
||||
$rm = new \ReflectionFunction(eval('return function(' . implode(', ', $params) . ') {};'));
|
||||
$arguments = Helpers::autowireArguments($rm, $arguments, $this);
|
||||
$entity = '@' . $service;
|
||||
|
||||
} elseif ($entity === 'not') { // operator
|
||||
|
||||
} elseif (is_string($entity)) { // class name
|
||||
if (!class_exists($entity)) {
|
||||
throw new ServiceCreationException("Class $entity not found.");
|
||||
} elseif ((new ReflectionClass($entity))->isAbstract()) {
|
||||
throw new ServiceCreationException("Class $entity is abstract.");
|
||||
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
|
||||
$visibility = $rm->isProtected() ? 'protected' : 'private';
|
||||
throw new ServiceCreationException("Class $entity has $visibility constructor.");
|
||||
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
|
||||
$this->addDependency($constructor);
|
||||
$arguments = Helpers::autowireArguments($constructor, $arguments, $this);
|
||||
} elseif ($arguments) {
|
||||
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
|
||||
}
|
||||
|
||||
} elseif (!Nette\Utils\Arrays::isList($entity) || count($entity) !== 2) {
|
||||
throw new ServiceCreationException(sprintf('Expected class, method or property, %s given.', PhpHelpers::dump($entity)));
|
||||
|
||||
} elseif (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?\z#', $entity[1])) {
|
||||
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
|
||||
|
||||
} elseif ($entity[0] === '') { // globalFunc
|
||||
if (!Nette\Utils\Arrays::isList($arguments)) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
|
||||
} elseif (!function_exists($entity[1])) {
|
||||
throw new ServiceCreationException("Function $entity[1] doesn't exist.");
|
||||
}
|
||||
|
||||
$rf = new \ReflectionFunction($entity[1]);
|
||||
$this->addDependency($rf);
|
||||
$arguments = Helpers::autowireArguments($rf, $arguments, $this);
|
||||
|
||||
} else {
|
||||
if ($entity[0] instanceof Statement) {
|
||||
$entity[0] = $this->completeStatement($entity[0]);
|
||||
} elseif ($service = $this->getServiceName($entity[0])) { // service method
|
||||
$entity[0] = '@' . $service;
|
||||
}
|
||||
|
||||
if ($entity[1][0] === '$') { // property getter, setter or appender
|
||||
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
|
||||
if (!$arguments && substr($entity[1], -2) === '[]') {
|
||||
throw new ServiceCreationException("Missing argument for $entity[1].");
|
||||
}
|
||||
} elseif (
|
||||
$type = empty($service) || $entity[1] === 'create'
|
||||
? $this->resolveEntityType($entity[0])
|
||||
: $this->definitions[$service]->getType()
|
||||
) {
|
||||
$arguments = $this->autowireArguments($type, $entity[1], $arguments);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
array_walk_recursive($arguments, function (&$val) {
|
||||
if ($val instanceof Statement) {
|
||||
$val = $this->completeStatement($val);
|
||||
|
||||
} elseif ($val === $this) {
|
||||
trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
|
||||
$val = self::literal('$this');
|
||||
|
||||
} elseif ($val instanceof ServiceDefinition) {
|
||||
$val = '@' . current(array_keys($this->getDefinitions(), $val, true));
|
||||
|
||||
} elseif (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
|
||||
$pair = explode('::', $val, 2);
|
||||
$name = $this->getServiceName($pair[0]);
|
||||
if (!isset($pair[1])) { // @service
|
||||
$val = '@' . $name;
|
||||
} elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
|
||||
$val = self::literal($this->getDefinition($name)->getType() . '::' . $pair[1]);
|
||||
} else { // @service::property
|
||||
$val = new Statement(['@' . $name, '$' . $pair[1]]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (ServiceCreationException $e) {
|
||||
if ((is_string($entity) || is_array($entity)) && !strpos($e->getMessage(), ' (used in')) {
|
||||
$desc = is_string($entity)
|
||||
? $entity . '::__construct()'
|
||||
: (is_string($entity[0]) ? ($entity[0] . '::') : '')
|
||||
. $entity[1] . (strpos($entity[1], '$') === false ? '()' : '');
|
||||
$e->setMessage($e->getMessage() . " (used in $desc)");
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return new Statement($entity, $arguments);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds item to the list of dependencies.
|
||||
* @param ReflectionClass|\ReflectionFunctionAbstract|string
|
||||
* @return static
|
||||
* @internal
|
||||
*/
|
||||
public function addDependency($dep)
|
||||
{
|
||||
$this->dependencies[] = $dep;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list of dependencies.
|
||||
* @return array
|
||||
*/
|
||||
public function getDependencies()
|
||||
{
|
||||
return $this->dependencies;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expands %placeholders% in strings.
|
||||
* @return mixed
|
||||
* @deprecated
|
||||
*/
|
||||
public function expand($value)
|
||||
{
|
||||
return Helpers::expand($value, $this->parameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Nette\PhpGenerator\PhpLiteral
|
||||
*/
|
||||
public static function literal($code, array $args = null)
|
||||
{
|
||||
return new Nette\PhpGenerator\PhpLiteral($args === null ? $code : PhpHelpers::formatArgs($code, $args));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|array Class, @service, [Class, member], [@service, member], [, globalFunc], [Statement, member]
|
||||
* @internal
|
||||
*/
|
||||
public function normalizeEntity($entity)
|
||||
{
|
||||
if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
|
||||
$entity = explode('::', $entity);
|
||||
}
|
||||
|
||||
if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
|
||||
$entity[0] = '@' . current(array_keys($this->definitions, $entity[0], true));
|
||||
|
||||
} elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
|
||||
$entity = '@' . current(array_keys($this->definitions, $entity, true));
|
||||
|
||||
} elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
|
||||
trigger_error("Replace object ContainerBuilder in Statement entity with '@container'.", E_USER_DEPRECATED);
|
||||
$entity[0] = '@' . self::THIS_CONTAINER;
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts @service or @\Class -> service name and checks its existence.
|
||||
* @return string of false, if argument is not service name
|
||||
* @internal
|
||||
*/
|
||||
public function getServiceName($arg)
|
||||
{
|
||||
if (!is_string($arg) || !preg_match('#^@[\w\\\\.][^:]*\z#', $arg)) {
|
||||
return false;
|
||||
}
|
||||
$service = substr($arg, 1);
|
||||
if ($service === self::THIS_SERVICE) {
|
||||
$service = $this->currentService;
|
||||
}
|
||||
if (Strings::contains($service, '\\')) {
|
||||
if ($this->classList === false) { // may be disabled by prepareClassList
|
||||
return $service;
|
||||
}
|
||||
$res = $this->getByType($service);
|
||||
if (!$res) {
|
||||
throw new ServiceCreationException("Reference to missing service of type $service.");
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
$service = isset($this->aliases[$service]) ? $this->aliases[$service] : $service;
|
||||
if (!isset($this->definitions[$service])) {
|
||||
throw new ServiceCreationException("Reference to missing service '$service'.");
|
||||
}
|
||||
return $service;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a list of arguments using autowiring.
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public function autowireArguments($class, $method, array $arguments)
|
||||
{
|
||||
$rc = new ReflectionClass($class);
|
||||
if (!$rc->hasMethod($method)) {
|
||||
if (!Nette\Utils\Arrays::isList($arguments)) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
|
||||
}
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
$rm = $rc->getMethod($method);
|
||||
if (!$rm->isPublic()) {
|
||||
throw new ServiceCreationException("$class::$method() is not callable.");
|
||||
}
|
||||
$this->addDependency($rm);
|
||||
return Helpers::autowireArguments($rm, $arguments, $this);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function generateClasses($className = 'Container', $parentName = null)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
|
||||
return (new PhpGenerator($this))->generate($className);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function formatStatement(Statement $statement)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
|
||||
return (new PhpGenerator($this))->formatStatement($statement);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function formatPhp($statement, $args)
|
||||
{
|
||||
array_walk_recursive($args, function (&$val) {
|
||||
if ($val instanceof Statement) {
|
||||
$val = $this->completeStatement($val);
|
||||
|
||||
} elseif ($val === $this) {
|
||||
trigger_error("Replace object ContainerBuilder in Statement arguments with '@container'.", E_USER_DEPRECATED);
|
||||
$val = self::literal('$this');
|
||||
|
||||
} elseif ($val instanceof ServiceDefinition) {
|
||||
$val = '@' . current(array_keys($this->getDefinitions(), $val, true));
|
||||
}
|
||||
});
|
||||
return (new PhpGenerator($this))->formatPhp($statement, $args);
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* DI container loader.
|
||||
*/
|
||||
class ContainerLoader
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var bool */
|
||||
private $autoRebuild = false;
|
||||
|
||||
/** @var string */
|
||||
private $tempDirectory;
|
||||
|
||||
|
||||
public function __construct($tempDirectory, $autoRebuild = false)
|
||||
{
|
||||
$this->tempDirectory = $tempDirectory;
|
||||
$this->autoRebuild = $autoRebuild;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param callable function (Nette\DI\Compiler $compiler): string|null
|
||||
* @param mixed
|
||||
* @return string
|
||||
*/
|
||||
public function load($generator, $key = null)
|
||||
{
|
||||
if (!is_callable($generator)) { // back compatiblity
|
||||
trigger_error(__METHOD__ . ': order of arguments has been swapped.', E_USER_DEPRECATED);
|
||||
list($generator, $key) = [$key, $generator];
|
||||
}
|
||||
$class = $this->getClassName($key);
|
||||
if (!class_exists($class, false)) {
|
||||
$this->loadFile($class, $generator);
|
||||
}
|
||||
return $class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getClassName($key)
|
||||
{
|
||||
return 'Container_' . substr(md5(serialize($key)), 0, 10);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
private function loadFile($class, $generator)
|
||||
{
|
||||
$file = "$this->tempDirectory/$class.php";
|
||||
if (!$this->isExpired($file) && (@include $file) !== false) { // @ file may not exist
|
||||
return;
|
||||
}
|
||||
|
||||
Nette\Utils\FileSystem::createDir($this->tempDirectory);
|
||||
|
||||
$handle = @fopen("$file.lock", 'c+'); // @ is escalated to exception
|
||||
if (!$handle) {
|
||||
throw new Nette\IOException("Unable to create file '$file.lock'. " . error_get_last()['message']);
|
||||
} elseif (!@flock($handle, LOCK_EX)) { // @ is escalated to exception
|
||||
throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'. " . error_get_last()['message']);
|
||||
}
|
||||
|
||||
if (!is_file($file) || $this->isExpired($file, $updatedMeta)) {
|
||||
if (isset($updatedMeta)) {
|
||||
$toWrite["$file.meta"] = $updatedMeta;
|
||||
} else {
|
||||
list($toWrite[$file], $toWrite["$file.meta"]) = $this->generate($class, $generator);
|
||||
}
|
||||
|
||||
foreach ($toWrite as $name => $content) {
|
||||
if (file_put_contents("$name.tmp", $content) !== strlen($content) || !rename("$name.tmp", $name)) {
|
||||
@unlink("$name.tmp"); // @ - file may not exist
|
||||
throw new Nette\IOException("Unable to create file '$name'.");
|
||||
} elseif (function_exists('opcache_invalidate')) {
|
||||
@opcache_invalidate($name, true); // @ can be restricted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((@include $file) === false) { // @ - error escalated to exception
|
||||
throw new Nette\IOException("Unable to include '$file'.");
|
||||
}
|
||||
flock($handle, LOCK_UN);
|
||||
}
|
||||
|
||||
|
||||
private function isExpired($file, &$updatedMeta = null)
|
||||
{
|
||||
if ($this->autoRebuild) {
|
||||
$meta = @unserialize((string) file_get_contents("$file.meta")); // @ - file may not exist
|
||||
$orig = $meta[2];
|
||||
return empty($meta[0])
|
||||
|| DependencyChecker::isExpired(...$meta)
|
||||
|| ($orig !== $meta[2] && $updatedMeta = serialize($meta));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array of (code, file[])
|
||||
*/
|
||||
protected function generate($class, $generator)
|
||||
{
|
||||
$compiler = new Compiler;
|
||||
$compiler->setClassName($class);
|
||||
$code = call_user_func_array($generator, [&$compiler]) ?: $compiler->compile();
|
||||
return [
|
||||
"<?php\n$code",
|
||||
serialize($compiler->exportDependencies()),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\ServiceCreationException;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* Accessor definition.
|
||||
*/
|
||||
final class AccessorDefinition extends Definition
|
||||
{
|
||||
private const METHOD_GET = 'get';
|
||||
|
||||
/** @var Reference|null */
|
||||
private $reference;
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setImplement(string $type)
|
||||
{
|
||||
if (!interface_exists($type)) {
|
||||
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
|
||||
}
|
||||
$rc = new \ReflectionClass($type);
|
||||
|
||||
$method = $rc->getMethods()[0] ?? null;
|
||||
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_GET || count($rc->getMethods()) > 1) {
|
||||
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method get().");
|
||||
} elseif ($method->getNumberOfParameters()) {
|
||||
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Method $type::get() must have no parameters.");
|
||||
}
|
||||
return parent::setType($type);
|
||||
}
|
||||
|
||||
|
||||
public function getImplement(): ?string
|
||||
{
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|Reference $reference
|
||||
* @return static
|
||||
*/
|
||||
public function setReference($reference)
|
||||
{
|
||||
if ($reference instanceof Reference) {
|
||||
$this->reference = $reference;
|
||||
} else {
|
||||
$this->reference = substr($reference, 0, 1) === '@'
|
||||
? new Reference(substr($reference, 1))
|
||||
: Reference::fromType($reference);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getReference(): ?Reference
|
||||
{
|
||||
return $this->reference;
|
||||
}
|
||||
|
||||
|
||||
public function resolveType(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function complete(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
if (!$this->reference) {
|
||||
$interface = $this->getType();
|
||||
$method = new \ReflectionMethod($interface, self::METHOD_GET);
|
||||
$returnType = Nette\DI\Helpers::getReturnType($method);
|
||||
|
||||
if (!$returnType) {
|
||||
throw new ServiceCreationException("Method $interface::get() has not return type hint or annotation @return.");
|
||||
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
|
||||
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::get() method, class '$returnType' cannot be found.");
|
||||
}
|
||||
$this->setReference($returnType);
|
||||
}
|
||||
|
||||
$this->reference = $resolver->normalizeReference($this->reference);
|
||||
}
|
||||
|
||||
|
||||
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
|
||||
{
|
||||
$class = (new Nette\PhpGenerator\ClassType)
|
||||
->addImplement($this->getType());
|
||||
|
||||
$class->addProperty('container')
|
||||
->setVisibility('private');
|
||||
|
||||
$class->addMethod('__construct')
|
||||
->addBody('$this->container = $container;')
|
||||
->addParameter('container')
|
||||
->setTypeHint($generator->getClassName());
|
||||
|
||||
$rm = new \ReflectionMethod($this->getType(), self::METHOD_GET);
|
||||
|
||||
$class->addMethod(self::METHOD_GET)
|
||||
->setBody('return $this->container->getService(?);', [$this->reference->getValue()])
|
||||
->setReturnType(Reflection::getReturnType($rm));
|
||||
|
||||
$method->setBody('return new class ($this) ' . $class . ';');
|
||||
}
|
||||
}
|
||||
+227
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Definition used by ContainerBuilder.
|
||||
*/
|
||||
abstract class Definition
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var string */
|
||||
private $name;
|
||||
|
||||
/** @var string|null class or interface name */
|
||||
private $type;
|
||||
|
||||
/** @var array */
|
||||
private $tags = [];
|
||||
|
||||
/** @var bool|string[] */
|
||||
private $autowired = true;
|
||||
|
||||
/** @var callable|null */
|
||||
private $notifier;
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
* @internal This is managed by ContainerBuilder and should not be called by user
|
||||
*/
|
||||
final public function setName(string $name)
|
||||
{
|
||||
if ($this->name) {
|
||||
throw new Nette\InvalidStateException('Name already has been set.');
|
||||
}
|
||||
$this->name = $name;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
final public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
protected function setType(?string $type)
|
||||
{
|
||||
if ($this->autowired && $this->notifier && $this->type !== $type) {
|
||||
($this->notifier)();
|
||||
}
|
||||
if ($type === null) {
|
||||
$this->type = null;
|
||||
} elseif (!class_exists($type) && !interface_exists($type)) {
|
||||
throw new Nette\InvalidArgumentException("Service '$this->name': Class or interface '$type' not found.");
|
||||
} else {
|
||||
$this->type = Nette\DI\Helpers::normalizeClass($type);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
final public function getType(): ?string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
final public function setTags(array $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
final public function getTags(): array
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param mixed $attr
|
||||
* @return static
|
||||
*/
|
||||
final public function addTag(string $tag, $attr = true)
|
||||
{
|
||||
$this->tags[$tag] = $attr;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
final public function getTag(string $tag)
|
||||
{
|
||||
return $this->tags[$tag] ?? null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool|string|string[] $state
|
||||
* @return static
|
||||
*/
|
||||
final public function setAutowired($state = true)
|
||||
{
|
||||
if ($this->notifier && $this->autowired !== $state) {
|
||||
($this->notifier)();
|
||||
}
|
||||
$this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool|string[]
|
||||
*/
|
||||
final public function getAutowired()
|
||||
{
|
||||
return $this->autowired;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setExported(bool $state = true)
|
||||
{
|
||||
return $this->addTag('nette.exported', $state);
|
||||
}
|
||||
|
||||
|
||||
public function isExported(): bool
|
||||
{
|
||||
return (bool) $this->getTag('nette.exported');
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->notifier = $this->name = null;
|
||||
}
|
||||
|
||||
|
||||
/********************* life cycle ****************d*g**/
|
||||
|
||||
|
||||
abstract public function resolveType(Nette\DI\Resolver $resolver): void;
|
||||
|
||||
|
||||
abstract public function complete(Nette\DI\Resolver $resolver): void;
|
||||
|
||||
|
||||
abstract public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void;
|
||||
|
||||
|
||||
final public function setNotifier(?callable $notifier): void
|
||||
{
|
||||
$this->notifier = $notifier;
|
||||
}
|
||||
|
||||
|
||||
/********************* deprecated stuff from former ServiceDefinition ****************d*g**/
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use setType()
|
||||
*/
|
||||
public function setClass(?string $type)
|
||||
{
|
||||
return $this->setType($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use getType()
|
||||
*/
|
||||
public function getClass(): ?string
|
||||
{
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use '$def instanceof Nette\DI\Definitions\ImportedDefinition'
|
||||
*/
|
||||
public function isDynamic(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use Nette\DI\Definitions\FactoryDefinition or AccessorDefinition
|
||||
*/
|
||||
public function getImplement(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use getAutowired()
|
||||
*/
|
||||
public function isAutowired()
|
||||
{
|
||||
return $this->autowired;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\ServiceCreationException;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* Definition of standard service.
|
||||
*/
|
||||
final class FactoryDefinition extends Definition
|
||||
{
|
||||
private const METHOD_CREATE = 'create';
|
||||
|
||||
/** @var array */
|
||||
public $parameters = [];
|
||||
|
||||
/** @var Definition */
|
||||
private $resultDefinition;
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->resultDefinition = new ServiceDefinition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setImplement(string $type)
|
||||
{
|
||||
if (!interface_exists($type)) {
|
||||
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface '$type' not found.");
|
||||
}
|
||||
$rc = new \ReflectionClass($type);
|
||||
$method = $rc->getMethods()[0] ?? null;
|
||||
if (!$method || $method->isStatic() || $method->getName() !== self::METHOD_CREATE || count($rc->getMethods()) > 1) {
|
||||
throw new Nette\InvalidArgumentException("Service '{$this->getName()}': Interface $type must have just one non-static method create().");
|
||||
}
|
||||
return parent::setType($type);
|
||||
}
|
||||
|
||||
|
||||
public function getImplement(): ?string
|
||||
{
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
|
||||
final public function getResultType(): ?string
|
||||
{
|
||||
return $this->resultDefinition->getType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setResultDefinition(Definition $definition)
|
||||
{
|
||||
$this->resultDefinition = $definition;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return ServiceDefinition
|
||||
*/
|
||||
public function getResultDefinition(): Definition
|
||||
{
|
||||
return $this->resultDefinition;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->setFactory()
|
||||
* @return static
|
||||
*/
|
||||
public function setFactory($factory, array $args = [])
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
$this->resultDefinition->setFactory($factory, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->getFactory()
|
||||
*/
|
||||
public function getFactory(): ?Statement
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
return $this->resultDefinition->getFactory();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->getEntity()
|
||||
* @return mixed
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getEntity()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
return $this->resultDefinition->getEntity();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->setArguments()
|
||||
* @return static
|
||||
*/
|
||||
public function setArguments(array $args = [])
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setArguments()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
$this->resultDefinition->setArguments($args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->setSetup()
|
||||
* @return static
|
||||
*/
|
||||
public function setSetup(array $setup)
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->setSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
$this->resultDefinition->setSetup($setup);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->getSetup()
|
||||
*/
|
||||
public function getSetup(): array
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->getSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
return $this->resultDefinition->getSetup();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use ->getResultDefinition()->addSetup()
|
||||
* @return static
|
||||
*/
|
||||
public function addSetup($entity, array $args = [])
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use ->getResultDefinition()->addSetup()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
$this->resultDefinition->addSetup($entity, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
$this->parameters = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getParameters(): array
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
public function resolveType(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
$resultDef = $this->resultDefinition;
|
||||
try {
|
||||
$resolver->resolveDefinition($resultDef);
|
||||
return;
|
||||
} catch (ServiceCreationException $e) {
|
||||
}
|
||||
|
||||
if (!$resultDef->getType()) {
|
||||
$interface = $this->getType();
|
||||
if (!$interface) {
|
||||
throw new ServiceCreationException('Type is missing in definition of service.');
|
||||
}
|
||||
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
|
||||
$returnType = Nette\DI\Helpers::getReturnType($method);
|
||||
if (!$returnType) {
|
||||
throw new ServiceCreationException("Method $interface::create() has not return type hint or annotation @return.");
|
||||
} elseif (!class_exists($returnType) && !interface_exists($returnType)) {
|
||||
throw new ServiceCreationException("Check a type hint or annotation @return of the $interface::create() method, class '$returnType' cannot be found.");
|
||||
}
|
||||
$resultDef->setType($returnType);
|
||||
}
|
||||
|
||||
$resolver->resolveDefinition($resultDef);
|
||||
}
|
||||
|
||||
|
||||
public function complete(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
$resultDef = $this->resultDefinition;
|
||||
|
||||
if ($resultDef instanceof ServiceDefinition) {
|
||||
if (!$this->parameters) {
|
||||
$this->completeParameters($resolver);
|
||||
}
|
||||
|
||||
if ($resultDef->getEntity() instanceof Reference && !$resultDef->getFactory()->arguments) {
|
||||
$resultDef->setFactory([ // render as $container->createMethod()
|
||||
new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER),
|
||||
Nette\DI\Container::getMethodName($resultDef->getEntity()->getValue()),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
$resolver->completeDefinition($resultDef);
|
||||
}
|
||||
|
||||
|
||||
private function completeParameters(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
$interface = $this->getType();
|
||||
$method = new \ReflectionMethod($interface, self::METHOD_CREATE);
|
||||
|
||||
$ctorParams = [];
|
||||
if (
|
||||
($class = $resolver->resolveEntityType($this->resultDefinition->getFactory()))
|
||||
&& ($ctor = (new \ReflectionClass($class))->getConstructor())
|
||||
) {
|
||||
foreach ($ctor->getParameters() as $param) {
|
||||
$ctorParams[$param->getName()] = $param;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$hint = Reflection::getParameterType($param);
|
||||
if (isset($ctorParams[$param->getName()])) {
|
||||
$arg = $ctorParams[$param->getName()];
|
||||
$argHint = Reflection::getParameterType($arg);
|
||||
if ($hint !== $argHint && !is_a($hint, (string) $argHint, true)) {
|
||||
throw new ServiceCreationException("Type hint for \${$param->getName()} in $interface::create() doesn't match type hint in $class constructor.");
|
||||
}
|
||||
$this->resultDefinition->getFactory()->arguments[$arg->getPosition()] = Nette\DI\ContainerBuilder::literal('$' . $arg->getName());
|
||||
|
||||
} elseif (!$this->resultDefinition->getSetup()) {
|
||||
$hint = Nette\Utils\ObjectHelpers::getSuggestion(array_keys($ctorParams), $param->getName());
|
||||
throw new ServiceCreationException("Unused parameter \${$param->getName()} when implementing method $interface::create()" . ($hint ? ", did you mean \${$hint}?" : '.'));
|
||||
}
|
||||
$nullable = $hint && $param->allowsNull() && (!$param->isDefaultValueAvailable() || $param->getDefaultValue() !== null);
|
||||
$paramDef = ($nullable ? '?' : '') . $hint . ' ' . $param->getName();
|
||||
if ($param->isDefaultValueAvailable()) {
|
||||
$this->parameters[$paramDef] = Reflection::getParameterDefaultValue($param);
|
||||
} else {
|
||||
$this->parameters[] = $paramDef;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
|
||||
{
|
||||
$class = (new Nette\PhpGenerator\ClassType)
|
||||
->addImplement($this->getType());
|
||||
|
||||
$class->addProperty('container')
|
||||
->setVisibility('private');
|
||||
|
||||
$class->addMethod('__construct')
|
||||
->addBody('$this->container = $container;')
|
||||
->addParameter('container')
|
||||
->setTypeHint($generator->getClassName());
|
||||
|
||||
$methodCreate = $class->addMethod(self::METHOD_CREATE);
|
||||
$this->resultDefinition->generateMethod($methodCreate, $generator);
|
||||
$body = $methodCreate->getBody();
|
||||
$body = str_replace('$this', '$this->container', $body);
|
||||
$body = str_replace('$this->container->container', '$this->container', $body);
|
||||
|
||||
$rm = new \ReflectionMethod($this->getType(), self::METHOD_CREATE);
|
||||
$methodCreate
|
||||
->setParameters($generator->convertParameters($this->parameters))
|
||||
->setReturnType(Reflection::getReturnType($rm) ?: $this->getResultType())
|
||||
->setBody($body);
|
||||
|
||||
$method->setBody('return new class ($this) ' . $class . ';');
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
parent::__clone();
|
||||
$this->resultDefinition = unserialize(serialize($this->resultDefinition));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\PhpGenerator;
|
||||
|
||||
|
||||
/**
|
||||
* Imported service injected to the container.
|
||||
*/
|
||||
final class ImportedDefinition extends Definition
|
||||
{
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setType(?string $type)
|
||||
{
|
||||
return parent::setType($type);
|
||||
}
|
||||
|
||||
|
||||
public function resolveType(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function complete(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function generateMethod(Nette\PhpGenerator\Method $method, PhpGenerator $generator): void
|
||||
{
|
||||
$method->setReturnType('void')
|
||||
->setBody(
|
||||
'throw new Nette\\DI\\ServiceCreationException(?);',
|
||||
["Unable to create imported service '{$this->getName()}', it must be added using addService()"]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use '$def instanceof ImportedDefinition'
|
||||
*/
|
||||
public function isDynamic(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* Multi accessor/factory definition.
|
||||
*/
|
||||
final class LocatorDefinition extends Definition
|
||||
{
|
||||
/** @var Reference[] */
|
||||
private $references = [];
|
||||
|
||||
/** @var string|null */
|
||||
private $tagged;
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setImplement(string $type)
|
||||
{
|
||||
if (!interface_exists($type)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface '%s' not found.", $this->getName(), $type));
|
||||
}
|
||||
$methods = (new \ReflectionClass($type))->getMethods();
|
||||
if (!$methods) {
|
||||
throw new Nette\InvalidArgumentException(sprintf("Service '%s': Interface %s must have at least one method.", $this->getName(), $type));
|
||||
}
|
||||
|
||||
foreach ($methods as $method) {
|
||||
if ($method->isStatic() || !(
|
||||
(preg_match('#^(get|create)$#', $method->getName()) && $method->getNumberOfParameters() === 1)
|
||||
|| (preg_match('#^(get|create)[A-Z]#', $method->getName()) && $method->getNumberOfParameters() === 0)
|
||||
)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf(
|
||||
"Service '%s': Method %s::%s() does not meet the requirements: is create(\$name), get(\$name), create*() or get*() and is non-static.",
|
||||
$this->getName(), $type, $method->getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
return parent::setType($type);
|
||||
}
|
||||
|
||||
|
||||
public function getImplement(): ?string
|
||||
{
|
||||
return $this->getType();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setReferences(array $references)
|
||||
{
|
||||
$this->references = [];
|
||||
foreach ($references as $name => $ref) {
|
||||
$this->references[$name] = substr($ref, 0, 1) === '@'
|
||||
? new Reference(substr($ref, 1))
|
||||
: Reference::fromType($ref);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Reference[]
|
||||
*/
|
||||
public function getReferences(): array
|
||||
{
|
||||
return $this->references;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setTagged(?string $tagged)
|
||||
{
|
||||
$this->tagged = $tagged;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getTagged(): ?string
|
||||
{
|
||||
return $this->tagged;
|
||||
}
|
||||
|
||||
|
||||
public function resolveType(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
public function complete(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
if ($this->tagged !== null) {
|
||||
$this->references = [];
|
||||
foreach ($resolver->getContainerBuilder()->findByTag($this->tagged) as $name => $tag) {
|
||||
if (isset($this->references[$tag])) {
|
||||
trigger_error("Service '{$this->getName()}': duplicated tag '$this->tagged' with value '$tag'.", E_USER_NOTICE);
|
||||
}
|
||||
$this->references[$tag] = new Reference($name);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->references as $name => $ref) {
|
||||
$this->references[$name] = $resolver->normalizeReference($ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
|
||||
{
|
||||
$class = (new Nette\PhpGenerator\ClassType)
|
||||
->addImplement($this->getType());
|
||||
|
||||
$class->addProperty('container')
|
||||
->setVisibility('private');
|
||||
|
||||
$class->addMethod('__construct')
|
||||
->addBody('$this->container = $container;')
|
||||
->addParameter('container')
|
||||
->setTypeHint($generator->getClassName());
|
||||
|
||||
foreach ((new \ReflectionClass($this->getType()))->getMethods() as $rm) {
|
||||
preg_match('#^(get|create)(.*)#', $rm->getName(), $m);
|
||||
$name = lcfirst($m[2]);
|
||||
$nullable = $rm->getReturnType()->allowsNull();
|
||||
|
||||
$methodInner = $class->addMethod($rm->getName())
|
||||
->setReturnType(Reflection::getReturnType($rm))
|
||||
->setReturnNullable($nullable);
|
||||
|
||||
if (!$name) {
|
||||
$class->addProperty('mapping', array_map(function ($item) { return $item->getValue(); }, $this->references))
|
||||
->setVisibility('private');
|
||||
|
||||
$methodInner->setBody('if (!isset($this->mapping[$name])) {
|
||||
' . ($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service \'$name\' is not defined.");') . '
|
||||
}
|
||||
return $this->container->' . $m[1] . 'Service($this->mapping[$name]);')
|
||||
->addParameter('name');
|
||||
|
||||
} elseif (isset($this->references[$name])) {
|
||||
$ref = $this->references[$name]->getValue();
|
||||
if ($m[1] === 'get') {
|
||||
$methodInner->setBody('return $this->container->getService(?);', [$ref]);
|
||||
} else {
|
||||
$methodInner->setBody('return $this->container->?();', [Nette\DI\Container::getMethodName($ref)]);
|
||||
}
|
||||
} else {
|
||||
$methodInner->setBody($nullable ? 'return null;' : 'throw new Nette\DI\MissingServiceException("Service is not defined.");');
|
||||
}
|
||||
}
|
||||
|
||||
$method->setBody('return new class ($this) ' . $class . ';');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to service. Either by name or by type or reference to the 'self' service.
|
||||
*/
|
||||
final class Reference
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public const SELF = 'self';
|
||||
|
||||
/** @var string */
|
||||
private $value;
|
||||
|
||||
|
||||
public static function fromType(string $value): self
|
||||
{
|
||||
if (strpos($value, '\\') === false) {
|
||||
$value = '\\' . $value;
|
||||
}
|
||||
return new static($value);
|
||||
}
|
||||
|
||||
|
||||
public function __construct(string $value)
|
||||
{
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
public function isName(): bool
|
||||
{
|
||||
return strpos($this->value, '\\') === false && $this->value !== self::SELF;
|
||||
}
|
||||
|
||||
|
||||
public function isType(): bool
|
||||
{
|
||||
return strpos($this->value, '\\') !== false;
|
||||
}
|
||||
|
||||
|
||||
public function isSelf(): bool
|
||||
{
|
||||
return $this->value === self::SELF;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Definitions\Reference;
|
||||
use Nette\DI\ServiceCreationException;
|
||||
use Nette\PhpGenerator\Helpers as PhpHelpers;
|
||||
|
||||
|
||||
/**
|
||||
* Definition of standard service.
|
||||
*
|
||||
* @property string|null $class
|
||||
* @property Statement $factory
|
||||
* @property Statement[] $setup
|
||||
*/
|
||||
final class ServiceDefinition extends Definition
|
||||
{
|
||||
/** @var Statement */
|
||||
private $factory;
|
||||
|
||||
/** @var Statement[] */
|
||||
private $setup = [];
|
||||
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->factory = new Statement(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated Use setType()
|
||||
*/
|
||||
public function setClass(?string $type)
|
||||
{
|
||||
$this->setType($type);
|
||||
if (func_num_args() > 1) {
|
||||
trigger_error(sprintf('Service %s: %s() second parameter $args is deprecated, use setFactory()', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
if ($args = func_get_arg(1)) {
|
||||
$this->setFactory($type, $args);
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setType(?string $type)
|
||||
{
|
||||
return parent::setType($type);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array|Definition|Reference|Statement $factory
|
||||
* @return static
|
||||
*/
|
||||
public function setFactory($factory, array $args = [])
|
||||
{
|
||||
$this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getFactory(): Statement
|
||||
{
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|array|Definition|null
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->factory->getEntity();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setArguments(array $args = [])
|
||||
{
|
||||
$this->factory->arguments = $args;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setArgument($key, $value)
|
||||
{
|
||||
$this->factory->arguments[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Statement[] $setup
|
||||
* @return static
|
||||
*/
|
||||
public function setSetup(array $setup)
|
||||
{
|
||||
foreach ($setup as $v) {
|
||||
if (!$v instanceof Statement) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Definitions\Statement[].');
|
||||
}
|
||||
}
|
||||
$this->setup = $setup;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Statement[]
|
||||
*/
|
||||
public function getSetup(): array
|
||||
{
|
||||
return $this->setup;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array|Definition|Reference|Statement $entity
|
||||
* @return static
|
||||
*/
|
||||
public function addSetup($entity, array $args = [])
|
||||
{
|
||||
$this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public function getParameters(): array
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated.', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use $builder->addImportedDefinition(...)
|
||||
*/
|
||||
public function setDynamic(): void
|
||||
{
|
||||
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addImportedDefinition(...)', $this->getName(), __METHOD__));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use $builder->addFactoryDefinition(...) or addAccessorDefinition(...)
|
||||
*/
|
||||
public function setImplement(): void
|
||||
{
|
||||
throw new Nette\DeprecatedException(sprintf('Service %s: %s() is deprecated, use $builder->addFactoryDefinition(...)', $this->getName(), __METHOD__));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @deprecated use addTag('nette.inject')
|
||||
*/
|
||||
public function setInject(bool $state = true)
|
||||
{
|
||||
trigger_error(sprintf('Service %s: %s() is deprecated, use addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT)', $this->getName(), __METHOD__), E_USER_DEPRECATED);
|
||||
return $this->addTag(Nette\DI\Extensions\InjectExtension::TAG_INJECT, $state);
|
||||
}
|
||||
|
||||
|
||||
public function resolveType(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
if (!$this->getEntity()) {
|
||||
if (!$this->getType()) {
|
||||
throw new ServiceCreationException('Factory and type are missing in definition of service.');
|
||||
}
|
||||
$this->setFactory($this->getType(), $this->factory->arguments ?? []);
|
||||
|
||||
} elseif (!$this->getType()) {
|
||||
$type = $resolver->resolveEntityType($this->factory);
|
||||
if (!$type) {
|
||||
throw new ServiceCreationException('Unknown service type, specify it or declare return type of factory.');
|
||||
}
|
||||
$this->setType($type);
|
||||
$resolver->addDependency(new \ReflectionClass($type));
|
||||
}
|
||||
|
||||
// auto-disable autowiring for aliases
|
||||
if ($this->getAutowired() === true && $this->getEntity() instanceof Reference) {
|
||||
$this->setAutowired(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function complete(Nette\DI\Resolver $resolver): void
|
||||
{
|
||||
$entity = $this->factory->getEntity();
|
||||
if ($entity instanceof Reference && !$this->factory->arguments && !$this->setup) {
|
||||
$ref = $resolver->normalizeReference($entity);
|
||||
$this->setFactory([new Reference(Nette\DI\ContainerBuilder::THIS_CONTAINER), 'getService'], [$ref->getValue()]);
|
||||
}
|
||||
|
||||
$this->factory = $resolver->completeStatement($this->factory);
|
||||
|
||||
foreach ($this->setup as &$setup) {
|
||||
if (is_string($setup->getEntity()) && strpbrk($setup->getEntity(), ':@?\\') === false) { // auto-prepend @self
|
||||
$setup = new Statement([new Reference(Reference::SELF), $setup->getEntity()], $setup->arguments);
|
||||
}
|
||||
$setup = $resolver->completeStatement($setup, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function generateMethod(Nette\PhpGenerator\Method $method, Nette\DI\PhpGenerator $generator): void
|
||||
{
|
||||
$entity = $this->factory->getEntity();
|
||||
$code = '$service = ' . $generator->formatStatement($this->factory) . ";\n";
|
||||
$type = $this->getType();
|
||||
|
||||
if (
|
||||
$this->setup
|
||||
&& $type !== $entity
|
||||
&& !(is_array($entity) && $entity[0] instanceof Reference && $entity[0]->getValue() === Nette\DI\ContainerBuilder::THIS_CONTAINER)
|
||||
&& !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $type))
|
||||
) {
|
||||
$code .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n"
|
||||
. "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
|
||||
["Unable to create service '{$this->getName()}', value returned by factory is not $type type."]
|
||||
);
|
||||
}
|
||||
|
||||
foreach ($this->setup as $setup) {
|
||||
$code .= $generator->formatStatement($setup) . ";\n";
|
||||
}
|
||||
|
||||
$code .= 'return $service;';
|
||||
$method->setBody($code);
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
parent::__clone();
|
||||
$this->factory = unserialize(serialize($this->factory));
|
||||
$this->setup = unserialize(serialize($this->setup));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Definitions;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Strings;
|
||||
|
||||
|
||||
/**
|
||||
* Assignment or calling statement.
|
||||
*
|
||||
* @property string|array|Definition|Reference|null $entity
|
||||
*/
|
||||
final class Statement
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
public $arguments;
|
||||
|
||||
/** @var string|array|Definition|Reference|null */
|
||||
private $entity;
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array|Definition|Reference|null $entity
|
||||
*/
|
||||
public function __construct($entity, array $arguments = [])
|
||||
{
|
||||
if (
|
||||
$entity !== null
|
||||
&& !is_string($entity) // Class, @service, not, tags, types, PHP literal, entity::member
|
||||
&& !$entity instanceof Definition
|
||||
&& !$entity instanceof Reference
|
||||
&& !(is_array($entity)
|
||||
&& array_keys($entity) === [0, 1]
|
||||
&& (is_string($entity[0])
|
||||
|| $entity[0] instanceof self
|
||||
|| $entity[0] instanceof Reference
|
||||
|| $entity[0] instanceof Definition)
|
||||
)) {
|
||||
throw new Nette\InvalidArgumentException('Argument is not valid Statement entity.');
|
||||
}
|
||||
|
||||
// normalize Class::method to [Class, method]
|
||||
if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) {
|
||||
$entity = explode('::', $entity);
|
||||
}
|
||||
if (is_string($entity) && substr($entity, 0, 1) === '@') { // normalize @service to Reference
|
||||
$entity = new Reference(substr($entity, 1));
|
||||
} elseif (is_array($entity) && is_string($entity[0]) && substr($entity[0], 0, 1) === '@') {
|
||||
$entity[0] = new Reference(substr($entity[0], 1));
|
||||
}
|
||||
|
||||
$this->entity = $entity;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
|
||||
/** @return string|array|Definition|Reference|null */
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\Utils\Reflection;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
|
||||
|
||||
/**
|
||||
* Cache dependencies checker.
|
||||
*/
|
||||
class DependencyChecker
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const VERSION = 1;
|
||||
|
||||
/** @var array of ReflectionClass|\ReflectionFunctionAbstract|string */
|
||||
private $dependencies = [];
|
||||
|
||||
|
||||
/**
|
||||
* Adds dependencies to the list.
|
||||
* @return static
|
||||
*/
|
||||
public function add(array $deps)
|
||||
{
|
||||
$this->dependencies = array_merge($this->dependencies, $deps);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Exports dependencies.
|
||||
* @return array
|
||||
*/
|
||||
public function export()
|
||||
{
|
||||
$files = $phpFiles = $classes = $functions = [];
|
||||
foreach ($this->dependencies as $dep) {
|
||||
if (is_string($dep)) {
|
||||
$files[] = $dep;
|
||||
|
||||
} elseif ($dep instanceof ReflectionClass) {
|
||||
if (empty($classes[$name = $dep->getName()])) {
|
||||
$all = [$name] + class_parents($name) + class_implements($name);
|
||||
foreach ($all as &$item) {
|
||||
$all += class_uses($item);
|
||||
$phpFiles[] = (new ReflectionClass($item))->getFileName();
|
||||
$classes[$item] = true;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($dep instanceof \ReflectionFunctionAbstract) {
|
||||
$phpFiles[] = $dep->getFileName();
|
||||
$functions[] = Reflection::toString($dep);
|
||||
|
||||
} else {
|
||||
throw new Nette\InvalidStateException('Unexpected dependency ' . gettype($dep));
|
||||
}
|
||||
}
|
||||
|
||||
$classes = array_keys($classes);
|
||||
$functions = array_unique($functions, SORT_REGULAR);
|
||||
$hash = self::calculateHash($classes, $functions);
|
||||
$files = @array_map('filemtime', array_combine($files, $files)); // @ - file may not exist
|
||||
$phpFiles = @array_map('filemtime', array_combine($phpFiles, $phpFiles)); // @ - file may not exist
|
||||
return [self::VERSION, $files, $phpFiles, $classes, $functions, $hash];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Are dependencies expired?
|
||||
* @return bool
|
||||
*/
|
||||
public static function isExpired($version, $files, &$phpFiles, $classes, $functions, $hash)
|
||||
{
|
||||
$current = @array_map('filemtime', array_combine($tmp = array_keys($files), $tmp)); // @ - files may not exist
|
||||
$origPhpFiles = $phpFiles;
|
||||
$phpFiles = @array_map('filemtime', array_combine($tmp = array_keys($phpFiles), $tmp)); // @ - files may not exist
|
||||
return $version !== self::VERSION
|
||||
|| $files !== $current
|
||||
|| ($phpFiles !== $origPhpFiles && $hash !== self::calculateHash($classes, $functions));
|
||||
}
|
||||
|
||||
|
||||
private static function calculateHash($classes, $functions)
|
||||
{
|
||||
$hash = [];
|
||||
foreach ($classes as $name) {
|
||||
try {
|
||||
$class = new ReflectionClass($name);
|
||||
} catch (\ReflectionException $e) {
|
||||
return;
|
||||
}
|
||||
$hash[] = [
|
||||
$name,
|
||||
Reflection::getUseStatements($class),
|
||||
$class->isAbstract(),
|
||||
get_parent_class($name),
|
||||
class_implements($name),
|
||||
class_uses($name),
|
||||
];
|
||||
|
||||
foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
|
||||
if ($prop->getDeclaringClass() == $class) { // intentionally ==
|
||||
$hash[] = [$name, $prop->getName(), $prop->getDocComment()];
|
||||
}
|
||||
}
|
||||
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
if ($method->getDeclaringClass() == $class) { // intentionally ==
|
||||
$hash[] = [
|
||||
$name,
|
||||
$method->getName(),
|
||||
$method->getDocComment(),
|
||||
self::hashParameters($method),
|
||||
PHP_VERSION_ID >= 70000 && $method->hasReturnType()
|
||||
? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()]
|
||||
: null,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$flip = array_flip($classes);
|
||||
foreach ($functions as $name) {
|
||||
try {
|
||||
$method = strpos($name, '::') ? new ReflectionMethod($name) : new \ReflectionFunction($name);
|
||||
} catch (\ReflectionException $e) {
|
||||
return;
|
||||
}
|
||||
$class = $method instanceof ReflectionMethod ? $method->getDeclaringClass() : null;
|
||||
if ($class && isset($flip[$class->getName()])) {
|
||||
continue;
|
||||
}
|
||||
$hash[] = [
|
||||
$name,
|
||||
$class ? Reflection::getUseStatements($method->getDeclaringClass()) : null,
|
||||
$method->getDocComment(),
|
||||
self::hashParameters($method),
|
||||
PHP_VERSION_ID >= 70000 && $method->hasReturnType()
|
||||
? [(string) $method->getReturnType(), $method->getReturnType()->allowsNull()]
|
||||
: null,
|
||||
];
|
||||
}
|
||||
|
||||
return md5(serialize($hash));
|
||||
}
|
||||
|
||||
|
||||
private static function hashParameters(\ReflectionFunctionAbstract $method)
|
||||
{
|
||||
$res = [];
|
||||
if (PHP_VERSION_ID < 70000 && $method->getNumberOfParameters() && $method->getFileName()) {
|
||||
$res[] = file($method->getFileName())[$method->getStartLine() - 1];
|
||||
}
|
||||
foreach ($method->getParameters() as $param) {
|
||||
$res[] = [
|
||||
$param->getName(),
|
||||
PHP_VERSION_ID >= 70000 ? [Reflection::getParameterType($param), $param->allowsNull()] : null,
|
||||
$param->isVariadic(),
|
||||
$param->isDefaultValueAvailable()
|
||||
? [Reflection::getParameterDefaultValue($param)]
|
||||
: null,
|
||||
];
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class DynamicParameter extends Nette\PhpGenerator\PhpLiteral implements Nette\Schema\DynamicParameter
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Constant definitions.
|
||||
*/
|
||||
class ConstantsExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
foreach ($this->getConfig() as $name => $value) {
|
||||
$class->getMethod('initialize')->addBody('define(?, ?);', [$name, $value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* DI extension.
|
||||
*/
|
||||
class DIExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $defaults = [
|
||||
'debugger' => true,
|
||||
'accessors' => false,
|
||||
'excluded' => [],
|
||||
'parentClass' => null,
|
||||
];
|
||||
|
||||
/** @var bool */
|
||||
private $debugMode;
|
||||
|
||||
/** @var int */
|
||||
private $time;
|
||||
|
||||
|
||||
public function __construct($debugMode = false)
|
||||
{
|
||||
$this->debugMode = $debugMode;
|
||||
$this->time = microtime(true);
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$config = $this->validateConfig($this->defaults);
|
||||
$builder = $this->getContainerBuilder();
|
||||
$builder->addExcludedClasses($config['excluded']);
|
||||
}
|
||||
|
||||
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
if ($this->config['parentClass']) {
|
||||
$class->setExtends($this->config['parentClass']);
|
||||
}
|
||||
|
||||
$initialize = $class->getMethod('initialize');
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
if ($this->debugMode && $this->config['debugger']) {
|
||||
Nette\Bridges\DITracy\ContainerPanel::$compilationTime = $this->time;
|
||||
$initialize->addBody($builder->formatPhp('?;', [
|
||||
new Nette\DI\Statement('@Tracy\Bar::addPanel', [new Nette\DI\Statement(Nette\Bridges\DITracy\ContainerPanel::class)]),
|
||||
]));
|
||||
}
|
||||
|
||||
foreach (array_filter($builder->findByTag('run')) as $name => $on) {
|
||||
$initialize->addBody('$this->getService(?);', [$name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Decorators for services.
|
||||
*/
|
||||
class DecoratorExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public $defaults = [
|
||||
'setup' => [],
|
||||
'tags' => [],
|
||||
'inject' => null,
|
||||
];
|
||||
|
||||
|
||||
public function beforeCompile()
|
||||
{
|
||||
foreach ($this->getConfig() as $type => $info) {
|
||||
$info = $this->validateConfig($this->defaults, $info, $this->prefix($type));
|
||||
if ($info['inject'] !== null) {
|
||||
$info['tags'][InjectExtension::TAG_INJECT] = $info['inject'];
|
||||
}
|
||||
$info = Nette\DI\Helpers::filterArguments($info);
|
||||
$this->addSetups($type, (array) $info['setup']);
|
||||
$this->addTags($type, (array) $info['tags']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function addSetups($type, array $setups)
|
||||
{
|
||||
foreach ($this->findByType($type) as $def) {
|
||||
foreach ($setups as $setup) {
|
||||
if (is_array($setup)) {
|
||||
$setup = new Nette\DI\Statement(key($setup), array_values($setup));
|
||||
}
|
||||
$def->addSetup($setup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function addTags($type, array $tags)
|
||||
{
|
||||
$tags = Nette\Utils\Arrays::normalize($tags, true);
|
||||
foreach ($this->findByType($type) as $def) {
|
||||
$def->setTags($def->getTags() + $tags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function findByType($type)
|
||||
{
|
||||
return array_filter($this->getContainerBuilder()->getDefinitions(), function ($def) use ($type) {
|
||||
return is_a($def->getImplement(), $type, true)
|
||||
|| ($def->getImplementMode() !== $def::IMPLEMENT_MODE_GET && is_a($def->getType(), $type, true));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Enables registration of other extensions in $config file
|
||||
*/
|
||||
class ExtensionsExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public function loadConfiguration()
|
||||
{
|
||||
foreach ($this->getConfig() as $name => $class) {
|
||||
if (is_int($name)) {
|
||||
$name = null;
|
||||
}
|
||||
if ($class instanceof Nette\DI\Statement) {
|
||||
$rc = new \ReflectionClass($class->getEntity());
|
||||
$this->compiler->addExtension($name, $rc->newInstanceArgs($class->arguments));
|
||||
} else {
|
||||
$this->compiler->addExtension($name, new $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* Calls inject methods and fills @inject properties.
|
||||
*/
|
||||
class InjectExtension extends DI\CompilerExtension
|
||||
{
|
||||
const TAG_INJECT = 'inject';
|
||||
|
||||
|
||||
public function beforeCompile()
|
||||
{
|
||||
foreach ($this->getContainerBuilder()->getDefinitions() as $def) {
|
||||
if ($def->getTag(self::TAG_INJECT) && $def->getType()) {
|
||||
$this->updateDefinition($def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateDefinition(DI\ServiceDefinition $def)
|
||||
{
|
||||
$class = $def->getType();
|
||||
$setups = $def->getSetup();
|
||||
|
||||
foreach (self::getInjectProperties($class) as $property => $type) {
|
||||
$builder = $this->getContainerBuilder();
|
||||
$inject = new DI\Statement('$' . $property, ['@\\' . ltrim($type, '\\')]);
|
||||
foreach ($setups as $key => $setup) {
|
||||
if ($setup->getEntity() === $inject->getEntity()) {
|
||||
$inject = $setup;
|
||||
$builder = null;
|
||||
unset($setups[$key]);
|
||||
}
|
||||
}
|
||||
self::checkType($class, $property, $type, $builder);
|
||||
array_unshift($setups, $inject);
|
||||
}
|
||||
|
||||
foreach (array_reverse(self::getInjectMethods($def->getType())) as $method) {
|
||||
$inject = new DI\Statement($method);
|
||||
foreach ($setups as $key => $setup) {
|
||||
if ($setup->getEntity() === $inject->getEntity()) {
|
||||
$inject = $setup;
|
||||
unset($setups[$key]);
|
||||
}
|
||||
}
|
||||
array_unshift($setups, $inject);
|
||||
}
|
||||
|
||||
$def->setSetup($setups);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates list of inject methods.
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public static function getInjectMethods($class)
|
||||
{
|
||||
$res = [];
|
||||
foreach (get_class_methods($class) as $name) {
|
||||
if (substr($name, 0, 6) === 'inject') {
|
||||
$res[$name] = (new \ReflectionMethod($class, $name))->getDeclaringClass()->getName();
|
||||
}
|
||||
}
|
||||
uksort($res, function ($a, $b) use ($res) {
|
||||
return $res[$a] === $res[$b]
|
||||
? strcmp($a, $b)
|
||||
: (is_a($res[$a], $res[$b], true) ? 1 : -1);
|
||||
});
|
||||
return array_keys($res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates list of properties with annotation @inject.
|
||||
* @return array
|
||||
* @internal
|
||||
*/
|
||||
public static function getInjectProperties($class)
|
||||
{
|
||||
$res = [];
|
||||
foreach (get_class_vars($class) as $name => $foo) {
|
||||
$rp = new \ReflectionProperty($class, $name);
|
||||
if (DI\Helpers::parseAnnotation($rp, 'inject') !== null) {
|
||||
if ($type = DI\Helpers::parseAnnotation($rp, 'var')) {
|
||||
$type = Reflection::expandClassName($type, Reflection::getPropertyDeclaringClass($rp));
|
||||
}
|
||||
$res[$name] = $type;
|
||||
}
|
||||
}
|
||||
ksort($res);
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calls all methods starting with with "inject" using autowiring.
|
||||
* @return void
|
||||
*/
|
||||
public static function callInjects(DI\Container $container, $service)
|
||||
{
|
||||
if (!is_object($service)) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Service must be object, %s given.', gettype($service)));
|
||||
}
|
||||
|
||||
foreach (self::getInjectMethods($service) as $method) {
|
||||
$container->callMethod([$service, $method]);
|
||||
}
|
||||
|
||||
foreach (self::getInjectProperties(get_class($service)) as $property => $type) {
|
||||
self::checkType($service, $property, $type, $container);
|
||||
$service->$property = $container->getByType($type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @internal */
|
||||
private static function checkType($class, $name, $type, $container = null)
|
||||
{
|
||||
$propName = Reflection::toString(new \ReflectionProperty($class, $name));
|
||||
if (!$type) {
|
||||
throw new Nette\InvalidStateException("Property $propName has no @var annotation.");
|
||||
} elseif (!class_exists($type) && !interface_exists($type)) {
|
||||
throw new Nette\InvalidStateException("Class or interface '$type' used in @var annotation at $propName not found. Check annotation and 'use' statements.");
|
||||
} elseif ($container && !$container->getByType($type, false)) {
|
||||
throw new Nette\InvalidStateException("Service of type $type used in @var annotation at $propName not found. Did you register it in configuration file?");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\DynamicParameter;
|
||||
|
||||
|
||||
/**
|
||||
* Parameters.
|
||||
*/
|
||||
final class ParametersExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
/** @var string[] */
|
||||
public $dynamicParams = [];
|
||||
|
||||
/** @var string[][] */
|
||||
public $dynamicValidators = [];
|
||||
|
||||
/** @var array */
|
||||
private $compilerConfig;
|
||||
|
||||
|
||||
public function __construct(array &$compilerConfig)
|
||||
{
|
||||
$this->compilerConfig = &$compilerConfig;
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
$params = $this->config;
|
||||
|
||||
foreach ($this->dynamicParams as $key) {
|
||||
$params[$key] = array_key_exists($key, $params)
|
||||
? new DynamicParameter(Nette\PhpGenerator\Helpers::format('($this->parameters[?] \?\? ?)', $key, $params[$key]))
|
||||
: new DynamicParameter(Nette\PhpGenerator\Helpers::format('$this->parameters[?]', $key));
|
||||
}
|
||||
|
||||
$builder->parameters = Nette\DI\Helpers::expand($params, $params, true);
|
||||
|
||||
// expand all except 'services'
|
||||
$slice = array_diff_key($this->compilerConfig, ['services' => 1]);
|
||||
$this->compilerConfig = Nette\DI\Helpers::expand($slice, $builder->parameters) + $this->compilerConfig;
|
||||
}
|
||||
|
||||
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
$cnstr = $class->getMethod('__construct');
|
||||
foreach ($this->dynamicValidators as [$param, $expected]) {
|
||||
$cnstr->addBody('Nette\Utils\Validators::assert(?, ?, ?);', [$param, $expected, 'dynamic parameter']);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP directives definition.
|
||||
*/
|
||||
class PhpExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
public function afterCompile(Nette\PhpGenerator\ClassType $class)
|
||||
{
|
||||
$initialize = $class->getMethod('initialize');
|
||||
foreach ($this->getConfig() as $name => $value) {
|
||||
if ($value === null) {
|
||||
continue;
|
||||
|
||||
} elseif (!is_scalar($value)) {
|
||||
throw new Nette\InvalidStateException("Configuration value for directive '$name' is not scalar.");
|
||||
|
||||
} elseif ($name === 'include_path') {
|
||||
$initialize->addBody('set_include_path(?);', [str_replace(';', PATH_SEPARATOR, $value)]);
|
||||
|
||||
} elseif ($name === 'ignore_user_abort') {
|
||||
$initialize->addBody('ignore_user_abort(?);', [$value]);
|
||||
|
||||
} elseif ($name === 'max_execution_time') {
|
||||
$initialize->addBody('set_time_limit(?);', [$value]);
|
||||
|
||||
} elseif ($name === 'date.timezone') {
|
||||
$initialize->addBody('date_default_timezone_set(?);', [$value]);
|
||||
|
||||
} elseif (function_exists('ini_set')) {
|
||||
$initialize->addBody('ini_set(?, ?);', [$name, $value]);
|
||||
|
||||
} elseif (ini_get($name) != $value) { // intentionally ==
|
||||
throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
use Nette\Loaders\RobotLoader;
|
||||
use Nette\Schema\Expect;
|
||||
use Nette\Utils\Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* Services auto-discovery.
|
||||
*/
|
||||
final class SearchExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
/** @var array */
|
||||
private $classes = [];
|
||||
|
||||
/** @var string */
|
||||
private $tempDir;
|
||||
|
||||
|
||||
public function __construct(string $tempDir)
|
||||
{
|
||||
$this->tempDir = $tempDir;
|
||||
}
|
||||
|
||||
|
||||
public function getConfigSchema(): Nette\Schema\Schema
|
||||
{
|
||||
return Expect::arrayOf(
|
||||
Expect::structure([
|
||||
'in' => Expect::string()->required(),
|
||||
'files' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'exclude' => Expect::structure([
|
||||
'classes' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'extends' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
'implements' => Expect::anyOf(Expect::listOf('string'), Expect::string()->castTo('array'))->default([]),
|
||||
]),
|
||||
'tags' => Expect::array(),
|
||||
])
|
||||
)->before(function ($val) {
|
||||
return is_string($val['in'] ?? null)
|
||||
? ['default' => $val]
|
||||
: $val;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
foreach (array_filter($this->config) as $name => $batch) {
|
||||
if (!is_dir($batch->in)) {
|
||||
throw new Nette\DI\InvalidConfigurationException("Option '{$this->name} › {$name} › in' must be valid directory name, '{$batch->in}' given.");
|
||||
}
|
||||
|
||||
foreach ($this->findClasses($batch) as $class) {
|
||||
$this->classes[$class] = array_merge($this->classes[$class] ?? [], $batch->tags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function findClasses(\stdClass $config): array
|
||||
{
|
||||
$robot = new RobotLoader;
|
||||
$robot->setTempDirectory($this->tempDir);
|
||||
$robot->addDirectory($config->in);
|
||||
$robot->acceptFiles = $config->files ?: ['*.php'];
|
||||
$robot->reportParseErrors(false);
|
||||
$robot->refresh();
|
||||
$classes = array_unique(array_keys($robot->getIndexedClasses()));
|
||||
$classes = array_filter($classes, 'class_exists');
|
||||
|
||||
$exclude = $config->exclude;
|
||||
$acceptRE = self::buildNameRegexp($config->classes);
|
||||
$rejectRE = self::buildNameRegexp($exclude->classes);
|
||||
$acceptParent = array_merge($config->extends, $config->implements);
|
||||
$rejectParent = array_merge($exclude->extends, $exclude->implements);
|
||||
|
||||
$found = [];
|
||||
foreach ($classes as $class) {
|
||||
$rc = new \ReflectionClass($class);
|
||||
if (
|
||||
$rc->isInstantiable()
|
||||
&& (!$acceptRE || preg_match($acceptRE, $rc->getName()))
|
||||
&& (!$rejectRE || !preg_match($rejectRE, $rc->getName()))
|
||||
&& (!$acceptParent || Arrays::some($acceptParent, function ($nm) use ($rc) { return $rc->isSubclassOf($nm); }))
|
||||
&& (!$rejectParent || Arrays::every($rejectParent, function ($nm) use ($rc) { return !$rc->isSubclassOf($nm); }))
|
||||
) {
|
||||
$found[] = $rc->getName();
|
||||
}
|
||||
}
|
||||
return $found;
|
||||
}
|
||||
|
||||
|
||||
public function beforeCompile()
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
|
||||
foreach ($this->classes as $class => $tags) {
|
||||
if (!$builder->findByType($class)) {
|
||||
$builder->addDefinition(null)
|
||||
->setType($class)
|
||||
->setTags(Arrays::normalize($tags, true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static function buildNameRegexp(array $masks): ?string
|
||||
{
|
||||
$res = [];
|
||||
foreach ((array) $masks as $mask) {
|
||||
$mask = (strpos($mask, '\\') === false ? '**\\' : '') . $mask;
|
||||
$mask = preg_quote($mask, '#');
|
||||
$mask = str_replace('\*\*\\\\', '(.*\\\\)?', $mask);
|
||||
$mask = str_replace('\\\\\*\*', '(\\\\.*)?', $mask);
|
||||
$mask = str_replace('\*', '\w*', $mask);
|
||||
$res[] = $mask;
|
||||
}
|
||||
return $res ? '#^(' . implode('|', $res) . ')$#i' : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI\Extensions;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Definitions;
|
||||
use Nette\DI\Definitions\Statement;
|
||||
use Nette\DI\Helpers;
|
||||
|
||||
|
||||
/**
|
||||
* Service definitions loader.
|
||||
*/
|
||||
final class ServicesExtension extends Nette\DI\CompilerExtension
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
public function getConfigSchema(): Nette\Schema\Schema
|
||||
{
|
||||
return Nette\Schema\Expect::arrayOf(new Nette\DI\Config\DefinitionSchema($this->getContainerBuilder()));
|
||||
}
|
||||
|
||||
|
||||
public function loadConfiguration()
|
||||
{
|
||||
$this->loadDefinitions($this->config);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads list of service definitions.
|
||||
*/
|
||||
public function loadDefinitions(array $config)
|
||||
{
|
||||
foreach ($config as $key => $defConfig) {
|
||||
$this->loadDefinition($this->convertKeyToName($key), $defConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Loads service definition from normalized configuration.
|
||||
*/
|
||||
private function loadDefinition(?string $name, \stdClass $config): void
|
||||
{
|
||||
try {
|
||||
if ((array) $config === [false]) {
|
||||
$this->getContainerBuilder()->removeDefinition($name);
|
||||
return;
|
||||
} elseif (!empty($config->alteration) && !$this->getContainerBuilder()->hasDefinition($name)) {
|
||||
throw new Nette\DI\InvalidConfigurationException('missing original definition for alteration.');
|
||||
}
|
||||
|
||||
$def = $this->retrieveDefinition($name, $config);
|
||||
|
||||
static $methods = [
|
||||
Definitions\ServiceDefinition::class => 'updateServiceDefinition',
|
||||
Definitions\AccessorDefinition::class => 'updateAccessorDefinition',
|
||||
Definitions\FactoryDefinition::class => 'updateFactoryDefinition',
|
||||
Definitions\LocatorDefinition::class => 'updateLocatorDefinition',
|
||||
Definitions\ImportedDefinition::class => 'updateImportedDefinition',
|
||||
];
|
||||
$this->{$methods[$config->defType]}($def, $config);
|
||||
$this->updateDefinition($def, $config);
|
||||
} catch (\Exception $e) {
|
||||
throw new Nette\DI\InvalidConfigurationException(($name ? "Service '$name': " : '') . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates service definition according to normalized configuration.
|
||||
*/
|
||||
private function updateServiceDefinition(Definitions\ServiceDefinition $definition, \stdClass $config): void
|
||||
{
|
||||
if ($config->factory) {
|
||||
$definition->setFactory(Helpers::filterArguments([$config->factory])[0]);
|
||||
$definition->setType(null);
|
||||
}
|
||||
|
||||
if ($config->type) {
|
||||
$definition->setType($config->type);
|
||||
}
|
||||
|
||||
if ($config->arguments) {
|
||||
$arguments = Helpers::filterArguments($config->arguments);
|
||||
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
|
||||
$arguments += $definition->getFactory()->arguments;
|
||||
}
|
||||
$definition->setArguments($arguments);
|
||||
}
|
||||
|
||||
if (isset($config->setup)) {
|
||||
if (!empty($config->reset['setup'])) {
|
||||
$definition->setSetup([]);
|
||||
}
|
||||
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
|
||||
if (is_array($setup)) {
|
||||
$setup = new Statement(key($setup), array_values($setup));
|
||||
}
|
||||
$definition->addSetup($setup);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config->inject)) {
|
||||
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateAccessorDefinition(Definitions\AccessorDefinition $definition, \stdClass $config): void
|
||||
{
|
||||
if (isset($config->implement)) {
|
||||
$definition->setImplement($config->implement);
|
||||
}
|
||||
|
||||
if ($ref = $config->factory ?? $config->type ?? null) {
|
||||
$definition->setReference($ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateFactoryDefinition(Definitions\FactoryDefinition $definition, \stdClass $config): void
|
||||
{
|
||||
$resultDef = $definition->getResultDefinition();
|
||||
|
||||
if (isset($config->implement)) {
|
||||
$definition->setImplement($config->implement);
|
||||
$definition->setAutowired(true);
|
||||
}
|
||||
|
||||
if ($config->factory) {
|
||||
$resultDef->setFactory(Helpers::filterArguments([$config->factory])[0]);
|
||||
}
|
||||
|
||||
if ($config->type) {
|
||||
$resultDef->setFactory($config->type);
|
||||
}
|
||||
|
||||
if ($config->arguments) {
|
||||
$arguments = Helpers::filterArguments($config->arguments);
|
||||
if (empty($config->reset['arguments']) && !Nette\Utils\Arrays::isList($arguments)) {
|
||||
$arguments += $resultDef->getFactory()->arguments;
|
||||
}
|
||||
$resultDef->setArguments($arguments);
|
||||
}
|
||||
|
||||
if (isset($config->setup)) {
|
||||
if (!empty($config->reset['setup'])) {
|
||||
$resultDef->setSetup([]);
|
||||
}
|
||||
foreach (Helpers::filterArguments($config->setup) as $id => $setup) {
|
||||
if (is_array($setup)) {
|
||||
$setup = new Statement(key($setup), array_values($setup));
|
||||
}
|
||||
$resultDef->addSetup($setup);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($config->parameters)) {
|
||||
$definition->setParameters($config->parameters);
|
||||
}
|
||||
|
||||
if (isset($config->inject)) {
|
||||
$definition->addTag(InjectExtension::TAG_INJECT, $config->inject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateLocatorDefinition(Definitions\LocatorDefinition $definition, \stdClass $config): void
|
||||
{
|
||||
if (isset($config->implement)) {
|
||||
$definition->setImplement($config->implement);
|
||||
}
|
||||
|
||||
if (isset($config->references)) {
|
||||
$definition->setReferences($config->references);
|
||||
}
|
||||
|
||||
if (isset($config->tagged)) {
|
||||
$definition->setTagged($config->tagged);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateImportedDefinition(Definitions\ImportedDefinition $definition, \stdClass $config): void
|
||||
{
|
||||
if ($config->type) {
|
||||
$definition->setType($config->type);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function updateDefinition(Definitions\Definition $definition, \stdClass $config): void
|
||||
{
|
||||
if (isset($config->autowired)) {
|
||||
$definition->setAutowired($config->autowired);
|
||||
}
|
||||
|
||||
if (isset($config->tags)) {
|
||||
if (!empty($config->reset['tags'])) {
|
||||
$definition->setTags([]);
|
||||
}
|
||||
foreach ($config->tags as $tag => $attrs) {
|
||||
if (is_int($tag) && is_string($attrs)) {
|
||||
$definition->addTag($attrs);
|
||||
} else {
|
||||
$definition->addTag($tag, $attrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function convertKeyToName($key): ?string
|
||||
{
|
||||
if (is_int($key)) {
|
||||
return null;
|
||||
} elseif (preg_match('#^@[\w\\\\]+\z#', $key)) {
|
||||
return $this->getContainerBuilder()->getByType(substr($key, 1), true);
|
||||
}
|
||||
return $key;
|
||||
}
|
||||
|
||||
|
||||
private function retrieveDefinition(?string $name, \stdClass $config): Definitions\Definition
|
||||
{
|
||||
$builder = $this->getContainerBuilder();
|
||||
if (!empty($config->reset['all'])) {
|
||||
$builder->removeDefinition($name);
|
||||
}
|
||||
|
||||
return $name && $builder->hasDefinition($name)
|
||||
? $builder->getDefinition($name)
|
||||
: $builder->addDefinition($name, new $config->defType);
|
||||
}
|
||||
}
|
||||
Vendored
+255
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\PhpGenerator\PhpLiteral;
|
||||
use Nette\Utils\Reflection;
|
||||
|
||||
|
||||
/**
|
||||
* The DI helpers.
|
||||
* @internal
|
||||
*/
|
||||
class Helpers
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Expands %placeholders%.
|
||||
* @param mixed
|
||||
* @param array
|
||||
* @param bool|array
|
||||
* @return mixed
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expand($var, array $params, $recursive = false)
|
||||
{
|
||||
if (is_array($var)) {
|
||||
$res = [];
|
||||
foreach ($var as $key => $val) {
|
||||
$res[$key] = self::expand($val, $params, $recursive);
|
||||
}
|
||||
return $res;
|
||||
|
||||
} elseif ($var instanceof Statement) {
|
||||
return new Statement(self::expand($var->getEntity(), $params, $recursive), self::expand($var->arguments, $params, $recursive));
|
||||
|
||||
} elseif (!is_string($var)) {
|
||||
return $var;
|
||||
}
|
||||
|
||||
$parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE);
|
||||
$res = [];
|
||||
$php = false;
|
||||
foreach ($parts as $n => $part) {
|
||||
if ($n % 2 === 0) {
|
||||
$res[] = $part;
|
||||
|
||||
} elseif ($part === '') {
|
||||
$res[] = '%';
|
||||
|
||||
} elseif (isset($recursive[$part])) {
|
||||
throw new Nette\InvalidArgumentException(sprintf('Circular reference detected for variables: %s.', implode(', ', array_keys($recursive))));
|
||||
|
||||
} else {
|
||||
$val = $params;
|
||||
foreach (explode('.', $part) as $key) {
|
||||
if (is_array($val) && array_key_exists($key, $val)) {
|
||||
$val = $val[$key];
|
||||
} elseif ($val instanceof PhpLiteral) {
|
||||
$val = new PhpLiteral($val . '[' . var_export($key, true) . ']');
|
||||
} else {
|
||||
throw new Nette\InvalidArgumentException("Missing parameter '$part'.");
|
||||
}
|
||||
}
|
||||
if ($recursive) {
|
||||
$val = self::expand($val, $params, (is_array($recursive) ? $recursive : []) + [$part => 1]);
|
||||
}
|
||||
if (strlen($part) + 2 === strlen($var)) {
|
||||
return $val;
|
||||
}
|
||||
if ($val instanceof PhpLiteral) {
|
||||
$php = true;
|
||||
} elseif (!is_scalar($val)) {
|
||||
throw new Nette\InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$var'.");
|
||||
}
|
||||
$res[] = $val;
|
||||
}
|
||||
}
|
||||
if ($php) {
|
||||
$res = array_filter($res, function ($val) { return $val !== ''; });
|
||||
$res = array_map(function ($val) { return $val instanceof PhpLiteral ? "($val)" : var_export((string) $val, true); }, $res);
|
||||
return new PhpLiteral(implode(' . ', $res));
|
||||
}
|
||||
return implode('', $res);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates list of arguments using autowiring.
|
||||
* @return array
|
||||
* @throws ServiceCreationException
|
||||
*/
|
||||
public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, $container)
|
||||
{
|
||||
$optCount = 0;
|
||||
$num = -1;
|
||||
$res = [];
|
||||
$methodName = Reflection::toString($method) . '()';
|
||||
|
||||
foreach ($method->getParameters() as $num => $parameter) {
|
||||
$paramName = $parameter->getName();
|
||||
if (!$parameter->isVariadic() && array_key_exists($paramName, $arguments)) {
|
||||
$res[$num] = $arguments[$paramName];
|
||||
unset($arguments[$paramName], $arguments[$num]);
|
||||
$optCount = 0;
|
||||
|
||||
} elseif (array_key_exists($num, $arguments)) {
|
||||
$res[$num] = $arguments[$num];
|
||||
unset($arguments[$num]);
|
||||
$optCount = 0;
|
||||
|
||||
} elseif (($type = Reflection::getParameterType($parameter)) && !Reflection::isBuiltinType($type)) {
|
||||
try {
|
||||
$res[$num] = $container->getByType($type, false);
|
||||
} catch (ServiceCreationException $e) {
|
||||
throw new ServiceCreationException("{$e->getMessage()} (needed by $$paramName in $methodName)", 0, $e);
|
||||
}
|
||||
if ($res[$num] === null) {
|
||||
if ($parameter->allowsNull()) {
|
||||
$optCount++;
|
||||
} elseif (class_exists($type) || interface_exists($type)) {
|
||||
throw new ServiceCreationException("Service of type $type needed by $$paramName in $methodName not found. Did you register it in configuration file?");
|
||||
} else {
|
||||
throw new ServiceCreationException("Class $type needed by $$paramName in $methodName not found. Check type hint and 'use' statements.");
|
||||
}
|
||||
} else {
|
||||
if ($container instanceof ContainerBuilder) {
|
||||
$res[$num] = '@' . $res[$num];
|
||||
}
|
||||
$optCount = 0;
|
||||
}
|
||||
|
||||
} elseif (($type && $parameter->allowsNull()) || $parameter->isOptional() || $parameter->isDefaultValueAvailable()) {
|
||||
// !optional + defaultAvailable = func($a = null, $b) since 5.4.7
|
||||
// optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ...
|
||||
$res[$num] = $parameter->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($parameter) : null;
|
||||
$optCount++;
|
||||
|
||||
} else {
|
||||
throw new ServiceCreationException("Parameter $$paramName in $methodName has no class type hint or default value, so its value must be specified.");
|
||||
}
|
||||
}
|
||||
|
||||
// extra parameters
|
||||
while (array_key_exists(++$num, $arguments)) {
|
||||
$res[$num] = $arguments[$num];
|
||||
unset($arguments[$num]);
|
||||
$optCount = 0;
|
||||
}
|
||||
if ($arguments) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $methodName.");
|
||||
}
|
||||
|
||||
return $optCount ? array_slice($res, 0, -$optCount) : $res;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes ... and process constants recursively.
|
||||
* @return array
|
||||
*/
|
||||
public static function filterArguments(array $args)
|
||||
{
|
||||
foreach ($args as $k => $v) {
|
||||
if ($v === '...') {
|
||||
unset($args[$k]);
|
||||
} elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) {
|
||||
$args[$k] = constant(ltrim($v, ':'));
|
||||
} elseif (is_array($v)) {
|
||||
$args[$k] = self::filterArguments($v);
|
||||
} elseif ($v instanceof Statement) {
|
||||
$tmp = self::filterArguments([$v->getEntity()]);
|
||||
$args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments));
|
||||
}
|
||||
}
|
||||
return $args;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replaces @extension with real extension name in service definition.
|
||||
* @param mixed
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function prefixServiceName($config, $namespace)
|
||||
{
|
||||
if (is_string($config)) {
|
||||
if (strncmp($config, '@extension.', 10) === 0) {
|
||||
$config = '@' . $namespace . '.' . substr($config, 11);
|
||||
}
|
||||
} elseif ($config instanceof Statement) {
|
||||
return new Statement(
|
||||
self::prefixServiceName($config->getEntity(), $namespace),
|
||||
self::prefixServiceName($config->arguments, $namespace)
|
||||
);
|
||||
} elseif (is_array($config)) {
|
||||
foreach ($config as &$val) {
|
||||
$val = self::prefixServiceName($val, $namespace);
|
||||
}
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function parseAnnotation(\Reflector $ref, $name)
|
||||
{
|
||||
if (!Reflection::areCommentsAvailable()) {
|
||||
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
|
||||
}
|
||||
$name = preg_quote($name, '#');
|
||||
if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) {
|
||||
return isset($m[1]) ? $m[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func)
|
||||
{
|
||||
if ($type = Reflection::getReturnType($func)) {
|
||||
return $type;
|
||||
} elseif ($type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'))) {
|
||||
if ($type === 'object' || $type === 'mixed') {
|
||||
return null;
|
||||
} elseif ($func instanceof \ReflectionMethod) {
|
||||
return $type === 'static' || $type === '$this'
|
||||
? $func->getDeclaringClass()->getName()
|
||||
: Reflection::expandClassName($type, $func->getDeclaringClass());
|
||||
} else {
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static function normalizeClass($type)
|
||||
{
|
||||
return class_exists($type) || interface_exists($type)
|
||||
? (new \ReflectionClass($type))->getName()
|
||||
: $type;
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\PhpGenerator\Helpers as PhpHelpers;
|
||||
use Nette\PhpGenerator\PhpLiteral;
|
||||
use Nette\Utils\Reflection;
|
||||
use Nette\Utils\Strings;
|
||||
|
||||
|
||||
/**
|
||||
* Container PHP code generator.
|
||||
*/
|
||||
class PhpGenerator
|
||||
{
|
||||
/** @var ContainerBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var string */
|
||||
private $className;
|
||||
|
||||
/** @var Nette\PhpGenerator\ClassType[] */
|
||||
private $generatedClasses = [];
|
||||
|
||||
/** @var string */
|
||||
private $currentService;
|
||||
|
||||
|
||||
public function __construct(ContainerBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates PHP classes. First class is the container.
|
||||
* @return Nette\PhpGenerator\ClassType[]
|
||||
*/
|
||||
public function generate($className)
|
||||
{
|
||||
$this->builder->complete();
|
||||
|
||||
$this->generatedClasses = [];
|
||||
$this->className = $className;
|
||||
$containerClass = $this->generatedClasses[] = new Nette\PhpGenerator\ClassType($this->className);
|
||||
$containerClass->setExtends(Container::class);
|
||||
$containerClass->addMethod('__construct')
|
||||
->addBody('$this->parameters = $params;')
|
||||
->addBody('$this->parameters += ?;', [$this->builder->parameters])
|
||||
->addParameter('params', [])
|
||||
->setTypeHint('array');
|
||||
|
||||
$definitions = $this->builder->getDefinitions();
|
||||
ksort($definitions);
|
||||
|
||||
$meta = $containerClass->addProperty('meta')
|
||||
->setVisibility('protected')
|
||||
->setValue([Container::TYPES => $this->builder->getClassList()]);
|
||||
|
||||
foreach ($definitions as $name => $def) {
|
||||
$meta->value[Container::SERVICES][$name] = $def->getImplement() ?: $def->getType() ?: null;
|
||||
foreach ($def->getTags() as $tag => $value) {
|
||||
$meta->value[Container::TAGS][$tag][$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($definitions as $name => $def) {
|
||||
try {
|
||||
$name = (string) $name;
|
||||
$methodName = Container::getMethodName($name);
|
||||
if (!PhpHelpers::isIdentifier($methodName)) {
|
||||
throw new ServiceCreationException('Name contains invalid characters.');
|
||||
}
|
||||
$containerClass->addMethod($methodName)
|
||||
->addComment(PHP_VERSION_ID < 70000 ? '@return ' . ($def->getImplement() ?: $def->getType()) : '')
|
||||
->setReturnType(PHP_VERSION_ID >= 70000 ? ($def->getImplement() ?: $def->getType()) : null)
|
||||
->setBody($name === ContainerBuilder::THIS_CONTAINER ? 'return $this;' : $this->generateService($name))
|
||||
->setParameters($def->getImplement() ? [] : $this->convertParameters($def->parameters));
|
||||
} catch (\Exception $e) {
|
||||
throw new ServiceCreationException("Service '$name': " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
$aliases = $this->builder->getAliases();
|
||||
ksort($aliases);
|
||||
$meta->value[Container::ALIASES] = $aliases;
|
||||
|
||||
return $this->generatedClasses;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates body of service method.
|
||||
* @return string
|
||||
*/
|
||||
private function generateService($name)
|
||||
{
|
||||
$def = $this->builder->getDefinition($name);
|
||||
|
||||
if ($def->isDynamic()) {
|
||||
return PhpHelpers::formatArgs('throw new Nette\\DI\\ServiceCreationException(?);',
|
||||
["Unable to create dynamic service '$name', it must be added using addService()"]
|
||||
);
|
||||
}
|
||||
|
||||
$entity = $def->getFactory()->getEntity();
|
||||
$serviceRef = $this->builder->getServiceName($entity);
|
||||
$factory = $serviceRef && !$def->getFactory()->arguments && !$def->getSetup() && $def->getImplementMode() !== $def::IMPLEMENT_MODE_CREATE
|
||||
? new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$serviceRef])
|
||||
: $def->getFactory();
|
||||
|
||||
$this->currentService = null;
|
||||
$code = '$service = ' . $this->formatStatement($factory) . ";\n";
|
||||
|
||||
if (
|
||||
(PHP_VERSION_ID < 70000 || $def->getSetup())
|
||||
&& ($type = $def->getType())
|
||||
&& !$serviceRef && $type !== $entity
|
||||
&& !(is_string($entity) && preg_match('#^[\w\\\\]+\z#', $entity) && is_subclass_of($entity, $type))
|
||||
) {
|
||||
$code .= PhpHelpers::formatArgs("if (!\$service instanceof $type) {\n"
|
||||
. "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
|
||||
["Unable to create service '$name', value returned by factory is not $type type."]
|
||||
);
|
||||
}
|
||||
|
||||
$this->currentService = $name;
|
||||
foreach ($def->getSetup() as $setup) {
|
||||
$code .= $this->formatStatement($setup) . ";\n";
|
||||
}
|
||||
|
||||
$code .= 'return $service;';
|
||||
|
||||
if (!$def->getImplement()) {
|
||||
return $code;
|
||||
}
|
||||
|
||||
$factoryClass = (new Nette\PhpGenerator\ClassType)
|
||||
->addImplement($def->getImplement());
|
||||
|
||||
$factoryClass->addProperty('container')
|
||||
->setVisibility('private');
|
||||
|
||||
$factoryClass->addMethod('__construct')
|
||||
->addBody('$this->container = $container;')
|
||||
->addParameter('container')
|
||||
->setTypeHint($this->className);
|
||||
|
||||
$rm = new \ReflectionMethod($def->getImplement(), $def->getImplementMode());
|
||||
|
||||
$factoryClass->addMethod($def->getImplementMode())
|
||||
->setParameters($this->convertParameters($def->parameters))
|
||||
->setBody(str_replace('$this', '$this->container', $code))
|
||||
->setReturnType(PHP_VERSION_ID >= 70000 ? (Reflection::getReturnType($rm) ?: $def->getType()) : null);
|
||||
|
||||
if (PHP_VERSION_ID < 70000) {
|
||||
$this->generatedClasses[] = $factoryClass;
|
||||
$factoryClass->setName(str_replace(['\\', '.'], '_', "{$this->className}_{$def->getImplement()}Impl_{$name}"));
|
||||
return "return new {$factoryClass->getName()}(\$this);";
|
||||
}
|
||||
|
||||
return 'return new class ($this) ' . $factoryClass . ';';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats PHP code for class instantiating, function calling or property setting in PHP.
|
||||
* @return string
|
||||
*/
|
||||
private function formatStatement(Statement $statement)
|
||||
{
|
||||
$entity = $statement->getEntity();
|
||||
$arguments = $statement->arguments;
|
||||
|
||||
if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
|
||||
return $this->formatPhp($entity, $arguments);
|
||||
|
||||
} elseif ($service = $this->builder->getServiceName($entity)) { // factory calling
|
||||
return $this->formatPhp('$this->?(...?)', [Container::getMethodName($service), $arguments]);
|
||||
|
||||
} elseif ($entity === 'not') { // operator
|
||||
return $this->formatPhp('!?', [$arguments[0]]);
|
||||
|
||||
} elseif (is_string($entity)) { // class name
|
||||
return $this->formatPhp("new $entity" . ($arguments ? '(...?)' : ''), $arguments ? [$arguments] : []);
|
||||
|
||||
} elseif ($entity[0] === '') { // globalFunc
|
||||
return $this->formatPhp("$entity[1](...?)", [$arguments]);
|
||||
|
||||
} elseif ($entity[0] instanceof Statement) {
|
||||
$inner = $this->formatPhp('?', [$entity[0]]);
|
||||
if (substr($inner, 0, 4) === 'new ') {
|
||||
$inner = "($inner)";
|
||||
}
|
||||
return $this->formatPhp("$inner->?(...?)", [$entity[1], $arguments]);
|
||||
|
||||
} elseif ($entity[1][0] === '$') { // property getter, setter or appender
|
||||
$name = substr($entity[1], 1);
|
||||
if ($append = (substr($name, -2) === '[]')) {
|
||||
$name = substr($name, 0, -2);
|
||||
}
|
||||
if ($this->builder->getServiceName($entity[0])) {
|
||||
$prop = $this->formatPhp('?->?', [$entity[0], $name]);
|
||||
} else {
|
||||
$prop = $this->formatPhp($entity[0] . '::$?', [$name]);
|
||||
}
|
||||
return $arguments
|
||||
? $this->formatPhp($prop . ($append ? '[]' : '') . ' = ?', [$arguments[0]])
|
||||
: $prop;
|
||||
|
||||
} elseif ($service = $this->builder->getServiceName($entity[0])) { // service method
|
||||
return $this->formatPhp('?->?(...?)', [$entity[0], $entity[1], $arguments]);
|
||||
|
||||
} else { // static method
|
||||
return $this->formatPhp("$entity[0]::$entity[1](...?)", [$arguments]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Formats PHP statement.
|
||||
* @return string
|
||||
* @internal
|
||||
*/
|
||||
public function formatPhp($statement, $args)
|
||||
{
|
||||
array_walk_recursive($args, function (&$val) {
|
||||
if ($val instanceof Statement) {
|
||||
$val = new PhpLiteral($this->formatStatement($val));
|
||||
|
||||
} elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
|
||||
$val = substr($val, 1);
|
||||
|
||||
} elseif (is_string($val) && substr($val, 0, 1) === '@' && strlen($val) > 1) { // service reference
|
||||
$name = substr($val, 1);
|
||||
if ($name === ContainerBuilder::THIS_CONTAINER) {
|
||||
$val = new PhpLiteral('$this');
|
||||
} elseif ($name === $this->currentService) {
|
||||
$val = new PhpLiteral('$service');
|
||||
} else {
|
||||
$val = new PhpLiteral($this->formatStatement(new Statement(['@' . ContainerBuilder::THIS_CONTAINER, 'getService'], [$name])));
|
||||
}
|
||||
}
|
||||
});
|
||||
return PhpHelpers::formatArgs($statement, $args);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts parameters from ServiceDefinition to PhpGenerator.
|
||||
* @return Nette\PhpGenerator\Parameter[]
|
||||
*/
|
||||
private function convertParameters(array $parameters)
|
||||
{
|
||||
$res = [];
|
||||
foreach ($parameters as $k => $v) {
|
||||
$tmp = explode(' ', is_int($k) ? $v : $k);
|
||||
$param = $res[] = new Nette\PhpGenerator\Parameter(end($tmp));
|
||||
if (!is_int($k)) {
|
||||
@$param->setOptional(true); // @ deprecated in nette/php-generator 3.1
|
||||
$param->setDefaultValue($v);
|
||||
}
|
||||
if (isset($tmp[1])) {
|
||||
$param->setTypeHint($tmp[0]);
|
||||
}
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
+273
@@ -0,0 +1,273 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* PHP reflection helpers.
|
||||
* @internal
|
||||
* @deprecated
|
||||
*/
|
||||
class PhpReflection
|
||||
{
|
||||
use Nette\StaticClass;
|
||||
|
||||
/**
|
||||
* Returns an annotation value.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function parseAnnotation(\Reflector $ref, $name)
|
||||
{
|
||||
static $ok;
|
||||
if (!$ok) {
|
||||
if (!(new \ReflectionMethod(__METHOD__))->getDocComment()) {
|
||||
throw new Nette\InvalidStateException('You have to enable phpDoc comments in opcode cache.');
|
||||
}
|
||||
$ok = true;
|
||||
}
|
||||
$name = preg_quote($name, '#');
|
||||
if ($ref->getDocComment() && preg_match("#[\\s*]@$name(?:\\s++([^@]\\S*)?|$)#", trim($ref->getDocComment(), '/*'), $m)) {
|
||||
return isset($m[1]) ? $m[1] : '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns declaring class or trait.
|
||||
* @return \ReflectionClass
|
||||
*/
|
||||
public static function getDeclaringClass(\ReflectionProperty $prop)
|
||||
{
|
||||
foreach ($prop->getDeclaringClass()->getTraits() as $trait) {
|
||||
if ($trait->hasProperty($prop->getName())) {
|
||||
return self::getDeclaringClass($trait->getProperty($prop->getName()));
|
||||
}
|
||||
}
|
||||
return $prop->getDeclaringClass();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getParameterType(\ReflectionParameter $param)
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70000) {
|
||||
$type = $param->hasType() ? (string) $param->getType() : null;
|
||||
return strtolower($type) === 'self' ? $param->getDeclaringClass()->getName() : $type;
|
||||
} elseif ($param->isArray() || $param->isCallable()) {
|
||||
return $param->isArray() ? 'array' : 'callable';
|
||||
} else {
|
||||
try {
|
||||
return ($ref = $param->getClass()) ? $ref->getName() : null;
|
||||
} catch (\ReflectionException $e) {
|
||||
if (preg_match('#Class (.+) does not exist#', $e->getMessage(), $m)) {
|
||||
return $m[1];
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public static function getReturnType(\ReflectionFunctionAbstract $func)
|
||||
{
|
||||
if (PHP_VERSION_ID >= 70000 && $func->hasReturnType()) {
|
||||
$type = (string) $func->getReturnType();
|
||||
return strtolower($type) === 'self' ? $func->getDeclaringClass()->getName() : $type;
|
||||
}
|
||||
$type = preg_replace('#[|\s].*#', '', (string) self::parseAnnotation($func, 'return'));
|
||||
if ($type) {
|
||||
return $func instanceof \ReflectionMethod
|
||||
? self::expandClassName($type, $func->getDeclaringClass())
|
||||
: ltrim($type, '\\');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return bool
|
||||
*/
|
||||
public static function isBuiltinType($type)
|
||||
{
|
||||
return in_array(strtolower($type), ['string', 'int', 'float', 'bool', 'array', 'callable'], true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns class and all its descendants.
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getClassTree(\ReflectionClass $class)
|
||||
{
|
||||
$addTraits = function ($types) use (&$addTraits) {
|
||||
if ($traits = array_merge(...array_map('class_uses', array_values($types)))) {
|
||||
$types += $traits + $addTraits($traits);
|
||||
}
|
||||
return $types;
|
||||
};
|
||||
$class = $class->getName();
|
||||
return array_values($addTraits([$class] + class_parents($class) + class_implements($class)));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Expands class name into full name.
|
||||
* @param string
|
||||
* @return string full name
|
||||
* @throws Nette\InvalidArgumentException
|
||||
*/
|
||||
public static function expandClassName($name, \ReflectionClass $rc)
|
||||
{
|
||||
$lower = strtolower($name);
|
||||
if (empty($name)) {
|
||||
throw new Nette\InvalidArgumentException('Class name must not be empty.');
|
||||
|
||||
} elseif (self::isBuiltinType($lower)) {
|
||||
return $lower;
|
||||
|
||||
} elseif ($lower === 'self' || $lower === 'static' || $lower === '$this') {
|
||||
return $rc->getName();
|
||||
|
||||
} elseif ($name[0] === '\\') { // fully qualified name
|
||||
return ltrim($name, '\\');
|
||||
}
|
||||
|
||||
$uses = self::getUseStatements($rc);
|
||||
$parts = explode('\\', $name, 2);
|
||||
if (isset($uses[$parts[0]])) {
|
||||
$parts[0] = $uses[$parts[0]];
|
||||
return implode('\\', $parts);
|
||||
|
||||
} elseif ($rc->inNamespace()) {
|
||||
return $rc->getNamespaceName() . '\\' . $name;
|
||||
|
||||
} else {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array of [alias => class]
|
||||
*/
|
||||
public static function getUseStatements(\ReflectionClass $class)
|
||||
{
|
||||
static $cache = [];
|
||||
if (!isset($cache[$name = $class->getName()])) {
|
||||
if ($class->isInternal()) {
|
||||
$cache[$name] = [];
|
||||
} else {
|
||||
$code = file_get_contents($class->getFileName());
|
||||
$cache = self::parseUseStatements($code, $name) + $cache;
|
||||
}
|
||||
}
|
||||
return $cache[$name];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses PHP code.
|
||||
* @param string
|
||||
* @return array of [class => [alias => class, ...]]
|
||||
*/
|
||||
public static function parseUseStatements($code, $forClass = null)
|
||||
{
|
||||
$tokens = token_get_all($code);
|
||||
$namespace = $class = $classLevel = $level = null;
|
||||
$res = $uses = [];
|
||||
|
||||
while ($token = current($tokens)) {
|
||||
next($tokens);
|
||||
switch (is_array($token) ? $token[0] : $token) {
|
||||
case T_NAMESPACE:
|
||||
$namespace = ltrim(self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]) . '\\', '\\');
|
||||
$uses = [];
|
||||
break;
|
||||
|
||||
case T_CLASS:
|
||||
case T_INTERFACE:
|
||||
case T_TRAIT:
|
||||
if ($name = self::fetch($tokens, T_STRING)) {
|
||||
$class = $namespace . $name;
|
||||
$classLevel = $level + 1;
|
||||
$res[$class] = $uses;
|
||||
if ($class === $forClass) {
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_USE:
|
||||
while (!$class && ($name = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR]))) {
|
||||
$name = ltrim($name, '\\');
|
||||
if (self::fetch($tokens, '{')) {
|
||||
while ($suffix = self::fetch($tokens, [T_STRING, T_NS_SEPARATOR])) {
|
||||
if (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name . $suffix;
|
||||
} else {
|
||||
$tmp = explode('\\', $suffix);
|
||||
$uses[end($tmp)] = $name . $suffix;
|
||||
}
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (self::fetch($tokens, T_AS)) {
|
||||
$uses[self::fetch($tokens, T_STRING)] = $name;
|
||||
|
||||
} else {
|
||||
$tmp = explode('\\', $name);
|
||||
$uses[end($tmp)] = $name;
|
||||
}
|
||||
if (!self::fetch($tokens, ',')) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case T_CURLY_OPEN:
|
||||
case T_DOLLAR_OPEN_CURLY_BRACES:
|
||||
case '{':
|
||||
$level++;
|
||||
break;
|
||||
|
||||
case '}':
|
||||
if ($level === $classLevel) {
|
||||
$class = $classLevel = null;
|
||||
}
|
||||
$level--;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
|
||||
private static function fetch(&$tokens, $take)
|
||||
{
|
||||
$res = null;
|
||||
while ($token = current($tokens)) {
|
||||
list($token, $s) = is_array($token) ? $token : [$token, $token];
|
||||
if (in_array($token, (array) $take, true)) {
|
||||
$res .= $s;
|
||||
} elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) {
|
||||
break;
|
||||
}
|
||||
next($tokens);
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
Vendored
+533
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
use Nette\DI\Definitions\Definition;
|
||||
use Nette\DI\Definitions\Reference;
|
||||
use Nette\DI\Definitions\Statement;
|
||||
use Nette\PhpGenerator\Helpers as PhpHelpers;
|
||||
use Nette\Utils\Reflection;
|
||||
use Nette\Utils\Strings;
|
||||
use Nette\Utils\Validators;
|
||||
use ReflectionClass;
|
||||
|
||||
|
||||
/**
|
||||
* Services resolver
|
||||
* @internal
|
||||
*/
|
||||
class Resolver
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var ContainerBuilder */
|
||||
private $builder;
|
||||
|
||||
/** @var Definition|null */
|
||||
private $currentService;
|
||||
|
||||
/** @var string|null */
|
||||
private $currentServiceType;
|
||||
|
||||
/** @var bool */
|
||||
private $currentServiceAllowed;
|
||||
|
||||
/** @var \SplObjectStorage circular reference detector */
|
||||
private $recursive;
|
||||
|
||||
|
||||
public function __construct(ContainerBuilder $builder)
|
||||
{
|
||||
$this->builder = $builder;
|
||||
$this->recursive = new \SplObjectStorage;
|
||||
}
|
||||
|
||||
|
||||
public function getContainerBuilder(): ContainerBuilder
|
||||
{
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
||||
public function resolveDefinition(Definition $def): void
|
||||
{
|
||||
if ($this->recursive->contains($def)) {
|
||||
$names = array_map(function ($item) { return $item->getName(); }, iterator_to_array($this->recursive));
|
||||
throw new ServiceCreationException(sprintf('Circular reference detected for services: %s.', implode(', ', $names)));
|
||||
}
|
||||
|
||||
try {
|
||||
$this->recursive->attach($def);
|
||||
|
||||
$def->resolveType($this);
|
||||
|
||||
if (!$def->getType()) {
|
||||
throw new ServiceCreationException('Type of service is unknown.');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
throw $this->completeException($e, $def);
|
||||
|
||||
} finally {
|
||||
$this->recursive->detach($def);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function resolveReferenceType(Reference $ref): ?string
|
||||
{
|
||||
if ($ref->isSelf()) {
|
||||
return $this->currentServiceType;
|
||||
} elseif ($ref->isType()) {
|
||||
return ltrim($ref->getValue(), '\\');
|
||||
}
|
||||
$def = $this->resolveReference($ref);
|
||||
if (!$def->getType()) {
|
||||
$this->resolveDefinition($def);
|
||||
}
|
||||
return $def->getType();
|
||||
}
|
||||
|
||||
|
||||
public function resolveEntityType(Statement $statement): ?string
|
||||
{
|
||||
$entity = $this->normalizeEntity($statement);
|
||||
|
||||
if (is_array($entity)) {
|
||||
if ($entity[0] instanceof Reference || $entity[0] instanceof Statement) {
|
||||
$entity[0] = $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]));
|
||||
if (!$entity[0]) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$reflection = Nette\Utils\Callback::toReflection($entity[0] === '' ? $entity[1] : $entity);
|
||||
$refClass = $reflection instanceof \ReflectionMethod ? $reflection->getDeclaringClass() : null;
|
||||
} catch (\ReflectionException $e) {
|
||||
}
|
||||
|
||||
if (isset($e) || ($refClass && (!$reflection->isPublic()
|
||||
|| ($refClass->isTrait() && !$reflection->isStatic())
|
||||
))) {
|
||||
throw new ServiceCreationException(sprintf('Method %s() is not callable.', Nette\Utils\Callback::toString($entity)), 0, $e ?? null);
|
||||
}
|
||||
$this->addDependency($reflection);
|
||||
|
||||
$type = Helpers::getReturnType($reflection);
|
||||
if ($type && !class_exists($type) && !interface_exists($type)) {
|
||||
throw new ServiceCreationException(sprintf("Class or interface '%s' not found. Is return type of %s() correct?", $type, Nette\Utils\Callback::toString($entity)));
|
||||
}
|
||||
return $type;
|
||||
|
||||
} elseif ($entity instanceof Reference) { // alias or factory
|
||||
return $this->resolveReferenceType($entity);
|
||||
|
||||
} elseif (is_string($entity)) { // class
|
||||
if (!class_exists($entity)) {
|
||||
throw new ServiceCreationException("Class $entity not found.");
|
||||
}
|
||||
return $entity;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public function completeDefinition(Definition $def): void
|
||||
{
|
||||
$this->currentService = in_array($def, $this->builder->getDefinitions(), true) ? $def : null;
|
||||
$this->currentServiceType = $def->getType();
|
||||
$this->currentServiceAllowed = false;
|
||||
|
||||
try {
|
||||
$def->complete($this);
|
||||
|
||||
$this->addDependency(new \ReflectionClass($def->getType()));
|
||||
|
||||
} catch (\Exception $e) {
|
||||
throw $this->completeException($e, $def);
|
||||
|
||||
} finally {
|
||||
$this->currentService = $this->currentServiceType = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function completeStatement(Statement $statement, bool $currentServiceAllowed = false): Statement
|
||||
{
|
||||
$this->currentServiceAllowed = $currentServiceAllowed;
|
||||
$entity = $this->normalizeEntity($statement);
|
||||
$arguments = $this->convertReferences($statement->arguments);
|
||||
|
||||
switch (true) {
|
||||
case is_string($entity) && Strings::contains($entity, '?'): // PHP literal
|
||||
break;
|
||||
|
||||
case $entity === 'not':
|
||||
$entity = ['', '!'];
|
||||
break;
|
||||
|
||||
case is_string($entity): // create class
|
||||
if (!class_exists($entity)) {
|
||||
throw new ServiceCreationException("Class $entity not found.");
|
||||
} elseif ((new ReflectionClass($entity))->isAbstract()) {
|
||||
throw new ServiceCreationException("Class $entity is abstract.");
|
||||
} elseif (($rm = (new ReflectionClass($entity))->getConstructor()) !== null && !$rm->isPublic()) {
|
||||
$visibility = $rm->isProtected() ? 'protected' : 'private';
|
||||
throw new ServiceCreationException("Class $entity has $visibility constructor.");
|
||||
} elseif ($constructor = (new ReflectionClass($entity))->getConstructor()) {
|
||||
$arguments = self::autowireArguments($constructor, $arguments, $this, $this->currentService);
|
||||
$this->addDependency($constructor);
|
||||
} elseif ($arguments) {
|
||||
throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
|
||||
}
|
||||
break;
|
||||
|
||||
case $entity instanceof Reference:
|
||||
$entity = [new Reference(ContainerBuilder::THIS_CONTAINER), Container::getMethodName($entity->getValue())];
|
||||
break;
|
||||
|
||||
case is_array($entity):
|
||||
if (!preg_match('#^\$?(\\\\?' . PhpHelpers::PHP_IDENT . ')+(\[\])?\z#', $entity[1])) {
|
||||
throw new ServiceCreationException("Expected function, method or property name, '$entity[1]' given.");
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
case $entity[0] === '': // function call
|
||||
if (!Nette\Utils\Arrays::isList($arguments)) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $entity[0].");
|
||||
} elseif (!function_exists($entity[1])) {
|
||||
throw new ServiceCreationException("Function $entity[1] doesn't exist.");
|
||||
}
|
||||
$rf = new \ReflectionFunction($entity[1]);
|
||||
$arguments = self::autowireArguments($rf, $arguments, $this, $this->currentService);
|
||||
$this->addDependency($rf);
|
||||
break;
|
||||
|
||||
case $entity[0] instanceof Statement:
|
||||
$entity[0] = $this->completeStatement($entity[0], $this->currentServiceAllowed);
|
||||
// break omitted
|
||||
|
||||
case is_string($entity[0]): // static method call
|
||||
case $entity[0] instanceof Reference:
|
||||
if ($entity[1][0] === '$') { // property getter, setter or appender
|
||||
Validators::assert($arguments, 'list:0..1', "setup arguments for '" . Nette\Utils\Callback::toString($entity) . "'");
|
||||
if (!$arguments && substr($entity[1], -2) === '[]') {
|
||||
throw new ServiceCreationException("Missing argument for $entity[1].");
|
||||
}
|
||||
} elseif (
|
||||
$type = $entity[0] instanceof Reference
|
||||
? $this->resolveReferenceType($entity[0])
|
||||
: $this->resolveEntityType($entity[0] instanceof Statement ? $entity[0] : new Statement($entity[0]))
|
||||
) {
|
||||
$rc = new ReflectionClass($type);
|
||||
if ($rc->hasMethod($entity[1])) {
|
||||
$rm = $rc->getMethod($entity[1]);
|
||||
if (!$rm->isPublic()) {
|
||||
throw new ServiceCreationException("$type::$entity[1]() is not callable.");
|
||||
}
|
||||
$arguments = self::autowireArguments($rm, $arguments, $this, $this->currentService);
|
||||
$this->addDependency($rm);
|
||||
|
||||
} elseif (!Nette\Utils\Arrays::isList($arguments)) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $type::$entity[1]().");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$arguments = $this->completeArguments($arguments);
|
||||
} catch (ServiceCreationException $e) {
|
||||
if (!strpos($e->getMessage(), ' (used in')) {
|
||||
$e->setMessage($e->getMessage() . " (used in {$this->entityToString($entity)})");
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return new Statement($entity, $arguments);
|
||||
}
|
||||
|
||||
|
||||
private function completeArguments(array $arguments): array
|
||||
{
|
||||
array_walk_recursive($arguments, function (&$val): void {
|
||||
if ($val instanceof Statement) {
|
||||
$entity = $val->getEntity();
|
||||
if ($entity === 'typed' || $entity === 'tagged') {
|
||||
$services = [];
|
||||
$current = $this->currentService ? $this->currentService->getName() : null;
|
||||
foreach ($val->arguments as $argument) {
|
||||
foreach ($entity === 'tagged' ? $this->builder->findByTag($argument) : $this->builder->findAutowired($argument) as $name => $foo) {
|
||||
if ($name !== $current) {
|
||||
$services[] = new Reference($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
$val = $this->completeArguments($services);
|
||||
} else {
|
||||
$val = $this->completeStatement($val, $this->currentServiceAllowed);
|
||||
}
|
||||
|
||||
} elseif ($val instanceof Definition || $val instanceof Reference) {
|
||||
$val = $this->normalizeEntity(new Statement($val));
|
||||
}
|
||||
});
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|array|Reference literal, Class, Reference, [Class, member], [, globalFunc], [Reference, member], [Statement, member]
|
||||
*/
|
||||
private function normalizeEntity(Statement $statement)
|
||||
{
|
||||
$entity = $statement->getEntity();
|
||||
if (is_array($entity)) {
|
||||
$item = &$entity[0];
|
||||
} else {
|
||||
$item = &$entity;
|
||||
}
|
||||
|
||||
if ($item instanceof Definition) {
|
||||
$name = current(array_keys($this->builder->getDefinitions(), $item, true));
|
||||
if ($name == false) {
|
||||
throw new ServiceCreationException("Service '{$item->getName()}' not found in definitions.");
|
||||
}
|
||||
$item = new Reference($name);
|
||||
}
|
||||
if ($item instanceof Reference) {
|
||||
$item = $this->normalizeReference($item);
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Normalizes reference to 'self' or named reference (or leaves it typed if it is not possible during resolving) and checks existence of service.
|
||||
*/
|
||||
public function normalizeReference(Reference $ref): Reference
|
||||
{
|
||||
$service = $ref->getValue();
|
||||
if ($ref->isSelf()) {
|
||||
return $ref;
|
||||
} elseif ($ref->isName()) {
|
||||
if (!$this->builder->hasDefinition($service)) {
|
||||
throw new ServiceCreationException("Reference to missing service '$service'.");
|
||||
}
|
||||
return $this->currentService && $service === $this->currentService->getName()
|
||||
? new Reference(Reference::SELF)
|
||||
: $ref;
|
||||
} else {
|
||||
try {
|
||||
$res = $this->getByType($service);
|
||||
} catch (NotAllowedDuringResolvingException $e) {
|
||||
return new Reference($service);
|
||||
}
|
||||
if (!$res) {
|
||||
throw new ServiceCreationException("Reference to missing service of type $service.");
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function resolveReference(Reference $ref): Definition
|
||||
{
|
||||
return $ref->isSelf()
|
||||
? $this->currentService
|
||||
: $this->builder->getDefinition($ref->getValue());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns named reference to service resolved by type (or 'self' reference for local-autowiring).
|
||||
* @throws ServiceCreationException when multiple found
|
||||
* @throws MissingServiceException when not found
|
||||
*/
|
||||
public function getByType(string $type): Reference
|
||||
{
|
||||
if (
|
||||
$this->currentService
|
||||
&& $this->currentServiceAllowed
|
||||
&& is_a($this->currentServiceType, $type, true)
|
||||
) {
|
||||
return new Reference(Reference::SELF);
|
||||
}
|
||||
return new Reference($this->builder->getByType($type, true));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds item to the list of dependencies.
|
||||
* @param \ReflectionClass|\ReflectionFunctionAbstract|string $dep
|
||||
* @return static
|
||||
*/
|
||||
public function addDependency($dep)
|
||||
{
|
||||
$this->builder->addDependency($dep);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
private function completeException(\Exception $e, Definition $def): ServiceCreationException
|
||||
{
|
||||
if ($e instanceof ServiceCreationException && Strings::startsWith($e->getMessage(), "Service '")) {
|
||||
return $e;
|
||||
} else {
|
||||
$name = $def->getName();
|
||||
$type = $def->getType();
|
||||
if (!$type) {
|
||||
$message = "Service '$name': " . $e->getMessage();
|
||||
} elseif (!$name || ctype_digit($name)) {
|
||||
$message = "Service of type $type: " . str_replace("$type::", '', $e->getMessage());
|
||||
} else {
|
||||
$message = "Service '$name' (type of $type): " . str_replace("$type::", '', $e->getMessage());
|
||||
}
|
||||
return $e instanceof ServiceCreationException
|
||||
? $e->setMessage($message)
|
||||
: new ServiceCreationException($message, 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private function entityToString($entity): string
|
||||
{
|
||||
$referenceToText = function (Reference $ref): string {
|
||||
return $ref->isSelf() && $this->currentService
|
||||
? '@' . $this->currentService->getName()
|
||||
: '@' . $ref->getValue();
|
||||
};
|
||||
if (is_string($entity)) {
|
||||
return $entity . '::__construct()';
|
||||
} elseif ($entity instanceof Reference) {
|
||||
$entity = $referenceToText($entity);
|
||||
} elseif (is_array($entity)) {
|
||||
if (strpos($entity[1], '$') === false) {
|
||||
$entity[1] .= '()';
|
||||
}
|
||||
if ($entity[0] instanceof Reference) {
|
||||
$entity[0] = $referenceToText($entity[0]);
|
||||
} elseif (!is_string($entity[0])) {
|
||||
return $entity[1];
|
||||
}
|
||||
return implode('::', $entity);
|
||||
}
|
||||
return (string) $entity;
|
||||
}
|
||||
|
||||
|
||||
private function convertReferences(array $arguments): array
|
||||
{
|
||||
array_walk_recursive($arguments, function (&$val): void {
|
||||
if (is_string($val) && strlen($val) > 1 && $val[0] === '@' && $val[1] !== '@') {
|
||||
$pair = explode('::', substr($val, 1), 2);
|
||||
if (!isset($pair[1])) { // @service
|
||||
$val = new Reference($pair[0]);
|
||||
} elseif (preg_match('#^[A-Z][A-Z0-9_]*\z#', $pair[1], $m)) { // @service::CONSTANT
|
||||
$val = ContainerBuilder::literal($this->resolveReferenceType(new Reference($pair[0])) . '::' . $pair[1]);
|
||||
} else { // @service::property
|
||||
$val = new Statement([new Reference($pair[0]), '$' . $pair[1]]);
|
||||
}
|
||||
|
||||
} elseif (is_string($val) && substr($val, 0, 2) === '@@') { // escaped text @@
|
||||
$val = substr($val, 1);
|
||||
}
|
||||
});
|
||||
return $arguments;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add missing arguments using autowiring.
|
||||
* @param Resolver|Container $resolver
|
||||
* @throws ServiceCreationException
|
||||
*/
|
||||
public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, $resolver, Definitions\Definition $current = null): array
|
||||
{
|
||||
$optCount = 0;
|
||||
$num = -1;
|
||||
$res = [];
|
||||
$methodName = Reflection::toString($method) . '()';
|
||||
|
||||
foreach ($method->getParameters() as $num => $parameter) {
|
||||
$paramName = $parameter->getName();
|
||||
if (!$parameter->isVariadic() && array_key_exists($paramName, $arguments)) {
|
||||
$res[$num] = $arguments[$paramName];
|
||||
unset($arguments[$paramName], $arguments[$num]);
|
||||
$optCount = 0;
|
||||
|
||||
} elseif (array_key_exists($num, $arguments)) {
|
||||
$res[$num] = $arguments[$num];
|
||||
unset($arguments[$num]);
|
||||
$optCount = 0;
|
||||
|
||||
} elseif (($type = Reflection::getParameterType($parameter)) && !Reflection::isBuiltinType($type)) {
|
||||
try {
|
||||
$res[$num] = $resolver->getByType($type);
|
||||
} catch (MissingServiceException $e) {
|
||||
$res[$num] = null;
|
||||
} catch (ServiceCreationException $e) {
|
||||
throw new ServiceCreationException("{$e->getMessage()} (needed by $$paramName in $methodName)", 0, $e);
|
||||
}
|
||||
if ($res[$num] === null) {
|
||||
if ($parameter->allowsNull()) {
|
||||
$optCount++;
|
||||
} elseif (class_exists($type) || interface_exists($type)) {
|
||||
throw new ServiceCreationException("Service of type $type needed by $$paramName in $methodName not found. Did you register it in configuration file?");
|
||||
} else {
|
||||
throw new ServiceCreationException("Class $type needed by $$paramName in $methodName not found. Check type hint and 'use' statements.");
|
||||
}
|
||||
} else {
|
||||
$optCount = 0;
|
||||
}
|
||||
|
||||
} elseif (
|
||||
$method instanceof \ReflectionMethod
|
||||
&& $parameter->isArray()
|
||||
&& preg_match('#@param[ \t]+([\w\\\\]+)\[\][ \t]+\$' . $paramName . '#', (string) $method->getDocComment(), $m)
|
||||
&& ($type = Reflection::expandClassName($m[1], $method->getDeclaringClass()))
|
||||
&& (class_exists($type) || interface_exists($type))
|
||||
) {
|
||||
$list = $resolver instanceof self
|
||||
? $resolver->getContainerBuilder()->findAutowired($type)
|
||||
: array_map([$resolver, 'getService'], $resolver->findAutowired($type));
|
||||
$res[$num] = [];
|
||||
foreach ($list as $item) {
|
||||
if ($item !== $current) {
|
||||
$res[$num][] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (($type && $parameter->allowsNull()) || $parameter->isOptional() || $parameter->isDefaultValueAvailable()) {
|
||||
// !optional + defaultAvailable = func($a = null, $b) since 5.4.7
|
||||
// optional + !defaultAvailable = i.e. Exception::__construct, mysqli::mysqli, ...
|
||||
$res[$num] = $parameter->isDefaultValueAvailable() ? Reflection::getParameterDefaultValue($parameter) : null;
|
||||
$optCount++;
|
||||
|
||||
} else {
|
||||
throw new ServiceCreationException("Parameter $$paramName in $methodName has no class type hint or default value, so its value must be specified.");
|
||||
}
|
||||
}
|
||||
|
||||
// extra parameters
|
||||
while (array_key_exists(++$num, $arguments)) {
|
||||
$res[$num] = $arguments[$num];
|
||||
unset($arguments[$num]);
|
||||
$optCount = 0;
|
||||
}
|
||||
if ($arguments) {
|
||||
throw new ServiceCreationException("Unable to pass specified arguments to $methodName.");
|
||||
}
|
||||
|
||||
return $optCount ? array_slice($res, 0, -$optCount) : $res;
|
||||
}
|
||||
}
|
||||
+381
@@ -0,0 +1,381 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Definition used by ContainerBuilder.
|
||||
*
|
||||
* @property string|null $class
|
||||
* @property Statement|null $factory
|
||||
* @property Statement[] $setup
|
||||
*/
|
||||
class ServiceDefinition
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
const
|
||||
IMPLEMENT_MODE_CREATE = 'create',
|
||||
IMPLEMENT_MODE_GET = 'get';
|
||||
|
||||
/** @var array */
|
||||
public $parameters = [];
|
||||
|
||||
/** @var string|null class or interface name */
|
||||
private $type;
|
||||
|
||||
/** @var Statement|null */
|
||||
private $factory;
|
||||
|
||||
/** @var Statement[] */
|
||||
private $setup = [];
|
||||
|
||||
/** @var array */
|
||||
private $tags = [];
|
||||
|
||||
/** @var bool|string[] */
|
||||
private $autowired = true;
|
||||
|
||||
/** @var bool */
|
||||
private $dynamic = false;
|
||||
|
||||
/** @var string|null interface name */
|
||||
private $implement;
|
||||
|
||||
/** @var string|null create | get */
|
||||
private $implementMode;
|
||||
|
||||
/** @var callable 'pi' is noop */
|
||||
private $notifier = 'pi';
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null
|
||||
* @return static
|
||||
* @deprecated Use setType() instead.
|
||||
*/
|
||||
public function setClass($type, array $args = [])
|
||||
{
|
||||
call_user_func($this->notifier);
|
||||
$this->type = $type;
|
||||
if ($args) {
|
||||
$this->setFactory($type, $args);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
* @deprecated Use getType() instead.
|
||||
*/
|
||||
public function getClass()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string|null
|
||||
* @return static
|
||||
*/
|
||||
public function setType($type)
|
||||
{
|
||||
call_user_func($this->notifier);
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setFactory($factory, array $args = [])
|
||||
{
|
||||
call_user_func($this->notifier);
|
||||
$this->factory = $factory instanceof Statement ? $factory : new Statement($factory, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Statement|null
|
||||
*/
|
||||
public function getFactory()
|
||||
{
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|array|ServiceDefinition|null
|
||||
*/
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->factory ? $this->factory->getEntity() : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setArguments(array $args = [])
|
||||
{
|
||||
if (!$this->factory) {
|
||||
$this->factory = new Statement($this->type);
|
||||
}
|
||||
$this->factory->arguments = $args;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Statement[]
|
||||
* @return static
|
||||
*/
|
||||
public function setSetup(array $setup)
|
||||
{
|
||||
foreach ($setup as $v) {
|
||||
if (!$v instanceof Statement) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be Nette\DI\Statement[].');
|
||||
}
|
||||
}
|
||||
$this->setup = $setup;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Statement[]
|
||||
*/
|
||||
public function getSetup()
|
||||
{
|
||||
return $this->setup;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function addSetup($entity, array $args = [])
|
||||
{
|
||||
$this->setup[] = $entity instanceof Statement ? $entity : new Statement($entity, $args);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setParameters(array $params)
|
||||
{
|
||||
$this->parameters = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getParameters()
|
||||
{
|
||||
return $this->parameters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function setTags(array $tags)
|
||||
{
|
||||
$this->tags = $tags;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getTags()
|
||||
{
|
||||
return $this->tags;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function addTag($tag, $attr = true)
|
||||
{
|
||||
$this->tags[$tag] = $attr;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTag($tag)
|
||||
{
|
||||
return isset($this->tags[$tag]) ? $this->tags[$tag] : null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool|string|string[]
|
||||
* @return static
|
||||
*/
|
||||
public function setAutowired($state = true)
|
||||
{
|
||||
call_user_func($this->notifier);
|
||||
$this->autowired = is_string($state) || is_array($state) ? (array) $state : (bool) $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool|string[]
|
||||
*/
|
||||
public function isAutowired()
|
||||
{
|
||||
return $this->autowired;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool|string[]
|
||||
*/
|
||||
public function getAutowired()
|
||||
{
|
||||
return $this->autowired;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param bool
|
||||
* @return static
|
||||
*/
|
||||
public function setDynamic($state = true)
|
||||
{
|
||||
$this->dynamic = (bool) $state;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDynamic()
|
||||
{
|
||||
return $this->dynamic;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return static
|
||||
*/
|
||||
public function setImplement($interface)
|
||||
{
|
||||
call_user_func($this->notifier);
|
||||
$this->implement = $interface;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getImplement()
|
||||
{
|
||||
return $this->implement;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string
|
||||
* @return static
|
||||
*/
|
||||
public function setImplementMode($mode)
|
||||
{
|
||||
if (!in_array($mode, [self::IMPLEMENT_MODE_CREATE, self::IMPLEMENT_MODE_GET], true)) {
|
||||
throw new Nette\InvalidArgumentException('Argument must be get|create.');
|
||||
}
|
||||
$this->implementMode = $mode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function getImplementMode()
|
||||
{
|
||||
return $this->implementMode;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function setImplementType($type)
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use setImplementMode()', E_USER_DEPRECATED);
|
||||
return $this->setImplementMode($type);
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function getImplementType()
|
||||
{
|
||||
trigger_error(__METHOD__ . '() is deprecated, use getImplementMode()', E_USER_DEPRECATED);
|
||||
return $this->implementMode;
|
||||
}
|
||||
|
||||
|
||||
/** @return static */
|
||||
public function setInject($state = true)
|
||||
{
|
||||
//trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
|
||||
return $this->addTag(Extensions\InjectExtension::TAG_INJECT, $state);
|
||||
}
|
||||
|
||||
|
||||
/** @return bool|null */
|
||||
public function getInject()
|
||||
{
|
||||
//trigger_error(__METHOD__ . '() is deprecated.', E_USER_DEPRECATED);
|
||||
return $this->getTag(Extensions\InjectExtension::TAG_INJECT);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setNotifier(callable $notifier)
|
||||
{
|
||||
$this->notifier = $notifier;
|
||||
}
|
||||
|
||||
|
||||
public function __clone()
|
||||
{
|
||||
$this->factory = unserialize(serialize($this->factory));
|
||||
$this->setup = unserialize(serialize($this->setup));
|
||||
$this->notifier = 'pi';
|
||||
}
|
||||
}
|
||||
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Assignment or calling statement.
|
||||
*
|
||||
* @property string|array|ServiceDefinition|null $entity
|
||||
*/
|
||||
class Statement
|
||||
{
|
||||
use Nette\SmartObject;
|
||||
|
||||
/** @var array */
|
||||
public $arguments;
|
||||
|
||||
/** @var string|array|ServiceDefinition|null */
|
||||
private $entity;
|
||||
|
||||
|
||||
/**
|
||||
* @param string|array|ServiceDefinition|null
|
||||
*/
|
||||
public function __construct($entity, array $arguments = [])
|
||||
{
|
||||
if (
|
||||
!is_string($entity) // Class, @service, not, PHP literal, entity::member
|
||||
&& !(is_array($entity) && isset($entity[0], $entity[1])) // [Class | @service | '' | Statement | ServiceDefinition, method | $property | $appender]
|
||||
&& !$entity instanceof ServiceDefinition
|
||||
&& $entity !== null
|
||||
) {
|
||||
throw new Nette\InvalidArgumentException('Argument is not valid Statement entity.');
|
||||
}
|
||||
$this->entity = $entity;
|
||||
$this->arguments = $arguments;
|
||||
}
|
||||
|
||||
|
||||
/** @deprecated */
|
||||
public function setEntity($entity)
|
||||
{
|
||||
trigger_error(__METHOD__ . ' is deprecated, change Statement object itself.', E_USER_DEPRECATED);
|
||||
$this->__construct($entity, $this->arguments);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function getEntity()
|
||||
{
|
||||
return $this->entity;
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
namespace Nette\DI;
|
||||
|
||||
use Nette;
|
||||
|
||||
|
||||
/**
|
||||
* Service not found exception.
|
||||
*/
|
||||
class MissingServiceException extends Nette\InvalidStateException
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Service creation exception.
|
||||
*/
|
||||
class ServiceCreationException extends Nette\InvalidStateException
|
||||
{
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This file is part of the Nette Framework (https://nette.org)
|
||||
* Copyright (c) 2004 David Grudl (https://davidgrudl.com)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class_alias(Nette\DI\Config\Adapter::class, Nette\DI\Config\IAdapter::class);
|
||||
class_alias(Nette\DI\Definitions\Statement::class, Nette\DI\Statement::class);
|
||||
class_alias(Nette\DI\Definitions\ServiceDefinition::class, Nette\DI\ServiceDefinition::class);
|
||||
Reference in New Issue
Block a user