Issei.M's Techlog

Web/iOS エンジニアの僕が技術関連のメモ等をつらつらと。主に Symfony について書いています。

[Symfony] Buzz\Browser でお手軽 HTTP リクエスト

HTTP リクエストを送る PHP ライブラリは数多くありますが、中でも Buzz\Browser が使いやすかったので Symfony での使用例をご紹介。

インストール

いつも通り、Composer でインストールします。作者は Assetic で有名な Kirs Wallsmith さんです。

php composer.phar require kriswallsmith/Buzz:dev-master

折角 Symfony で使うのでサービスコンテナに登録しちゃいます。

# services.yml
services:
    buzz.client.curl:
        class:  Buzz\Client\Curl
        public: false
        # 証明書の検証が必要な場合は calls を削除
        calls:
          - [ setVerifyPeer, [false] ]
    buzz.browser:
        class:     Buzz\Browser
        arguments: ["@buzz.client.curl"]

使い方はこんな感じ

適当なコントローラに実装します。

$browser = $this->get('buzz.browser'); # ContainerInterface::get()

# 診断くん
$response = $browser->get('http://taruo.net/e/');
if (!$response->isSuccessful()) {
    throw new HttpException($response->getReasonPhrase(), $response->getStatusCode());
}

$content = $response->getContent(); # 診断くんの HTML ソース

返却された Response オブジェクトの::isSuccessful()でリクエスト結果の成否を調べる事ができます。 エラーの際は::getStatusCode(),::getReasonPhrase()で内容の確認しましょう。
本文の取得は::getContent()です。

またBrowser::get()の第2引数には任意のリクエストヘッダを配列で指定する事ができます。

$browser->get('http://taruo.net/e/', array('User-Agent', 'buzz.browser'));

::get()の他にも::post()::put()等基本的なものが揃っています。

cURL が使えない環境の人は...

file_get_contentsもサポートされているので安心。サービスコンテナの設定を次のように変更すればOKです。

# services.yml
services:
    buzz.client.file_get_contents:
        class:  Buzz\Client\FileGetContents
        public: false
        calls:
          - [ setVerifyPeer, [false] ]
    buzz.browser:
        class:     Buzz\Browser
        arguments: ["@buzz.client.file_get_contents"]

もちろん実装部のソースコードを変える必要はありません。DI コンテナさまさまです♪

[Symfony] LiipThemeBundle でスマホ対応&Assetic を使う際の諸注意

Symfonyスマートフォン対応するにあたって LiipThemeBundle がよさげだったのでご紹介。また当バンドルを使用する際 Assetic でハマる箇所がある為メモ。

Symfony Standard Edition 2.2.1 の AcmeDemoBundle で試してみます。

LiipThemeBundle をインストール

Composer でインストール。

$ php composer.phar require liip/theme-bundle:dev-master

Bundle を登録。

# app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Liip\ThemeBundle\LiipThemeBundle(),
    );
}

config.yml に追記。themesには使用するテーマを定義します。今回は PC とスマホのみなのでweb,phoneを定義します。 path_patternsについては後述。

# app/config/config.yml
liip_theme:
    themes:           ['web', 'phone']
    active_theme:     'web'
    autodetect_theme: true
    path_patterns:
        app_resource:
            - %%app_path%%/views/themes/%%current_theme%%/%%template%%
        bundle_resource:
            - %%bundle_path%%/Resources/views/themes/%%current_theme%%/%%template%%
    cookie:
        name: site_theme
        lifetime: 31536000 # 1 year in seconds
        path: /
        domain: ~
        secure: false
        http_only: false

続いてテーマ切り替えページ用に routing.yml に追記。

# app/config/routing.yml
liip_theme:
    resource: "@LiipThemeBundle/Resources/config/routing.xml"
    prefix:   /theme

スマホ用テンプレートの追加

準備が整ったので早速スマホ用のテンプレートを作成していきます。 src/Acme/DemoBundle/Resources/views 以下に themes/phone/Welcome とディレクトリを作っていき、その中に次のファイルを作成します。

{# src/Acme/DemoBundle/Resources/views/themes/phone/Welcome/index.html.twig #}

{% extends 'AcmeDemoBundle::layout.html.twig' %}
{% block title %}Symfony - Welcome Smartphone!{% endblock %}
{% block content_header '' %}
{% block content %}
    <h1 class="title">スマホレイアウト!!</h1>
    スマホだよ~ん
{% endblock %}

それでは Web ブラウザで確認してみましょう。まず /theme/phone にアクセスします。トップにリダイレクトされたのち、先ほど追加したテンプレートが適用されているのが確認できます。

f:id:issei_m:20130514204557p:plain

※今回autodetect_themeを有効にしているのでスマホで直接アクセスしてもOKです。また、/theme/web にアクセスすると PC 用レイアウトに戻す事ができます。

次にベースレイアウトをスマホに最適化する為、以下を追加します。

{# src/Acme/DemoBundle/Resources/views/themes/phone/layout.html.twig #}

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Demo Bundle{% endblock %}</title>
        <meta content="telephone=no" name="format-detection">
        <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
        <meta content="noarchive" name="robots">
        <style>h1{font-size:16pt;}</style>
    </head>
    <body>
        {% block content %}
        {% endblock %}
    </body>
</html>

試しにスマホで表示するとこのような感じになります。(だいぶ質素になりました)

f:id:issei_m:20130514214742p:plain

ちなみに app/Resources/views にも同様のディレクトリ構造を作ることでスマホ対応が可能です。

このライブラリの最も優れている所は、スマホ用にテンプレートが用意されていないページでも通常のテンプレートを使用してフォールバックしてくれる点です。
試しに /demo/hello/World にアクセスすると、PC 用テンプレートではありますがきちんとページが表示されます。

Assetic を併用する場合の注意点

Assetic は非常に便利ですが、LiipThemeBundle との併用にあたってひとつ注意点があります。
README によると本来ディレクトリ構造は Resources/themes/phone/... のようにするのですが、これだと中のテンプレートで定義しているアセットがphp app/console assetic:dumpでダンプできなくなります。(デバッグ環境では Routing Error が発生します)

これを回避する為テーマディレクトリは Resources/views 以下に配置し、前述のとおり config.yml にpath_patternsを指定します。

それでも問題が発生する時は...

その時はphp app/console cache:clear --no-warmupを試してみて下さい。
--no-warmupが重要です。

[Monolog][Symfony] Monolog が神過ぎる件 ~エラーログをメールで送ろう~

Symfony で採用されているロガーライブラリ Monolog は Python の LogBook と言うライブラリに影響を受けており、 柔軟なロギング処理が可能です。
今回は、Symfony の運用環境でERRORレベル以上のログをファイルに書き込みつつ、メールでも通知するように設定してみます。

以下、config.yml記述例。

monolog:
    handlers:
        # ERROR 以上でログファイル書き込み+メール送信
        main:
            type:         fingers_crossed
            action_level: ERROR
            handler:      grouped
        # 以下 fingers_crossed 用
        grouped:
            type:    group
            members: [file, email]
        file:
            type:  stream
            path:  %kernel.logs_dir%/%kernel.environment%.log
            level: DEBUG
        email:
            type:       swift_mailer
            from_email: no-reply@example.com
            to_email:   issei.m7@gmail.com
            subject:    "[example.com] An error occurred!"
            level:      DEBUG

mainにはERRORレベル以上でgroupedが処理され、fileemailが実行されるように設定しています。

このfingers_crossedが非常に優秀でgrouped, file, emailmainの下位ハンドラとなっており、通常は処理されません。この為fileemailはレベルがDEBUGですが、エラー発生時にだけ該当セッション中の全ログが書き込まれます。

これで無駄にログを肥大させる事なく、デバッグ効率が格段上がりまくりんぐです。

[Symfony] JSON レスポンスを返す

    /**
     * @Route("/test.json", name="json")
     */
    public function jsonAction()
    {
        // do something

        return new JsonResponse(array('id' => 12345, 'title' => 'json test'));
    }

バージョン 2.1 で追加された JsonResponse をコントローラ内で返すだけです。コンストラクタの引数に渡した配列がそのまま出力されます。
Content-Type も自動で application/json にしてくれます。更に、JSONP のコールバック処理もできたりします。
※詳しくは JsonResponse

とっても便利☆

[Symfony] サブドメイン別にルーティングを制御する

Symfony 2.2 で正式に追加された機能の様です。
フロントエンドをexample.com、バックエンドをsystem.example.comとしてそれぞれ別のバンドルで運用する場合の設定例をご紹介。

まずはバックエンド用のバンドルを作成します。

$ php app/console generate:bundle --namespace=Acme/BackendBundle --format=annotation

続いて上記コマンドで自動追記されたBackendBundleへのルーティングを修正します。

# app/config/routing.yml

# バックエンド
system:
    resource: "@BackendBundle/Controller/"
    type:     annotation
    prefix:   /
    host:     system.example.com # 追加!

# フロントエンド
homepage:
    resource: "@FrontendBundle/Controller/"
    type:     annotation
    prefix:   /

新しく追加されたhostによってサブドメインのルーティングを制御できるようになりました。これでsystem.example.comはすべてBackendBundleのコントローラにルーティングされます。
とっても簡単!