読者です 読者をやめる 読者になる 読者になる

Symfony2のRequestクラスの解説

Symfony Advent Calendar JP 2011 の20日目のエントリーです。今回はSymfony2のRequestクラスについて解説しちゃいます。

RequestクラスはSymfony2のHttpFoundationコンポーネントに含まれており、HTTPリクエストに関する情報(リクエストパラメータやヘッダ、セッションなど)へアクセスするためのAPIを提供します。

Requestオブジェクトの生成


Requestオブジェクトの生成はフロントコントローラ(app.php/app_dev.php)にて行われます。Request::createFromGlobals()メソッドが呼び出されると、内部でスーパーグローバル変数を元にRequestオブジェクトが生成されます。Requestを独自クラスにしたい場合はここを直接差し替えます。

<?php
// ...
$kernel->handle(Request::createFromGlobals())->send();

リクエストパラメータや環境変数などへのアクセス

PHPでは$_GETや$_POSTといったスーパーグローバル変数へアクセスして環境変数を取得しますが、Requestオブジェクトを用いた場合はParameterBagというオブジェクトを経由して取得します。queryやrequestといったプロパティがそれぞれParameterBagオブジェクトで、get($key)で取得、has($key)で有無の確認、all()で全データの取得など共通のインターフェイスでデータにアクセスできます。

<?php
// $_GET['foo']
$request->query->get('foo');

// $_POST['foo']
$request->request->get('foo');

// ルーティングパラメータ / ex) @Route('/{foo}')
$request->attributes->get('foo');

// $_COOKIE['foo']
$request->cookies->get('foo');

// $_FILES['foo']
$request->files->get('foo');

// $_SERVER['SCRIPT_FILENAME']
$request->server->get('SCRIPT_FILENAME');

// $_SERVER['HTTP_USER_AGENT']
$request->headers->get('User-Agent');

// query > attribute  > request の順で検索
$request->get('foo');

filesに格納されているファイル情報はUploadedFileというオブジェクトに変換されています。これはSplFileInfoを継承しており、同じインターフェイスでファイルを操作できます。

headersは$_SERVERに入っている情報のうち、HTTPリクエストヘッダの情報(HTTP_*)のみが取得できます。User-AgentやAccept-LanguageのようにHTTPヘッダ形式で取得できます。

HTTPメソッドの取得

getMethod()メソッドでHTTPメソッドを取得できます。

<?php
if ('POST' === $request->getMethod()) {
    echo 'post';
}

セッション操作

セッションはSessionオブジェクトを経由してアクセスします。SessionオブジェクトはRequest::getSession()メソッドで取得できます。

<?php
$session = $request->getSession();

$session->has('foo.bar');
$session->set('foo.bar', 'value');
$value = $session->get('foo.bar');
$session->remove('foo.bar');
$session->clear();

$session->setFlash('notice', 'value');
$value = $session->getFlash('notice');

$session->getId();

// IDを変更
$session->migrate();

// 中身をクリアしてIDを変更
$session->invalidate();

URI情報を取得

下記のメソッド参照。getUriForPath()メソッドはよく使います。

getRequestUri()など

<?php
// http://fvstr.jp:8080/mysite/app_dev.php/demo?foo=bar
$request->getUri();

// http
$request->getScheme();

//  fvstr.jp
$request->getHost();

// 8080
$request->getPort();

// fvstr.jp:8080
$request->getHttpHost();

// /mysite
$request->getBasePath();

// /mysite/app_dev.php
$request->getBaseUrl();

// /demo
$request->getPathInfo();

// foo=bar
$request->getQueryString();

// http://fvstr.jp:8080/mysite/app_dev.php/foobar
$request->getUriForPath('/foobar');

HTTPSの判定

isSecure()メソッドを使います。

<?php
if ($request->isSecure()) {
    echo 'https!';
}

リクエストされたコンテンツの拡張子を取得

URLに拡張子をつけたい場合、Symfonyではルーティングに_formatというパラメータを指定します。そうするとgetRequestFormat()メソッドで拡張子が取得でき、さらに@Templateアノテーションでテンプレート呼び出しをしている場合も自動的に拡張子部分に使われます。ちなみにデフォルト値を設定しておくと省略可能になるので、下記の場合は/about/meだけでもアクセス可能です。

<?php
// URI = http://fvstr.jp/about/me.json

/**
 * @Route("/about")
 */
class AboutController
{
    /**
     * @Route("/me.{_format}", defaults={"_format": "html"}, requirements={"_format"="(html|json)"})
     * @Template
     */
    public function meAction(Request $request)
    {
        // json
        $request->getRequestFormat();
    }
}

Ajaxの判定

isXmlHttpRequest()メソッドを使います。

<?php
if ($request->isXmlHttpRequest()) {
    echo 'XHR!!';
}

ユーザのIPアドレスを判定する

getClientIp()を使います。なお引数にtrueを渡すと$_SERVER['HTTP_CLIENT_IP']や$_SERVER['HTTP_X_FORWARDED_FOR']の値があった場合、優先的にそちらを返します。

<?php
// $_SERVER['REMOTE_ADDR'];
$request->getClientIp();

X-Forwarded系のヘッダを有効にする

いくつかのメソッドではX-Forwarded-Forなどのプロキシによって設定されるヘッダを優先的に参照するようになっています(getHost()やgetClientIp()など)。しかしデフォルトでは参照されないようになっており、app/config.ymlで有効にする必要があります。有効にする場合はframeworkの中にtrust_proxy_headers: trueを追加します。

framework:
    # ...
    trust_proxy_headers: true

RequestオブジェクトとDIコンテナ

RequestオブジェクトはrequestというキーでDIコンテナに格納されていますが、扱いが少々特殊です。DIコンテナは1回のアクセスにつき1インスタンスのみ作られます。しかしRequestはforwardした際に複製されるため、呼び出す場所によってインスタンスが変わる可能性があります。それを管理するために「request」というスコープが設定されています。

$container->get('request'); のように自分で取得する場合はあまり意識しませんが、RequestオブジェクトをDIの対象にしたい場合にスコープを意識する必要が出てきます。requestをDIしたい場合、次のようにscopeという属性を追加します。こうすると、Requestが別インスタンスになったあとにacme.foo_serviceを取得しようとすると、別のAcme\DemoBundle\FooServiceインスタンスが作成されるようになります。

services:
    acme.foo_service:
        class: Acme\DemoBundle\FooService
        arguments:
            - @request
        scope: request

また、次のように末尾に=をつけることで、制約を無視するようになります。PHPの@演算子みたいなものです。

services:
    acme.foo_service:
        class: Acme\DemoBundle\FooService
        arguments:
            - @request=

なおCLIの場合、requestがなくてDIできずに怒られる、ということがありますので、まあその辺はうまいことやってください。コンテナごと渡して中で判定するとか、引数として渡すようにするとか。

Requestオブジェクトをアクションの引数として受け取る

下記のようにタイプヒントつきでアクションの引数に書いておくとRequestオブジェクトを受け取れます。

<?php

namespace Acme\DemoBundle\Controller;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class DemoController extends Controller

    public function indexAction(Request $request)
    {
    }
}

RequestオブジェクトをTwigのテンプレート内で取得

app.requestで取得できます。

{% if not app.request.secure %}
<a href="https://{{ app.request.httpHost ~ app.request.baseUrl ~ app.request.pathInfo }}">HTTPS切り替え</a>
{% endif %}


解説は以上です。アップロードされたファイルをSplFileInfoと同じインターフェイスで扱えるのが結構扱いやすくていいです。Rackみたいなのが欲しいっていってる人いますけど、HttpFoundationコンポーネントでいいじゃんって思います。