PHPでSlim3フレームワークを使ったプロジェクトを作る場合、プロジェクト構成のひな型としてslim/slim-skeletonパッケージがあります。今回は、プロジェクトひな型として、slim-skeletonが役立つか評価したい人のため、プロジェクトを構成している全ファイルの解説を行います。
本記事を作成するにあたって確認したバージョン
slim/slim-skeletonのバージョンは、2017年10時点での最新版であるver3.1.4をもとに確認しています。
slim-skeletonパッケージの取得
slim/slim-skeletonパッケージは、PHPのプロジェクト管理ツールであるComposerで取得します。
このスケルトンは、パッケージではなくプロジェクトのテンプレートなので、composer require
コマンドではなくcomposer create-project
コマンドを使用します。
composer create-project
コマンドは、開発用のパッケージが必要か否かで下記のどちらかを実行します。(開発を始めるときは、通常--no-dev無しで実行することが多いです)
# 開発用のパッケージ(phpunit)も含めて取得する場合
composer create-project slim/slim-skeleton [ProjectName]
# 本番環境で使用するパッケージだけを取得する場合
composer create-project --no-dev slim/slim-skeleton [ProjectName]
[ProjectName]の部分は、プロジェクトの名称に置き換えてコマンドを実行して下さい。
composer.jsonの定義
composer.jsonでの依存パッケージは以下のとおりです。アプリの動作に必要な依存パッケージが4つと、開発時に使用するパッケージが1つ定義されています。
"require": {
"php": ">=5.5.0",
"slim/slim": "^3.1",
"slim/php-view": "^2.0",
"monolog/monolog": "^1.17"
},
"require-dev": {
"phpunit/phpunit": ">=4.8 < 6.0"
},
Composerで取り込まれるパッケージの一覧
Composerでは、上記の依存パッケージに加えて、依存パッケージがさらに依存している孫パッケージ・ひ孫パッケージも同時に取得可能です。
composer create-projectでプロジェクトを作成すると、結果的には下記のパッケージが入ります。
--no-dev付きでcreate-projectしたとき
$ composer show
container-interop/container-interop 1.2.0 Promoting the interoperability of container objects (DIC, SL, etc.)
monolog/monolog 1.23.0 Sends your logs to files, sockets, inboxes, databases and various web services
nikic/fast-route v1.2.0 Fast request router for PHP
pimple/pimple v3.2.2 Pimple, a simple Dependency Injection Container
psr/container 1.0.0 Common Container Interface (PHP FIG PSR-11)
psr/http-message 1.0.1 Common interface for HTTP messages
psr/log 1.0.2 Common interface for logging libraries
slim/php-view 2.2.0 Render PHP view scripts into a PSR-7 Response object.
slim/slim 3.8.1 Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications an...
psr/*とcontainer-interopはPSR関連のインターフェース定義だけなので、実質6パッケージだけです。各パッケージはそれぞれ下記の機能を提供しています。
monolog/monolog ログ出力
nikic/fast-route ルーティング処理
pimple/pimple DIコンテナ
slim/php-view Viewのテンプレート(smartyやtwigではなく、phpをviewに使うもの)
slim/slim slimのフレームワーク本体
--no-dev無しでcreate-projectしたとき
--no-devオプションをつけずにcreate-projextすると、さらに下記のパッケージが追加で入ります。
$ composer show
doctrine/instantiator 1.1.0 A small, lightweight utility to instantiate objects in PHP without invoking the...
myclabs/deep-copy 1.6.1 Create deep copies (clones) of your objects
phpdocumentor/reflection-common 1.0.1 Common reflection classes used by phpdocumentor to reflect the code structure
phpdocumentor/reflection-docblock 4.1.1 With this component, a library can provide support for annotations via DocBlock...
phpdocumentor/type-resolver 0.4.0
phpspec/prophecy v1.7.2 Highly opinionated mocking framework for PHP 5.3+
phpunit/php-code-coverage 4.0.8 Library that provides collection, processing, and rendering functionality for P...
phpunit/php-file-iterator 1.4.2 FilterIterator implementation that filters files based on a list of suffixes.
phpunit/php-text-template 1.2.1 Simple template engine.
phpunit/php-timer 1.0.9 Utility class for timing
phpunit/php-token-stream 2.0.1 Wrapper around PHP's tokenizer extension.
phpunit/phpunit 5.7.22 The PHP Unit Testing framework.
phpunit/phpunit-mock-objects 3.4.4 Mock Object library for PHPUnit
sebastian/code-unit-reverse-lookup 1.0.1 Looks up which function or method a line of code belongs to
sebastian/comparator 1.2.4 Provides the functionality to compare PHP values for equality
sebastian/diff 1.4.3 Diff implementation
sebastian/environment 2.0.0 Provides functionality to handle HHVM/PHP environments
sebastian/exporter 2.0.0 Provides the functionality to export PHP variables for visualization
sebastian/global-state 1.1.1 Snapshotting of global state
sebastian/object-enumerator 2.0.1 Traverses array structures and object graphs to enumerate all referenced objects
sebastian/recursion-context 2.0.0 Provides functionality to recursively process PHP variables
sebastian/resource-operations 1.0.0 Provides a list of PHP built-in functions that operate on resources
sebastian/version 2.0.1 Library that helps with managing the version number of Git-hosted PHP projects
symfony/yaml v3.3.10 Symfony Yaml Component
webmozart/assert 1.2.0 Assertions to validate method input/output with nice error messages.
require-devしているのはphpunitだけだったので、上記のパッケージ群はPHPUnit関係の物のみです。
ファイルの構成
パッケージを管理しているvendorディレクトリを除くと、17ファイルで構成されています。
その内、プログラム関するファイルは以下の7つで、ファイルはpublic, src, templatesディレクトリに存在しています。
public/.htaccess
public/index.php
src/dependencies.php
src/middleware.php
src/routes.php
src/settings.php
templates/index.phtml
testsディレクトリには、ユニットテスト用のファイルが2つあります。
tests/Functional/BaseTestCase.php
tests/Functional/HomepageTest.php
上記以外に、8個のファイルがあります。これらのファイルはアプリの動作に直接関係しないものです。
.gitignore gitの定義ファイル
composer.json composerの定義ファイル
composer.lock composerの定義ファイル
docker-compose.yml Dockerの定義ファイル
logs/README.md ログディレクトリであることの説明
CONTRIBUTING.md slim-skeletonへコントリビュート方法の説明(プルリクエストの送り方など)
phpunit.xml UnitTest用の定義
README.md プロジェクトの説明
その他ファイル系の確認
まずは、ドキュメント、環境設定系のファイルを確認していきます。
README.mdの内容
README.mdには、使い方の説明が記載されています。ざっくり日本語に訳すと以下のような感じです。
Slim Framework 3 Skeletonアプリケーション
このSkeletonを使用して、新しいSlim Framework 3アプリケーションを素早くにセットアップして作業を開始できます。このアプリケーションでは、PHP-Viewテンプレートを使った、最新のSlim 3フレームワークを使用しています。また、Monolog loggerも使用しています。
このSkeletonは、Composer向けに作成されています。これにより、新しいSlim Frameworkアプリケーションを簡単にセットアップできます。
アプリケーションのインストール
Slim Frameworkアプリケーションのプロジェクトを作りたい場所で、下記のコマンドを実行します。
php composer.phar create-project slim/slim-skeleton [my-app-name]
[my-app-name]
の部分には、作成するアプリケーションのディレクトリ名を指定します。さらに、プロジェクトを実行するために、追加で以下の作業が必要です。
1. virtual hostのdocument rootをpublic/に指定します。
2. logs/に書き込み権限を付与しておきます。開発中にアプリケーションを実行する場合は、以下のコマンドで実行可能です。(phpのビルトインサーバ経由でアプリが実行されます)
php composer.phar start
また、以下のコマンドで、PHPUnitによる単体テストを実行できます。
php composer.phar test
これで全部です! 今すぐクールなアプリを作りましょう。
under the MIT License in Rob Allen(Original) and akamist.com(Translation).
docker-compose.ymlの内容
以下のように、php:7-alpineベースでphpのビルトインサーバを起動させるような設定になっています。本番サーバ用の設定ではなく、開発時の動作確認で実行するためのものです。
ymlファイルに記載されている通り、Dockerコンテナ作ってrunすると8080ポート経由でアクセスできます。また、環境変数dockerに"true"の文字列をセットしています。
version: '2'
volumes:
logs:
driver: local
services:
slim:
image: php:7-alpine
working_dir: /var/www
command: php -S 0.0.0.0:8080 -t public public/index.php
environment:
docker: "true"
ports:
- 8080:8080
volumes:
- .:/var/www
- logs:/var/www/logs
.gitignoreの内容
vendorとlogsに書かれるログファイルをgitの管理外にしています。よくある設定なので特に解説はありません。
/vendor/
/logs/*
!/logs/README.md
phpunit.xmlの内容
testディレクトリのにUnitTestのコードを置くようになっています。テストスイートの名前がSlimSkeletonになっているので、実際のアプリケーションを作る場合は名前を変更した方が分かりやすいでしょう。
<phpunit bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="SlimSkeleton">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>
このPHPUnitの実行ですが、composer.jsonを見るとscriptsにtestの定義があるので...
{
"name": "slim/slim-skeleton",
...
"scripts": {
"start": "php -S localhost:8080 -t public public/index.php",
"test": "phpunit"
}
}
コマンドラインからcomposer test
コマンドを実行すれば、単体テストが走ります。
$ composer test
> phpunit
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 149 ms, Memory: 4.00MB
OK (3 tests, 7 assertions)
設定されていたテストスイート名を明示的に指定する場合は、"composer test"ではなく、vendor\bin\phpunit --testsuite SlimSkeleton
と、vendor以下にあるスクリプト名を明示的に指定する必要があるようです。
$ vendor\bin\phpunit.bat --testsuite SlimSkeleton
PHPUnit 5.7.22 by Sebastian Bergmann and contributors.
... 3 / 3 (100%)
Time: 66 ms, Memory: 4.00MB
OK (3 tests, 7 assertions)
テストコードが保存されているtestsディレクトリには、2つのファイルが登録済みです。
tests/Functional/BaseTestCase.php
tests/Functional/HomepageTest.php
2つのファイルの関係ですが、BaseTestCaseクラス側は共通処理が書かれた基底クラスです。実際のUnitTestコードはHomepageTestの方に記載されています。
$ grep "^class" tests\Functional\BaseTestCase.php
class BaseTestCase extends \PHPUnit_Framework_TestCase
$ grep "^class" tests\Functional\HomepageTest.php
class HomepageTest extends BaseTestCase
HomepageTest.phpでは3つのテストケースが記載されています。それぞれ、特定のURLへアクセスして想定した文字列を含むHtmlBodyが帰ってくるかをチェックしています。このコードは、新たなテストを書くときの参考になりますね。
<?php
namespace Tests\Functional;
class HomepageTest extends BaseTestCase
{
/**
* Test that the index route returns a rendered response containing the text 'SlimFramework' but not a greeting
*/
public function testGetHomepageWithoutName()
{
$response = $this->runApp('GET', '/');
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('SlimFramework', (string)$response->getBody());
$this->assertNotContains('Hello', (string)$response->getBody());
}
/**
* Test that the index route with optional name argument returns a rendered greeting
*/
public function testGetHomepageWithGreeting()
{
$response = $this->runApp('GET', '/name');
$this->assertEquals(200, $response->getStatusCode());
$this->assertContains('Hello name!', (string)$response->getBody());
}
/**
* Test that the index route won't accept a post request
*/
public function testPostHomepageNotAllowed()
{
$response = $this->runApp('POST', '/', ['test']);
$this->assertEquals(405, $response->getStatusCode());
$this->assertContains('Method not allowed', (string)$response->getBody());
}
}
メソッド内で呼ばれている$this->runApp()ですが、これは継承元のBaseTestCaseクラスで定義されています。内容は後述のpublic/index.phpとほぼ同じなので、ここでの説明は省略します(気になる人は確認してみてください)。
プログラムの確認
public/.htaccess
.htaccessの中身はコメントが大部分です。コメント部を除いた定義本体のみを抜粋すると、Rewrite定義が5つだけになります。
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</IfModule>
全てのアクセスをindex.phpに処理させる、いわゆるFront-ControllerパターンのためのURLリライト定義です。
public/index.php
先ほど確認したpublic/.htaccessで全ての処理がindex.phpに到達する来ることが分かりました。ですので、public/index.phpがアプリケーション全体のエントリポイントとなります。
public/index.phpの中身は重要ですので、細かく見ていきます。
public/index.php
<?php
if (PHP_SAPI == 'cli-server') {
// To help the built-in PHP dev server, check if the request was actually for
// something which should probably be served as a static file
$url = parse_url($_SERVER['REQUEST_URI']);
$file = __DIR__ . $url['path'];
if (is_file($file)) {
return false;
}
}
先頭のif (PHP_SAPI == 'cli-server')
は、PHPのビルトインサーバを使うときのお約束の書き方で、URLで指定されたファイルがpublic以下に存在していた場合は、該当ファイルをそのまま返す処理です。例えば、"public/asset/css/style.css"といった形で、publicディレクトリの下に、cssやjs,画像ファイルなどを置いておき、これらのファイルが指定された時に、該当ファイルの中身をそのまま返します。
PHPのビルトインサーバというのは、phpコマンドを"-S"オプション付きで実行すると,phpコマンドを簡易的なWebサーバとして実行させることができる機能の事です。ビルトインサーバを具体的にどうやって動かすかは、PHPUnitの時にも確認した、composer.jsonのscripts定義を見るとわかりますが、php -S localhost:8080 -t public public/index.php
というコマンドを実行すればよいです。(もちろんPHPUnitと同じようにcomposer start
コマンドでもアプリを起動できます)
public/index.php (続き)
require __DIR__ . '/../vendor/autoload.php';
session_start();
requireで、ComposerのAutoload処理を読み込んでいます。これはcomposerによるパッケージ管理ではお約束の記述です。
次に出てくるsession_start()は、Slim3フレームワークの処理ではなくPHP標準で提供されている関数で、セッション開始の処理です。
public/index.php (続き)
// Instantiate the app
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
src/settings.phpの中身を読み込んで、その内容をSlimフレームワークのエントリポイントである、\Slim\Appクラスのコンストラクタに渡しています。
src/settings.phpの中身は後で確認しますが、ファイル内では配列をreturnしています。PHPのrequireは読み込んだファイルの中にreturnを実行した場合その内容を呼び元に返すという仕様なので、結果的にreturnされた配列が$settings変数に格納されます。
このようにして、起動の処理(index.php)と、パラメータファイル(src/settings.php)を別ファイルに分離し、起動時のsettingが変わってもindex.phpを書き換えなくて良いように工夫しています。
public/index.php (続き)
// Set up dependencies
require __DIR__ . '/../src/dependencies.php';
// Register middleware
require __DIR__ . '/../src/middleware.php';
// Register routes
require __DIR__ . '/../src/routes.php';
次に3つ続けてrequire文が続きます。3つのファイルにはそれぞれ、DIコンテナの初期化、認証やCsrf対策などの、前・後処理を行うミドルウェア、ルーティング処理が記載されています。
reuireされている3つのファイルは、アプリの内容に応じて書き換えていくファイルです。
public/index.php (続き)
// Run app
$app->run();
最後に\Slim\Appクラスのインスタンスだった$appオブジェクトの、\Slim\App::run()メソッドを呼び、アプリケーションの処理を実行します。
src/の下のファイル達
先ほど確認したpublic/index.phpでは、下記の4ファイルがrequireされていました。
- src/settings.php
- src/dependencies.php
- src/middleware.php
- src/routes.php
これら4ファイルの内容を確認していきます。
src/settings.php
まずはsrc/settings.phpを確認します。このファイルは\Slim\Appクラスのコンストラクタ引数になっていましたね。
src/settings.php
<?php
return [
'settings' => [
'displayErrorDetails' => true, // set to false in production
'addContentLengthHeader' => false, // Allow the web server to send the content-length header
public/index.phpを確認したときにも説明したように、このファイル全体としては配列をreturnしています。配列の中には'settings'というキーを持つハッシュがあり、この中にSlim3フレームワークの設定を記述していきます。
displayErrorDetailsをtrueにすると、エラー発生時にSlim3フレームワークが、エラーハンドラへ詳細なエラーログを出力させるよう指示します。コメントにも記載されている通り、本番サーバへ展開する時には、この値をfalseに書き換える必要があります。
addContentLengthHeaderは、HTTP応答ヘッダのContent-Length
の出力制御です。デフォルト値はtrueなのですがこのスケルトンでは明示的にfalseにしています。もし、New Relicのようなランタイムでの分析ツールを使っている場合はfalseにして置く必要があります。
src/settings.php (続き)
// Renderer settings
'renderer' => [
'template_path' => __DIR__ . '/../templates/',
],
rendererでは、Viewテンプレートの保存ディレクトリを指定しています。
src/settings.php (続き)
// Monolog settings
'logger' => [
'name' => 'slim-app',
'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
'level' => \Monolog\Logger::DEBUG,
],
],
];
loggerの項目では、monologのログ出力定義です。
docker環境で動作する場合はログを標準出力に出す必要があるため、環境変数"docker"が設定されている場合は、php://stdoutを出力先としています。ログ出力レベルはDEBUG以上なので、本番環境で動作する場合は変更が必要です。
src/dependencies.php
次は、DIコンテナの定義を行うdependencies.phpです。
src/dependencies.php (続き)
<?php
// DIC configuration
$container = $app->getContainer();
Slim3では、\Slim\App::getContainer()メソッドでDIコンテナであるpimpleパッケージのコンテナオブジェクトが取得できます。
public/index.phpで生成した$appインスタンスからコンテナオブジェクトを取得し、$container変数にセットします。
src/dependencies.php (続き)
// view renderer
$container['renderer'] = function ($c) {
$settings = $c->get('settings')['renderer'];
return new Slim\Views\PhpRenderer($settings['template_path']);
};
rendererの名称でViewレンダラであるSlim\Views\PhpRendererクラスのインスタンが取得できるようにしています。
Viewのテンプレートファイルは、先ほど\Slim\Appのコンストラクタでセットした、settingsのrendererから取得しています。
src/dependencies.php (続き)
// monolog
$container['logger'] = function ($c) {
$settings = $c->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
return $logger;
};
loggerでは、Monolog\Loggerのインスタンスが取得できるようにします。こちらも設定情報をsettingsから取得しています。
src/middleware.php
middleware.phpはコメントだけが書かれた空のファイルです。
src/middleware.php
<?php
// Application middleware
// e.g: $app->add(new \Slim\Csrf\Guard);
コメントに記載があるように、Slimのミドルウェアを定義したい場合は、$app->add()にオブジェクトを渡すよう、定義を追加すればよいです。
src/routes.php
routes.phpは、ルーティングの定義を行うファイルです。ルーティングというのは、指定されたURLパスに対して、実行させる処理の対応付けを行う処理でのことです。
src/routes.php
<?php
use Slim\Http\Request;
use Slim\Http\Response;
// Routes
$app->get('/[{name}]', function (Request $request, Response $response, array $args) {
// Sample log message
$this->logger->info("Slim-Skeleton '/' route");
// Render index view
return $this->renderer->render($response, 'index.phtml', $args);
});
このスケルトンでは、route定義の中で直接ロジックを記載していますが、大きめのプログラムだとActionクラスを作って処理を委譲させるなど、役割を分けることなります。
URLの定義が'/[{name}]'
となっているため、http://localhost/alice
やhttp://localhost/bob
、もしくはnameが省略されたhttp://localhost/
のパターンでアクセスされるとルーティング対象となり処理が行われます。
指定されたnameは、コールバック関数の第三引数である、$argsにセットされます。
ここでは、monologでログだけ出力したのち、そのままviewに処理を委譲しています。
$this->renderer
は、DIコンテナでrendererを定義していたため、Slim\Views\PhpRendererクラスのインスタンスが返されます。その後、Slim\Views\PhpRenderer::render()メソッドをコールし、その結果がクライアント(ブラウザ)に返されます。
ビューテンプレートであるindex.phtml
ファイルは、settings.phpで記載した/../templates/
ディレクトリに存在します。
ビュー
先ほどのルート定義で、index.phtmlが指定されたので、templates/index.phtmlを確認してみます。
templates/index.phtml
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Slim 3</title>
<link href='//fonts.googleapis.com/css?family=Lato:300' rel='stylesheet' type='text/css'>
<style>
body {
margin: 50px 0 0 0;
padding: 0;
width: 100%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
text-align: center;
color: #aaa;
font-size: 18px;
}
h1 {
color: #719e40;
letter-spacing: -3px;
font-family: 'Lato', sans-serif;
font-size: 100px;
font-weight: 200;
margin-bottom: 0;
}
</style>
</head>
<body>
<h1>Slim</h1>
<div>a microframework for PHP</div>
<?php if (isset($name)) : ?>
<h2>Hello <?= htmlspecialchars($name); ?>!</h2>
<?php else: ?>
<p>Try <a href="http://www.slimframework.com">SlimFramework</a></p>
<?php endif; ?>
</body>
</html>
少し長いですがほとんどがcss等のhtmlデザイン定義です。プログラムに関係するのはhtml>bodyの中にある下記の5行だけなので、この部分をピックアップします。
templates/index.phtml (抜粋)
<?php if (isset($name)) : ?>
<h2>Hello <?= htmlspecialchars($name); ?>!</h2>
<?php else: ?>
<p>Try <a href="http://www.slimframework.com">SlimFramework</a></p>
<?php endif; ?>
ルーティングから、render($response, 'index.phtml', $args);
の形で、パラメータ$argsが渡されていました。
$argsは連想配列で$args['name']
の形でパラメータがセット渡されているのですが、viewではこれを$name変数として受け取れます。
値がセットされていれば、htmlspecialchars()でエスケープしたうえで、画面に表示しています。
まとめ
以上で、Slim3プロジェクトのひな型である、slim/slim-skeletonパッケージの調査は完了です。
Slim3フレームワーク自体が非常にシンプルなため、スケルトンプロジェクトも短時間で全ソースが確認できるレベルのシンプルさを保っています。
DBアクセスや、Csrf対策、認証処理など、他の大型フレームワークに用意されているようなライブラリはSlimでは用意されていません。これは欠点ではなく、むしろ開発者が自由にパッケージを選定して好みのものを利用すればよいという、自由度を提示してくれています。
今回の記事を機会に、ぜひ自分が考えたアイデアを形にしてみてください。
この記事のおかげでslim3でサイトを構築する事が出来ました。
ありがとうございます。
slim4のskeletonは更に複雑になっており解説サイトも皆無です。
slim4も是非ともお願い致します。
以前slim3を使用してサイトを作成する時に凄く勉強になりました!
可能であるならばslim4のskeletonも解説頂けないでしょうか?
何卒宜しくお願い致します!
このように解説してくださってるサイトが他になく、非常に面白く、また大変役に立ち助かりました。ありがとうございます。