2011年12月8日木曜日

Symfony2を試してみて[一人Web系AC2011]

連続でお送りする、PHPの有名FWお試し&比較記事シリーズです。
今日は、Symfony2についてです。

まずは、これまでと同じ仕様のサンプルを作成しました。
「同じ仕様って、どんな仕様なんじゃい?」
と言う方は、こちらの記事をどうぞ。最初のほうに書いてあります。

内容的にですが、Symfony2もblogチュートリアルがあるので、そちらを参考に一部手を加えました。
ページャ追加したり、formをせっかくだからクラスで作ってみたり、ですね。

そのソースが、こち…ら…。
すんませんまた上げ忘れて手元にありません何やって(ry
後日上げなおします。

■大体の流れ
前回と同様、Symfony2を読むための流れです。
  1. app/config/routing.ymlから、利用するバンドルを調べる
    今回のサンプルの場合、以下のような形になっています。
    sample:
        resource: "@MySampleBundle/Resources/config/routing.yml"
        prefix:   /sample
    resourceが挿す先は、バンドルのrouting設定ファイルです。
  2. バンドルのrouting.ymlを見て、コントローラと対応するメソッドを調べる。
    バンドル自体はsrcディレクトリの先にあります。
    今回の場合、パスは以下になります。
    src/My/SampleBundle/Resources/config/routing.yml
    patternにURIのパターンと、そのパターンでリクエストが来た場合に呼び出される内容がdefaultsに記載されています。
    sample_index:
        pattern: /
        defaults: { _controller: MySampleBundle:Default:index }
    この場合、MySampleBundleのDefaultControllerのindexAction()が呼ばれる事になります。
    {}で囲まれたものがある場合、それはアクションの引数になります。
  3. 対応するコントローラの処理を読む
    この辺の内容は、基本的なPHPです。
    MVCでいうMにあたる、DB操作についてはdoctrineを利用します。
    "MySampleBundle:Data"の場合、接続先DBの"Data"テーブルを読むと言う事さえ判れば、なんとなく読めると思います。
    ビューについては、$this->render()にて指定しています。
    最初の引数がテンプレート、次の引数がテンプレートに引き渡す値の配列ですね。
  4. テンプレートを読む
    テンプレートは[バンドルルート]/Resources/viewsにあります。
    コントローラ上で
    'MySampleBundle:Default:index.html.twig'
    として呼び出されていた場合、テンプレートのパスは以下になります。
    src/My/SampleBundle/Resources/views/Default/index.html.twig
    Symfonyの場合、テンプレートはTwigと言う物を使っています。
    書式的に難しい物では無いので、見れば大体は判ると思います。
    Twigは継承タイプのテンプレートなので、{% extends %}により他のテンプレートを継承している場合、まずそちらの内容が読み込まれます。
■実装しての感想
  • JavaのStruts/SpringFrameworkの組み合わせを思い出した。
    書式自体はぜんぜん違うけど、この、設定ファイルありきの書き方は、本当にソレっぽいなと思います。
    設定ファイルの書式自体は簡単なので、Springほど設定ファイルがゴミゴミすることもありませんが…。
  • 癖が強い。
    慣れの問題もあるかとは思うのですが、中々に癖が強いように感じられました。
    doctrineやTwig等、他のFW利用時にはあまり使わないものが多くあるからかもしれません。
  • Pagerにあたる機能はコアに無い。
    らしいです。
    今回は、本当に簡単なページャだったので、直接一覧ページのコードに埋め込む形で実装してしまいました。
    実際にサイトを実装する場合は、使い回しが出来るよう、独自バンドルを作ったりするんじゃないかと思います。
今日はここまで。
次回はCodeIgniterで実装してみようと思います。

2011年12月7日水曜日

PHPのボトルネック調査方法[Web系一人AC2011]

昨日は、ツレと布団の取り合いに負けた結果、体調不良で死にました。OMG...。
いい年して何たる失態。すみませんでした。

気を取り直して今日の記事。
今日の記事はSymfony2...ではなく、PHPのボトルネック調査方法について、です。
検証用のコードが一部間に合わなかったため、予定を変更してお送りさせていただきます…。

皆さん、PHPで作ったWebシステムのボトルネックって、どうやって調査してますか?
割と「コレ!」と言う手段を聞かなかったので、ちょっと自分が調査するときのポイントをまとめてみました。
まずは、全体的にざっくりと、どんなツールを使って見ているのか?

それでは、本題に参りましょう。


■どのリソースが重たいのかチェック
ブラウザ上からどのリソースの取得に時間がかかっているのかを調査します。
ここで利用するのが、FireFoxならFireBug、Chromeならデベロッパーツールです。
(デベロッパーツール=右クリックして「要素を検証」を選ぶと出てくるアレ)
得に、Networkタブを良く利用します。
実際に読み込んだすべてのリソースを、どの順番でどのくらい時間をかけて読み込んだのか?等が見れます。
ここで、実際に読み込みに時間がかかっているリソースを絞り込みます。

■関連するログを追跡
基本ですが…。
サーバ上で取っているログを一通り追います。
とくに、MySQL環境にてマズいSQLが問題になっている場合、slow.logはとても参考になります。
まれに、slow.logにたくさんクエリログが残っているのに放置しているプロジェクトなんてものもあります…。オソロシイ。
SQLに問題がある場合、大抵ログからもはっきりと追えるので、ここで原因がはっきりすれば、DBまわりのチューニングを行い、実際の効果を一度見てみても良いと思います。
時間帯によって重い時間帯がある、と言う場合、munin等リソースをグラフ化して見れるツールを入れておくと役に立ちます。

■試験環境で再現する場合は、本番を想定した負荷をかけながらやる
処理の内容や使っているライブラリによっては、一定以上の負荷があって始めて再現するようなボトルネックも多くあります。
あらかじめ、JMeter等のツールを利用して、本番を想定した負荷をかけながらチューニングしていきます。
※試験環境によっては、JMeterで負荷をかけすぎると同じ回線/サーバの利用者に迷惑がかかる可能性もあるので、気をつけてくださいね。
※くれぐれも、本番稼働中のサイトや他所のサイトに打ち込まないように。

■Xdebugのプロファイラを利用する
PHPスクリプトに対するプロファイラです。
これで、実際のどのメソッドのどのコードで時間がかかっているのか?を調べることができます。
ボトルネックがありそうなコードを見つけたときに、すぐに生のソースを見て重たそうなコードを目視で調べて直す、という行動を取ってしまう人も結構見てきましたが、
実際のボトルネックは目に見えない部分にあるケースもあるので、ちゃんとプロファイラでチェックしたほうが、経験上回り道になりません。
Xdebugが導入済みなのであれば、php.iniのxdebug.profiler_enableをOnにすると利用できます。
※プロファイリングするときは当然、普段以上に負荷がかかるので、本番サイトでONにしないように。
また、プロファイラの吐いたログは、単体では可読性にかけるので、GUIで表示してくれるツールを利用します。
私は、Linux環境だということと、コールグラフ等がグラフィカルに表示されるのがうれしいので、KCachegrindを使っています。



JavaやObjective-c(これはWeb系ではありませんが…)だと、割とボトルネック調査やプロファイラの使い方についての記事が多いのですが、いまいちPHPには無かったので挙げてみました。
自己流の部分が多く、当然これが最善とは言えませんが、参考になれば、と思います。

2011年12月5日月曜日

[CakePHP]Model名’Datum’、テーブル名'data'の罠[Web系一人AC2011]

土日は、YAuth的な理由(僕の場合はDですが。)により、blogを書けないことが判明しました。
と言うわけで、平日のみの更新とさせていただきます…。すいません。

さて。
今回は、前回話をした、CakePHPにてDatumというモデル名(テーブル名はdata)を使うとハマる、と言う件についてです。

■まえがき。
実はこの問題、「CakePHP Datum」というキーワードで検索すると、@uechocoさんのblogがクリティカルヒットします。
http://labs.uechoco.com/blog/2010/12/cakephp-model-lovers.html
こちらの記事です。
しかし、当時の自分は
「CakePHP data テーブル名」
なんて、ノイズが多そうな検索ばっかり試していて、自力で気づくまで延々と悩むと言う恐ろしい失態をしてしまいました。OMG....

せっかく自分でもソースを追ったので、記事にしておきます。

べ、別にネタが無いわけじゃないんだからねっ

■きっかけ

  • 最初に作った、ただのPHP版のサンプルでは、テーブル名を「data」にしていた。
  • CakePHPは、モデル名とテーブル名の命名に「モデル名は単数形、テーブル名は複数形の英単語」というルールがある。
    このルールを守ることにより、コーディング時に設定ファイルを書かなくても良い。
    モデル名/テーブル名とした場合の例):Sample/samples, User/users, Woman/women
  • 例を見て解るとおり、こちら単純に英単語に「s」をつければ良いわけではなく、ちゃんと英語のルールに沿っている。
    具体的には、以下のソースで判別しているので、詳細が気になるなら参考にするとおk
    https://github.com/cakephp/cakephp/blob/master/lib/Cake/Utility/Inflector.php
  • 「じゃあ、テーブル名を'datas'に変えないといけないのかな?」と思ったが、「'data'って実は複数形だったらしいよ」と最近話題になっていたのを思い出し、調べる。
  • 調べた結果、dataの単数形はdatumであることが判明。
    CakePHPでも問題なく利用できるルールのようだ。
    ちなみに、このサイトで、規約に沿ったワードを調べられます。
    http://www.cpa-lab.com/tech2/inflects/
  • よーし、じゃあ、「Datum/data」の組み合わせでやろう!
  • 一覧表示、データ登録、削除はうまく動いたぞ、さて後は更新…あれ?FatalErrorが出るよ?あれ?
    Fatal error: Call to a member function getColumnType() on a non-object in /var/www/php_samples/cakephp/lib/Cake/Model/Model.php on line 1258

■原因調査
エラーが発生したコードはこちら。
https://github.com/cakephp/cakephp/blob/master/lib/Cake/Model/Model.php

getColumnType呼び出し時の引数$columnに渡される値は、多くの場合
「モデル名.カラム名」
となっています。
例えばDatum/dataの場合なら
Datum.id
Datum.title
Datum.body
等となります。

しかし、こちら更新時には、更新対象列の絞込みのため
「テーブル名.カラム名」
という値で呼ばれる事があります。
(おそらく、更新以外にも複雑なことをやろうとした場合、同じ呼ばれ方をします。今回発生はしませんでしたが。)
具体的には、プライマリキーのカラムのみそのような呼ばれ方をするので、
data.id
となります。

さて、問題のコードを見てみましょう。
Datum.idDatum.title にて呼ばれた場合、問題の1257行目のIF文はFalseとなり、
その先の$colsよりカラムの型を取得し、返します。
しかし data.id にて呼ばれた場合、Modelクラスは$dataというメンバ変数を保持しているため、問題のIF文がTRUEとなり、
$this->data->getColumnType($column);
という呼び出しを行おうとします。
しかし、実際にModelが持っている$dataは配列なので、メソッドを呼び出せずFatal Errorとなるわけです。

■解決策
モデル名をSample, テーブル名をsamplesに変更

■まとめ/教訓

  • フレームワーク利用時に一般的過ぎる単語を使うときは、注意する。
    というか、実際にサイトを作るときに、dataなんて一般的過ぎる単語は誤用が発生する可能性が高いので、NG。
  • 予約語リストがあれば、真っ先に参照するべき。
    CakePHPの場合、僕が探した限りでは見つかりませんでした。
    あったら是非教えてください。
  • 予約語リスト等が見つからない場合、関係するクラスのメンバ変数名はチェックしておいたほうが良い、かも。
    頻度的には、問題が起きたら疑うレベルでも良いけど、一度見ておくとそのフレームワークの命名のルールやよく使われる単語が見えてくるので。
  • Google先生に聞くときは、なるべくノイズが混じらない検索方法を行う。
  • 迷ったら、机上で考える前に一通りソースを舐めろ。デバッガ使え。
    (実は「あれー?おかしいなぁ、あってるはずなのになー。」と、コードと公式ドキュメントを見比べる時間が30分以上あったり…orz)


今回は以上になります。
次回は、予告どおりSymfony2にてサンプルの実装を行ってみます。

2011年12月2日金曜日

CakePHP 2.0 を試してみて。 [Web系一人AC2011]

PHP各種FWとただのPHPの比較シリーズ
開催です。

■前書き
さて、比較と言うからには、条件が必要になります。
シンプルなPHPによるWebサイトってどんなのだろう?と考えた結果、以下のような仕様のサイトとしてみました。
  • MySQLと接続し、テスト用のテーブルに対して以下の基本的な操作を行う。
    • データの一覧(10行でページング有り)
    • データの登録
    • データの更新
    • データの削除
  • レイアウト等の機能外の部分については、基本的にこだわらない。
  • バリデーション等も、基本的に無し。
  • まずはなるべくシンプルに実装する。早くするための細かいテクニック等はナシ。
  • セキュリティについても、最低限のみ考慮する。実際に運用するサイトではないので細かい部分まで調整はしない。
  • フレームワークのバージョンについては、最新の安定板を利用する。1系、2系と分かれているものについては、基本的に2系のみ。
まずは、ただのPHPによって比較用の仕様を満たすサイトを作りました。
比較用なので、
「クラスや関数に処理を分けず、一直線に書く」
という縛りを追加して書きました。

そのソースが、こちらです。
https://github.com/FAL/php_samples/tree/master/normal
なんという、前時代のコード!!!
CGIなんて単語が日常的に使われていた時代を思い出しますね!



実は、昨日の記事はこのノーマル版の解説にしようかな?と考えていたのですが、
自分がこんなイケてないコードしか書けないって言っているような気がして悲しかったのでやめました。
動作中のサンプルは出しませんが、ソースコード的にも単純なので、見ていただければ大体の動作はわかるはずです。

■CakePHP2.0版
ようやく本題。
CakePHP2.0にて、同様の仕様のサイトを実装してみました。
それがこちらです。
https://github.com/FAL/php_samples/tree/master/cakephp
…と、やろうと思ったんですが!
リポジトリに反映漏れがあって、ちょっと足りないファイルが多いので、後々あげなおします。

内容ですが、基本的にblogチュートリアルの内容をベースに、
テーブル名を変えて、
画面構成をちょっと変えてページングを実装して、
defaultレイアウトを簡素にしたりしたくらいです。

■大体の流れ
サンプルコードを読む際に、大体こんな感じで追って行けば、7割程度はすぐにわかるよ、という、大体の流れです。
単純なCakePHPのアプリであれば、同様の流れで追えると思います。
コーディングする側が手を入れないような、コアの部分でのデータの流れについては割愛します。

  1. /app/config/route.php
    mod_rewriteで飛んできたリクエストの、ルーティングのルールが書いてある。
  2. /app/Controller/[コントローラ名]Controller.php
    コントローラ名は、route.phpを参考に。ただし頭は大文字になる。
    最近流行の形式のとおり、"/"はindex()。"/[アクション名]/"は[アクション名]()にマッピングされる。
  3. /app/Model/[モデル名].php
    モデル名は、コントローラ名を単数形にしたもの。
    (コントローラクラス内にて$usesで定義してある場合は、そちらも。)
    今回のサンプルの場合、データ入力時のバリデーションルールがここにあります。
  4. /app/View/Layouts/default.ctp
    全てのページの基本的なレイアウトが記述されている。
    $content_for_layoutに、後述のビューの内容が展開される。
  5. /app/View/[コントローラ名]/[アクション名].ctp
    各アクションに対応したビュー。内容はPHP。
■実装しての感想
最終的な比較まとめは、一通りコードがそろった時点でやります。
まずは、今回CakePHP 2.0で実装していて感じた率直な内容です。
  • CakePHP1.x系に比べて、命名規約が感覚的にわかりやすくなった。
    1.x系はかつて触った事があったのですが、ファイル名の命名規約が結構独特な部分が多く、慣れるまで面倒だった記憶があります。
    2系になってから、命名規約が普通っぽく(Javaっぽく?)なったので、あまり戸惑う事なく決めれるようになりました。
    逆に、1.x系の記憶があって「え!?こんな命名でいいの?」とびっくりしたくらい…。
  • モデル周りが楽!
    規約に沿った名前付けさえしていれば、何も記述しなくてもデータ取得や更新が出来るのは楽です。
    今メインで使っているフレームワーク(職場伝統の俺俺…)は、単純なデータ取得でもいちいちモデルにメソッドを追加しなくてはいけないので、かなりうらやましいです。
  • "data"の罠。
    テーブル名を"data"でやろうとすると、データ更新でFatal errorになります。
    詳細を追った内容を書こうとすると、それだけで1記事になっちゃいますので今は省略します。(後日のネタになりますw)
  • ちょっとアクが取れた?
    Cake1.x系は、どこか「これがCakeのルールだ!」って感じのルールが多かったように感じたのですが、2系は結構素直に理解できる記述が増えたように思います。
    PHP5.2以降対応となった事で、自然な記述が出来るようになったのかもしれません。
    (単純に、私のコーディングスタイルに合っているだけ、のような気もしますが;)
今日はここまで、です。
まずはCakePHPのインプレッションという形で。

明日はSymfony2…の前に、dataの罠についてちょっとだけ書こうと思います。

2011年12月1日木曜日

Web系一人Advent Calendar 2011

12月になりました。
早速、宣言どおり一人ACが始まります。
今日は正直時間が無かった初日なので、基本的な内容の説明にしておきます。

技術系全般、というゆるーい縛りで行こうと思っていましたが、さすがにソレもどうかな?と思ったので、Web系、という縛りを入れてみました。
Web系といってもひろーくなっていますが、私個人のスキル的なものもありまして、PHPの話題が基本です。

PHP>MySQL=Linux>その他言語いろいろ

という頻度を想定しています。

PHPといってもひろーい範囲になりますが、具体的には
「各種FWと生PHPの比較」
というテーマで進めていこうと思います。

各FWごとの特徴をまとめた記事や、HelloWorldくらいのサイトに対するベンチマークの比較という記事は結構あるのですが、
もうちょっと実用的な範囲での、実装コストやパフォーマンスの比較記事があってもいいんじゃないかな?
というのが発想の元です。

当然、いろいろなFWで同じ機能を実装していく事になるのですが、その中で気づいた話題があれば別途あげていきます。
また、ネタ切れという事態が発生してしまった場合は、PHP以外の言語での比較記事もあげるかもしれません。

----------

さて、ここからはちょっと与太話。
上記の準備のために、今日はただのPHPによる簡単なDB操作ページを作ってみました。
クラス構造を考えたり、データ操作を関数として切り分けたりすると、俺俺FWになってしまいそうなので、「手続き型」っぽい書き方になるよう、気をつけて書きました。
(具体的には、既存のクラスは利用するけど、自分でクラスや関数を作らない、という方針です。)

めんどっくさいですね!ほんと。
ソースは後日Githubあたりに公開しますが…。涙が出そうでした。
出来上がったソースも、「これはいったい何年前のCGIだろう?」という。中々に懐かしさを感じる構成に…。

書こうと思えば、かなりOOPなカッコイイ(?)書き方が出来る一方で、
個人的センスで言えばかなーり「イケてない」書き方でも、ちゃんと動いてしまうあたりが、PHPという言語のカオスさと自由さを表しているように感じました。
だからこそ、人に見られ、参考にされても恥ずかしくない、誇れるようなきれいなソースを書いていく必要がありますね。
面倒だからといって、うっかりイケてないコードを書いて、ソレを後輩が真似して、スパゲッティ、なんて事件の原因にはなりたくないですし。
余裕があれば、OOPの利点を語るための比較なんてのも、やってみよう。