k-holyのPHPとか諸々メモ

Webで働くk-holyがPHP(スクリプト言語)とか諸々のことをメモしていきます。ソースコードはだいたいWindowsで動かしてます。

マイクロフレームワークをつくろう - Pimpleの上に(Symfony HttpFoundation導入編)

Pimpleを拡張して自分好みに使うために作成した小さなアプリケーションクラスを使って、マイクロフレームワークっぽいものを作る試みです。

コードはWindows版 PHP5.4 ビルトインWebサーバにて動作確認しています。

Step 4 Symfony HttpFoundationコンポーネントを導入

とりあえずフォーム画面を作ることにしましたが、せっかくComposerを使っていながら一つ一つ部品を自作するというのも大変ですし、この辺りで少しSymfonyコンポーネントの力を借りることにします。

Webアプリケーションということで、まずはSilexアプリケーションでもよく使ったHttpFoundationコンポーネントを導入します。

composer.json

{
    "license": "MIT",
    "authors": [
        {
            "name": "k-holy",
            "email": "k.holy74@gmail.com"
        }
    ],
    "config": {
            "vendor-dir": "vendor"
    },
    "autoload": {
        "psr-0": {
            "Acme":"src"
        }
    },
    "require": {
        "php": ">=5.4",
        "pimple/pimple": "1.0.2",
        "symfony/http-foundation": ">=2.3,<2.4-dev"
    }
}

また今後ページを増やすことも想定して、アプリケーションオブジェクトの生成とコンテナへの関数やオブジェクトの登録を別ファイルで行うことにします。

app/app.php

<?php
/**
 * Create my own framework on top of the Pimple
 *
 * アプリケーション共通初期処理
 *
 * @copyright 2011-2013 k-holy <k.holy74@gmail.com>
 * @license The MIT License (MIT)
 */
include_once realpath(__DIR__ . '/../vendor/autoload.php');

use Acme\Application;

use Symfony\Component\HttpFoundation\Request;

$app = new Application();

// リクエストオブジェクトを生成
$app->request = $app->share(function(Application $app) {
    return Request::createFromGlobals();
});

// リクエスト変数を取得する
$app->findVar = $app->protect(function($key, $name, $default = null) use ($app) {
    $value = null;
    switch ($key) {
    // $_GET
    case 'G':
        $value = $app->request->query->get($name);
        break;
    // $_POST
    case 'P':
        $value = $app->request->request->get($name);
        break;
    // $_COOKIE
    case 'C':
        $value = $app->request->cookies->get($name);
        break;
    // $_SERVER
    case 'S':
        $value = $app->request->server->get($name);
        break;
    }
    if (isset($value)) {
        $value = $app->normalize($value);
    }
    if (!isset($value) ||
        (is_string($value) && strlen($value) === 0) ||
        (is_array($value) && count($value) === 0)
    ) {
        $value = $default;
    }
    return $value;
});

// リクエスト変数の正規化
$app->normalize = $app->protect(function($value) use ($app) {
    $filters = array(
        // HT,LF,CR,SP以外の制御コード(00-08,11,12,14-31,127,128-159)を除去
        // ※参考 http://en.wikipedia.org/wiki/C0_and_C1_control_codes
        function($val) {
            return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]|\xC2[\x80-\x9F]/S', '', $val);
        },
        // 改行コードを統一
        function($val) {
            return str_replace("\r", "\n", str_replace("\r\n", "\n", $val));
        },
    );
    foreach ($filters as $filter) {
        $value = $app->map($filter, $value);
    }
    return $value;
});

// HTMLエスケープ
$app->escape = $app->protect(function($value, $default = '') use ($app) {
    return $app->map(function($value) use ($default) {
        $value = (string)$value;
        if (strlen($value) > 0) {
            return htmlspecialchars($value, ENT_QUOTES);
        }
        return $default;
    }, $value);
});

// 全ての要素に再帰処理
$app->map = $app->protect(function($filter, $value) use ($app) {
    if (is_array($value) || $value instanceof \Traversable) {
        $results = array();
        foreach ($value as $val) {
            $results[] = $app->map($filter, $val);
        }
        return $results;
    }
    return $filter($value);
});

return $app;

Silexでもお馴染みのPimpleのshare()メソッドにより、$app->request でstaticに参照される Requestオブジェクトを生成する関数を登録しています。

リクエスト変数を取得する $app->findVar() の内容も、Requestオブジェクトを利用したものに書き換えています。

$app->normalize() など従来の処理は変わらず有効ですし、アプリケーションオブジェクトの利用側スクリプトにも影響はありません。

利用側のスクリプトは以下のようになりました。

www/index.php

<?php
/**
 * Create my own framework on top of the Pimple
 *
 * Step 4
 *
 * @copyright 2011-2013 k-holy <k.holy74@gmail.com>
 * @license The MIT License (MIT)
 */
$app = include realpath(__DIR__ . '/../app/app.php');

$form = array(
    'name'    => $app->findVar('P', 'name'),
    'comment' => $app->findVar('P', 'comment'),
);
?>
<html>
<body>
<h1>test</h1>

<form method="post" action="<?=$app->escape($app->findVar('S', 'REQUEST_URI'))?>">

<dl>
<dt>名前</dt>
<dd>
<input type="text" name="name" value="<?=$app->escape($form['name'])?>" />
</dd>
<dt>コメント</dt>
<dd>
<textarea name="comment">
<?=$app->escape($form['comment'])?></textarea>
</dd>
</dl>

<input type="submit" value="送信" />
</form>

<hr />

<dl>
<dt>名前</dt>
<dd><?=$app->escape($form['name'])?></dd>
<dt>コメント</dt>
<dd><pre><?=$app->escape($form['comment'])?></pre></dd>
</dl>

</body>
</html>

相変わらずHTMLと一緒になった醜いコードですが、まだフォーム送信後の処理が何もないだけに、すっきりしたものになりました。

アプリケーションオブジェクトに登録する関数のインタフェースが固まっていれば、その中の実装が変わっても、利用側のコードには影響を与えないのがポイントです。

直接クラスを利用している場合だと、こうはいきません。

(もちろん $app->request を直接扱うことになれば、そこに Requestクラスへの依存が生まれてしまうのですが…)

Pimpleの場合は無名関数を使ってオブジェクトを生成するため、初めからきっちりとクラスを設計する必要がないことも嬉しいです。

型による保証がない、ゆるいインタフェース依存になりますが、個人で小規模なアプリケーションをボトムアップ的に開発する場合、このスタイルが適していると感じます。

次はスクリプトとHTMLを分離するために、Rendererクラスを作成してみます。