Create your PHP Frameworkをやってみる④
Create your PHP Frameworkをやってみる③の続きです。
この記事は、version 2.7に基づいています。
The Routing Component (current)
テンプレートを読みやすく
現状だとテンプレート上に、Requestからデータを取得してテンプレートで必要な変数を定義する処理があります。
できればこのような処理はテンプレートから削除したいですね。
そこで、front.php
を以下のように変更します。
--- a/web/front.php
+++ b/web/front.php
@@ -6,21 +6,20 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$request = Request::createFromGlobals();
-$response = new Response();
$map = array(
- '/hello' => __DIR__.'/../src/pages/hello.php',
- '/bye' => __DIR__.'/../src/pages/bye.php',
+ '/hello' => 'hello',
+ '/bye' => 'bye',
);
$path = $request->getPathInfo();
if (isset($map[$path])) {
ob_start();
- include $map[$path];
- $response->setContent(ob_get_clean());
+ extract($request->query->all(), EXTR_SKIP);
+ include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
+ $response = new Response(ob_get_clean());
} else {
- $response->setStatusCode(404);
- $response->setContent('Not Found');
+ $response = new Response('Not Found', 404);
}
$response->send();
これで、テンプレートで$request
からデータを取得するPHPコードを削除できます。
--- a/src/pages/hello.php
+++ b/src/pages/hello.php
@@ -1,3 +1 @@
-<?php $name = $request->get('name', 'World') ?>
-
Hello <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8') ?>
ただし、これだとクエリ文字列にname
がない場合にエラーになりますが、それはこの後で対処されるようです。
Routingコンポーネントの導入
変数$map
で定義していたURLパスとテンプレートの関係(ルーティング)を処理するため、Symfonyコンポーネントの1つであるRoutingコンポーネントを導入します。
$ composer require symfony/routing
Using version ^2.7 for symfony/routing
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing symfony/routing (v2.7.2)
Downloading: 100%
symfony/routing suggests installing symfony/config (For using the all-in-one router or any loader)
symfony/routing suggests installing symfony/yaml (For using the YAML loader)
symfony/routing suggests installing symfony/expression-language (For using expression matching)
symfony/routing suggests installing doctrine/annotations (For using the annotation loader)
Writing lock file
Generating autoload files
v2.7.2がインストールされました。
front.php
をRoutingコンポーネントを使うように書き換えます。
--- a/web/front.php
+++ b/web/front.php
@@ -4,22 +4,25 @@ require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\Routing;
$request = Request::createFromGlobals();
+$routes = include __DIR__.'/../src/app.php';
-$map = array(
- '/hello' => 'hello',
- '/bye' => 'bye',
-);
+$context = new Routing\RequestContext();
+$context->fromRequest($request);
+$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
-$path = $request->getPathInfo();
-if (isset($map[$path])) {
+try {
+ extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
- extract($request->query->all(), EXTR_SKIP);
- include sprintf(__DIR__.'/../src/pages/%s.php', $map[$path]);
+ include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
+
$response = new Response(ob_get_clean());
-} else {
+} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
+} catch (Exception $e) {
+ $response = new Response('An error occurred', 500);
}
$response->send();
ルーティングを定義するsrc/app.php
を作成します。
src/app.php
<?php
use Symfony\Component\Routing;
$routes = new Routing\RouteCollection();
$routes->add('hello', new Routing\Route('/hello/{name}', array('name' => 'World')));
$routes->add('bye', new Routing\Route('/bye'));
return $routes;
ルーティングの動作を確認しておきましょう。以下のようにfront.php
を変更して、ブラウザでアクセスしてみます。
--- a/web/front.php
+++ b/web/front.php
@@ -13,6 +13,12 @@ $context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
+var_dump($matcher->match('/bye'));
+var_dump($matcher->match('/hello/Fabien'));
+var_dump($matcher->match('/hello'));
+//$matcher->match('/not-found');
+exit;
+
try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
結果は以下のようになりました。
array (size=1)
'_route' => string 'bye' (length=3)
array (size=2)
'name' => string 'Fabien' (length=6)
'_route' => string 'hello' (length=5)
array (size=2)
'name' => string 'World' (length=5)
'_route' => string 'hello' (length=5)
以下のようになっていることがわかります。
- URLパスが
/bye
の場合は、_route
はbye
/hello/Fabien
の場合は、_route
はhello
でname
はFabien
/hello
の場合は、_route
はhello
でname
はWorld
また、マッチしないURLパスの場合は、例外Symfony\Component\Routing\Exception\ResourceNotFoundException
が発生しました。
現在のfront.php
を確認しておきましょう。
web/front.php
<?php
require_once __DIR__.'/../vendor/autoload.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../src/app.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
ob_start();
include sprintf(__DIR__.'/../src/pages/%s.php', $_route);
$response = new Response(ob_get_clean());
} catch (Routing\Exception\ResourceNotFoundException $e) {
$response = new Response('Not Found', 404);
} catch (Exception $e) {
$response = new Response('An error occurred', 500);
}
$response->send();
全部で28行です。かなり、フレームワークらしくなりました。
(続く)
Date: 2015/07/17