ブートストラップ
ブートストラップとは、アプリケーションの環境を初期化し、依存性注入(DI)コンテナを作り、アプリケーションを起動する過程のことです。ここでは次のことを扱います。
- Bootstrap クラスが環境をどう初期化するか
- NEON ファイルでアプリケーションをどう設定するか
- 本番モードと開発モードをどう見分けるか
- DI コンテナをどう作り、どう設定するか
ウェブのアプリケーションであれ、コマンドラインから実行するスクリプトであれ、その実行は何らかの環境の初期化から始まります。昔は
include.inc.php
のような名前のファイルがそれを担い、最初のファイルから読み込まれていました。現代の Nette
のアプリケーションでは、それが Bootstrap
クラスに置き換わり、アプリケーションの一部として app/Bootstrap.php
ファイルにあります。たとえば次のような形になります。
namespace App;
use Nette;
use Nette\Bootstrap\Configurator;
class Bootstrap
{
private Configurator $configurator;
private string $rootDir;
public function __construct()
{
$this->rootDir = dirname(__DIR__);
// Configurator はアプリケーションの環境とサービスの設定を担当します。
$this->configurator = new Configurator;
// Nette が生成する一時ファイル(コンパイル済みテンプレートなど)のディレクトリを設定します
$this->configurator->setTempDirectory($this->rootDir . '/temp');
}
public function bootWebApplication(): Nette\DI\Container
{
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}
private function initializeEnvironment(): void
{
// Nette は賢いので開発モードは自動的に有効になります。
// あるいは次の行のコメントを外して、特定の IP アドレスに対して有効にできます:
// $this->configurator->setDebugMode('secret@23.75.345.200');
// Tracy を有効にします。究極の「万能ナイフ」的デバッグツールです。
$this->configurator->enableTracy($this->rootDir . '/log');
// RobotLoader: 選んだディレクトリのすべてのクラスを自動的に読み込みます
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
}
private function setupContainer(): void
{
// 設定ファイルを読み込みます
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
index.php
ウェブアプリケーションの場合、最初のファイルは公開ディレクトリ
www/ にある index.php です。これは Bootstrap クラスに、環境を初期化して DI
コンテナを作るよう指示します。そしてコンテナから Application
サービスを取り出し、それがウェブアプリケーションを実行します。
$bootstrap = new App\Bootstrap;
// 環境を初期化し、DI コンテナを作ります
$container = $bootstrap->bootWebApplication();
// DI コンテナが Nette\Application\Application オブジェクトを作ります
$application = $container->getByType(Nette\Application\Application::class);
// Nette のアプリケーションを起動し、届いたリクエストを処理します
$application->run();
$application オブジェクトは、リクエストを処理しながらイベントを発します。onStartup、onRequest、onPresenter、onResponse、onShutdown、そして(処理されなかった例外に対する)onError
です。ハンドラを結びつけられるので、ログ記録やアプリケーション全体の監視に便利です。
ご覧のとおり、Nette\Bootstrap\Configuratorクラスが環境の設定と依存性注入(DI)コンテナの生成を助けます。ここからそれを詳しく紹介します。
開発モードと本番モード
Nette は、開発サーバーで動いているか本番サーバーで動いているかによって振る舞いを変えます。
- 🛠️ 開発モード
- 役立つ情報(SQL クエリ、実行時間、使用メモリ)を載せた Tracy のデバッグバーを表示します
- エラーのときは、関数の呼び出しと変数の内容を含む詳しいエラーページを表示します
- Latte のテンプレートや設定ファイルなどが変わると、キャッシュを自動的に更新します
- 🚀 本番モード
- デバッグ情報は一切表示せず、すべてのエラーをログに書きます
- エラーのときは ErrorPresenter か、一般的な「Server Error」のページを表示します
- キャッシュは決して自動更新されません
- 速度と安全性のために最適化されています
モードの選択は自動検出で行われるので、ふつうは何も設定したりモードを手で切り替えたりする必要はありません。
- 開発モード: localhost(IP アドレス
127.0.0.1または::1)で、プロキシがない場合(つまりその HTTP ヘッダーが検出されない場合) - 本番モード: それ以外のすべての場所
たとえば特定の IP
アドレスからアクセスするプログラマーのために、ほかの場合にも開発モードを有効にしたいなら、setDebugMode() を使います。
$this->configurator->setDebugMode('23.75.345.200'); // IP アドレスの配列も渡せます
IP アドレスと cookie の組み合わせを強くおすすめします。nette-debug の cookie
に秘密のトークン、たとえば secret1234 を保存し、特定の IP
アドレスからアクセスし、かつその cookie
にそのトークンを持つプログラマーに対して開発モードを有効にします。
$this->configurator->setDebugMode('secret1234@23.75.345.200');
localhost も含めて、開発モードを完全に無効にすることもできます。
$this->configurator->setDebugMode(false);
値 true
は開発モードを強制することに注意してください。本番サーバーでは決してあってはなりません。
自動検出は内部で静的メソッド Configurator::detectDebugMode()
が行います。これは自分で呼ぶこともでき、たとえば configurator
の外で開発モードを検出するのに使えます。IP
アドレスやコンピュータ名の許可リストを任意で受け取り、現在のリクエストを開発モードで動かすべきかを返します。
$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200');
デバッグツール Tracy
デバッグを楽にするために、素晴らしい道具 Tracyを有効にします。開発モードではエラーを可視化し、本番モードでは指定したディレクトリにエラーを記録します。
$this->configurator->enableTracy($this->rootDir . '/log');
一時ファイル
Nette は DI コンテナ、RobotLoader、テンプレートなどにキャッシュを使います。ですからキャッシュを保存するディレクトリのパスを設定する必要があります。
$this->configurator->setTempDirectory($this->rootDir . '/temp');
Linux や macOS では、log/ と temp/ ディレクトリに書き込みの権限を設定してください。
RobotLoader
ふつうはクラスを RobotLoaderで自動的に読み込みたいので、それを起動し、Bootstrap.php
があるディレクトリ(つまり
__DIR__)とそのすべてのサブディレクトリからクラスを読み込ませます。
$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
別の方法として、PSR-4 に従って Composerだけでクラスを読み込むこともできます。
タイムゾーン
既定のタイムゾーンは configurator で設定できます。
$this->configurator->setTimeZone('Europe/Prague');
DI コンテナの設定
起動の過程の一部が DI コンテナ、つまりオブジェクトのファクトリの生成で、これはアプリケーション全体の心臓です。実際には Nette が生成してキャッシュディレクトリに保存する PHP のクラスです。このファクトリがアプリケーションの主要なオブジェクトを作り、設定ファイルでその作り方と設定の仕方を指示することで、アプリケーション全体の振る舞いに影響を与えます。
設定ファイルはふつう NEON 形式で書きます。何を設定できるかは別の章で読めます。
開発モードでは、コードや設定ファイルが変わるたびにコンテナが自動的に更新されます。本番モードでは一度だけ生成され、性能を最大にするために変更は確認されません。
createContainer()
がコンテナを構築してそのインスタンスを返すのに対し、loadContainer()
メソッドは生成されたコンテナのクラス名だけを返すので、自分でインスタンス化できます。高度な場面で役立ちます。
設定ファイルは addConfig() で読み込みます。
$this->configurator->addConfig($this->rootDir . '/config/common.neon');
設定ファイルをさらに足したい場合は、addConfig() 関数を何度も呼べます。
$configDir = $this->rootDir . '/config';
$this->configurator->addConfig($configDir . '/common.neon');
$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
$this->configurator->addConfig($configDir . '/cli.php');
}
cli.php という名前は誤りではありません。設定は、それを配列として返す PHP
ファイルで書くこともできます。
ほかの設定ファイルは includes
セクションでも足せます。
設定ファイルに同じキーの項目が現れた場合、それらは上書きされるか、配列なら統合されます。あとで読み込まれたファイルのほうが、前のものより優先度が高くなります。includes
セクションを持つファイルは、そこで読み込まれるファイルより優先度が高くなります。
静的パラメータ
設定ファイルで使うパラメータは parameters
セクションで定義でき、addStaticParameters() メソッド(古い、いまは非推奨の別名は
addParameters())で渡す(または上書きする)こともできます。大事なのは、パラメータの値が違えば別の
DI コンテナ、つまり別のクラスが生成されるという点です。
$this->configurator->addStaticParameters([
'projectId' => 23,
]);
projectId パラメータは、設定の中で標準の書き方 %projectId%
で参照できます。
動的パラメータ
コンテナには動的パラメータも足せます。静的パラメータと違い、その値が違っても新しい DI コンテナは生成されません。
$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
こうすれば、たとえば環境変数を簡単に足せます。設定の中では %env.variable%
の書き方で参照できます。
$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
既定のパラメータ
設定ファイルでは次のパラメータが使えます。
%appDir%はBootstrap.phpファイルがあるディレクトリへの絶対パスです%wwwDir%は入口のファイルindex.phpがあるディレクトリへの絶対パスです%tempDir%は一時ファイル用のディレクトリへの絶対パスです%vendorDir%は Composer がライブラリをインストールするディレクトリへの絶対パスです%rootDir%はプロジェクトのルートディレクトリへの絶対パスです%baseUrl%はルートディレクトリへの絶対 URL です(実行時に解決される動的パラメータです)%debugMode%はアプリケーションがデバッグモードかどうかを示します%consoleMode%はリクエストがコマンドラインから来たかどうかを示します
取り込まれるサービス
さらに深く踏み込みましょう。DI
コンテナの目的はオブジェクトを作ることですが、ときには既存のオブジェクトをコンテナに入れる必要が出てきます。そのためには
imported: true フラグを付けてサービスを定義します。
services:
myservice:
type: App\Model\MyCustomService
imported: true
そして bootstrap でオブジェクトをコンテナに入れます。
$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
さまざまな環境
Bootstrap
クラスは必要に応じて自由に書き換えてください。ウェブのプロジェクトを見分けるために
bootWebApplication()
メソッドにパラメータを足せます。あるいはメソッドを増やしてもよいでしょう。ユニットテスト用に環境を初期化する
bootTestEnvironment()、コマンドラインから呼ばれるスクリプト用の
bootConsoleApplication() などです。
public function bootTestEnvironment(): Nette\DI\Container
{
Tester\Environment::setup(); // Nette Tester の初期化
$this->setupContainer();
return $this->configurator->createContainer();
}
public function bootConsoleApplication(): Nette\DI\Container
{
$this->configurator->setDebugMode(false);
$this->initializeEnvironment();
$this->setupContainer();
return $this->configurator->createContainer();
}