PHPのマイクロフレームワーク「Slim4」の「slim-skeleton」を使用してハマったこと
Slim は PHP のマイクロフレームワークです。
アプリケーション開発までのセットアップを短時間でできるように、公式で slim-skeleton を提供しています。
今回は PHP のバージョン 7.2 以降で使用可能な Slim4 を使用してハマったことの記録です。
記事で使用した slim-skeleton のバージョンは 4.1.0 になります。
INDEX
slim-skeleton のインストール
composer 公式イメージを使ってslim-skeletonをインストールしてきます。
$ docker run --rm -u $(id -u):$(id -g) -v $(pwd):/app composer create-project slim/slim-skeleton
開発環境(Docker)の準備
slim-skeleton の公式リポジトリから作成したプロジェクトには docker-compose.yml が付属しています。
docker-compose.yml
version: '3.7'
volumes:
logs:
driver: local
services:
slim:
image: php:7-alpine
working_dir: /var/www
command: php -S 0.0.0.0:8080 -t public
environment:
docker: "true"
ports:
- 8080:8080
volumes:
- .:/var/www
- logs:/var/www/logs
この記事ではデフォルト提供の docker-compose.yml で定義されているPHPのビルトインサーバを使用します。
以下のコマンドで Docker を起動します。
$ docker-compose up -d
curl コマンドでPHPのビルトインサーバが起動されていることを確認します。
$ curl http://localhost:8080
Hello world!
$ curl http:/localhost:8080/users
{
"statusCode": 200,
"data": [
{
"id": 1,
"username": "bill.gates",
"firstName": "Bill",
"lastName": "Gates"
},
{
"id": 2,
"username": "steve.jobs",
"firstName": "Steve",
"lastName": "Jobs"
},
{
"id": 3,
"username": "mark.zuckerberg",
"firstName": "Mark",
"lastName": "Zuckerberg"
},
{
"id": 4,
"username": "evan.spiegel",
"firstName": "Evan",
"lastName": "Spiegel"
},
{
"id": 5,
"username": "jack.dorsey",
"firstName": "Jack",
"lastName": "Dorsey"
}
]
}
開発環境の準備は以上です。
サブディレクトリ運用でハマったこと
Slim3 まではサブディレクトリでシステムを扱う場合でも特別な設定は必要なかったのですが、 Slim4 をサブディレクトリで運用する場合には別途設定が必要になったようです。
public/index.php
+ $app->setBasePath('/myapp');
ハードコードが嫌な場合は以下の関数で設定すればいいようです。
$app->setBasePath((function () {
$scriptDir = str_replace('\\', '/', dirname($_SERVER['SCRIPT_NAME']));
$uri = (string) parse_url('http://a' . $_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
if (stripos($uri, $_SERVER['SCRIPT_NAME']) === 0) {
return $_SERVER['SCRIPT_NAME'];
}
if ($scriptDir !== '/' && stripos($uri, $scriptDir) === 0) {
return $scriptDir;
}
return '';
})());
Loggerの設定でハマったこと
デフォルトでは Slim のLoggerを使用しており、PHPの error_log() 関数が使用されるようになっています。
Logger を Monolog に変更しようとしたときの試行錯誤です。
Monolog は slim-skeleton でプロジェクトを立ち上げると vendor に既に追加されているので、Monolog を使うように変更します。
diff --git a/public/index.php b/public/index.php
index 57722ec..a90b930 100644
--- a/public/index.php
+++ b/public/index.php
@@ -5,6 +5,7 @@ use App\Application\Handlers\HttpErrorHandler;
use App\Application\Handlers\ShutdownHandler;
use App\Application\ResponseEmitter\ResponseEmitter;
use DI\ContainerBuilder;
+use Psr\Log\LoggerInterface;
use Slim\Factory\AppFactory;
use Slim\Factory\ServerRequestCreatorFactory;
@@ -54,7 +55,8 @@ $request = $serverRequestCreator->createServerRequestFromGlobals();
// Create Error Handler
$responseFactory = $app->getResponseFactory();
-$errorHandler = new HttpErrorHandler($callableResolver, $responseFactory);
+$logger = $container->get(LoggerInterface::class);
+$errorHandler = new HttpErrorHandler($callableResolver, $responseFactory, $logger);
// Create Shutdown Handler
$shutdownHandler = new ShutdownHandler($request, $errorHandler, $displayErrorDetails);
@@ -64,7 +66,10 @@ register_shutdown_function($shutdownHandler);
$app->addRoutingMiddleware();
// Add Error Middleware
-$errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, false, false);
+// 第二引数と第三引数を true にする
+// 第二引数 $logErrors はエラーを記録するか
+// 第三引数は $logErrorDetails エラー詳細を表示するか
+$errorMiddleware = $app->addErrorMiddleware($displayErrorDetails, true, true);
$errorMiddleware->setDefaultErrorHandler($errorHandler);
// Run App & Emit Response
意図的に例外を発生させてログに出力されるか動作を確認してみます。
ListUserAction.php
diff --git a/src/Application/Actions/User/ListUsersAction.php b/src/Application/Actions/User/ListUsersAction.php
index 21b4750..01a1094 100644
--- a/src/Application/Actions/User/ListUsersAction.php
+++ b/src/Application/Actions/User/ListUsersAction.php
@@ -15,6 +15,8 @@ class ListUsersAction extends UserAction
$users = $this->userRepository->findAll();
$this->logger->info("Users list was viewed.");
+ echo EXPECT_NOTICE_ERROR;
+ throw new \RuntimeException('An error was occured.');
return $this->respondWithData($users);
}
$ curl http://localhost:8080/users
<br />
<b>Warning</b>: Use of undefined constant EXPECT_NOTICE_ERROR - assumed 'EXPECT_NOTICE_ERROR' (this will throw an Error in a future version of PHP) in <b>/var/www/src/Application/Actions/User/ListUsersAction.php</b> on line <b>18</b><br />
{
"statusCode": 500,
"error": {
"type": "SERVER_ERROR",
"description": "An error was occured."
}
}{
"statusCode": 500,
"error": {
"type": "SERVER_ERROR",
"description": "ERROR: Use of undefined constant EXPECT_NOTICE_ERROR - assumed 'EXPECT_NOTICE_ERROR' (this will throw an Error in a future version of PHP) on line 18 in file \/var\/www\/src\/Application\/Actions\/User\/ListUsersAction.php."
}
}
slim-skeleton のログ出力は特に設定していないと Docker 経由だと標準出力に出力されます。 以下のコマンドでログを確認します。
$ docker-compose logs slim
Attaching to slim-skeleton_slim_1
slim_1 | [Mon Jan 4 09:16:38 2021] PHP 7.4.13 Development Server (http://0.0.0.0:8080) started
slim_1 | [Mon Jan 4 09:16:47 2021] 192.168.80.1:38852 Accepted
slim_1 | [2021-01-04T09:16:47.315350+00:00] slim-app.INFO: Users list was viewed. [] {"uid":"40c8453"}
slim_1 | [2021-01-04T09:16:47.316259+00:00] slim-app.ERROR: Slim Application Error Type: RuntimeException Code: 0 Message: An error was occured. File: /var/www/src/Application/Actions/User/ListUsersAction.php Line: 19 Trace: #0 /var/www/src/Application/Actions/Action.php(58): App\Application\Actions\User\ListUsersAction->action() #1 /var/www/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php(43): App\Application\Actions\Action->__invoke(Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array) #2 /var/www/vendor/slim/slim/Slim/Routing/Route.php(384): Slim\Handlers\Strategies\RequestResponse->__invoke(Array, Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array) #3 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Slim\Routing\Route->handle(Object(Slim\Psr7\Request)) #4 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #5 /var/www/vendor/slim/slim/Slim/Routing/Route.php(341): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #6 /var/www/vendor/slim/slim/Slim/Routing/RouteRunner.php(84): Slim\Routing\Route->run(Object(Slim\Psr7\Request)) #7 /var/www/src/Application/Middleware/SessionMiddleware.php(23): Slim\Routing\RouteRunner->handle(Object(Slim\Psr7\Request)) #8 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(209): App\Application\Middleware\SessionMiddleware->process(Object(Slim\Psr7\Request), Object(Slim\Routing\RouteRunner)) #9 /var/www/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php(59): class@anonymous->handle(Object(Slim\Psr7\Request)) #10 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Slim\Middleware\RoutingMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous)) #11 /var/www/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(107): class@anonymous->handle(Object(Slim\Psr7\Request)) #12 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Slim\Middleware\ErrorMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous)) #13 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): class@anonymous->handle(Object(Slim\Psr7\Request)) #14 /var/www/vendor/slim/slim/Slim/App.php(215): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #15 /var/www/public/index.php(76): Slim\App->handle(Object(Slim\Psr7\Request)) #16 {main} [] {"uid":"40c8453"}
slim_1 | [Mon Jan 4 09:16:47 2021] 192.168.80.1:38852 [200]: GET /users
slim_1 | [Mon Jan 4 09:16:47 2021] 192.168.80.1:38852 Closing
例外がログに出力されるようになりましたが、PHP の Notice と Warning がログに出力されていません。
slim-skeleton で提供されている ShutdownHandler.php の以下の引数を変更すると出力されるようになりました。
src/Application/Handlers/ShutdownHandler.php
diff --git a/src/Application/Handlers/ShutdownHandler.php b/src/Application/Handlers/ShutdownHandler.php
index 323f16a..73d5b29 100644
--- a/src/Application/Handlers/ShutdownHandler.php
+++ b/src/Application/Handlers/ShutdownHandler.php
@@ -74,7 +74,7 @@ class ShutdownHandler
}
$exception = new HttpInternalServerErrorException($this->request, $message);
- $response = $this->errorHandler->__invoke($this->request, $exception, $this->displayErrorDetails, false, false);
+ $response = $this->errorHandler->__invoke($this->request, $exception, $this->displayErrorDetails, true, true);
$responseEmitter = new ResponseEmitter();
$responseEmitter->emit($response);
slim_1 | [Mon Jan 4 09:22:06 2021] PHP 7.4.13 Development Server (http://0.0.0.0:8080) started
slim_1 | [Mon Jan 4 09:22:19 2021] 192.168.96.1:34806 Accepted
slim_1 | [2021-01-04T09:22:19.114663+00:00] slim-app.INFO: Users list was viewed. [] {"uid":"c8a86bb"}
slim_1 | [2021-01-04T09:22:19.115963+00:00] slim-app.ERROR: Slim Application Error Type: RuntimeException Code: 0 Message: An error was occured. File: /var/www/src/Application/Actions/User/ListUsersAction.php Line: 19 Trace: #0 /var/www/src/Application/Actions/Action.php(58): App\Application\Actions\User\ListUsersAction->action() #1 /var/www/vendor/slim/slim/Slim/Handlers/Strategies/RequestResponse.php(43): App\Application\Actions\Action->__invoke(Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array) #2 /var/www/vendor/slim/slim/Slim/Routing/Route.php(384): Slim\Handlers\Strategies\RequestResponse->__invoke(Array, Object(Slim\Psr7\Request), Object(Slim\Psr7\Response), Array) #3 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Slim\Routing\Route->handle(Object(Slim\Psr7\Request)) #4 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #5 /var/www/vendor/slim/slim/Slim/Routing/Route.php(341): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #6 /var/www/vendor/slim/slim/Slim/Routing/RouteRunner.php(84): Slim\Routing\Route->run(Object(Slim\Psr7\Request)) #7 /var/www/src/Application/Middleware/SessionMiddleware.php(23): Slim\Routing\RouteRunner->handle(Object(Slim\Psr7\Request)) #8 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(209): App\Application\Middleware\SessionMiddleware->process(Object(Slim\Psr7\Request), Object(Slim\Routing\RouteRunner)) #9 /var/www/vendor/slim/slim/Slim/Middleware/RoutingMiddleware.php(59): class@anonymous->handle(Object(Slim\Psr7\Request)) #10 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Slim\Middleware\RoutingMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous)) #11 /var/www/vendor/slim/slim/Slim/Middleware/ErrorMiddleware.php(107): class@anonymous->handle(Object(Slim\Psr7\Request)) #12 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(147): Slim\Middleware\ErrorMiddleware->process(Object(Slim\Psr7\Request), Object(class@anonymous)) #13 /var/www/vendor/slim/slim/Slim/MiddlewareDispatcher.php(81): class@anonymous->handle(Object(Slim\Psr7\Request)) #14 /var/www/vendor/slim/slim/Slim/App.php(215): Slim\MiddlewareDispatcher->handle(Object(Slim\Psr7\Request)) #15 /var/www/public/index.php(76): Slim\App->handle(Object(Slim\Psr7\Request)) #16 {main} [] {"uid":"c8a86bb"}
slim_1 | [Mon Jan 4 09:22:19 2021] 192.168.96.1:34806 [200]: GET /users
slim_1 | [2021-01-04T09:22:19.117483+00:00] slim-app.ERROR: 500 Internal Server Error Type: Slim\Exception\HttpInternalServerErrorException Code: 500 Message: ERROR: Use of undefined constant EXPECT_NOTICE_ERROR - assumed 'EXPECT_NOTICE_ERROR' (this will throw an Error in a future version of PHP) on line 18 in file /var/www/src/Application/Actions/User/ListUsersAction.php. File: /var/www/src/Application/Handlers/ShutdownHandler.php Line: 76 Trace: #0 [internal function]: App\Application\Handlers\ShutdownHandler->__invoke() #1 {main} [] {"uid":"c8a86bb"}
プロダクション環境では app/settings.php の displayErrorsの値を false にしてエンドユーザーにはエラー詳細を見せない形にすると思いますが、 困ったことに displayErrors の値を false にすると、ログに出力されるエラーの詳細も非表示になってしまいます。
app/settings.php
diff --git a/app/settings.php b/app/settings.php
index 9db7ad1..7b400c9 100644
--- a/app/settings.php
+++ b/app/settings.php
@@ -8,7 +8,7 @@ return function (ContainerBuilder $containerBuilder) {
// Global Settings Object
$containerBuilder->addDefinitions([
'settings' => [
- 'displayErrorDetails' => true, // Should be set to false in production
+ 'displayErrorDetails' => false, // Should be set to false in production
'logger' => [
'name' => 'slim-app',
'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
slim_1 | [2021-01-05T00:44:43.291938+00:00] slim-app.ERROR: 500 Internal Server Error Type: Slim\Exception\HttpInternalServerErrorException Code: 500 Message: An error while processing your request. Please try again later. File: /var/www/src/Application/Handlers/ShutdownHandler.php Line: 76 Trace: #0 [internal function]: App\Application\Handlers\ShutdownHandler->__invoke() #1 {main} Tips: To display error details in HTTP response set "displayErrorDetails" to true in the ErrorHandler constructor. [] {"uid":"e570d39"}
内部的には Slim\App::addErrorMiddleware メソッド で Slim\Middleware\ErrorMiddleware インスタンス作成時のコンストラクタの第4、第5引数に $logErrors と $logErrorDetails が渡されています。
ErrorMiddleware が使用されるのは register_shutdown_function() で登録された ShutdownHandler.php のここで ShutdownHandler クラスのコンストラクタに渡した HttpErrorHandler のインスタンスを実行しています。
HttpErrorHandler クラスでは __invoke メソッド は実装されておらず、その継承元の Slim\Handlers\ErrorMiddleware のここでログ出力の有無を振り分けているようです。
この辺り slim-skeleton のアプリケーションコードから Slim4 のコードに飛んだり、Slim4 のコードが基底クラスとなって slim-skeleton の子クラスを呼び出していたりと理解が難しい印象です。
ShutdownHandler の $displayErrorDetails を HttpErrorHandler に渡しているので、ログ出力の時とレスポンスの時とでエラー詳細の表示/非表示が同じ値を参照するようになっていて、レスポンスにはエラー詳細を非表示、ログ出力時にはエラー詳細を表示するといった Noticeや Warning の細かいログ出力設定方法を見つけられませんでした。
slim-skeleton のディレクトリデザイン
ハマったことではないですが、公式の slimphp/slim-skeleton のディレクトリデザインは ADRパターン (Action Domain Responder) に沿って作られているようです。
ShutdownHandler.php と HttpErrorHandler.php と Actions が密接に絡み合っているので、別のディレクトリデザインパターンを採用する時には slim-skeleton 経由でプロジェクトを作成するより Slim4 をインストールして1からプロジェクトを作成した方がいいかもしれません。
まとめ
Slim4 slim-skeleton を使用してハマったことの一部をご紹介しました。
同じような問題を抱えている方の助けになれば幸いです。
この記事を書いた人
- 2013年にアーティスに入社。システムエンジニアとしてアーティスCMSを使用したWebサイトや受託システムの構築・保守に携わる。環境構築が好き。
関連記事
最新記事
FOLLOW US
最新の情報をお届けします
- facebookでフォロー
- Twitterでフォロー
- Feedlyでフォロー