軽量PHPフレームワークSlimを使ってサクッとWebアプリひな形を作ってみる

PHPには数多くのフレームワークがあります。
LaravelCakePHPのようなWebアプリ開発に必要な機能が一通りそろったフレームワークをフルスタックフレームワークと呼ぶのに対し、Slim軽量フレームワークマイクロフレームワーク)と呼ばれます。

フルスタックフレームワークとの違いは、必要最小限の機能だけを有していることによる、学習コストの低さと、軽量さ(ファイルサイズ、実行速度)です。

Slimを使って必要最小限の機能を備えたWebアプリのひな形を作ってみます。

公式サイト

GitHub

プロジェクトの作成

Slim本体はComposerを使ってインストールします。
バージョン4が出てますが3系の方が分かりやすかったのでこちらを使います。


まっさらな状態で始めることもできますが、スケルトンと呼ばれる、初期構成のフォルダ・ファイルが用意されたものでプロジェクトを作成します。
composer create-project slim/slim-skeleton:^3.* myapp
できあがるファイル・ディレクトリ構成は次のようになります(一部割愛)。
myapp/
├───logs/
├───public/
│       index.php
│       .htaccess
├───src/
│       dependencies.php
│       middleware.php
│       routes.php
│       settings.php
├───templates/
│       index.phtml
├───tests/
└───vendor
インストールできたらローカルサーバーで実行してみます。
cd myapp
php -S localhost:8888 -t public public/index.php
http://localhost:8888/」にアクセスするとデフォルトページが表示されます。

サンプルページとフレームワークの処理

http://localhost:8888/Slim」にアクセスすると、デフォルトで用意されているサンプルページが表示されます。
このページの処理を追いかけてフレームワークの詳細をつかみます。

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;
    }
}

require __DIR__ . '/../vendor/autoload.php';

session_start();

// Instantiate the app
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);

// Set up dependencies
$dependencies = require __DIR__ . '/../src/dependencies.php';
$dependencies($app);

// Register middleware
$middleware = require __DIR__ . '/../src/middleware.php';
$middleware($app);

// Register routes
$routes = require __DIR__ . '/../src/routes.php';
$routes($app);

// Run app
$app->run();
フレームワークの起点となるファイルです。
.htaccessで静的ファイル以外のURLへのリクエストをこのファイルにリライトします。 内部で以下のファイルを順にインクルードしていて、フレームワークそのものであるAppインスタンスにセット・実行しています。
  1. src/settings.php
  2. src/dependencies.php
  3. src/middleware.php
  4. src/routes.php

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

        // Renderer settings
        'renderer' => [
            'template_path' => __DIR__ . '/../templates/',
        ],

        // Monolog settings
        'logger' => [
            'name' => 'slim-app',
            'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
            'level' => \Monolog\Logger::DEBUG,
        ],
    ],
];
アプリケーションの設定を記述するファイルです。
エラー表示の有無や、テンプレートやログの配置をここで設定しています。
任意のキーでユーザー独自の設定も作れます。

設定は次のようにフレームワーク内で取得できます。
$container = $app->getContainer();
$settings  = $container->get('settings');

src/dependencies.php

<?php

use Slim\App;

return function (App $app) {
    $container = $app->getContainer();

    // view renderer
    $container['renderer'] = function ($c) {
        $settings = $c->get('settings')['renderer'];
        return new \Slim\Views\PhpRenderer($settings['template_path']);
    };

    // 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;
    };
};
アプリケーションとコンポーネントの依存関係を設定するファイルです。


デフォルトではテンプレートエンジンとログの依存関係が設定してあります。

例えばロガーのコンポーネントを変えてもフレームワーク上は$container['logger']で変わらずインスタンスにアクセスできるようにする工夫ですね。

src/middleware.php

<?php

use Slim\App;

return function (App $app) {
    // e.g: $app->add(new \Slim\Csrf\Guard);
};
リクエスト・レスポンスとMVC処理の間に挟まる処理を記述するファイルです。


例えばデバッグツールバーの設定をここで行ったりします。

src/routes.php

<?php

use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;

return function (App $app) {
    $container = $app->getContainer();

    $app->get('/[{name}]', function (Request $request, Response $response, array $args) use ($container) {
        // Sample log message
        $container->get('logger')->info("Slim-Skeleton '/' route");

        // Render index view
        return $container->get('renderer')->render($response, 'index.phtml', $args);
    });
};
URLから対応する処理へのルーティングを行う部分です。

上に書いたものから順にURLのパターンとHTTPメソッドに一致するか見ていき、一致した場合割り当てられた関数の処理が実行されます。

サンプルページは、URLの文字列を$name変数に入れ、templates/index.phtmlのレンダリングを行っています。
以上がSlim3フレームワークの処理の大まかな流れです。
フルスタックフレームワークに比べシンプルでわかりやすいですね。

カスタマイズする

このままでも、十分ちょっとしたPHPアプリの作成には事足りそうですが、せっかくなのでもうちょっとMVCっぽくしてみます。

コントローラーを分離する

デフォルトだとルーティングの中に匿名関数が埋め込まれていて、処理が大きくなると、コードの見通しが悪くなりそうです。

コントローラーをクラスとして独立させて、ルーティングではメソッドを呼び出すだけにしてみます。

クラス作成

myappディレクトリの中に新たにcontrollersディレクトリを作成し、その中にHello.phpというクラスファイルを作成します。
<?php
namespace Slim\App\Controllers;

class Hello
{
    private $c;

    public function __construct($container) {
        $this->c = $container;
    }

    public function index($request, $response, $args)
    {
        // Sample log message
        $this->c->get('logger')->info("Slim-Skeleton '/' route");

        // Render index view
        return $this->c->get('renderer')->render($response, 'index.phtml', $args);
    }
}
コントローラーのメソッドの処理はルーティングの中に書かれていた匿名関数の中身です。

ルーティングからコントローラーのメソッドを呼び出す。

<?php

use Slim\App;
use Slim\Http\Request;
use Slim\Http\Response;

return function (App $app) {
    $container = $app->getContainer();

    $app->get('/[{name}]', function (Request $request, Response $response, array $args) use ($container) {
        $Hello = new Slim\App\Controllers\Hello($container);
        return $Hello->index($request, $response, $args);
    });
};
ルーティングの中では先ほど用意したクラスのインスタンス化、メソッド呼び出しを行います。

オートロードの設定

このままでは、コントローラークラスがロードされないので、オートロードされるようcomposer.jsonに記述を追加します。
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/",
            "Slim\\App\\Controllers\\": "controllers/"
        }
    },
追加したら、下記のコマンドでオートローダーをリフレッシュします。
composer dump-autoload
これで、サンプルページにアクセスすると先ほどと変わらない表示ですが、コントローラーは分離されています。

データベースを使う

データベースを使ってみます。

DB接続定義を外部ファイルに設定する

環境によって異なるDB接続定義は外部ファイルに記述するのが定石です。

先にdotenvを使えるようにして、そこにDB接続定義を記述できるようにします。
composer require vlucas/phpdotenv
composerを使ってdotenvをインストールしたら、srcフォルダに.envファイルを作成し、データベースの情報を記述します。
host=localhost
dbname=test
user=test_user
pass=test_pass
src/settings.php.envを読み込み、配列に接続情報をセットします。
<?php
$dotenv = Dotenv\Dotenv::create(__DIR__);
$dotenv->load();

return [
    'settings' => [
        'displayErrorDetails' => true, // set to false in production
        'addContentLengthHeader' => false, // Allow the web server to send the content-length header

        // Renderer settings
        'renderer' => [
            'template_path' => __DIR__ . '/../templates/',
        ],

        // Monolog settings
        'logger' => [
            'name' => 'slim-app',
            'path' => isset($_ENV['docker']) ? 'php://stdout' : __DIR__ . '/../logs/app.log',
            'level' => \Monolog\Logger::DEBUG,
        ],

        // Database
        'db' => [
            'host'   => getenv('host'),
            'dbname' => getenv('dbname'),
            'user'   => getenv('user'),
            'pass'   => getenv('pass'),
        ],
    ],
];

依存関係を設定する

src/dependencies.phpにPDOの依存関係を設定します。
    // PDO
    $container['db'] = function ($c) {
        $db = $c['settings']['db'];
        $pdo = new PDO('mysql:host=' . $db['host'] . ';dbname=' . $db['dbname'],
            $db['user'], $db['pass']);
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
        return $pdo;
    };
ここではMySQLを使います。

これでデータベース(PDO)を使う準備は完了です。

WebAPIを作る

PDOを使う機能をWebAPI形式で作ってみます。

まずはsrc/routes.phpにルーティングを追加します。
    $app->get('/api/[{name}]', function (Request $request, Response $response, array $args) use ($container) {
        $Hello = new Slim\App\Controllers\Hello($container);
        return $Hello->getJson($request, $response, $args);
    });
HelloクラスにgetJsonメソッドを追加します。
    public function getJson($request, $response, $args)
    {
        $sql  = <<< SQL
SELECT *
  FROM animals
ORDER BY name
SQL;
        try {
            $stmt = $this->c->get('db')->prepare($sql);
            $stmt->execute();
            $list = $stmt->fetchAll();
        }catch(Exception $e){
            $this->c->get('logger')->error($e->getMessage());
        }

        $name = $request->getAttribute('name');
        return $response->withJson([
            "name" => $name,
            "list" => $list,
        ], 200);
    }
SQLは実行環境に合わせて変更してください。
http://localhost:8888/api/Slim」と実行すると次のようにJSONが返ります。
{
  "name": "Slim",
  "list": [{
      "id": "3",
      "name": "bird"
    },{
      "id": "2",
      "name": "cat"
    },{
      "id": "1",
      "name": "dog"
    }]
}

ここまでやってみて、Slimが他のフレームワークと比べて圧倒的にシンプルで使いやすいことが分かりました。
個人的にも気に入ったので、ちょっとしたWebアプリの開発ならSlimをドンドン推していきたいですね。

では。