アプリケーションはどう動くのか
あなたは今、Nette ドキュメントの土台となる章を読んでいます。リクエストが生まれた瞬間から PHP スクリプトの実行が終わるまで、ウェブアプリケーションがどう動くのかを A から Z まで学びます。読み終わると次のことが分かります。
- 全体がどう動くのか
- Bootstrap、プレゼンター、DI コンテナとは何か
- ディレクトリ構成はどんな形か
ディレクトリ構成
WebProjectというウェブアプリケーションの雛形の例を開いてください。読みながら、話に出てくるファイルを参照できます。
ディレクトリ構成はおおよそ次のようになっています。
web-project/ ├── app/ ← アプリケーションのディレクトリ │ ├── Core/ ← 動作に必要な中核のクラス │ │ └── RouterFactory.php ← URL アドレスの設定 │ ├── Presentation/ ← プレゼンター、テンプレートなど │ │ ├── @layout.latte ← レイアウトのテンプレート │ │ └── Home/ ← Home プレゼンターのディレクトリ │ │ ├── HomePresenter.php ← Home プレゼンターのクラス │ │ └── default.latte ← default アクションのテンプレート │ └── Bootstrap.php ← 起動クラス Bootstrap ├── assets/ ← リソース(SCSS、TypeScript、元画像) ├── bin/ ← コマンドラインから実行するスクリプト ├── config/ ← 設定ファイル │ ├── common.neon │ └── services.neon ├── log/ ← 記録されたエラー ├── temp/ ← 一時ファイル、キャッシュなど ├── vendor/ ← Composer がインストールしたライブラリ │ ├── ... │ └── autoload.php ← インストールされた全パッケージのオートローディング ├── www/ ← 公開ディレクトリ。プロジェクトの document-root │ ├── assets/ ← コンパイル済みの静的ファイル(CSS、JS、画像など) │ ├── .htaccess ← mod_rewrite の規則 │ └── index.php ← アプリケーションを起動する最初のファイル └── .htaccess ← www 以外のすべてのディレクトリへのアクセスを禁じます
ディレクトリ構成は好きなように変えられ、フォルダの名前を変えたり移動したりできます。完全に柔軟です。Nette には賢い自動検出もあり、アプリケーションの場所とその URL の基準を自動的に認識します。
少し大きなアプリケーションでは、プレゼンターとテンプレートのフォルダをサブディレクトリにまとめ、クラスを名前空間でまとめられます。これをモジュールと呼びます。
www/ ディレクトリは、プロジェクトの公開ディレクトリ、つまり document-root
にあたります。アプリケーション側でほかに何も設定せずに名前を変えられます。document-root
がこのディレクトリを指すようホスティングを設定するだけです。
WebProject は Nette ごと Composerで直接ダウンロードすることもできます。
composer create-project nette/web-project
Linux や macOS では、log/ と temp/ ディレクトリに書き込みの権限を設定してください。
WebProject アプリケーションはすぐに動く状態です。何も設定する必要はなく、www/
フォルダにアクセスすればブラウザでそのまま見られます。
HTTP リクエスト
すべては、ユーザーがブラウザでページを開いたときに始まります。ブラウザはサーバーに
HTTP リクエストを送ります。このリクエストは、公開ディレクトリ www/
にあるただひとつの PHP ファイル、index.php に向かいます。リクエストがアドレス
https://example.com/product/123 に対するものだとしましょう。適切なサーバーの設定のおかげで、この
URL も index.php ファイルに割り当てられ、それが実行されます。
その役目は次のとおりです。
- 環境を初期化する
- ファクトリを手に入れる
- リクエストを処理する Nette アプリケーションを実行する
ファクトリ? 私たちはトラクターを作っているのではなく、ウェブサイトを作っているのですが。ご心配なく、すぐに説明します。
「環境の初期化」とは、たとえば Tracyを有効にすることです。Tracy はエラーの記録と可視化のための素晴らしい道具です。本番サーバーではエラーを記録し、開発環境では直接表示します。ですから初期化には、サイトが本番モードで動いているのか開発モードで動いているのかの判定も含まれます。Nette はそのために賢い自動検出を使います。localhost でサイトを動かせば開発モードになります。何も設定する必要はなく、アプリケーションは開発にも本番の公開にもすぐ対応できます。これらの手順は Bootstrap クラスの章で実行され、詳しく説明されています。
3 つめ(2 つめは飛ばしましたが、あとで戻ります)はアプリケーションの実行です。Nette では
HTTP リクエストの処理を Nette\Application\Application クラス(以下
Application)が担当します。ですからアプリケーションを実行するとは、具体的にはこのクラスのオブジェクトで、その名も
run() というメソッドを呼ぶことを指します。
Nette
は指導役として、実績のある方法論に沿ってきれいなアプリケーションを書くよう導いてくれます。その中でも最も確立されたもののひとつが依存性注入、略して
DI です。ここで DI の説明で負担をかけるつもりはありません。それには専用の章があります。肝心の帰結は、重要なオブジェクトがふつう
DI
コンテナ(DIC)と呼ばれるオブジェクトのファクトリによって作られるということです。そう、これが先ほど触れたファクトリです。Application
オブジェクトもここが作ってくれるので、まずコンテナが必要になります。コンテナは
Configurator クラスで手に入れ、Application オブジェクトを作らせ、その
run() メソッドを呼ぶ。こうして Nette のアプリケーションが始まります。これがまさに
index.phpファイルで起きていることです。
Nette Application
Application クラスの役目はひとつだけです。HTTP リクエストに応えることです。
Nette で書かれたアプリケーションは、いわゆるプレゼンターの集まりに分かれています(ほかのフレームワークでは「コントローラ」という言葉に出会うかもしれませんが、本質的には同じものです)。プレゼンターはクラスで、それぞれがウェブサイトの特定のページを表します。トップページ、ネットショップの商品、ログインフォーム、サイトマップのフィードなどです。アプリケーションはプレゼンターをひとつから何千まで持てます。
Application
はまず、いわゆるルーターに、現在のリクエストをどのプレゼンターが処理すべきかを決めさせます。ルーターが責任の所在を定めるのです。入力の
URL https://example.com/product/123 を調べ、その設定にもとづいて、この仕事はたとえば
Product プレゼンターのもので、id: 123 の商品について show
アクションを行うべきだと決めます。プレゼンターとアクションの組はコロンで区切って
Product:show と書くのが良い習慣です。
こうしてルーターは URL を Presenter:action
の組とパラメータに変換しました。この場合は Product:show と id: 123
です。そうしたルーターがどんな形かは app/Core/RouterFactory.php
ファイルで見られますし、ルーティングの章で詳しく説明します。
先へ進みましょう。Application
はプレゼンターの名前を知ったので、次に進めます。Product
プレゼンターのコードを含む ProductPresenter
クラスのインスタンスを作るのです。より正確には、オブジェクトの生成はその役目なので、DI
コンテナにプレゼンターを作らせます。
プレゼンターはたとえば次のような形になります。
class ProductPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private ProductRepository $repository,
) {
}
public function renderShow(int $id): void
{
// モデルからデータを取得してテンプレートに渡します
$this->template->product = $this->repository->getProduct($id);
}
}
プレゼンターがリクエストの処理を引き継ぎます。仕事ははっきりしています。id: 123
で show
アクションを実行することです。プレゼンターの用語でいえば、renderShow()
メソッドが呼ばれ、$id パラメータに 123 を受け取る、ということです。
プレゼンターは複数のアクションを扱えます。つまり render<Action>()
メソッドを複数持てます。とはいえ、プレゼンターはアクションひとつ、あるいはできるだけ少ない数で設計することをおすすめします。
というわけで renderShow(123)
メソッドが呼ばれました。そのコードは架空の例ですが、$this->template
に書き込むことでデータがテンプレートに渡される様子を示しています。
続いてプレゼンターはレスポンスを返します。HTML のページ、画像、XML
のドキュメント、ディスクからのファイルの送信、JSON、あるいは別のページへのリダイレクトかもしれません。大事なのは、どう応えるかを明示的に指定しなければ(ProductPresenter
の場合がそうです)、テンプレートを HTML
のページに描くのがレスポンスになる、という点です。なぜでしょうか。99 %
の場合、私たちはテンプレートを描きたいからです。ですからプレゼンターは、私たちの手間を省くためにこの振る舞いを既定としています。それが
Nette の本質です。
描くテンプレートを指定する必要さえありません。フレームワークがパスを自動的に導きます。show
アクションの場合は単に、ProductPresenter クラスと同じディレクトリにある
show.latte テンプレートを読み込もうとします。レイアウトも @layout.latte
ファイルから探そうとします(詳しくはテンプレートの探索をご覧ください)。
そしてテンプレートが描かれます。これでプレゼンターの、そしてアプリケーション全体の仕事が終わります。テンプレートが存在しなければ 404 のエラーページが返ります。プレゼンターについて詳しくはプレゼンターのページで学べます。
念のため、少し違う URL で全体の流れをおさらいしましょう。
- URL は
https://example.com - アプリケーションが起動し、DI コンテナが作られ、
Application::run()が実行されます。 - ルーターが URL を
Home:defaultの組に読み解きます。 HomePresenterクラスのインスタンスが作られます。- (存在すれば)
renderDefault()メソッドが呼ばれます。 - テンプレート、たとえば
default.latteが、レイアウト、たとえば@layout.latteとともに描かれます。
ここまでで新しい概念にたくさん出会ったかもしれませんが、どれも筋が通っていると感じてもらえたはずです。Nette でのアプリケーション開発は、驚くほど素直です。
テンプレート
テンプレートといえば、Nette は Latteというテンプレートシステムを使います。テンプレートのファイルの拡張子が
.latte なのはそのためです。Latte を使う第一の理由は、PHP
で最も安全なテンプレートシステムであり、しかも最も直感的だからです。新しく覚えることは多くありません。PHP
の知識といくつかのタグで十分です。必要なことはすべてドキュメントにあります。
テンプレートでは、ほかのプレゼンターやアクションへのリンクを作ります。次のようにです。
<a n:href="Product:show $productId">商品の詳細</a>
実際の URL の代わりに、見慣れた Presenter:action
の組を書き、必要なパラメータを添えるだけです。仕掛けは n:href
にあり、これがこの属性を処理するよう Nette に伝えます。すると次が生成されます。
<a href="/product/456">商品の詳細</a>
URL の生成は、先ほどのルーターが担当します。Nette のルーターが並外れているのは、URL から
Presenter:action
の組への変換だけでなく、その逆、つまりプレゼンター名、アクション、パラメータから URL
を生成することもできる点です。おかげで、完成した Nette のアプリケーション全体の URL
の形式を、テンプレートやプレゼンターの文字を 1
つも変えずに、ルーターを直すだけで丸ごと変えられます。これはいわゆる正規化も可能にします。同じ内容が異なる
URL に存在するのを自動的に防いで SEO を高める、Nette
ならではのもうひとつの機能です。多くのプログラマーがこの能力に驚きます。
インタラクティブなコンポーネント
プレゼンターについてもうひとつお伝えすることがあります。プレゼンターには組み込みのコンポーネントのしくみがあります。経験のある方は Delphi や ASP.NET Web Forms の似たものを思い出すかもしれません。React や Vue.js も、いくらか近い考え方の上に築かれています。PHP のフレームワークの世界では、これはまったく他にない機能です。
コンポーネントは独立した再利用できる部品で、ページ(つまりプレゼンター)に埋め込みます。フォーム、データグリッド、メニュー、アンケートなど、再利用する意味のあるものなら何でもです。自分でコンポーネントを作ることも、豊富な品揃えのオープンソースのコンポーネントを使うこともできます。
コンポーネントはアプリケーション開発への向き合い方を根本から変えます。あらかじめ用意された部品からページを組み立てるという新しい可能性を開きます。そしてハリウッドとも通じるものがあります。
DI コンテナと設定
DI コンテナ、つまりオブジェクトのファクトリは、アプリケーション全体の心臓です。
ご心配なく。これまでの行から想像されるような魔法のブラックボックスではありません。実際には、Nette
が生成してキャッシュディレクトリに保存する、かなり地味な PHP
のクラスです。createServiceAbcd()
のような名前のメソッドが数多く入っていて、それぞれが特定のオブジェクトを作って返せます。そう、index.php
でアプリケーションを実行するのに必要だった Nette\Application\Application
のインスタンスを作る createServiceApplication__application()
メソッドもあります。個々のプレゼンターを作るメソッドなどもあります。
DI コンテナが作るオブジェクトは、なぜかサービスと呼ばれます。
このクラスが本当に特別なのは、あなたがそれをプログラムしないという点です。フレームワークがするのです。実際に
PHP
のコードを生成してディスクに保存します。あなたは、コンテナがどのオブジェクトを作れるべきか、そしてそれを正確にどう作るかの指示を与えるだけです。この指示は設定ファイルに書きます。設定ファイルは
NEON形式を使うので、拡張子は .neon です。
設定ファイルは、純粋に DI コンテナへ指示を与えるためのものです。ですからたとえば sessionセクションで expiration: 14 days
を指定すると、DI コンテナはセッションを表す Nette\Http\Session
オブジェクトを作るときに setExpiration('14 days')
メソッドを呼び、その設定を実現します。
何を設定できるか、そして独自のサービスをどう定義するかを説明する章がまるごと用意されています。
サービスの生成に少し踏み込むと、オートワイヤリングという言葉に出会います。これはあなたの生活を驚くほど楽にしてくれる機能です。あなたが何もしなくても、必要な場所(たとえばクラスのコンストラクタ)にオブジェクトを自動的に渡してくれます。Nette の DI コンテナが小さな奇跡であることに気づくでしょう。
次は何を?
Nette のアプリケーションの基本原則を見てきました。ここまでは表面をなぞる概観でしたが、まもなくもっと深く踏み込み、やがては素晴らしいウェブアプリケーションを作れるようになります。次はどこへ行きましょうか。最初のアプリケーションを作ろうのチュートリアルはもう試しましたか。
ここまで説明したもののほかにも、Nette には便利なクラスの一式やデータベース層などがあります。ドキュメントをあちこちクリックしてみてください。あるいはブログを訪ねてみてください。面白いものがたくさん見つかります。
このフレームワークがあなたに大きな喜びをもたらしますように 💙