Създаване на разширения за Nette DI
Генерирането на DI контейнера, освен от конфигурационните
файлове, се влияе и от така наречените разширения. Активираме ги в
конфигурационния файл в секцията extensions
.
По този начин добавяме разширение, представено от класа
BlogExtension
, под името blog
:
extensions:
blog: BlogExtension
Всяко разширение на компилатора наследява от Nette\DI\CompilerExtension и може да имплементира следните методи, които се извикват последователно по време на изграждането на DI контейнера:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
Този метод се извиква пръв. Той дефинира схема за валидиране на конфигурационните параметри.
Конфигурираме разширението в секция, чието име е същото като това,
под което е добавено разширението, т.е. blog
:
# същото име като разширението
blog:
postsPerPage: 10
allowComments: 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, съдържа всички методи, които създават сървиси, и е готов за запис в кеша. Все още можем да модифицираме получения код на класа на този етап.
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]);
}
}
}