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

OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

Symfony2での処理の流れについてまとめた

PHP Symfony2

こんにちは!@mrtryです。 最近、スモークチーズのオイル漬けを作りました! チーズを燻製して、オリーブオイルにつけるだけでですが、とても美味しく、ハイボールが進みます。 燻製してみたいなぁ〜と、考えている人には、おすすめです!

さて、「Symfony2入門」2回目の投稿です。 前回は、全体としての基本動作をざっくり説明しました。 今回は、RequestオブジェクトからResponseオブジェクトを生成するまでの処理をもう少し掘り下げて行きたいと思います。

全体の処理の流れ

公式ドキュメントのフローチャートを参考に全体の処理の流れを追ってみましょう。 全体の処理の流れ

Symfonyでは、すべてのリクエストの処理が以下の流れに従っています。

  1. URLへのアクセスをフロントコントローラで受ける
  2. フロントコントローラ(app.php)が受けたリクエストからRequestオブジェクトを生成する
  3. カーネルがリクエストがあったURLをルータに知らせる
  4. ルータがURLにマッチするコントローラなどをまとめた「情報」をカーネルに返す
  5. カーネルがルータから受け取った情報を元にコントローラを実行する
  6. コントローラがResponseオブジェクトを返す

データを処理をするのはコントローラ、どのコントローラかを選択するのはルータ、コントローラとルータに依頼をするのがカーネル、と役割が分かれています。 それぞれについて、役割を確認していきます。

カーネル

カーネルは、クライアントからのRequestオブジェクトを受け取り、コントローラにそれを渡し、Response オブジェクトに変換します。 コードでは、フロントコントローラ(app.php(app_dev.php))にて呼びだされています。 気になった方は、前回の記事の終わりの方を見てみてください

ルータ

「このURLにアクセスがあったら、このコントローラを呼び出す」というURLのパスとコントローラを関連付け(マッピング)を行っています。 マッピングの設定は、設定ファイルかコントローラのPHPファイルに書くことで設定できます。

例として、前回も参考にしたsymfonyのデモアプリケーションの設定ファイルを見てみます。 設定ファイルは、app/config/routing.ymlにあります。 http://127.0.0.1:8000/にアクセスがあった時、 default/homepage.html.twigを引数に、FrameworkBundleTemplateControllerにあるtemplateActionを実行するという設定が書かれています。

homepage:
    path: /{_locale}
    requirements:
        _locale: '%app_locales%'
    defaults:
        _controller: FrameworkBundle:Template:template
        template:    default/homepage.html.twig
        _locale:     '%locale%'

コントローラ

コントローラは、Requestオブジェクトから情報を取得し、Responseオブジェクトを生成する関数群です。 ページのレンダリングやリダイレクト、画像などwebにアクセスして取得できる情報を任意にResponseオブジェクトとして返すことができます。

こちらも、実際にコントローラを見てみましょう。 見るコントローラは、先ほどルータで設定されていた、FrameworkBundleにあるTemplateControllerです。 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Controller/TemplateController.phpに実物があります。 このコントローラでは、引数で受け取ったファイルをレンダリングして、Responseとして返すという処理をしています。

namespace Symfony\Bundle\FrameworkBundle\Controller;

use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
use Symfony\Component\HttpFoundation\Response;

/**
 * TemplateController.
 *
 * @author Fabien Potencier <fabien@symfony.com>
 */
class TemplateController implements ContainerAwareInterface
{
    use ContainerAwareTrait;

    /**
     * Renders a template.
     *
     * @param string    $template  The template name
     * @param int|null  $maxAge    Max age for client caching
     * @param int|null  $sharedAge Max age for shared (proxy) caching
     * @param bool|null $private   Whether or not caching should apply for client caches only
     *
     * @return Response A Response instance
     */
    public function templateAction($template, $maxAge = null, $sharedAge = null, $private = null)
    {
        /** @var $response \Symfony\Component\HttpFoundation\Response */
        $response = $this->container->get('templating')->renderResponse($template);

        if ($maxAge) {
            $response->setMaxAge($maxAge);
        }

        if ($sharedAge) {
            $response->setSharedMaxAge($sharedAge);
        }

        if ($private) {
            $response->setPrivate();
        } elseif ($private === false || (null === $private && ($maxAge || $sharedAge))) {
            $response->setPublic();
        }

        return $response;
    }

あわせて、実行時にレンダリングされるファイルを見てみましょう。 ルータの設定で、引数には、default/homepage.html.twigが設定されていました。 実際のファイルパスとしては、app/Resources/views/default/homepage.html.twigにあります。 http://127.0.0.1:8000/にアクセスされた際は、このファイルがレンダリングされて表示されます。

{% extends 'base.html.twig' %}

{% block body_id 'homepage' %}

{#
    the homepage is a special page which displays neither a header nor a footer.
    this is done with the 'trick' of defining empty Twig blocks without any content
#}
{% block header %}{% endblock %}
{% block footer %}{% endblock %}

{% block body %}
    <div class="page-header">
        <h1>{{ 'title.homepage'|trans|raw }}</h1>
    </div>

    <div class="row">
        <div class="col-sm-6">
            <div class="jumbotron">
                <p>
                    {{ 'help.browse_app'|trans|raw }}
                </p>
                <p>
                    <a class="btn btn-primary btn-lg" href="{{ path('blog_index') }}">
                        <i class="fa fa-users"></i> {{ 'action.browse_app'|trans }}
                    </a>
                </p>
            </div>
        </div>

        <div class="col-sm-6">
            <div class="jumbotron">
                <p>
                    {{ 'help.browse_admin'|trans|raw }}
                </p>
                <p>
                    <a class="btn btn-primary btn-lg" href="{{ path('admin_index') }}">
                        <i class="fa fa-lock"></i> {{ 'action.browse_admin'|trans }}
                    </a>
                </p>
            </div>
        </div>
    </div>
{% endblock %}

例: トップページが表示されるまでの処理の流れ

実際に、ビルドインサーバを立ち上げて、http://127.0.0.1:8000/にアクセスすると、homepage.html.twigがレンダリングされたものが表示されます。

f:id:symmt9302:20160816104405p:plain

アクセスしてから、ページが表示するまでの流れを追うと以下のようになります。

  1. ユーザがhttp://127.0.0.1:8000/にアクセスする
  2. リクエストフロントコントローラ(app.php)が受け、カーネルにRequestオブジェクトを渡す
  3. カーネル/にアクセスがあったと、ルータに知らせる
  4. ルータが、設定ファイルを元にTemplateController.phpのtemplateActionを実行すれば良いと情報をカーネルに知らせる
  5. カーネルが情報にあったコントローラのアクションを実行する
  6. アクションした結果、ページがレンダリングされ、その情報はResponseオブジェクトとして生成される
  7. Responseオブジェクトを返す
  8. ユーザがページを見る

おわりに

今回は、Symfonyの処理流れについて、書きました。 次回は、ルータの設定方法について書きたいと思います。

参考

ログレベルちゃんと使い分けてますか?

PHP PSR

2回めましてこんばんわ。 @kalibora です。 焼き鳥のカシラは塩、シロはタレ派です。

さてさて、みなさまはプログラム中でログを吐くときのログレベルをどのように使い分けておりますでしょうか。

error 一択?

error info debug くらい?

そうだとして、それってどう使い分けていますか?

そしてそれをどのように気づき、どう対処していますか?

ログレベルの種類

まず、ログレベルはどのようなものが定義されているのか確認したいと思います。

PHP のデファクトスタンダートである PSR-3 で定義されているログレベルは下記の通りです。

ログレベル 説明
emergency System is unusable.
システムが使用不可能な状態
-
alert Action must be taken immediately.
直ちになんらかの対処の必要がある
Entire website down, database unavailable, etc. This should trigger the SMS alerts and wake you up.
完全にWebsiteがダウンした、データベースが使用不可能など。SMSで通知して起きる必要がある
critical Critical conditions.
危機的な状態
Application component unavailable, unexpected exception.
アプリケーションコンポーネントが使用不可能、予期しない例外。
error Runtime errors that do not require immediate action but should typically be logged and monitored.
直ちに対処する必要のない実行時エラーだが、通常はログに記録して監視すべき
-
warning Exceptional occurrences that are not errors.
エラーではない例外的な出来事
Use of deprecated APIs, poor use of an API, undesirable things that are not necessarily wrong.
廃止予定のAPI、中途半端なAPI、必ずしも間違っていないが望ましくないものの使用
notice Normal but significant events.
正常だが、重要な事象
-
info Interesting events.
興味深い事象
User logs in, SQL logs.
ユーザーのログインやSQLログ
debug Detailed debug information.
詳細なデバッグ情報
-

この定義をベースとして、サービスやシステム、チーム単位などで具体的にどう使うかや通知方法をあらかじめ決めておくと便利ですよね。

一例として

一例として弊社のあるサービスでは下記のように定義しています。

ログレベル 定義 使用例 本番環境でのロギング 通知方法
emergency 基本的に使わない - する Slackのalertチャンネルに通知
alert 1回発生したら必ずなにかしらの対応が必要なもの ここには使用例が書いてある する Slackのalertチャンネルに通知
critical 基本的には1回発生したら対応や調査が必要なもの ここには使用例が書いてある する Slackのerrorチャンネルに通知
error システムが原因で正常に処理ができないが、頻発しなければ未対応でもいいもの ここには使用例が書いてある する Slackのerrorチャンネルに通知
warning ユーザー起因で正常に処理ができなかったようなもの ここには使用例が書いてある する 通知しない
notice 正常に処理しているが、記録しておきたい重要なもの ここには使用例が書いてある する 通知しない
info そこまで重要じゃないが記録しておきたいもの ここには使用例が書いてある 一部する 通知しない
debug 開発のデバッグ時に必要な情報 ここには使用例が書いてある しない 通知しない

このように決めておけば、

ここでエラーが起きたら必ずデータのリカバリ作業が必要になるから alert だな。

とか、

ここでのエラーは問題だけど一時的な原因の可能性もあるし、再実行可能だから error だな。

とか、

正常なルートじゃないけど、システムには問題なくエンドユーザー起因だから warning だな。

などと判断できると思います。

そしてちゃんとレベルが定義できていれば、レベルごとに通知方法もわけられるので、 即対応が必要なものもすぐに気づくことができますよね。

ここで重要なのは定義をして各人での意識を合わせておく。ということで、この定義が正解だと言っているわけではないです。念のため。

おまけ: monolog の processor の話

ほとんどの phper のみなさんはロギングに Monolog を使っているかと思いますが、その際に Processor は使ってますでしょうか?

あらかじめちょっと定義・設定しておくだけでログに各種の付加情報を記録できる便利なやつです。

デフォルトでは

  • GitProcessor
    • git のブランチ名
  • IntrospectionProcessor
    • ファイル名, 行番号, クラス名, メソッド名
  • MemoryPeakUsageProcessor
    • メモリの最大使用量
  • MemoryUsageProcessor
    • メモリ使用量
  • ProcessIdProcessor
    • プロセスID(これがないとどのログが1つのリクエストなのか分からず混ざっちゃって大変ですね)
  • PsrLogMessageProcessor
  • TagProcessor.php
  • UidProcessor.php
  • WebProcessor.php
    • URL, IP, リファラーなど

が用意されています。

デフォルトで用意されていないもので必要になりそうなものとしては、ユーザーを特定するIDなどがあると思いますが、

そういったものも自前で Processor を定義することで簡単に付加できるので、Monolog を使っているのであれば是非使ってみるといいんじゃないでしょうか。

それではまた。

Symfonyの基本動作についてまとめた

PHP Symfony2

こんにちは!社員飲み会の準備で忙しい@mrtryです。

オトバンクでは、2週間に1回ペースで社内飲み会をしていまして、 私は料理好きということもあり、ケータリングの準備をお手伝いしております。 今回は、スモークチキンを準備しようと思い、この記事を書きつつ、燻製しています。 スモークチキンとビールを飲みたいと思った方は、こちらからご連絡いただければと思います!

それはさておき、今年新卒で入社した私ですが、学生の頃そこまでプログラミングをがっつり取り組んだことがありませんでした。 フレームワークも入社して初めて触ったばかりで、弊社で利用しているSymfonyについても理解できてないことが多い状態です。 この状況を打開すべく、Symfonyに関する勉強に力を入れ、更にブログを毎週書くことにしました! 基礎的なことから勉強したことをまとめて書いていくので、同じ初心者の方にはお役に立てるかもしれません!

今回は、基礎中の基礎からということで、Symfonyの基本動作について書きたいと思います。

Symfonyは、その動作から「Request/Response フレームワーク」と言われています。
説明のために、HTTPについて、簡単に復習していこうと思います。

HTTPの基本動作

HTTPとは、2台のコンピュータ間で、情報をやりとりするプロトコルです。
その動作は、以下のようになっています。

f:id:symmt9302:20160811145354p:plain

  1. クライアント側から「この情報をください」と「リクエスト」をサーバに送る
  2. それを受け取ったサーバがリクエストを元に処理を行う
  3. その処理結果を「レスポンス」としてクライアントに返す

これが、HTTPの基本動作です。
「相手に話しかけて、返事が返ってくる」という解釈をすると、
自然言語と一緒で理解しやすいですね!

Symfonyの基本動作

次に、Symfonyの基本動作を見ていきます。
Symfonyの基本動作は、HTTPの基本動作を元として設計されています。

f:id:symmt9302:20160809080152j:plain

  1. 「リクエストオブジェクト」を受け取る
  2. 「カーネル」に渡し、処理を行う
  3. その処理結果を「レスポンスオブジェクト」として返す

このように、SymfonyはHTTPと同じような振る舞いをします。
この設計から、Symfonyは「Request/Response フレームワーク」といわれています。

実際のプログラムでの対応

Symfonyの基本動作がわかったところで、実際のプログラムでは、どこが対応しているのかを確認してみます。

symfonyのデモアプリケーションを例として、基本動作とプログラムの対応を確認します。 symfony installerには、デモアプリケーションとしてブログを作成してくれるコマンドがあります。 任意のディレクトリにて、symfony demoを実行してみましょう。 実行すると、symfony_demoというディレクトリが作成されるかと思います。 動いているものを確認したければ、ビルドインサーバが入っているので、 php bin/console server:startと実行すれば、http://127.0.0.1:8000にて、デモのブログの確認できます。

作成されたsymfony_demoに移動し、web/app_dev.phpを見てみましょう。

<?php
...

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;

...

/**
 * @var Composer\Autoload\ClassLoader $loader
 */
$loader = require __DIR__.'/../app/autoload.php';
Debug::enable();

$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();

// リクエストを受け取る
$request = Request::createFromGlobals();

// リクエストをカーネルに渡して、処理した結果を受け取る
$response = $kernel->handle($request);

// レスポンスを返す
$response->send();

「リクエストを受け取り、処理行い、レスポンスを返す」という振る舞いが見て取れます。
振る舞いがそのままコードに表現されていて、わかりやすいですね!

おわりに

今回は、Symfonyの基本動作について紹介しました。
HTTPの振る舞いがそのままSymfonyに反映されていて、動きがイメージがしやすい、という印象でした。

次回は、カーネルが受け取ったリクエストがどのように処理されるかを書きたいと思います。

参考