fuelphpのfieldsetがもったいないので拡張してみた

fuelphpのfieldsetはすごく便利です。 formの出力とバリデーションが一体となって、似たようなコードを何回も欠かずに済みますし、文言の修正等も一箇所ですむようになります。 でも、webで検索していろいろ見ると、皆さんview画面が自由にカスタマイズできないから使えないと言ってバッサリ切ってしまっています。 すごくもったいないです。 かという自分も、view画面はきっちりと自分で作りたい派(というより仕事内容的に作らざるを得ない派)なので、fieldsetは使えないでいました。 しかし、個人サイトを作るときにfieldsetを使って以来、使えないのがストレスになってきたので、拡張して使えるようにしてみました! これでストレス無く、制作が簡単なfieldsetを使うことができます

どう拡張したのか

coreをいじるのは嫌だったので、継承したクラスたちをpackageでまとめるという形にしました。 できるようにしたのは

  • viewで簡単にinputタグ系とlabel系、エラー系を分けて出力できるようにした(メイン)
  • モデル側でfieldsetをまとめて書きたいけど、それぞれの画面で入力項目が微妙に違うよ!って場合用にaddだけじゃなくてremoveメソッドを追加
  • ついでにfieldsetにadd_textやadd_checkboxなどhtmlタグに沿ったaddメソッドを追加
  • さらについでに自分のよく使う日付選択に沿ったaddメソッドを追加

です。 htmlタグに沿ったaddメソッドを追加できるようにする部分はkenjiさんの FuelPHP の Fieldset を使おう(Form の自動生成) を参考にさせていただきました。

使い方

viewformという名前でgithubで公開しています。 使う前の準備としては次の4点です。

  1. githubよりダウンロード or チェックアウトして、fuel/packages/viewformに展開
  2. fuel/app/config/config.phpの中の
1
'packages' => array(...)

のarrayの中に’viewform’を追加

  1. (忘れがち)fuel/app/config/config.phpの中の
1
'whitelisted_classes' => array(...)

のarrayの中に’ViewForm¥¥fieldset’を追加
(viewにviewform¥fieldsetを渡すために必要です)

  1. (忘れがち)fuel/core/config/form.phpをfuel/app/config/form.phpにコピーし、中の
1
'inline_errors' => false,

をtrueに<
(これをやらないとエラー各項目でのエラーが表示されません)

実際に使うときはcontroller側は普通のfieldsetと大して変わりません。 適宜html用ショートカットメソッドを使ってラクラクーと書いてください。 view側はcontroller側から渡されたfieldsetインスタンスを使って

1
echo $fieldset->field('nickname')->label_text(); echo $fieldset->field('nickname')->field_text(); if $fieldset->field('nickname')->has_error()){ echo $fieldset->field('nickname')->error_text(); }

といった形でlabelとinputタグが別々にかけます。 もちろんタグの属性を引数に渡すこともできます。

1
echo $fieldset->field('nickname')->label_text(array('class'=>'control-label'));

(ただし、add_dateだけはちょっと特殊)

具体例

最後に、ひとつ自分が作ったものをそのままコピペしてみます 実際に使ってるコードなので、smartyだったりメソッド全部網羅してなかったりtwitter bootstrapだったりするのは勘弁してください controller側

1
public function action_user() { $fieldset = $this->_fieldset(); $fieldset->repopulate(); $val = $fieldset->validation(); if ($val->run()) { //保存処理 $user = Model_User::forge(); $saveKeys = array( 'nickname', 'gender', 'country', 'email', 'birthday',); foreach ($saveKeys as $key) { $user->$key = $val->validated($key); } $user->save(); return Fuel¥Core¥Response::redirect("setting/finished"); } $smarty = Parser¥View_Smarty::forge('setting/user.tpl') ->set('fieldset', $fieldset); return Fuel¥Core¥Response::forge($smarty); } /** * * @return ViewForm¥Fieldset */ protected function _fieldset() { $fieldset = ViewForm¥Fieldset::forge(); // textboxはadd をadd_textにするだけで使える $fieldset->add_text('nickname', 'ニックネーム') ->add_rule('required') ->add_rule('max_length', 10); // radio 用は add_radio // value => 表示名 の配列 で選択肢を設定 $genders = array('male' => "男性", 'female' => "女性",); $fieldset->add_radio('gender', '性別', $genders) ->add_rule('required'); // radio 用は add_select // value => 表示名 の配列 で選択肢を設定 (radioと同じ) $countries = array('jp' => '日本','us' => 'アメリカ'); $fieldset->add_select('country', '国籍', $countries) ->add_rule('required'); $fieldset->add_text('email', 'メールアドレス') ->add_rule('required') ->add_rule('valid_email'); // 年月日用selectbox // 年を数値で指定 または 現在の年からの差で指定できる // // ちなみにvalidate後はunixtimeに変換されている $fieldset->add_date_select('birthday', '誕生日', 1900, +0); return $fieldset; }

view側

1
{@Form::open(['class' => 'form-horizontal'])@} {@$fieldset->show_errors()@} <fieldset> <div class="control-group {@if $fieldset->field('nickname')->has_error()@}error{@/if@}"> {@$fieldset->field('nickname')->label_text(['class'=>"control-label"])@} <div class="controls"> {@$fieldset->field('nickname')->field_text(['class'=>'span9'])@} <span class="help-inline">{@$fieldset->field('nickname')->error_text()@}</span> </div> </div> <div class="control-group {@if $fieldset->field('gender')->has_error()@}error{@/if@}"> {@$fieldset->field('gender')->label_text(['class'=>"control-label"])@} <div class="controls"> {@$fieldset->field('gender')->field_text()@} <span class="help-inline">{@$fieldset->field('gender')->error_text()@}</span> </div> </div> <div class="control-group {@if $fieldset->field('country')->has_error()@}error{@/if@}"> {@$fieldset->field('country')->label_text(['class'=>"control-label"])@} <div class="controls"> {@$fieldset->field('country')->field_text()@} <span class="help-inline">{@$fieldset->field('country')->error_text()@}</span> </div> </div> <div class="control-group {@if $fieldset->field('email')->has_error()@}error{@/if@}"> {@$fieldset->field('email')->label_text(['class'=>"control-label"])@} <div class="controls"> {@$fieldset->field('email')->field_text()@} <span class="help-inline">{@$fieldset->field('email')->error_text()@}</span> </div> </div> <div class="control-group {@if $fieldset->field('birthday')->has_error()@}error{@/if@}"> {@$fieldset->field('birthday')->label_text(['class'=>"control-label"])@} <div class="controls"> {@$fieldset->field('birthday_year')->field_text(['class' => 'span2' ])@}年 {@$fieldset->field('birthday_month')->field_text(['class' => 'span2' ])@}月 {@$fieldset->field('birthday_day')->field_text(['class' => 'span2' ])@}日 <span class="help-inline">{@$fieldset->field('birthday')->error_text()@}</span> </div> </div> <div class="control-group"> <div class="controls"> <div class="actions"> {@Form::submit('submit', '保存',['class' => 'btn btn-primary btn-large'])@} </div></div> </div> </fieldset> {@Form::close()@}

phpでのincludeが意外と万能

c言語のincludeはファイルをその場所にコピペしたのと同じで使い方によってはいろいろ工夫できるなと思ってましたが、phpでも同じことができるようです。 具体的に挙げると、 template.php

1
こんにちは <?php echo $name;?> さん

index.php

1
<?php $name = "John"; include "template.php";

とやれば

1
こんにちは John さん

といった簡易テンプレートになります。 また、 config.php

1
<?php return array('name' => 'John', 'gender' => 'male');

index.php

1
<?php $config = include "config.php"; ?> こんにちは <?php echo $config['name'];?> さん

といったreturnで値を返すこともできるようです

fuelphpのmodelに補完用のphpdocをつける

昔、fuelphpのoil自動生成でphpdocを自動で書くようにするという記事を書きましたが、これでもだんだん不満に思ってきて、より便利な物を作りました。 補完ができるのとできないのでは開発効率も違いますし、何よりストレスがかからなくなるので重要です。

前作ったものの不満点

ちょっとだけ作った経緯を書いておきます。 昔作ったものは自分でもいいなと思って作ったわけですが、

  • 既存のソースを変更する必要がある
  • modelを作成するときにしか使えない

と、案外不満点がありました。 しかし、FuelPHP、コード補完用コード生成コードの記事を見て、taskで作ればいいのかと思い、新たに作成しました。

補完するもの

作成したtaskは単純にmodelのクラス宣言の直前にphpdocをつけるだけのものです。 モデルフォルダのファイル一覧を撮ってきて、それぞれのファイルにphpdocを強制的につけます。 phpdocの中身としては、

  • モデルの持つpropertyの定義
  • forgeでの返り値の型がそのモデルになるようにforgeのメソッド定義

の2種類です。 おもに自分の環境(netbeans用)として作ってますが、phpdocは共通のはずなので phpdocをベースに補完してくれるどのエディタでも使えるはずです。

ソースコード

まえがきが長くなりましたが、こちらが作ったtaskです。 app/tasksの下にmodelproperty.phpで作ってください。

1
<?php namespace Fuel\\Tasks; class ModelProperty{ public static function run(){ $modelFiles = \\File::read_dir(APPPATH . "classes/model", 0, array( '\\.php$' => 'file', )); foreach($modelFiles as $filename){ if(is_string($filename)){ self::addProperty(APPPATH . "classes/model/",$filename); } } } public static function addProperty($dir,$filename){ $fileString = \\File::read($dir . $filename,true); $className = self::getClassName($fileString); if(!$className)return; $output = "<?php" . PHP_EOL; $output .= "/**" . PHP_EOL; $properties = $className::properties(); foreach($properties as $name => $detail){ $output.= ' * @property '; if(isset($detail['type'])){ $output .= $detail['type'] . " "; } $output .= '$' . $name . PHP_EOL; } $output .= " *" . PHP_EOL; $output .= ' * @method '.$className.' forge($data = array(), $new = true, $view = null)' . PHP_EOL; $output .= " **/" . PHP_EOL; $output .= "class"; $replacedString = preg_replace("/<\\\?php(.|\\n)*class/mi",$output, $fileString); \\File::update($dir,$filename,$replacedString); } private static function getClassName($string){ $matches = array(); preg_match("/class\\\s(\\\S+)\\\sextends/", $string, $matches); return isset($matches[1]) ? $matches[1] : NULL; } }

実行は単純に呼ぶだけで、引数等もありません。

1
php oil r modelproperty

注意点として、phpdocがすでに書かれていた場合上書きします。 phpdoc以外に<?phpとclass Model_xxxの間に何か書かれてた場合にはそれは消されます。 netbeansでfuelphpを使ってて補完ができない!といらいらする方、ご活用ください

fuelphpでDBから特定のカラムだけ取ってくる

大量のデータを処理するとき、メモリをちょっとだけ気にしてデータベスからIDリストを取ってくる→それぞれのIDでデータを再度取得し、計算するといったことをよくやります。 fuelphpでもこれをやろう!と思ったのですが、ormを使っているとなかなかうまくいきませんでした。 一応出した自分的結論がこれ

1
$ids = Model_Sample::query()->select('id') ->get_query() ->execute() ->as_array(null,'t0_c0');

これをやると、

1
array ( 0 => '1', 1 => '3', 2 => '2', )

こういう形でデータが帰ってきます。 t0_c0はfuelphpが勝手につけるカラムの名前です。 一番最初のカラムにはこの名前が付けられるようなので、書きました。 本当は

1
$ids = DB::select()->select('id') ->from('samples') ->execute() ->as_array(null,'id')

のほうが正しいでしょうが、せっかくormがあるのにfromを直接書くのは負けた気がするので上のを使ってます。

phpでapiを使うなら必須なcurl_multi

curl_multiというのを見つけたので使ってみました. 外部APIを使う人にとっては必須かもしれません.

curl_multiとは

そのまんま,curlを複数並列に走らせるための関数です. 直列にすると待機時間が長いので,並列にリクエストを投げることで高速化が見込めます. php5から使えるようです.

基本的な使い方

使い方としては次の通り.

  1. それぞれのリクエスト用のcurlを作成する
  2. curl_multiを一つ作り,そこにそれぞれのリクエスト用のcurlをセットする
  3. 実行!&リクエスト終了待ち
  4. それぞれのリクエスト用curl,curl_multiの両方をcloseする

php関数リファレンスよりサンプルコード

1
<?php // cURL リソースを作成します $ch1 = curl_init(); $ch2 = curl_init(); // URL およびその他適切なオプションを設定します。 curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/"); curl_setopt($ch1, CURLOPT_HEADER, 0); curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/"); curl_setopt($ch2, CURLOPT_HEADER, 0); // マルチ cURL ハンドルを作成します $mh = curl_multi_init(); // ふたつのハンドルを追加します curl_multi_add_handle($mh,$ch1); curl_multi_add_handle($mh,$ch2); $active = null; // ハンドルを実行します do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active && $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } // ハンドルを閉じます curl_multi_remove_handle($mh, $ch1); curl_multi_remove_handle($mh, $ch2); curl_multi_close($mh); ?>

ですが,こんなの面倒なので,ひとまとまりに関数化してしまいましょう

お手軽curl_multi

関数にまとめました. arrayでurlを指定し,arrayでデータが返ってきます. 自分の環境ではなぜかよく失敗するので,失敗したurlをarrayで返すようにもしました.

1
public function fetchMultiUrl($urls, $timeout = 0, &$errorUrls = array()) { $mh = curl_multi_init(); foreach ($urls as $key => $url) { $conn[$key] = curl_init($url); curl_setopt($conn[$key], CURLOPT_RETURNTRANSFER, 1); curl_setopt($conn[$key], CURLOPT_FAILONERROR, 1); curl_setopt($conn[$key], CURLOPT_FOLLOWLOCATION, 1); curl_setopt($conn[$key], CURLOPT_MAXREDIRS, 3); if ($timeout) { curl_setopt($conn[$key], CURLOPT_TIMEOUT, $timeout); } curl_multi_add_handle($mh, $conn[$key]); } $active = null; do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); while ($active and $mrc == CURLM_OK) { if (curl_multi_select($mh) != -1) { do { $mrc = curl_multi_exec($mh, $active); } while ($mrc == CURLM_CALL_MULTI_PERFORM); } } //データを取得 $res = array(); foreach ($urls as $key => $url) { if (($err = curl_error($conn[$key])) == '') { $res[$key] = curl_multi_getcontent($conn[$key]); } else { $errorUrls[$key] = $url_list[$key]; } curl_multi_remove_handle($mh, $conn[$key]); curl_close($conn[$key]); } curl_multi_close($mh); return $res; }

実際に使ったところ,30アクセスで40秒ぐらいだったのが10秒ぐらいまで高速化できました. それほど難しくないですし,連続してAPIをたたく場合は便利です!

simplexmlでのネームスペース対応

phpでsimplexmlを使っていると、ネームスペースではまってしまったのでメモがてらに。 次のようなxmlがありました。

1
2
3
4
5
6
7
8
<entry>
<title>とある記事</title>
<im:updated>2012-01-10</im:updated>
</entry>
<entry>
<title>ちょっとした記事</title>
<im:updated>2012-02-03</im:updated>
</entry>

このとき、updatedを取るためにはちょっとだけ遠回りしないといけないようです。

1
$xml = simplexml_load_file($url); foreach( $xml->entry as $entry){ echo (string)$entry->title; //できる echo (string)$entry->updated; //できない echo (string)$entry->im:updated; //できない echo (string)$entry->children("im", true)->updated; //できる }

iPhoneアプリに必須なクラッシュレポートツール

久しぶりのiPhone関係記事です。 アプリを作っていると,自分のところでは再現しないものの リリースしていろんな人に使ってもらうとバグだらけのクラッシュだらけというのはよくあります. しかし,ユーザーに「再現できないからバグ修正しません」というわけにもいかず, どうやって再現するのかを詳しくユーザーに聞きたいけど,AppSoteのレビュー欄は返信できないしと結構問題があります. そんな悩みを抱えていた今から3か月ほど前に,友人からcrittercismというのを教えてもらって,すごい便利なので紹介します!

どんなシステムか

crittercismはすごい単純で,クラッシュして落ちた時のスタックトレースを記録し,統計を取ってくれるシステムです. 統計を取ってくれるので,どこで落ちた人が何人というのが分かり,より重要なバグから順番に直すことができます. また,ただたんにクラッシュしたスタックトレースを取るだけでなく,デバッグに必要な ・iOSバージョン ・アプリバージョン ・iPhone/iPod Touch/iPadのどれか ・メモリの使用状況 なども統計を取ってくれます.

導入

導入も簡単で,たった3ステップでできちゃいます.

  • ライブラリをDL&プロジェクトに追加
  • 必要なライブラリ(iOS提供)をプロジェクトに追加
  • AppDelegateに3行書く

費用対効果抜群です.

レポート

クラッシュした時,メールでエラーを見るのとwebで見るのの二種類があります. クラッシュ数が少ない場合はメールを,多い場合は統計してくれるwebがお勧めです. 飛んでくるメールはこんな感じにきちんとスタックトレースが付いてきます. webで見ると,もっと詳細にいろんな統計が付いてきます. クラッシュ数の変化や、クラッシュしたデバイスの情報などです. クラッシュ数の変化 デバイス情報 どちらもグラフで表示されるのでわかりやすいです。

まとめ

サービスへの登録を含めても全部で30分ぐらいで導入できるうえに これによってバグ修正がだいぶ楽になるので アプリ作ってる人は一度は試してみるのがいいんじゃないかと思います.

gitignoreが効かないファイルを治す

gitで.gitignoreに書いたのにファイルが全然無視されてない!ってなったので調べてみました。 どうやら一度コミットしてしまったら.gitignoreに追加されてても追跡対象になってしまうらしいです。 対処法としては、きちんとgit上で削除してあげること。 ファイル自体は削除する必要はない。

1
git rm --cached ファイル名

こうすれば、きちんと.gitignoreが効くくようになりました。

関連記事

GitHubで始めてプルリクエストをした話

半月ぐらい前にはじめてGitHubをつかってプルリクエストをしたお話. 英語力のない自分でも案外きちんと海外の人とコミュニケーションができ,GitHubってすごいと思いました.

これってバグ?

最初に疑問に思ったのはバイト先でファイルをアップロードする機能を作ってた時でした. 一般から応募するようなものなんですが,本体のファイルと概要ファイルの二つをアップロードして保存しないといけない案件でした. fuelphpなららくらく~と思って作っていると,なんかエラーメッセージがおかしい・・・・. あれ?と思ってCoreの中まで見てみると,forの外側でfor内の変数を使ってる明らかに怪しいところがあり,おぉこれはバグだ!と思ったわけです. で,バグ見つけたし,ちょうどfuelphpの本も読んだわけで,これはpull requestするべき!!・・・でもちょっと怖い的な内容をつぶやいたところ,@kenji_sさんに背中を押されてチャレンジしてみることにしました.

githubでチャレンジ

内容としては 1.Fuelphpのコアをフォークする 2.バグを修正する 3.コミットする 4.プルリクエストを送る とすごい簡単なんですが,初めてということもあり戸惑い,途中何回かリセットしつつどうにかこなしました. テストコードを書かないとマージされにくいと聞いていたので,このレベルでテストコードを書くべきなのかどうか悩みつつ,結局書かずにドキドキしてプルリクエストを押しました.

海外からの返信

海外からの第一の返信は”何でこんなことしてるの?”でした. すごいショック受けました….自分の説明では全然わからないのかと. でもそのあとよく考えたらわかりにくい説明ではあるし,もうちょっと詳しく説明しようと思い, ”何行目で始まるfor文の中で宣言されてる変数$keyがfor文終わった何行目以降で使われてるんだよ”って行数,プログラマー的キーワード(for,$keyなど)をふんだんに使ってメッセージを送ったところ,納得してくれたようで無事本家にマージされました.めでたしめでたし.

プルリクエストを終えてみて

プルリクエストを終えてみて思ったのは,案外受け入れてもらえるんだってこと. 大きなプロジェクトを作っていると汚くなるから他人のソースコードを入れたくないとか思う時があるんですが, 全然そんなこともなく受け入れてもらえました. (最初tabを使っていなかったのは突っ込まれましたが) また,英語ができなくてもキーワードやら行数やらを書いて,きちんと伝えれば低レベル英語でも伝わったというのはすごくうれしかったです.ちゃんと英語勉強してコミュニケーションとりたいなと思うぐらい刺激的でした.

「はじめてのフレームワークとしてのFuelPHP」のレビューをして

発売してしばらく経ってしまい,今更感がありますが, 「初めてのフレームワークとしてのFuelPHP」という本のレビューに参加させてもらいました. やっと時間が取れるようになったので、レビューしてみていろいろ思うところを書こうと思います.

レビューでやったこと

レビューで具体的にやったことは主に次の3つです。

  • 本のPDFを読み、わからないところ、おかしいんじゃないかと思うところを指摘する
  • かいてあることを自分のPCで実際に行い、きちんと動作出来るか確認する
  • 他人のレビューで出た問題に対して、自分のほうではどうだったのかetcを答える

あと、気づいたらレベルで誤字脱字も指摘しました。

レビューに参加してみて

レビューに参加してみて一番思ったのは、案外簡単だったということでした。 本のレビューと聞くと、自分はすごい立派なイメージがあって、その分野についての専門家ぐらいの知識がないとできないというイメージが合ったのですが、そんなことはありませんでした。 むしろ、本の対象者がphp初心者ということもあり、専門家よりもむしろ右も左も分からない人のほうがレビューに役立てたのではないかなと思いました。 本に対する指摘というのも普通にメールをするだけで、しかも直接著者の人にではなく、グループのメーリスにだったのですごく気軽に指摘できました。 また、グループの利点として他人の進み具合がわかり、それはそれで刺激になりました。 fuelphp自体は前から触っていましたが、なかなか知らないことも多く、レビューをしながら自分の知識が増えていき、すごいありがたかったです。 eclipsや、テスト等、使ったことのなかったものを使い、結構楽しめました。

本について

フレームワークとしての解説だけでなく,”初めて”フレームワークを使う人用にローカルサーバーの立て方、開発環境の作り方などがしっかり書いてあります。 それも各OSそれぞれで書かれています。 初心者にはこの時点で結構うれしいのではないでしょうか。 肝心のfuelphp自体の解説も、幅広く書かれており、ひと通り読めばだいたいのものは作れるんじゃないかなというレベルです。 さらに、品質を保証するためのテストの方法まで書かれていて、初心者を対象としつつ、しっかりと一定のレベルまで引っ張りあげてくれます。 すごい初心者にお勧めできる本です。 また、(自分がそう出会ったように)独学でfuelphpを使っていると使わないような部分まで解説されていて、さらにそれが便利だったりするのでお勧めです。

コミュニティについて

実は今回のレビューが開発関連のコミュニティとしては初めてでした。 コミュニティと聞くと硬いイメージが先行して、なかなか飛び込むことができなかったのですが、このレビューのコミュニティはすごいほんわかとしていて話やすかったです。 どうもいろんなコミュニティでこんな雰囲気らしいので、少しは苦手意識が消えたかなと思います。 このレビューにて普通のfuelphpのコミュニティにも参加するといいよ!とおすすめされたんですが、未だに怖くて参加していません

というわけで・・・

「はじめてのフレームワークとしてのFuelPHP」 おすすめです。 ※電子書籍でのみの販売らしいので、書店ではおていません。