gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

Slim skeletonの解析

さて続きましては slim/slim-skeleton の解析。
https://github.com/slimphp/Slim-Skeleton なので、なんとなし「多分公式が推奨してるんじゃないかと思われる方向性」が確認できるんじゃないかなぁ、と期待。
……で調べたら、別の人が作ったスケルトンもあるのね( https://github.com/oanhnn/slim-skeleton とか)。
ふぅんこれはこれで面白いなぁ、と思ったんだが、まぁいったんは「公式の推奨方向」の確認から。


まずは、起点から確認をしてみませう。
public/index.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
require __DIR__ . '/../src/dependencies.php';

// Register middleware
require __DIR__ . '/../src/middleware.php';

// Register routes
require __DIR__ . '/../src/routes.php';

// Run app
$app->run();

前回は(一応公式に書かれたものをベースにしてるとはいえ)手で作ったのが、今回はあらかじめ入っているという素晴らしさ。
さて、細かく見ていきませう。

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

へぇブロックしたいんだ。
「なぜブロックしたいのか」が微妙に不明なんだけど、まぁいったん。
「ビルトインのPHP devサーバを手助けするために、リクエストが実際には静的ファイルとして提供されるべきものであるかどうかを確認してください(機械翻訳)」って書いてあるので、まぁ、なんか 必要なんでしょう、程度にざっくりと。
いや「is_fileのif文の中がfalse returnなのが不思議だなぁ」程度の感触。

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

いわゆる「一般的なオートローダーの取り込み」。これは前述の時もあったのであんまり気にならず。

session_start();

へぇセッション使うようにしてるのか。
……うん確かに、plainなのだとセッション使ってなかったような記憶がかすかにあるなぁ。
でもこれ「セッション保存をテーブルにしたいとき」の処理とか、どうしよ?
プレファイル作る? おとなしくindex.phpに直書き?

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

Appに引数渡してる。
settings.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,
        ],
    ],
];

初手のkey「setting」は決まり文句かしらん。変えてもいいけど後々面倒だし、変えないほうが楽だよねぇ、的な。
…… config() 関数とか作って楽をしてもよいかも。
あと、自動でMonolog入れてる。
displayErrorDetails と addContentLengthHeader は「Slimがもともと使うことを想定していた変数」だねぇ。……今度その辺も整理してみようかしらん?


まぁいずれにしても「いろいろな設定情報を取り込んでる」のが見て取れるげ。
「環境差異のある情報」とかも、うまいことここに入れ込みたいなぁ。


次。

// Set up dependencies
require __DIR__ . '/../src/dependencies.php';


src/dependencies.php

$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に入れるやつだ。
PhpRendererって知らないなぁ……Zend2に入ってるやつかな??
この辺は差し替えたいかも。まぁ差し替えるのは極めて簡単でせう。

// Register middleware
require __DIR__ . '/../src/middleware.php';

src/middleware.php

<?php
// Application middleware

// e.g: $app->add(new \Slim\Csrf\Guard);

多分ってか確実に「ミドルウェアの設定を書くところ」。これが「実処理を書くところ」だったらびっくりだお(笑
ミドルウェアは、まだ、全然触ってないからなぁ。
ミドルウェア自体をちゃんと扱い始めてから、改めて考えてみませう。

// Register routes
require __DIR__ . '/../src/routes.php';

src/routes.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);
});

ルーティング書くところ。
まぁそのまんま、だねぇ。
戻り値が、おそらくはこれ「string型」のreturnだなぁ。まぁわかりやすくてよいと思う。


で、あとは

// Run app
$app->run();

は同じ。


あとはphpunit.xmlが入っていて tests/Functional があるあたりが興味深いかな。
ちと、軽く潜ってみませう。


まずはphpunit.xml

<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="SlimSkeleton">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

うんまぁなんていうか「これ以上ないってくらいスタンダード」なパターンだと思うんだがどうだろw
testsディレクトリって、大体「この名前」だよねぇ。それ以外のってあんまり見たことがない。
さて。testsん中にはFunctionalってディレクトリがあって。
BaseTestCase.phpが「継承元クラス」っぽいので、先に片付きそうなHomepageTest.phpを軽く触ってからBaseTestCase.phpにいきませう。


HomepageTest.php

    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から始まっているよねぇ」とかいうベーシックなところはおいといて。
「$this->runApp」とかが多分「このメソッドでこのURIを叩いてみる」的な動きなんだねぇ。………へぇ便利だ。
assertContainsとかあんまり使わないから記憶にないなぁ………なるほど「この文字が含まれる」か。
assertNotContainsは逆で「含まれないこと」と。
とりあえず「$this->assertEquals(200, $response->getStatusCode());」を全Pageぶん回すだけでも、色々と楽なんじゃなかろうか? 認証系とかあるんだろうけどさ。


では、速やかにBaseTestCase.php
直近はrunApp()と、あとは初期処理系のメソッドの確認かなぁ。


BaseTestCase.php

class BaseTestCase extends \PHPUnit_Framework_TestCase
{

「だ〜よ〜ね〜〜」なあたりの確認からstart。


………ふむ、__constructはなし、か。
PHPUnit、たしか

setUpBeforeClass() // 一回だけ
loop {
	setUp()
		テストメソッド本体
	tearDown()
} pool
tearDownAfterClass() // 一回だけ

こーゆー構造だったよねぇ。


setUpBeforeClass()は……なし。
setUp()……も、なし、か。したら直接runApp()いきますか。
……っつかこのクラス、runApp()のみだわ。

    public function runApp($requestMethod, $requestUri, $requestData = null)
    {
        // Create a mock environment for testing with
        $environment = Environment::mock(
            [
                'REQUEST_METHOD' => $requestMethod,
                'REQUEST_URI' => $requestUri
            ]
        );

引数は一旦、おいといて。
Environment::mock、ですか。


vendor/slim/slim/Slim/Http/Environment.php を見てみる……あぁCollectionの継承先クラス、なのか。
ようは「データをためておく」程度の感じ。
んで

        // Set up a request object based on the environment
        $request = Request::createFromEnvironment($environment);

か。

    public static function createFromEnvironment(Environment $environment)
    {

Environmentが引数の前提だし、完全に「これ専用のメソッド」だねぇ。

        // Set up a response object
        $response = new Response();

これは、まぁ、うん。

        // Use the application settings
        $settings = require __DIR__ . '/../../src/settings.php';

        // Instantiate the application
        $app = new App($settings);

        // Set up dependencies
        require __DIR__ . '/../../src/dependencies.php';

        // Register middleware
        if ($this->withMiddleware) {
            require __DIR__ . '/../../src/middleware.php';
        }

        // Register routes
        require __DIR__ . '/../../src/routes.php';

この辺も、まぁ、うん。
ただ、ここ「共通化」できねぇかなぁ?

        // Process the application
        $response = $app->process($request, $response);

process()………あぁrun()で呼ばれる所の「実処理担当」のあたりだ。ふむふむ。
んで

        // Return the response
        return $response;

か、なるほど。


たしかに「URIを渡して、実行部分だけ切り抜いてる」感じだなぁ。
これなら、多分
・Content-typeがjsonかどうか
jsonのフォーマットの確認
とかもできそうな気がするので、ざっくりしたテストくらいなら、一通りかけそうだ。


なお、tearDown()もtearDownAfterClass()も見つかりませんでしたまる。


細かい話をすると「なんで Functional ?」とか思うんだが、多分これはおいちゃんの英語能力の欠落が原因だろうなぁ、的な。
………あぁ、そうか。例えばCodeceptionなんかでも「機能テスト」ってあるし、URI見ると FunctionalTests だから、これでよいのか。
そうすると……同じレベルで「Unit」とか作るべきかなぁ。納品的には「Acceptance」も大事だろうし。
まぁ、その辺もおいおい、だねぇ。


ふむ………
なんとなく「もう少しいろいろと付け足したりしたい」感じではあるけど、確かに、ベースにはできそうな気がする。
その辺の「付け足したいところ」は、実際に書きながら、少しづつ考えていきませうか。