Създаване на разширения за 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),
]);
}
}
За информация вижте Схема. Възможно е също
така да определите кои опции могат да бъдат динамични с помощта на
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');
// ...
}
}
$инициализация
Конфигураторът се извиква от кода за инициализация след създаването на контейнер, който
се създава чрез запис в обекта $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]);
}
}
}