iPhoneSDKのライブラリインクルードに関するはなし

iPhoneアプリ作ってて、ASIHttpRequest という便利そうなHTTPクライアントがあったので使ってみようと思った。

iPhoneで使えるHTTPクライアントASIHTTPRequestについて - ninjinkun's diary
こちらを参考にさせて頂きました。


で、参考サイト通り必要なライブラリとフレームワークを追加したんですけど、いざビルドすると大量にエラーがでました。

どうも、ASIWebPageRequest という機能の中の

#import
#import
#import
#import

ここらへんが読み込めてない模様。


既存のフレームワークからそれらしい libxml2.dylib とか組み込んだけど駄目。
ググってみて http://stackoverflow.com/questions/405749/parsing-html-o これとかを見て解決。
以下方法で include_path 追加すれば良いみたい。


環境はOSX10.6.6 Xcode3.2.3です

メニューの「プロジェクト」>「プロジェクト設定を編集」>「ビルド」



この「ヘッダ検索パス」のところに…



再帰」をチェックして上のパス入れた。



さらに、「他のリンカフラグ」に、



上のように入力。



これで問題無くビルドできました。
肝心のライブラリはまだ使ってないw

PHPカンファレンス2010に関する記事自分用まとめ

Togetterから得られる情報をまとめてみた

PHPカンファレンス2010に参加したつもりになる為にTogetterから得られる情報を自分用にまとめてみました

皆さんのTweet内に貼ってあるリンク

おもにPHP5.3に関して個人的に気になったつぶやきからのメモ(あとで検証とかする)

  • __DIR__ = dirname( __FILE__ ); と書ける。読みは「だー」。
  • 三項演算子の二つ目を省略できるように: 省略した場合、条件式の内容がそのまま返される。ex) echo true ?: 'Hello';
  • HEREDOCの変数展開しないバージョンNOWDOC: 一行の場合のシングルクォートに相当
  • EOBはEnd Of Block
  • gotoは制限がある。今の構造から出ることは出来ても、構造内に飛び込むことは出来ない。ループから脱出するときのみ使えるようになった。
  • 長時間動かすスクリプトのためにガベージコレクションのONOFFができるようになった。gc_enable()、gc_disable()で切り替えを行う。
  • 5.3.3からはクラス名と同じ名前を持つメソッド名はコンストラクタとして扱わなくなった。(名前空間内のみ)
  • 遅延静的束縛(レイトスタティックバインディング) サブクラスとして実体化されたインスタンスのメソッドを明示的に呼び出せるようになった。
  • __call()のスタティックメソッド版__callStatic()を追加。クラス・メソッド名を変数で指定することも可能になった。
  • PHP4に比べてDate関連が大きく進歩した。Unixの記述に縛られることはない。日時の間隔を表すDateInterval/DatePeriod。翌月の第三火曜日、といった指定を(英語で)できるように。期間を与えてその期間内の条件にあった日時をIterateできるようになった。文字列から日時を生成するときに、ヒントのフォーマットを与えられるようにした。日時のパーズに失敗した時に親切な例外を出すようにした。
  • SPLの改良: ワイルドカードでのファイル列挙GlobIterator。配列をスタックとして使えるイテレーターSplStack。ソート済のヒープを作るSplMaxHeap/SplMinHeap
  • mojaviの後継AgaviはIIS7 + SQL Serverも対応
  • ZFDoctrine, Zend_Service_Tumblr, ZendFWバージョン1.11をリリース予定。Zend_Cloudが入ることが今日確定。
  • Symfony2はGitHubで管理。1.4の80倍の速さ。Synfony2は来年3月にリリース予定。
  • Shift-JISの5C問題によるSQLインジェクション 対応は Shift-JIS を使わない。
  • utf8 to utf8の変換でヤバイ文字列消せる。
  • nginx と PHP-FPM

PHP5.3.3入れたメモと超便利なStagehand_Testrunnerについて

最近になってDIコンテナについて社内勉強会をしようと思い立ち参考になる資料をググってたところ、Symfonyデベロッパである Fabien Potencier さんのスライド
Dependency Injection with PHP 5.3 を発見し、そのまま皆でスライドを読み進めていく等しました。

この資料はSymfony2に新しく加わるDIのコンポーネントである「Dependency Injection」の実装を参考に、PHP5.3系でのDIコンテナの実装とそのメリット等が説明されています。
クロージャの使い方等も解説してあって、PHP5.3でのOOPの学習にもなってかなり勉強になりました。


というわけで、PHP5.3を本格的に使い出したい…とモチベーションも高まったところで、最近リリースされたばかりの5.3.3をインストールしてみる事にしたので、備忘録として残します。
ついでにTDD環境も構築しようと思い、PHPUnitと、テストランナーであるStagehand_Testrunnerもインストールする事に。


PHP5.3.3

phpproのエントリー を見ると、「後方非互換の変更」があるとの事。

後方非互換な変更として、名前空間に属するクラスにおいてクラス名と同名のメソッドが定義されている場合、5.3.2まではコンストラクタとして扱われていましたが、5.3.3では通常のメソッドと同様に扱われるようになります。

という事で、PHP4から移行する場合には注意が必要ですね。
(PHP4システムのメンテナの皆様、お疲れ様です。。)


では以下作業ログ。まっさらなUbuntu10.04に入れました。(configureオプションは適宜)

% apt-get update
% apt-get install build-essential

% cd /usr/lib
% ln -s libxml2.so.2 libxml2.so

% apt-get install libxml2-dev
% apt-get install apache2-prefork-dev
% apt-get install libcurl4-openssl-dev
% apt-get install libpng12-dev

% cd /usr/local/lib
% wget http://jp2.php.net/get/php-5.3.3.tar.gz/from/jp.php.net/mirror
% tar zxvf php-5.3.3.tar.gz
% cd ./php-5.3.3
% ./configure --prefix=/usr/local/lib/php --with-apxs2=/usr/bin/apxs2 --enable-pcntl --enable-mbstring --enable-mbregex --enable-zend-multibyte --with-mysql --with-gd --with-curl --with-openssl --with-pear=/usr/local/lib/php
% make
% make install

PHPUnit3.4.15

次にPHPUnit。公式リファレンスは以下。
PHPUnit Manual

Pearコマンドからインストールできます。
現在最新版の3.5系がリリースされていますが今のところベータ版で、Pearコマンドでインストールされるのは3.4系になります。

% apt-get install php-pear
% pear channel-discover pear.phpunit.de
% pear channel-discover pear.symfony-project.com
% pear install phpunit/PHPUnit

Stagehand_Testrunner2.12.0

次にStagehand_Testrunner。
これはPHPフレームワークの一つである、Piece Framework のプロダクトの一環としてリリースされているライブラリで、
PHPUnit, PHPT, SimpleTest, PHPSpec といった各種テスティングフレームワークをより便利に利用できるようにしてくれるツールです。
id:heavenshell さんにTwitterで教えてもらいました。感謝)


プロジェクトホームは以下。
Stagehand_Testrunner

詳細は開発元である @iteman さんのエントリー
PHP で快適なテスト駆動開発を - Stagehand_TestRunner の特徴と使い方を知る
も参考に。


Pearコマンドからインストールできます。
依存しているライブラリが多いのですが、以下のようにインストールしていけばOKでした。

% pear channel-discover pear.piece-framework.com
% pear install piece/stagehand_accesscontrol-beta
% pear install piece/stagehand_legacyerror-0.3.1
% pear install piece/stagehand_directoryscanner
% pear install piece/stagehand_alterationmonitor
% pear install piece/stagehand_clicontroller-beta
% pear install piece/stagehand_testrunner

# 以下はオプション。任意で。
% pear install Console_Color
% pear install Net_Growl-beta

# 適宜パス通す
% vi /etc/php5/cli/php.ini
> include_path = ".:/usr/share/php:/usr/share/php/src/"


これで、各種機能が使えます。
詳しくはユーザーガイドを見ていただくと良いかと思います。

# 指定したディレクトリ配下のテストを全て実行
% phpunitrunner -R DIRECTORY

# テストの実行結果に色付けできる (Console_Color 必要)
% phpunitrunner -c DIRECTORY_OR_FILE

# Growlへテスト結果を通知する (Net_Growl 必要)
% phpunitrunner -g DIRECTORY_OR_FILE

Growlは現在動いていないようで、チケット192に登録されてました。次のバージョンに期待。


他にも色々できるんですが、自分が一番欲しかった機能が、ディレクトリの監視。

% phpunitrunner -a DIRECTORY

で、指定ディレクトリを監視。
ディレクトリ内のファイルを更新したり、新規ファイルを作成したりすると、テストが自動で走るというものです。
これを使えばファイル編集のたびに phpunit hogehoge... とタイプする手間がはぶけます。


で、使ってみたんですがエラーが出てどうにも使えない、という事態に。

% phpunitrunner -a DIRECTORY
で監視開始し、ファイルに変更を加えると…

ERROR: The directory or file [ -R ] is not found
というエラーメッセージが出て動かない

ファイル構成が悪いのかなあと思い、色々と変更してみたり、オプションを変えて試したりしたが動かない。
困ってTwitterでつぶやいていたところ @heavenshell さんが捕捉してくれて、開発者の @iteman さんに報告してくれました。

その後、一連のやりとりは @iteman さんが Togetter にまとめてくださいました。


最終的に僕の環境では以下のコマンドで正常に動きました。

% php /usr/bin/phpunitrunner -a DIRECTORY

この件はチケット196が切られており、次回リリース分では改修される予定のようです。

とても便利なツールだと思いますので、TDDされてる方は利用されてみてはいかがでしょうか!


最後になりますが @heavenshellさん、@itemanさん、今回は色々とお付き合い頂きありがとうございました!

私のiPhone3Gが破損して3GSに変わるまでの顛末

もはや生活に欠かせない相棒となっているiPhone。皆さんも愛でていらっしゃるかと思います。


この度発売当時に購入してそろそろ丸2年が経過しようとしていた愛しのiPhone3Gが私より先に旅立ったのですが、今後同じような悲しい思いをされる方の参考になればと思い、再び我が手にiPhoneが戻ってくるまでの顛末を書き残しておきたいと思います。


突然の別れ

いつものように日常の記録としてカメラで写真を撮りTwitterにポストする等の自己満足行為で悦に入りつつ、その日もiPhoneと戯れていました。

最後にホーム画面を確認したのが午後10時頃。再びiPhoneに手をかけた午後11時半には電源が切れている状態でした。

この間ただ置いていただけ。


蘇生処置

「ああ、またいつものように機嫌を損ねてるんだな。かわいいけど憎らしいやつめ」

私はそう思い、手馴れた手つきで本体右上の電源ボタン、ホームボタンを同時押しし、リンゴマークが出るその瞬間までじっと待ち続けました。


その状態のまま10秒、20秒と時間を過ぎ、30秒ほど経過した時、僕の頭の中にどんよりとした不穏な気配が立ち込めました。


手汗がじわりと出てくるのを感じながら、google先生に問い合せる事しきり。
しかし、出てくる情報はどれも「電源ボタンとホームボタンの長押し」ばかり。しかも最長でも20秒で復帰したという内容。


それから、母艦のMacにUSBで繋ぎつつ長押し、コンセントから電源を供給しつつ長押し等、考えうる限りの蘇生処置を行いましたが全く何も反応が無い。

「こいつ…逝っちゃったのかも」
私はにわかに信じ難い事実を受け止める準備を始めました。


情報収集

それから、「修理」となった場合どのくらいの費用がかかるのかを調べ始めました。


調べると、「iLab PC Factory」さんという名古屋のiPhone専用修理屋さんがある事を知りました。
ここは良心的価格でiPhoneの修理を行われているようで、SoftBankへ持ち込んだ場合の修理代金との比較表がありました。


http://ilab.cc/hikaku.html


今回の私のケースを考えると、3Gを発売当初に購入して既に2年近く経過している事もあり、「バッテリー切れ」が妥当な線ではないか、と考えました。
500回の充電くらいとうに行っているはずです。


そうした場合、このiLab PC Factoryさんに修理依頼した場合、4800円で済みそうです。SoftBankで行うと9800円かかります。
送料を考えてもiLabさんだと6000円弱で済みそう。希望的観測ですが若干の光が見えてきました。
ちなみに私のiPhoneJailBreakも行っていましたので、全く電源が入らない状態とはいえSoftBankでの修理となった場合そのあたりもどう響くのか気になっていました。


しかしiLabさんへ依頼すると仮定した場合、名古屋へ送付して戻ってくるまでに最短でも2日はiPhoneが無い、つまり携帯が無い状態になってしまいます。(それでもここの修理対応はかなり迅速なようですが)

これはやはり痛いので、他の方法も考えました。


嫁のiPhone強奪

運良く我が家は今年に入り嫁もiPhoneにしていました。
もちろん私の3Gより数倍機敏な3GSです。

これを自分のものにする方法はどうか。


良いアイデアだと思いました。
USIMを交換すれば、私の物として問題無く使えるはずです。
嫁はiPhoneにする前の携帯をまだ保管していたので、それを使えば問題あるまいと考えました。


そもそも、世間的には次世代iPhoneの発表を今か今かと待っているような時期です。
もはや使命を終えんとしている3Gに高い修理代を払うなど、愚の骨頂としか思えません。


「嫁の3GSを奪う」


シナリオは組み立てられました。


SoftBankショップへ

一切の熱を放たなくなったスマートフォンの残骸を、一筋の奇跡を信じて充電器に刺したままその日は眠り、翌日SBショップへ駆け込む事にしました。


翌朝いつものようにiPhoneを手に取りましたが、やはり昨日の状態のまま、全く反応はありません。


あきらめて事情を嫁に相談、「金がかからない方法」が選択の第一義である嫁は自分の3GSを私のものにする事をしぶしぶ了承し、すぐにSBショップへ向かいました。


ショップで動かなくなるまでの一切を説明すると、店員は確認の為にiPhoneを裏に持って行き、しばらく何かしていました。

結果はやはり、「全く反応がありません」
私が「バッテリーが切れているのではないか」と伝えたところ、バッテリー反応も調べてみたが、全く何の反応も示さない為、本体の故障の可能性が非常に高いとの事。

どういった対応になるのか確認したところ、「保証期間外の修理対応」となるとの事で、22,800円がかかるとの事でした。


この時点で「修理」という選択肢は消え去り、私は昨晩計画していた内容を提案しました。


計画実行にかかる費用

一通り説明すると、店員さんから以下のような説明がありました。

  • 嫁のiPhoneと私のiPhoneUSIMを差し替えて使用する分は当然問題無い
  • ただし、嫁が機種変更を行うという事になるので、事務手数料が必要
  • 更に、「wifiバリュープラン」から契約更新月以外での変更になるので、違約金が必要


嫁はiPhoneの契約の際、「wifiバリュープラン」という通常のパケ放題プランよりも安いプランに加入しており、このプランは2年間の縛りがある為、契約更新月以外での解約は9985円かかるとの事だった。
※以下のYahoo!知恵袋の問答が詳しい。

wi-fi バリュープランってお得なんでしょうか? - 3月中iPhone3Gsを買おうと思... - Yahoo!知恵袋


更に事務手数料が1995円。合計11980円。
支払いは次回請求に追加されるかたち。


これは思わぬ出費で流石に嫁の形相もみるみる悪化していたが、なんとか説得。この方法で進む事にした。

※ちなみに近い将来私が新型iPhoneに変更して、再度3GSを嫁のものにした場合は、事務手数料1995円のみでOKとの事だった。


復元と同期

事務処理を終え、早速嫁の3GSに刺さっているUSIMと、私の3GのUSIMを差し替えた。
これで3GSは私の電話番号となり、同時に@softbank.ne.jpアドレスも設定された。

この時点ではもちろんアプリ、メールの設定、その他のデータは全て嫁が使っていた状態で、電話とsoftbank.ne.jpアドレスのみ私のものが設定されている。


自宅に帰り、以下の手順で復元及び同期を行った。

  • 嫁のPCにiPhone3GSを接続し、入っているデータを全て同期。
  • そのまま「復元」で工場出荷時の状態に戻す。
  • 私のPCにiPhone3GSを接続し、「復元」でデータの同期を行う。


これで、嫁のデータは母艦に保存され、私のiPhone3Gに入っていたデータは最後に同期した時点のものに復活した。


次回嫁に3GSを返還する時は、同様の手順で行えば良し。


まとめとおまけ

もうすぐ2年が経って新型が出ようとする時点での故障に何らかの意図を考えずにはおれないほどの衝撃をうけましたが、やはりいつ故障しても良いようにAppleケアに入っていた方が良いのかなと思ったりしました。(とはいえやはり高いので多分入らないですが。。)


話しは変わって最近サービス開始された「ホームアンテナ」に同時に申し込んで来ましたが、「YahooBB」以外のブロードバンド契約の場合工事開始が9月からとの事で、私はフレッツ光だった為、おそらく年内はかかりそうな見込みとの事。

無料なので待つ事にしました。
工事は既に相当混み合っているとの事で、自宅で電波が入らないとお嘆きの方は早めに申し込んではいかがでしょうか。
SoftBank


…しっかし3GS早いっすね。

2010/08/09 追記

長崎原爆投下のこの日、久々に引き出しの中に眠っていたiPhone3Gの亡き骸を見つけ、なんとなく母艦であるmacbook proに接続しました。すると…

「充電中」の画像が表示されているではありませんか!

全く反応していなかったあいつが、復活している?
はやる想いを抑え、暫く充電した後、再起動してみました。すると…

復活。

あの騒動は何だったんだろう。SBに払った万単位のお金は…。
悔しさ等がこみ上げますが、冷静に考えるとこいつはまた同じ謀反を起こすかもしれません。

しばらくiPodTouchとして家庭内でwifi専用端末として頑張ってもらう事にしました。
さていつまで持つやら。

ロリポップのcronを使ってTwitterボットを作ってみよう

[PHP][Twitter]ロリポップのcronを使ってTwitterボットを作ってみよう

4月末に、格安レンタルサーバでお馴染みのロリポップが cron機能に対応しました。
そこで、Twitterボットを試しに作ってみました。


今回作ったのは、「みんながFavoriteした発言でつくる、全自動Twitterまとめサイトもどき ふぁぼったー」をクロールして、新しいふぁぼられがあったらReplyで通知してくれるボットです。

TwitterAPI認証は今後ベーシック認証は使えませんので、OAuthを使います。


※2012/1/11 追記
コードのメンテしていないので、favotter の構成が変わっているとそのままだと動かない可能性があります。(今はふぁぼられたらメールが来る時代ですから‥)

動作環境

ロリポップの新しい構成のサーバでの動作となります。

PHP Version 5.2.6
MySQL 5

使用するスクリプト

今回作成したスクリプトgithub に置いてます。(ふぁぼったーをクロールするボットなので fabot...)
GitHub - taketin/fabot: favotter_crawl_bot
ここからソースを落としてください。


あと、OAuth用のライブラリとしてTwitterの公式サイトからも紹介されている、twitteroauth というライブラリを使用します。

ボットの登録から、ライブラリのダウンロードまでは以下のサイトが詳しいです。
PHP+OAuthでTwitter - SDN Project
ここを参照に、ボットアカウントの作成及びライブラリのダウンロードを行ってください。

ボットの準備

先程 github からダウンロードした fabot ディレクトリ内の class ディレクトリ内に、twitteroauth ライブラリ中の、OAuth.php と twitteroauth.php を入れます。


さらに、fabot/config.ini の中を編集します。

[account]
;twitter account name
account[]           =
account[]           =

[bot]
;bot account name
account             =

; Consumer key - OAuth
consumer_key        =

; Consumer secret - OAuth
consumer_secret     =

; Access Token
access_token        =

; Access Token Secret
access_token_secret =

[account] の下の account[] は、クロールしたいアカウント名を入れます。たくさんある場合は行をコピーして増やしてもいいし、1人でいいのであれば1行に減らしてください。

それから先ほどの参考サイトをもとに、ボットの OAuth 認証キーを入力します。
(※config.iniの編集の際、値をクォーテーションでくくらないでください)

データベースを用意

続いて、データベースを用意します。(めんどいけど、既読管理する為にDB必要なのです。。)

ロリポのユーザー専用ページで、こんな感じでMySQL5のDBが作成できます。
作ったら、先程の config.ini の database の欄にデータを入力します。

[database]
host                = mysql515.phy.lolipop.jp
dbname              = LAxxxxxxxx-fabot
account             = LAxxxxxxxx
password            = 設定したパスワード

貼りつけてる画像で言うと、上記設定内容になります。


次に、ロリポPHPMyAdmin にログインして、テーブル作ります。
以下のSQL実行してください。

create table fav_tweet( 
    fav_id varchar(16), 
    account varchar(16),
    created_on varchar(19),
    modify_on varchar(19),
    primary key(fav_id)
) type=InnoDB;

create table fav_follower(
    id int auto_increment,
    fav_id varchar(16),
    account varchar(16),
    created_on varchar(19),
    modify_on varchar(19),
    primary key(id)
) type=InnoDB;


こんな感じになったらDB出来上がりです。

ロリポFTPでソースをUPします

ソースをUPします。
その前に、今回cronから実行させますので、include_pathを絶対パスにしておきます。
includeの記述があるのは以下の3ファイルです。

index.php
class/TwitterAPIHandler.php
class/twitteroauth.php

この中で、require_once してるとこのパスを絶対パスに書き換えます。

書き換えたらロリポFTPでファイルを上げます。

ファイル上げ終わったらこんな感じの構成になります。
ロリポFTP、複数ファイル一気にUPLOADできたりして良いですよ)

php.ini の設定をします

次に、必要な php.ini の設定をしときます。
今回、文字コードutf-8 で統一しますので、そのあたりの設定など。

これもロリポの設定メニューから上記のように登録します。

cron に登録します

ロリポの cron は最短10分置きから設定できます。
今回はふぁぼったーの更新ペースが毎時、という事なので、こちらも毎時一回実行するように設定してみます。

こんな感じ。


設定するとこのように表示されます。

※ ちなみに、通知メールアドレスを登録しておくと実行結果をメールでお知らせしてくれますので、うまく動くのを確認するまでは登録しておいた方がいいと思います。

実行結果を確認

うまく動いた場合、最初の実行で大量の reply が飛んでくると思います(1ページ分のふぁぼりを通知する為)。
その後はクロールの度に増えたふぁぼりを通知してくれます。

こんな感じで。
文言は TwitterAPIHandler.php 内の該当箇所を適当にいじったら良いと思います。


php.ini とファイルの文字コードがうまく設定できてないと、

<?xml version="1.0" encoding="UTF-8"?>
<hash>
 <request>/statuses/update.xml</request>
 <error>Incorrect signature</error>
</hash>

なんてエラー返されます。

それでは!

PHPによるデザインパターン入門について

デザインパターン勉強会の内容を記載して行こうという趣旨で、前回前々回と Singleton, FactoryMethod を書きました。

しかし内容が、参考にさせて頂いてる書籍「PHPによるデザインパターン入門」の内容ほぼそのままという事と、この書籍(絶版)を著者の id:simooka さんがご自身のブログで順次原稿公開されていく、という事から、オリジナルを見た方がいい!と思いましてご案内させて頂きます。

丁度良いタイミングで原稿公開だなーと思いました(simookaさんありがとうございます!)
書籍「PHPによるデザインパターン入門」の原稿テキストを公開します - Do You PHP はてブロ


この本でデザインパターンを覚えられたPHP使いの方は多いのではないでしょうか。
PHPに関する本はほとんど入門用ばかり、という中で、貴重な書籍だったと思います。

PHPによるデザインパターン2 FactoryMethod

デザインパターン勉強会の内容。第二回目。


参考にした書籍は以下。(すでに絶版)
http://www.amazon.co.jp/PHP%E3%81%AB%E3%82%88%E3%82%8B%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E5%85%A5%E9%96%80-%E4%B8%8B%E5%B2%A1-%E7%A7%80%E5%B9%B8/dp/4798015164/ref=sr_1_1?ie=UTF8&s=books&qid=1265290118&sr=1-1

FactoryMethodパターン

【目的】
使いたいオブジェクトを生成する為の窓口となるAPIを作成する事で、クライアント(呼び出し)側とオブジェクト側を疎結合に保つ。

インスタンスを生成する工場」のようなパターンなので、Factory Method と呼ばれる。

【具体的な実装内容】
「窓口となるクラス」、「実際に使いたいクラスのインターフェース」、「実際使う(インスタンスが返される)クラス」を実装する。

コード例

あるcsvファイルから一行ずつ読み込んで表示するプログラム

<?php

    $fileName = $_POST['fileName'];    // 'sample.csv' が入っているとする

    if (is_readable($fileName)) {
        $handle = fopen($fileName);
    } else {
        die();
    }

    while ($row = fgetcsv($handle, 4096, ',')) {
        echo $row;
 }


上記内容は 「csvファイル」を読み込む事しか想定していないが、例えば渡されるファイル名が xxxx.xml だった場合、パースするという処理も行いたい。
この場合、上記スクリプトを拡張するとしたら…

<?php

    $fileName = $_POST['fileName'];    // 'sample.xml' が入っているとする

    if (is_readable($fileName)) {
        if (false !== strpos($fileName, '.xml')) {    // ファイル名に.xml があったら…の分岐を追加
            $xml = simplexml_load_file($fileName);
        } else {
            $handle = fopen($fileName);
        }
        $handle = fopen($fileName);
    } else {
        die();
    }

    // ここも変えないと…
    while ($row = fgetcsv($handle, 4096, ',')) {
        echo $row;
 }


等と、ベタ書きで処理を追加していくしかない。これでは保守性も落ちるし、バグも内包しやすい。

そこでFactory Method。

使いたいクラスのインターフェースを実装

■reader.class.php

<?php

interface Reader
{
    /** 読み込み */
    public function read();
    /** 表示 */
    public function display();
}

ひとまず読み込みと表示さえ行えれば要求を満たすので、上記インターフェースを設計。

csvファイル対応クラスを実装

■CSVFileReader.class.php

<?php

class CSVFileReader implements Reader
{
    /** コンストラクタ */
    public function __construct($fileName)
    {
        if (!is_readable($fileName)) {
            throw new Exception($fileName . 'is not readable.');
        }
        $this->_fileName = $fileName;
    }

    /** 読み込み */
    public function read()
    {
        $this->_handler = fopen($this->_fileName, 'r');
    }

    /** 表示 */
    public function display()
    {
        while ($row = fgetcsv($this->_handle, 4096, ',')) {
            echo $row;
      }
    }
}

xmlファイル対応クラスを実装

■XMLFileReader .class.php

<?php

class XMLFileReader implements Reader
{
    /** コンストラクタ */
    public function __construct($fileName)
    {
        if (!is_readable($fileName)) {
            throw new Exception($fileName . 'is not readable.');
        }
        $this->_fileName = $fileName;
    }

    /** 読み込み */
    public function read()
    {
        $this->_handler = simplexml_load_file($this->_fileName);
    }

    /** 表示 */
    public function display()
    {
        foreach ($this->_handler->hoge as $hoge) {
            echo $hoge;
            foreach ($hoge->fuga as $fuga) {
                echo $fuga;
            }
        }
    }
}

インスタンス生成API (Factoryクラス) を実装

■ReaderFactory.class.php

<?php

class ReaderFactory
{
    require_once 'Reader.class.php';
    require_once '
CSVFileReader.class.php';

    require_once 'XMLFileReader.class.php'

    /** Readerクラスのインスタンスを生成するAPI */
    public function create($fileName)
    {
        $reader = $this->_createReader($fileName);

        return $reader;
    }

    /** 生成するReaderサブクラスを選定する */
    private function _createReader($fileName)
    {
        $poscsv = stripos($fileName, '.csv');
        $posxml = stripos($fileName, '.xml');

        if (false !== $poscsv) {
            $classPrefix = 'CSV';
        } elseif (false !== $posxml) {
            $classPrefix = 'XML';
        } else {
            throw new Exception($fileName . ' is not supported.');
        }

        return new {$classPrefix}FileReader($fileName);
    }
}

クライアント側コード

<?php

    require_once 'ReaderFactory.class.php';

    $fileName = $_POST['fileName'];

    $factory = new ReaderFactory();
    $obj = $factory->create($fileName);

    $obj->read();
    $obj->display();


【特徴】
クライアント側コードでは、扱いたいReaderクラスを直接 new せず、Factoryクラスのメソッドから生成している。
この事から、FactoryMethodパターンは「VirtualConstructor」とも呼ばれる。

実際扱いたいクラスとクライアントの間にFactoryクラスというレイヤーを一枚噛ませる事で、クラス間の結合をゆるく(疎結合化)できる為、保守性が向上する。

もう一つのOOP的特徴は、ポリモーフィズム多態性)を利用した実装となっている事。
interfaceを設計しサブクラスを実装する事で、クライアント側からは同じメソッドを呼んでいるが、ファイル名によって実際の動作が変わっている。

もっとコードを短く

■ReaderFactory.class.php

<?php

class ReaderFactory
{
    require_once 'Reader.class.php';
    require_once 'CSVFileReader.class.php';
    require_once 'XMLFileReader.class.php';

    /** Readerクラスのインスタンスを生成するAPI */
    public static function create($fileName)
    {
        $poscsv = stripos($fileName, '.csv');
        $posxml = stripos($fileName, '.xml');

        if (false !== $poscsv) {
            $classPrefix = 'CSV';
        } elseif (false !== $posxml) {
            $classPrefix = 'XML';
        } else {
            throw new Exception($fileName . ' is not supported.');
        }

        return new {$classPrefix}FileReader($fileName);
    }

}

クライアント側コード

<?php

    require_once 'ReaderFactory.class.php';

    $fileName = $_POST['fileName'];

    // $factory = new ReaderFactory();
    // $obj = $factory->create($fileName);

    $obj = ReaderFactory::create($fileName);

    $obj->read();
    $obj->display();


staticメソッドに変えてスッキリ(保守性は落ちるかも)!

さらに…

メソッドチェーンにしてもうちょっと短く

■CSVFileReader.class.php

<?php

class CSVFileReader implements Reader
{
    /** コンストラクタ */
    public function __construct($fileName)
    {
        if (!is_readable($fileName)) {
            throw new Exception($fileName . 'is not readable.');
        }
        $this->_fileName = $fileName;
    }

    /** 読み込み */
    public function read()
    {
        $this->_handler = fopen($this->_fileName, 'r');

        return $this;
    }

    /** 表示 */
    public function display()
    {
        while ($row = fgetcsv($this->_handle, 4096, ',')) {
            echo $row;
      }

        return $this;
    }
}

上記のように、各メソッドの返り値にとして自分自身を返す事で、メソッドチェーンが実装できる!

クライアント側コード

<?php

    require_once 'ReaderFactory.class.php';

    $fileName = $_POST['fileName'];

    // $factory = new ReaderFactory();
    // $obj = $factory->create($fileName);

    $obj = ReaderFactory::create($fileName);

    // $obj->read();
    // $obj->display();

    $obj->read()->display();

これで4行のコードが2行に(保守性は(ry)!
メソッドチェーン気持ちいいですよね!

ZendFrameworkでの利用例

■Pagenator.php より抜粋

<?php

    /**
     * Specifies that the factory should try to detect the proper adapter type first
     *
     * @var string
     */
    const INTERNAL_ADAPTER = 'Zend_Paginator_Adapter_Internal';

    /**
     * Factory.
     *
     * @param  mixed $data
     * @param  string $adapter
     * @param  array $prefixPaths
     * @return Zend_Paginator
     */
    public static function factory($data, $adapter = self::INTERNAL_ADAPTER,
                                   array $prefixPaths = null)
    {
        if ($adapter == self::INTERNAL_ADAPTER) {
            if (is_array($data)) {
                $adapter = 'Array';
            } else if ($data instanceof Zend_Db_Table_Select) {
                $adapter = 'DbTableSelect';
            } else if ($data instanceof Zend_Db_Select) {
                $adapter = 'DbSelect';
            } else if ($data instanceof Iterator) {
                $adapter = 'Iterator';
            } else if (is_integer($data)) {
                $adapter = 'Null';
            } else {
                $type = (is_object($data)) ? get_class($data) : gettype($data);
                
                /**
                 * @see Zend_Paginator_Exception
                 */
                require_once 'Zend/Paginator/Exception.php';
                
                throw new Zend_Paginator_Exception('No adapter for type ' . $type);
            }
        }
        
        $pluginLoader = self::getAdapterLoader();
        
        if (null !== $prefixPaths) {
            foreach ($prefixPaths as $prefix => $path) {
                $pluginLoader->addPrefixPath($prefix, $path);
            }
        }
        
        $adapterClassName = $pluginLoader->load($adapter);
        
        return new self(new $adapterClassName($data));
    }

    /**
     * Returns the adapter loader.  If it doesn't exist it's created.
     *
     * @return Zend_Loader_PluginLoader
     */
    public static function getAdapterLoader()
    {
        if (self::$_adapterLoader === null) {
            self::$_adapterLoader = new Zend_Loader_PluginLoader(
                array('Zend_Paginator_Adapter' => 'Zend/Paginator/Adapter')
            );
        }
        
        return self::$_adapterLoader;
    }


今回はこんな感じでした。