Create your PHP Frameworkをやってみる②

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

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

The HttpFoundation Component (current)

昨日の「最もシンプルなWebアプリ」を改良していきます。

最もシンプルなWebアプリの改良

クエリ文字列にnameがない場合に対応し、Content-Typeヘッダを出力し、XSS脆弱性をなくします。

--- a/index.php
+++ b/index.php
@@ -1,5 +1,7 @@
 <?php

-$input = $_GET['name'];
+$input = isset($_GET['name']) ? $_GET['name'] : 'World';

-printf('Hello %s', $input);
+header('Content-Type: text/html; charset=utf-8');
+
+printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));

これで少し改良されました。

PHPUnitでのテストを作成します。

test.php

<?php

class IndexTest extends \PHPUnit_Framework_TestCase
{
    public function testHello()
    {
        $_GET['name'] = 'Fabien';

        ob_start();
        include 'index.php';
        $content = ob_get_clean();

        $this->assertEquals('Hello Fabien', $content);
    }
}

ちなみにテストを実行したらエラーでした。

$ phpunit test.php 
PHPUnit 4.6.10 by Sebastian Bergmann and contributors.

E

Time: 425 ms, Memory: 2.25Mb

There was 1 error:

1) IndexTest::testHello
Cannot modify header information - headers already sent by (output started at /Users/kenji/.composer/vendor/phpunit/phpunit/src/Util/Printer.php:139)

/Users/kenji/work/framework/index.php:5
/Users/kenji/work/framework/test.php:10

FAILURES!
Tests: 1, Assertions: 0, Errors: 1.

どうしてもテストを通しておきたい人は、以下のように@runInSeparateProcessを指定してください。

--- a/test.php
+++ b/test.php
@@ -2,6 +2,9 @@

 class IndexTest extends \PHPUnit_Framework_TestCase
 {
+    /**
+     * @runInSeparateProcess
+     */
     public function testHello()
     {
         $_GET['name'] = 'Fabien';

HttpFoundationコンポーネントの導入

さて、ここからコンポーネントを使っていきます。

まず、Symfonyコンポーネントの1つであるHttpFoundationを導入します。

$ composer require symfony/http-foundation
Using version ^2.7 for symfony/http-foundation
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/http-foundation (v2.7.2)
    Loading from cache

Writing lock file
Generating autoload files

v2.7.2がインストールされました。

index.phpをHttpFoundationのRequestクラスとResponseクラスを使うように書き換えます。

--- a/index.php
+++ b/index.php
@@ -1,7 +1,14 @@
 <?php

-$input = isset($_GET['name']) ? $_GET['name'] : 'World';
+require_once __DIR__.'/vendor/autoload.php';

-header('Content-Type: text/html; charset=utf-8');
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;

-printf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8'));
+$request = Request::createFromGlobals();
+
+$input = $request->get('name', 'World');
+
+$response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));
+
+$response->send();

index.php全体は以下のようになります。

index.php

<?php

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

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

$request = Request::createFromGlobals();

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

$response = new Response(sprintf('Hello %s', htmlspecialchars($input, ENT_QUOTES, 'UTF-8')));

$response->send();

ちなみに、Requestクラスは以下のように使えます。

// the URI being requested (e.g. /about) minus any query parameters
$request->getPathInfo();

// retrieve GET and POST variables respectively
$request->query->get('foo');
$request->request->get('bar', 'default value if bar does not exist');

// retrieve SERVER variables
$request->server->get('HTTP_HOST');

// retrieves an instance of UploadedFile identified by foo
$request->files->get('foo');

// retrieve a COOKIE value
$request->cookies->get('PHPSESSID');

// retrieve an HTTP request header, with normalized, lowercase keys
$request->headers->get('host');
$request->headers->get('content_type');

$request->getMethod();    // GET, POST, PUT, DELETE, HEAD
$request->getLanguages(); // an array of languages the client accepts

Responseクラスは以下のように使えます。

$response = new Response();

$response->setContent('Hello world!');
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'text/html');

// configure the HTTP cache headers
$response->setMaxAge(10);

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

Date: 2015/07/15

Tags: php, symfony