Create your PHP Frameworkをやってみる③

Create your PHP Frameworkをやってみる②の続きです。

この記事は、version 2.7に基づいています。

The Front Controller (current)

Goodbyeページの作成

別のページ、Goodbyeページを作成します。

bye.php

<?php

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

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

$response = new Response('Goodbye!');
$response->send();

http://127.0.0.1:4321/bye.phpにブラウザでアクセスすると「Goodbye!」と表示されるはずです。

ただ、これだとindex.phpbye.phpどちらも以下のコードは同じです。

<?php

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

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();

また、この方法では、ページを増やすたびに同じコードをコピーすることになり、よくなさそうです。

そこで、重複するコードを別のファイルにまとめるようにリファクタリングします。

共通するコードを別のファイルにまとめる

init.phpを作成し、共通するコードをまとめます。

init.php

<?php

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

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$response = new Response();

index.phpinit.phpをインクルードするようにします。

index.php

<?php

require_once __DIR__.'/init.php';

$input = $request->get('name', 'World');

$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
$response->send();

bye.phpも同じように書き換えます。

bye.php

<?php

require_once __DIR__.'/init.php';

$response->setContent('Goodbye!');
$response->send();

かなりすっきりしました。しかし、まだ$response->send();は重複しています。

フロントコントローラの導入

さらに改良するために、フロントコントローラを導入します。

フロントコントローラとは、すべてのリクエストを受ける1つのPHPファイルのことです。

フロントコントローラとしてfront.phpを作成します。

front.php

<?php

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

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

$request = Request::createFromGlobals();
$response = new Response();

$map = array(
    '/hello' => __DIR__.'/hello.php',
    '/bye'   => __DIR__.'/bye.php',
);

$path = $request->getPathInfo();
if (isset($map[$path])) {
    require $map[$path];
} else {
    $response->setStatusCode(404);
    $response->setContent('Not Found');
}

$response->send();

上記のファイルがすべてのアクセスを受け、必要なファイルを呼び出すことになります。

どのファイルを呼び出すかは$mapで定義されています。

hello.phpを作成します。

hello.php

<?php

$input = $request->get('name', 'World');
$response->setContent(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

bye.phpを変更します。

bye.php

<?php

$response->setContent('Goodbye!');

これで、コードの重複はなくなり、以下のURLでアクセスできるようになりました。

  • http://127.0.0.1:4321/front.php/hello?name=Fabien
  • http://127.0.0.1:4321/front.php/bye

フォルダ構成の改良

フォルダ構成を改良します。

フロントコントローラ以外はブラウザからアクセスする必要はありませんので、webフォルダを作成しその中に移動します。

ページのソースもsrc/pagesフォルダに移動します。

いらないファイルは削除しましょう。

    deleted:    index.php
    deleted:    init.php
    renamed:    bye.php -> src/pages/bye.php
    renamed:    hello.php -> src/pages/hello.php
    deleted:    test.php
    renamed:    front.php -> web/front.php

ということで、以下のようになりました。よりちゃんとしたフレームワークっぽくなりました。

framework/
├── composer.json
├── composer.lock
│   src/
│   └── pages/
│       ├── hello.php
│       └── bye.php
├── vendor/
└── web/
    └── front.php

ファイルを移動したので、それに合うようにパスを修正しておきます。

--- a/web/front.php
+++ b/web/front.php
@@ -1,6 +1,6 @@
 <?php

-require_once __DIR__.'/vendor/autoload.php';
+require_once __DIR__.'/../vendor/autoload.php';

 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
@@ -9,8 +9,8 @@ $request = Request::createFromGlobals();
 $response = new Response();

 $map = array(
-    '/hello' => __DIR__.'/hello.php',
-    '/bye'   => __DIR__.'/bye.php',
+    '/hello' => __DIR__.'/../src/pages/hello.php',
+    '/bye'   => __DIR__.'/../src/pages/bye.php',
 );

 $path = $request->getPathInfo();

これで、PHPのビルトインWebサーバを以下のように起動すると、

$ php -S 127.0.0.1:4321 -t web/ web/front.php

http://127.0.0.1:4321/?name=Fabienのようにフロントコントローラのファイル名を消したURLでアクセスできるようになりました。

私たちのフレームワークの最初のバージョン

現在、ページのソースがPHPファイルですが、これをテンプレートに変更しましょう。

--- a/web/front.php
+++ b/web/front.php
@@ -15,7 +15,9 @@ $map = array(

 $path = $request->getPathInfo();
 if (isset($map[$path])) {
-    require $map[$path];
+    ob_start();
+    include $map[$path];
+    $response->setContent(ob_get_clean());
 } else {
     $response->setStatusCode(404);
     $response->setContent('Not Found');

これでページのソースは以下のようになります。

src/pages/hello.php

<?php $name = $request->get('name', 'World') ?>

Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>

src/pages/bye.php

Goodbye!

新たにページを増やす場合は、

  1. front.php$mapに登録
  2. src/pagesフォルダにテンプレートを追加

となり、テンプレート内では$request$responseが使えます。

これが、私たちのフレームワークの最初のバージョンです。

Create your PHP Frameworkをやってみる④へ続く。

Date: 2015/07/16

Tags: php, symfony