Nette DIの拡張機能の作成
DIコンテナの生成は、設定ファイルに加えて、いわゆる拡張機能によっても影響を受けます。これらは、設定ファイルの
extensions
セクションで有効化します。
このようにして、BlogExtension
クラスによって表現される拡張機能を blog
という名前で追加します:
extensions:
blog: BlogExtension
各コンパイラ拡張機能は Nette\DI\CompilerExtension を継承し、DIコンテナのビルド中に順次呼び出される以下のメソッドを実装できます:
- getConfigSchema()
- loadConfiguration()
- beforeCompile()
- afterCompile()
getConfigSchema()
このメソッドが最初に呼び出されます。設定パラメータの検証のためのスキーマを定義します。
拡張機能は、拡張機能が追加された名前と同じ名前のセクション、つまり blog
で設定します:
# extension と同じ名前
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),
]);
}
}
ドキュメントは スキーマ
ページにあります。さらに、dynamic()
を使用してどのオプションが動的であるかを指定できます。例:Expect::int()->dynamic()
。
設定には、stdClass
オブジェクトである $this->config
変数を通じてアクセスします:
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');
ファイルからのサービスのロード
サービスは、ContainerBuilderクラスのAPIを使用して作成するだけでなく、設定ファイル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クラスは、コンテナ作成後
に初期化コードを呼び出します。これは、メソッド addBody() を使用して
$this->initialization
オブジェクトに書き込むことによって作成されます。
例えば、初期化コードでセッションを開始したり、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]);
}
}
}