コンテナの仕組みに関する少し難しい話
前節で、コンテナの利用方法に関する基本の解説は終わりです。ここでは、このコンテナの仕組みを解説していきましょう。といっても、本格的に解説するとなると紙面が足りませんし、不要な読者もいらっしゃるでしょう。そこで、ポイントをかいつまんで解説します。それでも少し難しい話になるかもしれませんが、了承ください。
配列アクセスの謎
まず、リスト1の(2)から解説していきます。(1)でAppクラスから取得したコンテナ本体である$containerの実態は、\Slim\Containerクラスです。配列ではありません。それなのに、(2)でインスタンス生成関数を登録するのに配列アクセスのような方法をとっています。
これは、\Slim\Containerクラス(より正確には\Slim\Containerクラスの親クラスに当たる\Pimple\Containerクラス)がArrayAccessインターフェースを実装しているからです。
ArrayAccessインターフェースはPHPの定義済みインターフェースであり、クラスへのアクセスを配列のように扱えるものです。このインターフェースを実装しているおかげで、以下の記述が可能となっています。
$container["view"] = …
コンテナクラスからインスタンスを取得する別の記述
コンテナクラスがArrayAccessインターフェースを実装しているということは、コンテナからインスタンスを取得する記述も配列アクセスのように記述できます。例えば、リスト2の(1)は以下のように記述しても問題なく動作します。
$view = $this["view"];
さらに、以下のような記述も可能です。
$view = $this->view;
これは、\Slim\Containerクラスがマジックメソッド__get()※を実装しており、そのため、インスタンス識別文字列をさもプロパティかのようにアクセスできるようにしてくれているからです。
※マジックメソッドに関しては、拙記事「PHPの「マジックメソッド」とは――「__set()」「__get()」「__invoke()」の使い方」を参考にしてください。
コンテナクラスはPSR-11を実装
このように、コンテナは配列アクセスやプロパティのようなアクセス方法が可能なのに、なぜget()メソッドが存在するのでしょうか。それは、\Slim\ContainerクラスがPsr\Container\ContainerInterfaceインターフェースを実装しているからです。
このPsr\Container\ContainerInterfaceは、PSR-11として定義されたインターフェースです。PSR(PHP Standards Recommendations)というのは、PHPのフレームワークやCMS、ツールの開発者が集まったグループ(PHP-FIG)で策定されている、PHPの標準規約のことです。
そのNo.11として、こういったインスタンス生成を管理するコンテナに関するインターフェースが定義されています。このインターフェースには、表1のメソッドが2個定義されています。
| メソッド | 内容 |
|---|---|
| get() | 引数で渡されたインスタンス識別子でインスタンスを返すメソッド |
| has() | 引数で渡されたインスタンス識別子が存在するかどうかを返すメソッド |
このことから、必要に応じて、例えば以下のようにインスタンス生成が可能かどうかを事前に確認するコードを記述することもできます。
if($this->has("view")) {
Slimでログを扱う
ここまで紹介してきたコンテナですが、インスタンス生成をまとめておくことによってさらなる利点があります。それについて、ログの利用方法を題材に紹介したいと思います。そのために、Slimにおけるのログの扱いについて先に紹介します。
SlimではMonologを利用する
実運用を想定したWebアプリではログへの書き出し処理は必須です。全ての不具合、障害はログを通じてでないと原因究明がほぼ不可能だからです。そのログへの書き出し機能をSlimは内包していません。
そこで、別ライブラリを利用します。Slimで推奨しているのは、Monologの利用です。FirstSlimアプリにMonologを利用できるようにしていきましょう。
Monologパッケージの配置
まず、ComposerでMonologパッケージを配置します。srcフォルダで以下のコマンドを実行してください。
composer require monolog/monolog
すると、composer.jsonファイルとcomposer.lockファイルが更新され、必要なパッケージがダウンロードされます。特に問題なく処理が終了すれば、Monologの配置は完了しています。
Loggerインスタンスのコンテナ追加
次に、Monologの本体クラスであるLoggerのインスタンスをアプリ全体で利用できるように、コンテナに追加しましょう。リスト3の太字のコードをindex.phpに追記してください。
<?php
use Slim\App as App;
use Slim\Views\Twig;
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
use Monolog\Handler\FirePHPHandler;
〜省略〜
$container["logger"] = function($container) // (1)
{
$logger = new Logger("firstslim"); // (2)
$fileHandler = new StreamHandler("../../logs/app.log"); // (3)
$logger->pushHandler($fileHandler); // (4)
return $logger; // (5)
};
require_once("../routes.php");
〜省略〜
リスト3の(2)~(4)が、Monologを利用するための定型処理となります。まず、(2)のようにMonologの本体クラスであるLoggerクラスをnewします。その際、引数としてログでのアプリの識別文字列を渡します。
そのLoggerクラス自身は、ログへの書き出し機能を内包していません。ログへの書き出し機能はそれ専用のクラス(これをハンドラといいます)に任せます。つまり分業です。
そのハンドラクラスのうち、ファイルへの書き出しを行ってくれるクラスがStreamHandlerクラスです。そのStreamHandlerクラスをnewしているのがリスト3の(3)です。その際、ファイルパスを引数として渡します。ここでは、firstslimフォルダ直下のlogsフォルダにapp.logというファイル名で書き出すように指定しています。
最後に、LoggerクラスのpushHandler()メソッドを使って(3)で生成したハンドラインスタンスを登録します。それがリスト3の(4)です。
ここまでで、ログの書き出しの準備が整ったLoggerインスタンスが生成できました。それを(5)のようにリターンする無名関数をコンテナに登録します。それが(1)です。これで、コンテナからいつでもログへの書き出し準備が整ったLoogerインスタンスを取得できます。
Loggerを利用するコードの記述
最後に、ルーティング登録ファイルであるroutes5.phpにログへの書き出し実験を行うコードを記述しましょう。リスト4の内容を追記してください。
<?php
〜省略〜
$app->any("/no5/writeToLog",
function(Request $request, Response $response, array $args): void
{
$logger = $this->get("logger"); // (1)
$logger->info("ログに書き出しました。"); // (2)
print("<h1>ログへの書き出し実験</h1>");
}
);
追記が終了したら、以下のURLにアクセスしてください。
- http://localhost/firstslim/src/public/no5/writeToLog
ただし、念のためにあらかじめfirstslimフォルダ内にlogsフォルダを作成しておきましょう。さらに、Macの場合は、このフォルダに以下のコマンドで書き込み権限を与えておきましょう。でないとエラーとなります。
chmod 777 logs/
URLにアクセスすると、ブラウザには「ログへの書き出し実験」と表示され、logsフォルダ内にapp.logが生成されるはずです。以下の内容が記述されていれば、成功です。
[2019-05-28 09:05:26] firstslim.INFO: ログに書き出しました。 [] []
さて、リスト4のコードを確認しておきましょう。まず、コンテナからLoggerインスタンスを取得しているのが、(1)です。そのLoggerインスタンスのメソッドinfo()を使ってログへの書き出しを行っているのが(2)です。
ログに何かを記述する場合、その記述する内容の重要度がおのずと変わってきます。この重要度のことを「ログレベル」といい、Monologでは8段階あります。そのログレベルそれぞれに対応したメソッドがLoggerクラスには用意されており、そのメソッドを実行することで、ログへの書き出しを行ってくれます。8段階のログレベルを表2にまとめておきます。
| ログレベル | 対応メソッド | 内容 |
|---|---|---|
| DEBUG | debug() | デバッグ用の詳細情報 |
| INFO | info() | 一般的な情報 |
| NOTICE | notice() | 注意が必要な情報 |
| WARNING | warning() | エラーとまではいえないが例外的な動作の情報 |
| ERROR | error() | すぐに対応する必要はないが、実行エラー情報 |
| CRITICAL | critical() | 重大なエラー情報 |
| ALERT | alert() | すぐに対応が必要なエラー情報 |
| EMERGENCY | emergency() | システムが利用できないなどの緊急情報 |
