Help us understand the problem. What is going on with this article?

Slim 3 Frameworkリリースされたので、ざっと紹介

More than 3 years have passed since last update.

Slim 3 Framework

PHPのSlimフレームワークのバージョン3がリリースされたので、簡単に紹介してみます。

【2016/01/16:主にルーティングについて追記・変更しました】

SlimフレームワークはPHPのマイクロ・フレームワークのひとつです。今回のバージョン3は、

  • PSR-7とミドルウェア構造の採用、
  • インターフェースに依存することによる拡張性の高さ、
  • DIコンテナの利用(デフォルトはPimple)、
  • でも簡単に使い始められる

のが特徴です。

何やら難しそうな単語が特徴に並んでますが、Slimのユーザーガイド読めば、すぐ使えるぐらい簡単です。なので難しそうな特徴を中心に紹介してみます。

簡単に使える

本当に簡単に使えます。Slimのユーザーガイドからコードを貼り付けて、ちょっとコードを削っただけですが、これだけのコードで動きます。

<?php
require dirname(__DIR__).'/vendor/autoload.php';

$app = new Slim\App();
$app->get('/hello/{name}', function ($request, $response, $args) {
    $response->getBody()->write("Hello, ".$args['name']);
    return $response;
});
$app->run();

設定などの置き場所

とは言うものの、プロダクションとして使う場合には設定やルーティングがあります。そのためにSlim Framework 3 Skelton Applicationがあります。最初から簡単な設定ファイルがあるので、ここ開発を始めるのが楽だと思います。

始め方ですが、ドキュメントに書いてある通りです。

php composer.phar create-project slim/slim-skeleton [my-app-name]

[my-app-name]のところに、アプリケーションを作るディレクトリ名を入れて下さい。

  • logs/ディレクトリにサーバーの書き込み権限をつける。
  • public/をルートディレクトリと指定する。

で動くはず。

ルーティング

ルーティングの基本は、ユーザーガイドのRoutingに詳しく乗っているので、サラリと紹介します。

$app->{メソード名}('ルートパターン', {ルートコールバック});
  • メソード名
    get, postなどのHTTPメソード。anyを使うと全てのメソッドでマッチします。複数のメソードにマッチさせたい場合はmapを使います。
  • ルートパターン
    マッチするルートパターン。最初のコードのようにネームホルダーを使えます。デフォルトでは、(スラッシュを除いて)何にでもマッチするので、文字種を絞りたい場合は'/hello/{id:[0-9]+}'とできます。逆にスラッシュも含める場合は/hello/{path:.*}とします。
  • ルートコールバック
    ルートにマッチした場合の処理用コールバックルーチン。

anymapを使ったルーティングの例です。

// 全てのHTTPメソッドにマッチ。
$app->any('/any/method', 'AnyMethodHandler');

// マッチするHTTPメソッドを配列指定。
$app->map(['get', 'post'], '/get/or/post', 'GetOrPostHandler');

Slim3では、PHPでは結構有名なnikic/fastrouteをルーターとして使ってます。ルート数が増えても、マッチング処理のスピードが(ほぼ)変わらない、という特徴を持ってます。

ルート名

ルートに名前をつけて、後で呼び出すことも出来ます。

// ルートに名前をつけておいて、
$app->get('/hello/{name}', function ($req, $res, $args) {
    echo 'Hello ', $args['name'];
})->setName('hello');

// 後で名前でパスを呼び出す。
$app->router->pathFor('hello', ['name' => 'slim']);

詳細はユーザーガイドのRoute Namesにあります。コード補完が効きそうですね。

Groups

複数のルートをグループとして設定することが出来ます。

$app->group('/users/{id:[0-9]+}', function () {
    $this->map([], '', function ($req, $res, $next) {
        // ユーザーに対して何か処理をする。
    });
    $this->get('/reset-password', function ($req, $res, $next) {
        // パスワードリセット処理
        // ルートは /user/####/reset-pasword
    });
});

こちらも詳細はユーザーガイドのRoute groupsにあります。

ルートコールバック

ルーティングがマッチしたら、リクエストを処理するルーチンのことを「Route Callbacks」と呼んでます。

PSR-7オブジェクト

コールバックは、PSR-7のリクエストとレスポンスのオブジェクトを受け取ります。レスポンスに必要な情報を設定します。リダイレクトの例を書くと、

$app->get('/redirected', function($req, $res, $args) {
    return $res
        ->withStatus(302)
        ->withHeader('Location', '/back');
});

設定ができたら、最後にレスポンスオブジェクトを返します。

ドキュメントを読むと、ハンドラーからHTMLなどを出力するには、

  • いきなりechoするか、
  • 内容を書き込んだレスポンスを返す、

とありましたが、ちゃんとレスポンスを返したほうがテストしやすいと思います。

コールバックで受け取る変数の並びです。標準では、次のようになってます。

function(
    \Psr\Http\Message\ServerRequestInterface $request, 
    \Psr\Http\Message\ResponseInterface $response, 
    array $args);

最後の$argsには、ネームホールダーにマッチした値が入ってきます。'/user/{name}'の「name」のところです。

コンテナを使う場合

ルートコールバックをコンテナを使って生成することもできます。例えばMyHandler::classという名前で、ファクトリをコンテナに設定しておきます。

# dependencies.phpなどで
$app->getContainer()[MyHandler::class] = 
    function(ContainerInterfrace $c) {
        return new MyHandler(new MyDependency);
    };

ルートを設定するときに、同じくMyHandler::class

# routes.phpなどで
$app->get('/my/handler', MyHandler::class);

すると、MyHandlerクラスを生成してから__invokeメソッドを呼び出します。

コールバック関数の引数を変更する方法

実は、ユーザーガイドには引数を変更する方法が書いてあります。Slim\Interfaces\InvocationStrategyInterfaceを実装すれば、自分好みの動きに変更できるとあります。

例えば、こう。

class MyInvocation implements InvocationStrategyInterface
{
    public function __invoke(
        callable $callable,
        ServerRequestInterface $request,
        ResponseInterface $response,
        array $routeArguments
    ) {
        // ネームホールダーはクエリとして扱う。
        $query = $request->getQueryParams();
        $query = array_merge($query, $routeArguments);
        $request = $request->withQueryParams($query);

        return call_user_func($callable, $request, $response);
    }
}

こんなクラスを作っておきます。これを$app作成時に、

$app = new \Slim\App([
    'foundHandler' => function() {
        return new MyInvocation();
    } 
]);

とすると、先の例だとこうなります。

$app->get('/hello/{name}', function ($req, $res) {
    echo 'Hello ', $req->getQueryParams()['name'];
});

コードを見比べると少々バカバカしい例ですが、すでにコントローラーがある場合などに便利かもしれません。

コールバックに何が使えるか?

実はコールバックには色々なパターンが使えます。標準でコールバックとして使えるパターンを調べました。

Slim3では、文字列からオブジェクトに解決するのは、CallableResolverというクラスを使っています。コードを見ると、結構複雑で、色々なパターンに対応しているのが分かります。まとめると、こんなところでしょうか。

  • 実行可能なオブジェクト:
    • クロージャー、
    • __invoke可能なオブジェクト、
  • 文字列:
    • コンテナから取り出す文字列(コンテナ名)、
    • クラス名、
    • コンテナ名:メソッド名の文字列、
    • クラス名:メソッド名の文字列、

が使えます。文字列の場合は、コンテナから取り出してみて、無ければクラス名としてnewする感じですね。つまりクラス名の場合は

class MyCallback {
    function __construct(ContainerInterface $c) {
        $this->service = $c->get('MyService'); // SL!!!
    }
}

と書けます!

コンテナーが入ってくるので、何でもできちゃいますね。この例はサービスロケーターという使い方なので、あちこちから突っ込まれる方法です。

CallableResolverInterface: 自作で解決

一方、自作のResolverを使うことも可能です。CallableResolverInterfaceを実装したクラスを作ります。

class MyResolver implements CallableResolverInterface {
    public function resolve($toResolve) {
        return $toResolve::forge();
    }
}
$app = new Slim\App([
    'callableResolver' => function() {
        return new MyResolver();
    } 
]);

newする代わりにスタティックのforgeメソッドを使ってインスタンスを生成します。

ミドルウェア

Slim3の特徴の一つが、PSR-7のミドルウェアを使っていることによる拡張性の高さです。PSR-7を使っているので、フレームワークに限定されずに様々なミドルウェアを使うことが出来ます。

ミドルウェアの構造

PHPのPSR-7のミドルウェアといえば、この形です。

<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;

function __invoke(Request $request, Response $response, callable $next) {
    /**
     * 必要な処理を行う。
     */
    $response = $next($request, $response); // 次のミドルウェアを呼び出す。
    /**
     * 戻ってきたレスポンスを使った処理も可能。
     */

    return $response; // レスポンスは「必ず」返しましょう。
};

もう標準化してしまえばいいのに…

ミドルウェアとして使えるのは、クロージャー、invoke可能なオブジェクト、クラス名… あれ?どこかで見たことのあるリストですね。先のルートコールバックと同じく、ミドルウェアもResolverを使って解決しています。

実は__invokeを実装してなくても動く、はず(実証まだです)。

指定できる箇所

ミドルウェアを指定できるのは、Slimアプリケーション、ルート、あるいはルートグループ、となります。

// アプリ全体のミドルウェア。
$app->add(new MyMiddleware);

// 特定のルートのみでミドルウェアを実行する。
$app->get('/middle', function($req, $res) {return $res;})
    ->add(new MyRouteMiddleware);

// 特定のルートグループでミドルウェアを実行する。
$app->group(function() {})
    ->add(new MyGroupMiddleware);

実行順

複数のミドルウェアを使う場合、最後に登録したミドルウェアから順番に実行されます。

$app = new Slim\App();
$app->add(function($req, $res, $next) {
    $res->getBody()->write("middleware #1\n");
    return $next($req, $res);
});
$app->add(function($req, $res, $next) {
    $res->getBody()->write("middleware #2\n");
    return $next($req, $res);
});
$app->get('/hello/Slim', function ($request, $response, $args) {
    $response->getBody()->write("Hello, Slim");

    return $response->withHeader('Content-Type', 'text/plain');
});

の場合、出力は

middleware #2
middleware #1
Hello, Slim

となります。これは、ちょっと自分の感覚と逆なので注意が必要です。

マイクロ・フレームワーク

力尽きてきたので、ここらへんで切り上げます。

基本はマイクロ・フレームワークなので、必要な機能は自分で追加することになります。が、SlimPHPのレポジトリには、基本的な機能がいくつか存在します。

アドオン・ミドルウェア

  • Slim-Csrf :
    クロスサイトリソースフォーリッジ対策用のトークンの生成とバリデーションを行うミドルウェアです。

  • Slim-HttpCache :
    HttpのEtagを使ったレスポンスキャッシュを実装しています。ただし、まだ開発中のようで、(2012/12/14現在で)バージョン0.3.0です。

セッションも作ってるみたいで、レポジトリは存在します。けど、まだ空っぽです。

ビュー

ビュー、あるいはテンプレートだと、

がありました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした