Main menu:


 

2010年 9月
« 4月    
 12345
6789101112
13141516171819
20212223242526
27282930  

最近の投稿

最近のコメント

カテゴリー

アーカイブ

 

運営サイト

Twitter

MAの輪

リンク

RSS

ʸۿƱ

Profile

社会人を始めた時にはコンサルタントという名のC/C++プログラマーでした。それから12年、プログラミングから離れて10年近く、似非エンジニアと周りには言われつつ、35歳を目前になんだかやたら燃えてます。最近はRubyを覚えたいなと本を買ったのはいいけど、PHP/JavaScriptの便利さに引きずり込まれ、何もできていない状況、、、

 

MA3

最優秀賞を頂きました!

'PHP'

[CakePHP][ PHP][ メモ]CakePHP学習中メモ 〜 連続でsaveするときの注意

初心者なのでここでもはまりました。

idがキャッシュされている

とあるデータソースからデータを取得してDBに一括で登録したくて、Controllerの中に「batch_update」というメソッド(アクション?というべきか)を作ったのですが、うまくいかない。

ロジックはシンプルで、

  • 外部のデータソースからデータを1件取得
  • そのデータがDBに登録されていないか確認(findBy…メソッドを利用)
  • 既に登録されていたら、そのレコードのidをセットして最新の情報にupdate
  • 登録されていなかったら新たにinsert

updateとinsertの両方とも、Modelのsaveメソッドを呼び出して行うんですが、このsaveメソッドはその中でidの存在確認をしてupdateとinsertを使い分けるそうなので、これでOKかと思っていたら、何回やってもDB上にはレコードが1件しか作られない。

デバッグログのSQLを見ても、最初にinsertしたあと後は全部updateになってしまっている。

findByの使い方が悪いのかと思って調べてみたけど、そうでもない。

しょうがないので、model.phpとかdbo_source.phpあたりをよくよく読んでみたところ、、、

  • saveが呼ばれた後には、そこで作成/更新されたレコードのidがModelの中にキャッシュされていて、
  • そのまま連続でsaveを呼び出すとidが既にセットされているので自動的にupdateになってしまう

saveの前にcreateを呼ぶ

なので、連続でsaveを呼び出すためには、その前にModelのcreateメソッドを呼び出して一度キャッシュをリセットしてあげる必要がある。

・・・

というCakePHP使う人には常識なことなんでしょうけど、これで1時間少々はまってしまったのでメモ。

[プレゼン][ Ext JS][ PHP]PHPカンファレンス2008に参加&LTでプレゼンしました

@7ns.jp ~ PHPカンファレンス2008に参加&LTでプレゼンしました

「PHP meets Ext JS (MA4で受賞する5つの法則)」という題名でライトニングトークでプレゼンさせてもらいました。400人の参加者、壮観でした。

[PHP]PEAR::Package::Net_GeoIP

これまでONGMAPではユーザーのIPアドレスから最寄りの地図を推測する手段として、JavaScript版のGeoIPを利用していたのですが、体感速度的にちょっと遅いということと、レスポンスにバラツキがあることから、PHP/PEAR版に切り替えてみました。

JavaScript版よりは若干面倒ですが、それでも簡単です。

  1. pearコマンドで、Net_GeoIPをインストール(バージョンがまだ1.0.0RC1なので、「pear install -a Net_GeoIP-1.0.0RC1」としないと安定版が無いといって怒られました)
  2. MaxMindのサイトから、無料のデータベース(ファイルです)をダウンロード。バイナリ版とCSV版がありますが、今回は高速でコンパクトと思われるバイナリ版をダウンロード。データは「GeoIP.dat(国だけの判定用)」、「GeoLiteCity.dat(都市や緯度経度を判定)」の2種類あるので、両方ダウンロードして、適当な場所に解凍&配置
  3.  後はサンプルコードを適当に真似して、いっちょあがり、です

GeoLiteCity.datは27MBもあるので、アクセスが集中するとどういう挙動になるのかちょっと怖いですが、体感速度は結構あがったかなと思います。

あと、国の判定が簡単にできるようになったので、英語・日本語の切り替え処理が楽ちんになりました。

[PHP][ メモ]DOMDocument::loadHTMLでの文字化け

ちょっと笑ってしまったのでメモ。

以前も何かやっているときにはまったのが、HTMLファイルのDOMDocument::loadHTML()での読み込み。 PHPのDOMDocumentは現在のところ、日本語についてはUTF-8以外は読めなくて、さらにHEAD部分で↓のようにエンコーディングを指定してあげていないと、読み込んだときに文字化けしてしまいます:

<meta http-equiv=“content-type” content=“text/html; charset=UTF-8″>

で、今日とあるHTMLファイルを読み込もうとしていたら、文字コードはUTF-8でエンコーディングも指定しているのに、なぜか文字化けしてしまう。読み込んでいるHTMLファイルをよく見直してみて、ちょっと手を加えてみたら問題解決!

何をやったかというと、元々のHTMLが

<head>
  <title>バイナリセーフなファイル書き込み処理</title>
  <meta http-equiv=“content-type” content=“text/html; charset=UTF-8″>
</head>

だったのを

<head>
  <meta http-equiv=“content-type” content=“text/html; charset=UTF-8″>
  <title>バイナリセーフなファイル書き込み処理</title>
</head>

と、METAとTITLEの順番の入れ替え。これでTITLE部分が文字化けせずに出力されるようになりました。エンコーディング指定はできるだけ最初の方にもってこないといけない、というわけですね。

[PHP][ 開発日誌]get_headers()とX-JSON

ONGMAPのVersion2をいま作っているんですが、利用しているWeb-APIを久々に見直していたら、とあるWeb-APIの仕様が全く変わっていました(といっても、公開されているAPIではないので、文句は言えないのですが・・・)

で、新しくなったAPIを見てみると、初めて見る形。Prototype.jsあたりで使われているそうなんですが、HTTPヘッダーの中に、「X-JSON」という項目を埋め込んで、データとコンテンツを切り離して送るやり方だそうです。

ただ、ONGMAPでは、一旦サーバーサイドで処理したいので、PHPでそれを処理したい。でも、いつも使っている「file_get_contents()」ではヘッダー部分は取得できないので、調べたところ、get_headers()という関数が使えるらしい。

で、早速使ってみたんですが、get_headersのバグなのか仕様なのか、よく分からない挙動にはまってので、メモしておきます。

get_headersはヘッダーをハッシュに整形して出力してくれるので、X-JSONであれば、

$hd = get_headers($url,1);
$xjson = $hd['X-Json'];

で簡単に取得できると思っていたところ、なぜかうまくいかない。$xjsonを出力しても、尻切れトンボになってしまっていて、後ろ部分のデータがどこかにいってしまっている。

色々と出力を調べたところ、X-JSON部分のデータが複数にぶつ切りされてしまっていた。つまり、

$hd = array(
  ...
  ["X-Json"] => ([...][...]...[...],
  [5] => [...][...]...[...],
  [6]=> [...][...]...[...])
  ...
);

みたいな感じに、なんの脈絡もないフィールドに分断されて格納されてしまっている。これは、X-JSONフィールドが大きすぎたためにget_headersの仕様として分断したのか、 バグなのかよく分からないけど、とにかく分断されているものを結合しないとどうにもならないので、結合(とくに間に何も詰めないで結合しました)

で、その後に、最初と最後にくっついている”(”と”)”を削除して、json_decodeとやったら、無事デコードされてくれました。

[PHP][ 開発日誌]PHPでの型

はまったので、メモ

PHPで文字列と数値を比較すると、文字列が自動的に数値にキャストされてから比較される

そもそも比較するなという突っ込みもありますが 、PHPの本か何かに書いてあったようななかったような。つまり、$aが文字列、$bが数値だとすると、

$a == $b  → (integer)$a == $b

ということなんですね。で、何にはまったかというと、

function foo($a){
  if($a != 0){ 処理 }
  ...
}
$arr = array(0,"hello","world",...);
foreach($arr as $item){
  foo($item);
}

こんな感じの処理。$aに”hello”とか”world”とか入っていた場合、0じゃないので処理をしてもらいたいのが、$a!=0はfalseになってしまって、処理をやってくれない。

何が起きているかというと、

"hello" != 0  → (integer)"hello" != 0 → 0 != 0 → FALSE

という具合。つまり文字列は数値にキャストすると0になってしまう。

(でも、実はそんなに単純じゃなくて、”1cm”とか”4kg”みたいな文字列は、それぞれ数値の1と4に変換される。つまり文字列の最初に数字がある場合はその数字が続くところまでを取り出して数値 に変換して、何も無い場合は0に変換するということですね。)

まだまだ初心者の域をでてないなと痛感した「はまり」でした。

ちなみに上の処理は単純に、値と型の両方を比較する「===」や「!==」を素直に使えば済む問題です。

[PHP][ 開発日誌]AJAXリクエストの戻り値をgzencodeで圧縮、これって当たり前?

ちょっと前にApacheでのJavaScriptファイルの圧縮設定についてのメモを書きましたが、JSファイルだけじゃなくてAJAXリクエストをしたときにサーバーから返される文字列も圧縮できないかと思い、phpの「gzencode」関数を使ってみたら、サクッと動いてくれました(gzencode関数を使うにはzlibライブラリが必要になります)

やり方は、返したい文字列をgzencodeで圧縮するだけなんですが、ヘッダーに圧縮方式を指定してあげないとブラウザ側で解凍できないので、こんな感じになります:

$out = "何かの文字列";
if(strpos($_SERVER['HTTP_ACCEPT_ENCODING'],'gzip')!==false && function_exists('gzencode')){
  header('Content-Encoding:gzip');
  $out = gzencode($out);
}
echo $out;

それにしても、gzencodeでググったのですが、あんまり事例が出てこないですね。当たり前すぎのノウハウ?

[PHP][ 開発日誌]Xdebug on Leopard

Windowsでも利用していたXdebugをLeopardでも使おうと思ってインストールしようとしたところ、ここ数日のハマリ癖のせいか、またまたはまってしまいました・・・。

以前書きましたが、僕のMacBookProでは、ApacheとPHPはLeopardにデフォルトで入っているものを、MySQLはMacportを利用してインストールしました。

そんな環境にXdebugを入れようと思いコマンドラインから、

pecl install -a xdebug

と、やってみたところ、普通に「xdebug.so」が生成されて以下のディレクトリにインストールされていました

/usr/lib/php/extensions/no-debug-non-zts-20060613

そこで、php.iniに以下の3行を追加

extension=xdebug.so
xdebug.profiler_enable=1
xdebug.profiler_output_dir = "/var/log/xdebug"

そのあと、Apacheを再起動して、phpinfoで確認してみるけどxdebugがロードされていない。色々とググって「extension=」を「zend_extension=」としてみたり 、フルパスで指定してみたり試してみたけど、Apacheは起動するのに、xdebugのライブラリはどうにもロードされない(一旦xdebugをあきらめて、ZendDebuggerを入れようとしてみたけど、同様にロードされず)

あんまり気が進まないけど、どうもLeopardデフォルトのPHPがいまいちっぽいので Makeすることにしてみました(デフォルトのPHPは5.2.4。今日現在、最新の安定版は5.2.5なので、これをダウンロード)

色々とconfigが面倒なので、「Mac OS X LeopardでPHP開発環境を整える」を参考に自分の環境に合わせて:

./configure
--prefix=/usr/local/php5
--program-suffix=
--infodir=/usr/local/share/info
--mandir=/usr/local/share/man
--with-config-file-path=/usr/local/lib/php5
--with-config-file-scan-dir=/usr/local/lib/php.d
--with-pear=/usr/local/lib/php5/pear
--sysconfdir=/usr/local/lib/php5/etc
--with-apxs2=/usr/sbin/apxs **
--with-zlib-dir=/opt
--enable-sockets
--enable-exif
--with-mysqli=/opt/local/lib/mysql5/bin/mysql_config
--with-mysql=/usr/local/mysql5 **
--with-xml
--enable-mbstring
--with-libmbfl
--enable-wddx
--enable-zend-multibyte
--enable-discard-path
--enable-cgi
--enable-track-vars
--enable-force-cgi-redirect
--with-gd=/opt/local
--with-jpeg-dir=/opt/local
--with-png-dir=/opt/local
--with-freetype-dir=/opt/local
--with-pdo-mysql=/opt/local/lib/mysql5

(変更したのは、**のところ)

もちろん、案の定エラーでまくり。結果的これらのエラーを一つ一つ解決しようとしてはまったわけですが・・・。

まずは、なんだか色んなライブラリが入ってないみたいなので、上のサイトを参考にしながらMacportsを使って、適当にライブラリを入れてみました(jpegとかgd2とか)。

(自分用メモ:「–with-mysql=」ではmysqlのベースディレクトリを指定する必要があって、そのディレクトリの下には「include/」と「lib/」が必要なんだけど、そんな構成のディレクトリが見つからなかったので、作る必要がありました。なぜか、以前macportでインストールしたmysql5は、「/opt/local/」の下の、「include/mysql5/mysql」と「lib/mysql5/mysql」に散らばっていたので、「/usr/local/」の下にmysql5を作って、そこにそれぞれへのディレクトリへのリンクを張る形でベースディレクトリを作ってみました。「bin」もついでに作りました。)

あと、iconvまわりでもエラーがでるので、それについては「Leopard に PHP をインストール」

ext/iconv/iconv.c の iconv_open と iconv_close を libiconv_open と libiconv_close に書き換える。

を参考にしました。

これでようやくPHPのmake&installが完了したので、Apacheを再起動しようとしたら、今度は新しく作ったlibphp.so がロードできないと言って、Apacheが起動してくれない・・・。

頭に来たので、Apacheもデフォルトのものを捨ててインストールしようと思って、再度Macports。

が、しかし、エラー、エラー、エラー!まったく意味が分からないエラー が発生。

ググったところ、同じ問題に直面したかたのブログを発見→「MacPortsのApache2.2.8」。ここを参考にして、

/opt/local/var/macports/build/_opt_local_(中略)_www_php5/work/build/config_vars.mk

を修正して、再度 Macports(port install apache2)したところ、なんとかインストール完了。

これまでのApacheをkillして、新しいApacheをstartしたところ、よ・う・や・く、xdebugがロードされてた!

ただ、xdebugだけだとプロファイルログが全然読めないので、Windowsで使っていたWinCacheGrindの大元のKCacheGrindを今日散々お世話になったMacportsでインストール(port install kcachegrind)したところ、なんだか関連するライブラリのインストールが始まり、それが延々と続き、1時間以上かかってようやく完了。

xdebug入れるためだけに、環境全て再構築してしまいました ・・・。

ただ、やっぱりxdebugでプロファイル見たおかげで、濡れ雑巾絞るように非効率な部分のチューニングはできました。

[PHP][ メモ]file_get_contents()の設定メモ

メモというか備忘録

とあるサーバー(Solaris10)の設定をやっていたところ、なぜかPHPのfile_get_contents()が利用できない。

多用する関数なのに利用できないって、と調べたところ、php.iniに

allow_url_fopen = On

と設定をしないといけないらしい。デフォルトではオフだった。

さらに、ライブラリのcurl.soもエクステンションとしてphp.iniに記述する必要があった。

extension = 'curl.so'

さらにさらに、libcurl.soも別途インストールする必要があった(これはパッケージを入れるのを失念してただけ)

かなり、焦った・・・

[PHP][ メモ]file_get_contentsでのタイムアウト処理

久々のPHPネタです。

左側のメニューにある天気予報・住所・標高・エリアキーワード、これらの情報はパネルを開いたときか地図を動かしたときに、サーバー側でPHPがそれぞれの情報源に情報を取得しにいきます(キャッシュしているのもあります):

  • 天気予報→Yahoo! Weather / Livedoor Weather Hacks
  • 住所→日本国内は自前のDBで、海外の住所はGeonames
  • 標高→USGS
  • エリアキーワード→Geonames

YahooやGoogleなどは、サーバーが死んでいることは滅多にないのですが、Geonamesあたりはサーバーのレスポンスがなくなっているとき結構があります。

ただ、AJAXの定石?としては、XmlHttpのリクエスト数をできるだけ減らすため、情報源はそれぞれ異なるものの、ばらばらのリクエストとしてではなく1個のリクエストに集約してサーバー側のPHPに渡すようにしています。

PHPスクリプトの中では受け取ったリクエストに取得する情報が4つあったら、例えば順番に、天気予報→住所→標高→キーワードみたいな感じで別々のAPIを叩いていくわけですが、このときに使っている関数が便利な「file_get_contents」関数です。

ただこの場合、どこか1箇所でレスポンスが無いと、そこで処理が滞ってしまい、そのままXmlHttpのリクエストがタイムアウト→全滅みたいな悲しいことになってしまいます(実際にはタイムアウトを30秒にしているので、タイムアウトする前にイライラして、地図を動かして更にイライラするみたいな感じですが・・・)。

ということで、前置きが長くなりましたが、外部のデータソースが死んでいる、もしくはレスポンスが極端に低下している場合、file_get_contents自体をタイムアウトさせることできないかと思いちょっと調べたところ、PHPSPOT開発日誌さんの

PHPでのHTTPアクセスを超簡単に行える「PEAR::HTTP_Request」

という記事があったのですが、インストールするのがちょっと面倒だったので、ほかに無いか探したところ、PHP.NETのここにそれっぽいこと書いてあったので真似してみました。

使い方間違っているかもしれませんが、

ini_set('default_socket_timeout',3);
$res = file_get_contents("http:/.....);

といった感じです。動作確認がちょっとしにくいのですが、なんとなく動いているみたいです(間違っていたら突っ込み歓迎です)。タイムアウトに設定する数字の単位は「秒」です。

よかったら、試してみてください。