FuelPHP Model_XXX::find()->get()は注意が必要

追記 – 平成24年7月24日

find()からレコードがとれたかどうか確認するステップを無くして、
エラーを回避するがためにArrayAccessを使用するやり方は好ましくないとご指摘を頂きました。
確かにそのとおりだと思います。

この記事は、FuelPHPのORMの動きの理解につながると思いますが、
正しい方法では無いので決して使わないでください。

正しい方法はマニュアルに従って下さい。(後日ここに正しい手順で修正したものをあげときます。)

FuelPHP Monkey

// $pkがないときに処理が中断してしまう。
$name = Model_Monkey::find($pk)->get('name');

FuelPHPに含まれているORMパッケージのfindメソッドはPKが存在しないときnullを返します。

なのでそのまま続けて、メソッドチェーンで記述してしまうと、以下のエラーが出てしまいます。

Call to a member function get() on a non-object

これが出てしまうとPHPは処理を中断してしまうため色々と問題になります。

これを回避する方法

(環境はDocsに書いてあるScaffoldを使いました。FuelPHP1.2 Docs Scaffolding)

// findとgetを別々に書く方法 ←これは普通
$row = Model_Monkey::find($pk);
if ($row)
{
    $name = $row->get('name');
}

// PHP5.3 ArrayAccessインターフェースを使う方法 ←良い感じ
$row = Model_Monkey::find($pk);
$name = $row['name'];

// PHP5.4 ArrayAccessインターフェースを使う方法(未検証)
$name = Model_Monkey::find($pk)['name'];

ポイントはArrayAccessを使っていること

なぜArrayAccess

FuelPHPのORMはカラムの値を取得する方法が三種類あります。

存在しないPKでfindメソッドを実行し、
カラムの値を取得する時のエラーの比較をしてみます。

// getterメソッドを使う
// ErrorException [ Error ]: Call to a member function get() on a non-object
$name = $row->get('name');

// マジックメソッドを使う(getterが呼ばれる)
// ErrorException [ Notice ]: Trying to get property of non-object
$name = $row->name;

// ArrayAccessを使う(getterが呼ばれる)
$name = $row['name']; // エラーが出ない。

ArrayAccessを使った最後の例はエラーが出ないです。
なので、Viewで表示する時や色々な場面でこのArrayAccessを使った方法がいいとおもいます。

他にもスマートな方法がありましたら、是非教えて下さいお願いしますm(__)m
おわり。

あとがき

以下のコードはエラーになりません。
そんな特徴を活かした記事でした。

php > error_reporting(-1);
php > $row = null;
php > echo $row['name'];
php >

Tumblrカスタマイズでコード貼り付ける時のCSS追加

Markdown記法で先頭にスペース4つかタブ文字で

{スペース4つ}testtest
↓
<pre><code>testtest</code></pre>

になるのですが、テンプレによってはデザインが崩れたり背景色が無かったりと、
個人的には残念でした。なので簡単なCSSをカスタムCSSとして追加しました。

code{
    font-family: Verdana, Arial, sans-serif,monospace;
}
pre{
    width:700px;
    font-size:13px;
    padding:15px;
    margin-bottom:14px;
    word-wrap: break-word;
    background:#f4f6f7;
    border:2px solid #cdd3d6;
    border-radius:10px;
    box-shadowa:-3px 0 10px
    -webkit-border-radius:10px;
    -moz-border-radius:10px;
}

個人的に一番いいのは、 word-wrap: break-word; ですね。
横に長い文字を折り返して表示してくれます。
これ以外は、見た目の問題なので個人の好みがあります。

結構良い感じやろー

それにしても、MOUってアプリは素晴らしい。。。
プレビューを見ながらMarkdownが自然と書ける。

【FuelPHP】疑問から始まってコアにマージされるまでの話。

FuelPHPMerge

FuelPHPで疑問に思ってソースを読んで追加してみて動作確認して、
プルリクエストを投げてマージされるまでのお話です。

疑問

複数データベースをまたぐ分散トランザクション処理を行いたい場合、どう書けばいいのか悩んでいました。
XAトランザクション? 2相コミット?
PHPではあまりそういった実装を見たことがない。。。 一応こんなのもあるけど ibase-trans

// 動くけど微妙な気がするコード
// これで本当にACID特性が満たされるのか・・・
try
{
    DB::start_transaction('database1');
    DB::start_transaction('database2');

    // database1に対するCURD操作
    // database2に対するCURD操作

    DB::commit_transaction('database1');   
    DB::commit_transaction('database2');   
}
catch (Exception $e)
{
    DB::rollback_transaction('database1');
    DB::rollback_transaction('database2');
}

ぱっと答えが出なかったので、その後、単にトランザクション中なのかどうなのか知りたい場合どうすればいいか考えることにしました。
トランザクションを開始した時にフラグを立てて、コミットやロールバックしたらフラグを降ろすようなコードを書けば良いんだなと思いつつ・・・コアを覗いてみる

コアを見る

core/classes/database/connection.php

....

/**
 * Whether or not the connection is in transaction mode
 *
 * @return  bool
 */
abstract public function in_transaction();

abstract public function start_transaction();

abstract public function commit_transaction();

abstract public function rollback_transaction();

....

connection.phpは抽象クラスが定義されています。
ここに定義されているメソッドは全て子クラス(mysql,pdoのコネクションクラス)にも定義されています。

ソースをよく見ると、in_transactionというトランザクションの状態を管理するメソッドもしっかり用意されていました。

ですが、その操作を総括して行う、DBヘルパークラスにはin_transactionだけ定義されていないことに気づきました。

PDOにもこういったメソッドが用意されています => PDO::inTransaction()

追加して動作確認

ローカル環境で以下のコードを追加して動作確認しました。
db.php

/**
 * Checking a transaction on instance
 *
 *     DB::in_transaction();
 *
 * @param   string  db connection
 * @return  bool
 */
public static function in_transaction($db = null)
{
    return Database_Connection::instance($db)->in_transaction();
}

検証コード

try
{

    Log::info(DB::in_transaction() ? 1 : 0); // 0
    DB::start_transaction();
    Log::info(DB::in_transaction() ? 1 : 0); // 1

    // DB操作 insert update delete

    Log::info(DB::in_transaction() ? 1 : 0); // 1
    DB::commit_transaction();  
    Log::info(DB::in_transaction() ? 1 : 0); // 0
}
catch (Exception $e)
{
    // トランザクション中であればロールバック
    if ( DB::in_transaction())
    {
        DB::rollback_transaction();
    }
}

予め実装されていたものを使えるようにしただけなので、動作良好ですね。
これで、トランザクション中かどうかが簡単にわかるようになりました。
独自実装する手間も省かれました。

プルリクエスト

今回、コアのソースに直接追加したことと、忘れて抜けてただけなんじゃないかっていうくらいの事だったのでプルリクエストしてみることにしました。

FuelPHP is a simple, flexible, community driven PHP 5.3 web framework based on the best ideas of other frameworks with a fresh start.

FuelPHPはサイト上に明記されている通り、コミュニティ主導のフレームワークです。
なので、『意外といけるんじゃないか』って内心思ったり思わなかったりドキドキしながらやりました。

参考にしたサイトは @kenji_s さんのブログのこの記事です。

※今回私は全てGithub上だけで行いました。
もし FuelPHP のバグを見つけたら~バグ報告の仕方
もし FuelPHP のバグを見つけたら (2)~バグ修正コードを Pull Request するための準備
もし FuelPHP のバグを見つけたら (3)~バグ修正コードを Pull Request する方法
もし FuelPHP のバグを見つけたら (4)~ドキュメントの修正を Pull Request する方法
もし FuelPHP のバグを見つけたら (5)~Pull Request 後の修正
もし FuelPHP のバグを見つけたら (6)~Pull Request が本家に取り込まれたら
あとGoogle 翻訳

Githubアカウントは持っていたため、まずFuelPHPのコアをフォークしました。

フォークしたコアのdb.phpを編集して先ほどのコードを追加して保存しました。

その後、プルリクエスト(https://github.com/fuel/core/pull/1065)を行いました。
当たり前ですが、日本語は通じないので英語でやり取りしました。 (Google翻訳さん神)

マージされるまで

『マージされたらいいなー』なんて思っていたらプルリクエストにコメントがついていました。

Looks good, can you add docs for this? Then I’ll merge the pull.

どうやらドキュメント追加してくれたらマージしてくれるようです。

なのでFuelPHPのドキュメントも新たにフォークし、先ほどと同じ手順で該当テンプレートに説明を追加してプルリクエスト(https://github.com/fuel/docs/pull/383)を行いました。 (Google翻訳さん神 2回目)

それから2時間くらいで、2つともマージされて1.3/developに自分が追加したものが反映されました

https://github.com/fuel/core/commits/1.3/develop/classes/db.php
https://github.com/fuel/docs/commits/1.3/develop/classes/database/db.html

その後、数カ所間違った英語の修正とかでコミットされていましたが、とても助かりました。
今は仕方ないです、英語出来ないんですから。

最後に

大げさですが、これで私の人生初プルリクエスト&初マージ話は終わりです。

微力ながらOSSに貢献する事が出来てとても良かったです。
最初から最後までの出来事が半日だったのもビックリしています。
スピード感が違いますね。

今回、@kenji_s さんのブログが無ければプルリクエストすることもありませんでしたし、
ここまでFuelPHPにのめり込むことも無かったと思います。とても感謝しています。

なにより英語ってすばらしいと思いました。(Google翻訳さん神 3回目)
英語って楽しいって思いました。 英語勉強したくなりました。
(Google翻訳の結果が間違っていることに気づける男になりたいです。)

これからプルリクエストしてみたいと思っている方に、少しでもこの記事が参考になれば幸いです。

結局最初の疑問にあった、分散トランザクションの方法は知識不足で完全理解には至っていませんが、
もっとRDBMSの勉強をして理解しようと思います。こんなやつ?

以上。