Створення розширень для Nette DI
Створення контейнера DI на додаток до файлів конфігурації
також впливає на так звані розширення. Ми активуємо їх у файлі
конфігурації в секції extensions
.
Так ми додаємо розширення, представлене класом BlogExtension
з
іменем blog
:
extensions:
blog: BlogExtension
Кожне розширення компілятора успадковує від Nette\DI\CompilerExtension і може реалізувати такі методи, які викликаються під час компіляції DI:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Цей метод викликається першим. Він визначає схему, що використовується для перевірки параметрів конфігурації.
Розширення налаштовані в секції, ім'я якої збігається з ім'ям, під
яким додано розширення, наприклад, blog
.
# те саме ім'я, що й у розширення
blog:
postsPerPage: 10
comments: false
Ми визначимо схему, що описує всі параметри конфігурації, включно з їхніми типами, прийнятими значеннями і, можливо, значеннями за замовчуванням:
use Nette\Schema\Expect;
class BlogExtension extends Nette\DI\CompilerExtension
{
public function getConfigSchema(): Nette\Schema\Schema
{
return Expect::structure([
'postsPerPage' => Expect::int(),
'allowComments' => Expect::bool()->default(true),
]);
}
}
Див. Schema для отримання інформації. Крім того,
можна вказати, які опції можуть бути динамічними за допомогою
dynamic()', например `Expect::int()->dynamic()
.
Ми отримуємо доступ до конфігурації через $this->config
, який є
об'єктом stdClass
:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$num = $this->config->postPerPage;
if ($this->config->allowComments) {
// ...
}
}
}
loadConfiguration()
Цей метод використовується для додавання сервісів у контейнер. Це робиться за допомогою Nette\DI\ContainerBuilder:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
$builder->addDefinition($this->prefix('articles'))
->setFactory(App\Model\HomepageArticles::class, ['@connection']) // або setCreator()
->addSetup('setLogger', ['@logger']);
}
}
Конвенція передбачає префіксування сервісів, доданих розширенням з
його ім'ям, щоб не виникали конфлікти імен. Це робиться за допомогою
prefix()', так что если расширение называется 'blog', то служба будет называться `blog.articles
.
Якщо нам потрібно перейменувати сервіс, ми можемо створити псевдонім
з його початковим ім'ям, щоб зберегти зворотну сумісність. Так само
робить Nette для routing.router
, який також доступний під ранньою назвою
router
.
$builder->addAlias('router', 'routing.router');
Отримання сервісів із файлу
Ми можемо створювати сервіси за допомогою API ContainerBuilder, але також
можемо додати їх через знайомий файл конфігурації NEON і його секцію
services
. Префікс @extension
представляє поточне розширення.
services:
articles:
create: MyBlog\ArticlesModel(@connection)
comments:
create: MyBlog\CommentsModel(@connection, @extension.articles)
articlesList:
create: MyBlog\Components\ArticlesList(@extension.articles)
Ми додамо сервіси таким чином:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
$builder = $this->getContainerBuilder();
// завантажуємо файл конфігурації для розширення
$this->compiler->loadDefinitionsFromConfig(
$this->loadFromFile(__DIR__ . '/blog.neon')['services'],
);
}
}
beforeCompile()
Метод викликається, коли контейнер містить усі сервіси, додані
окремими розширеннями в методах loadConfiguration
, а також файли
конфігурації користувача. На цьому етапі складання ми можемо
змінювати визначення сервісу або додавати посилання між ними. Для
пошуку сервісів за тегами можна використовувати метод findByTag()
,
або метод findByType()
для пошуку за класом або інтерфейсом.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function beforeCompile()
{
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('logaware') as $serviceName => $tagValue) {
$builder->getDefinition($serviceName)->addSetup('setLogger');
}
}
}
afterCompile()
На цій стадії клас контейнера вже генерується як об'єкт ClassType, містить усі методи, які створюються сервісом, і готовий до кешування як файл PHP. Наразі ми можемо редагувати код класу.
class BlogExtension extends Nette\DI\CompilerExtension
{
public function afterCompile(Nette\PhpGenerator\ClassType $class)
{
$method = $class->getMethod('__construct');
// ...
}
}
$initialization
Configurator викликається кодом ініціалізації після створення контейнера, який
створюється шляхом запису в об'єкт $this->initialization
за допомогою методу addBody().
Ми покажемо приклад того, як запустити сесію або сервіси, які мають
тег run
за допомогою коду ініціалізації:
class BlogExtension extends Nette\DI\CompilerExtension
{
public function loadConfiguration()
{
// автоматичний запуск сесії
if ($this->config->session->autoStart) {
$this->initialization->addBody('$this->getService("session")->start()');
}
// сервіси з тегом 'run' мають бути створені після того, як контейнер буде інстантовано
$builder = $this->getContainerBuilder();
foreach ($builder->findByTag('run') as $name => $foo) {
$this->initialization->addBody('$this->getService(?);', [$name]);
}
}
}