about: S2PHP5
about: Io Language
2008/09/07
kunitさんと飲み
SeasarConferenceでしたが、ほぼ午前中の一コマとLTの途中からしかでてません。すいません。
んで、夜になってから kunit さんとお話してきた。
話してきた内容ってのは、ほぼ僕が日頃気になっていることでして。
順不同で抜粋すると
- 設計勉強会について
- 設計勉強会いきたかったー
- てか、設計勉強会なのに、実装っぽい話題がトップはどうよ?
- しかし、PHPで設計勉強会というトピックがでてきたのは凄いこと
- 2回め以降にも期待
- もくもく会
- もくもく会っていいっすね
- やっぱり少しでも集中する時間をどこかにあるといい
- わざわざもくもく会に参加するよりも、もくもくするだけなので、スタバでもいい。
- RESTについて
- RESTfulな設計って、クライアント側からみると、やっぱサーバは M(MVC的に考えて) だよね?んで、そこのフレームワークって煩雑すぎじゃね?
- REST的にリソースの国際化の概念てあるんすか?(http://jp.hoge.com/ と http://hoge.com/jp/の違いってどうなる?)
- RESTfulなフレームワークってどんなのっすか?
- PHPはステートフルにするほうが難しいじゃん
- symfonyについて
- やっぱPropelてアレですよねぇ
- sfBrowserはよく出来てる仕組みだと思うけど、やっぱ Unit なんですよね...
- selenuimも結局ブラウザな...
- S2Dao の Lite 実装な話題とか(これは twitter だっけ?) の続き
- DBレイヤーとかDBアクセスとかについて
- propel てやっぱり、なんとなく古いよーな(やっぱり torque なんだよね)
- どう抽象化する?
- 抽象化とパフォーマンスはホントにトレードオフか
- drupal は、ほぼ全てが関数ベースだけど、ネーミングルールだけで上手いことモジュール分けができてる。パフォーマンスはそんなに悪くないんす
- でも、ま、ふつーはクラスベースですよね。
- RDB(というかTable)に国際化の概念てどうする(ja/enは分けてない?)
- テストについて
- テストどうしてます?
- AcceptanceTest がしやすい仕組みのフレームワークとか
- お客さんと意識合わせができて、テストもそのままできると便利なのは分かってるけど、ツール無いっす
- やっぱExcelで管理できると便利よねー(国内は)
- S2Buri とか JBoss Rule な話とか
- ほか
- コンテナ世代と密結合世代
- AOPとかフレームワークを(作ったりとかして)知ると別の視点が見えてくる
- ぶっちゃけ Maple4 はいつでるんすか?
- 何かのニーズがあるけど、何かとニッチな事について
- ビューとかレイヤーとかブロックみたいのとか
- 5.3はホントに5.3ていうバージョンでいいのか(笑)
- 色々雑談とか
ってことで、細かい部分の内容は忘れた。(しかも、上のは主がどっちかわかりずらいし...)
でも、PHP っぽい話とは少し違って、設計とかその辺の情報を少し交換できたのはよかった。
ここでゲットした情報は少しずつ展開していきたいと思います。為になります。
でも、やっぱり僕はPropelが苦手です
2008/09/04
Symfony で ActionScript(AMF) と通信する
PHP と ActionScript が通信するためのライブラリとして AMFPHP とかが有名ですが、名前空間とかライセンスとか色々な部分が気になるので、SabreAMF を Symfony に乗っけて動かしてみるテスト
ちなみに、既に sfAMFPlugin っていうプラギンも同様のアプローチなのですが、こいつだと README に書いてあるコードが何故か動かないです。
↓こんなの
class amfAMFAction extends sfAction { public function execute($request) { $this->setLayout(false); $gateway = new sfAmfGateway; $response = sfContext::getInstance()->getResponse(); $response->setContent($gateway->service()); return sfView::NONE; } }
上記コードだと、SabreAMF_Server の __construct 中にある
public function __construct() { $data = file_get_contents('php://input'); if (!$data) throw new Exception('No valid AMF request received'); : : :
のコードにて、if(!$data) の評価に失敗して例外となるようです。
詳しい原因は掴んでいないですが、どうも ob_start() が関係している様子(素で通信したときと、上記コードで動かしたときとだと、content-length が結構違うってのも気になりますが...)
function hoge(){ $data = file_get_contents('php://input'); if(!$data) { echo 'hello world'; } else { echo 'no valid'; } } // ob_start(); ob_start(''); hoge(); $result = ob_get_contents(); ob_clean(); assert($result == 'hello world');
タイミングなのかなんなのかわからないですが、ob_start() と ob_start('') の動きが微妙に違うらしい
(てか、似たようなのがあった - IE6でダウンロード - ekurodaの日記)
(中略)
ということで、Controller を作って、そこでやってしまうことにした。
例外処理とかも結構ハンドリングできるようになって便利。
class fmServiceController extends sfFrontWebController { public function forward($moduleName, $actionName, $parameters = array()) { : : // 何か色々書く : $actionInstance = $this->getAction($moduleName, $actionName); : : // もう少し書く : $server = new SabreAMF_CallbackServer; $server->onInvokeService = array($actionInstance, 'service'); $server->exec(); } }
んで、Action とか
class amfAction extends fmServiceAction { public function service($service, $method, $data) { : : // 色々やって : return $results; } }
こんな感じ。
routing.ymlとかはこんな感じでいけるかな
amf_request:
url: /:module/gateway.amf
param: {action: amf}
json_request:
url: /:module/:id.json
param: {action: json}
また、ActionScript 自体はこんなもので試してみました。
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import flash.net.NetConnection; import flash.net.Responder; import flash.net.ObjectEncoding; import mx.utils.ObjectUtil; private function init():void { var connection:NetConnection = new NetConnection; connection.objectEncoding = ObjectEncoding.AMF3; connection.addEventListener(NetStatusEvent.NET_STATUS, function(e:NetStatusEvent):void { trace(ObjectUtil.toString(e)); }); connection.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void { trace(ObjectUtil.toString(e)); }); connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, function(e:AsyncErrorEvent):void { trace(ObjectUtil.toString(e)); }); connection.connect('http://test.server/service_dev.php/hoge/gateway.amf'); connection.call('HelloWorld.getData', new Responder(onResult, onFault)); connection.close(); } private function onResult(result:Object):void { dataGrid.dataProvider = new ArrayCollection; for(var i:uint = 0; i < result.length; ++i){ dataGrid.dataProvider.addItem({ 'AAA': result[i].AAA, 'BBB': result[i].BBB, 'CCC': result[i].CCC }); } } private function onFault(fault:Object):void { trace(ObjectUtil.toString(fault)); } ]]> </mx:Script> <mx:Panel layout="absolute"> <mx:DataGrid id="dataGrid"> <mx:columns> <mx:DataGridColumn headerText="aaa" dataField="AAA"/> <mx:DataGridColumn headerText="bbb" dataField="BBB"/> <mx:DataGridColumn headerText="ccc" dataField="CCC"/> </mx:columns> </mx:DataGrid> </mx:Panel> </mx:Application>
とりあえず、ob_start 中(?)の php://input 値が取得できるようになるまで、これで進めることにする。
2008/08/28
syslog にログを叩き込む
phpのsyslog関数だと、リモートのsyslogサーバに書き込めない(?)ので、こんな感じで逃げるテスト
class LoggerSyslog { private $ident; private $opt; private $facility; private $ip = '127.0.0.1'; private $port = 514; public function __construct($ident, $opt, $facility){ $this->ident = $ident; $this->opt = $opt; $this->facility = $facility; } public function setIp($ip){ $this->ip = $ip; } public function setPort($port){ $this->port = $port; } public function write($message, $priority){ $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); socket_set_nonblock($socket); if(socket_connect($socket, $this->ip, $this->port)){ //$data = '<' . ($this->facility | $priority) . '>' . $this->ident . ': ' . $message; $data = '<' . self::makePriority($this->facility, $priority) . '>' . $this->ident . ': ' . $message; socket_write($socket, $data, strlen($data)); } socket_close($socket); } private static function makePriority($facility, $priority){ return ($facility & SyslogFacility::LOG_FACMASK) | $priority; } }
んで、facility は何かってーと、これ
interface SyslogFacility { // kernel messages const LOG_KERN = 0; // random user-level messages(1 << 3) const LOG_USER = 8; // mail system(2<<3) const LOG_MAIL = 16; // system daemons(3<<3) const LOG_DAEMON = 24; // security/authorization(4<<3) const LOG_AUTH = 32; // internal syslogd use(5<<3) const LOG_SYSLOG = 40; // line printer subsystem(6<<3) const LOG_LPR = 48; // network news subsystem(7<<3) const LOG_NEWS = 56; // UUCP subsystem(8<<3) const LOG_UUCP = 64; // clock daemon(15<<3) const LOG_CRON = 120; // reserved for local use(16<<3) const LOG_LOCAL0 = 128; // reserved for local use(17<<3) const LOG_LOCAL1 = 136; // reserved for local use(18<<3) const LOG_LOCAL2 = 144; // reserved for local use(19<<3) const LOG_LOCAL3 = 152; // reserved for local use(20<<3) const LOG_LOCAL4 = 160; // reserved for local use(21<<3) const LOG_LOCAL5 = 168; // reserved for local use(22<<3) const LOG_LOCAL6 = 176; // reserved for local use(23<<3) const LOG_LOCAL7 = 184; // mask to extract facility const LOG_FACMASK = 0x03F8; }
同様に、priority はこんなの
interface SyslogPriority { // system is unusable const LOG_EMERG = 0; // action must be taken immediately const LOG_ALERT = 1; // critical conditions const LOG_CRIT = 2; // error conditions const LOG_ERR = 3; // warning conditions const LOG_WARNING = 4; // normal but significant condition const LOG_NOTICE = 5; // informational const LOG_INFO = 6; // debug-level messages const LOG_DEBUG = 7; // mask to extract priority const LOG_PRIMASK = 0x0007; }
これだけで使うのなら、こうなる
$syslog = new LoggerSyslog('syslogtest', null, SyslogFacility::LOG_USER); $syslog->write('hoge foo bar', SyslogPriority::LOG_INFO);
もし、Appenderが必要なLoggerの場合、こんなAppenderを用意すればいいかと
class SyslogAppender implements Appender { private $ident; private $opt; private $facility; private $ip = '127.0.0.1'; private $port = 514; public function __construct($ident, $opt, $facility){ $this->ident = $ident; $this->opt = $opt; $this->facility = $facility; } public function setIp($ip){ $this->ip = $ip; } public function setPort($port){ $this->port = $port; } public function append(LoggingEvent $event){ $level = $event->getLevel(); $priority = SyslogPriority::LOG_PRIMASK; switch($level->toInt()){ case LogLevel::ALL_INT: $priority = SyslogPriority::LOG_EMERG; break; case LogLevel::DEBUG_INT: $priority = SyslogPriority::LOG_DEBUG; break; case LogLevel::INFO_INT: $priority = SyslogPriority::LOG_DEBUG; break; case LogLevel::WARN_INT: $priority = SyslogPriority::LOG_WARNING; break; case LogLevel::ERROR_INT: $priority = SyslogPriority::LOG_ERR; break; case LogLevel::FATAL_INT: $priority = SyslogPriority::LOG_CRIT; break; case LogLevel::OFF_INT: default: return; } $this->write($event->getMessage(), $priority); } public function write($message, $priority){ $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); socket_set_nonblock($socket); if(socket_connect($socket, $this->ip, $this->port)){ $data = ''; $data .= '<' . self::makePriority($this->facility, $priority) . '>'; $data .= $this->ident . ': ' . $message; socket_write($socket, $data, strlen($data)); } socket_close($socket); } private static function makePriority($facility, $priority){ return ($facility & SyslogFacility::LOG_FACMASK) | $priority; } }
このケースだとこんな風になる($identはlogger::nameでもいいのかも)
$logger = new Logger; $logger->addAppender(new SyslogAppender('logsample', 0, SyslogFacility::LOG_USER)); $logger->info('%s %s %d', 'hello', 'world', 777);
やっぱログは、ログの集計とか面倒だからどこかのリモートログサーバに書き込んで、そこで一元管理したいよね。っていうお話し。
syslog-ngなんかを立てて、そこに叩き込めばなんとかならないかな?(TCPもあることだし)
2008/08/14
Testing_FIT を試す
Acceptance Testの為にTesting_FITを使ってみた。
UnitTestのツールは沢山あるけど、AcceptanceTestという形で探すと、なかなか少ないもんです。
少し前の記事だと、GANCHIKU.com » Testing_FITを使ってみる。があったけど、少し状況が変わっているみたいなので、それまでのログ
インストール
まだ、stable リリースが行われていないので、beta 版のインストールを行うことにする
yusuke.hata.local: ~> sudo pear install channel://pear.php.net/Testing_FIT-0.2.2 downloading Testing_FIT-0.2.2.tgz ... Starting to download Testing_FIT-0.2.2.tgz (29,584 bytes) .........done: 29,584 bytes install ok: channel://pear.php.net/Testing_FIT-0.2.2
ディレクトリ構成
fit/
+ fixture/
+ CalculateDiscount.php
+ spec/
+ CalculateDiscount.html
+ impl/
+ Discount.php
+ SampleDiscount.php
HTML用意
テストをこなす上で、その仕様が書かれたHTMLを用意します。これは僕等開発者が用意するんではなく、お客さんとかに用意してもらうらしい。
<!DOCTYPE html public "-//w3c//dtd html 4.0 transitional//en"> <html> <head> <title>TestCase1</title> </head> <body> <table> <tr> <td colspan="2">CalculateDiscount</td> </tr> <tr> <td>amount</td> <td>discount()</td> </tr> <tr> <td>0.00</td> <td>0.00</td> </tr> <tr> <td>100.00</td> <td>0.00</td> </tr> <tr> <td>999.00</td> <td>0.00</td> </tr> <tr> <td>1000.00</td> <td>0.00</td> </tr> <tr> <td>1010.00</td> <td>50.50</td> </tr> <tr> <td>1100.00</td> <td>55.00</td> </tr> <tr> <td>1200.00</td> <td>60.00</td> </tr> <tr> <td>2000.00</td> <td>100.00</td> </tr> </table> </body> </html>
<table> タグの一行めに書かれた(上の例だとCalculateDiscount) Fixture が、実行される Fixture というネーミングルールになる
つまり、CalculateDiscount という Fixture クラスを作成しなければならない
また、データ項目の宣言は Fixture の次の行に行う(ここでは、amount と discount)
<th> タグで宣言せずに、<td> タグとして宣言すること
Fixture 作成
実際のテストを行うFIT。public でプロパティを設定しているけど、ここには、HTML上のデータがマッピングされる
<?php class CalculateDiscount extends Testing_FIT_Fixture_Column { /** * @var string */ public $amount; private $discount; public function __construct(){ parent::__construct(); $this->discount = new SampleDiscount; } /** * @return string */ public function discount(){ return $this->discount->getDiscount($this->amount); } }
今回は、SampleDiscount の実装クラスを利用してテストを行う
html 上 <td> で書かれた項目名と同じプロパティを Fixtrue クラス内に宣言し、
プロパティドキュメントに @var 宣言を行って、型のタイプを記述する
また、項目名の最後に ( ) と記述されているものは、処理メソッドになるので、public のメソッドとして宣言し、値を返却する
メソッドドキュメントとして、@return 宣言を行い、型のタイプを記述する
実装クラス定義
こんなのを用意した
<?php interface Discount { public function getDiscount($amount = '0.00'); }
<?php class SampleDiscount implements Discount { public function getDiscount($amount = '0.00'){ return null; } }
runner作成
ちょっと汚いが、こんな感じで runner を用意
<?php ini_set('display_errors', 0); require_once 'Testing/FIT/Loader.php'; Testing_FIT_Loader::loadClass('Testing_FIT_Fixture', 'Testing_FIT_Runner'); Testing_FIT_Loader::loadClass('Testing_FIT_Fixture_Column'); Testing_FIT_Loader::addClassDir(dirname(__FILE__) . '/impl'); Testing_FIT_Loader::addClassDir(dirname(__FILE__) . '/fixture'); Testing_FIT_Loader::loadClass('CalculateDiscount', 'Discount', 'SampleDiscount'); $runner = new Testing_FIT_Runner; $iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(dirname(__FILE__) . '/spec')); foreach($iter as $file){ if($file->isDir()){ continue; } $path = $file->getPathName(); $runner->run($path, '-'); }
runメソッドの第二引数に '-' と指定しているのは、output の出力を行わない(?) という指定になる。
実行
このままでは、実行しても失敗となる
Sampleクラスの修正
本来なら、これを修正する時点でおかしい気がするが、まぁ、サンプルなのでクラスの修正を行って動きを確認
<?php class SampleDiscount implements Discount { public function getDiscount($amount = '0.00'){ return '0.00'; } }
これを実行すると、以下のように色付けが変更される
最終的に以下のように修正し、すべて期待通りの動きと確認されたらOK(サンプルなので、実際はちゃんとレビューとかしないといけないけど)
<?php class SampleDiscount implements Discount { private $results = array( '1010.00' => '50.50', '1100.00' => '55.00', '1200.00' => '60.00', '2000.00' => '100.00' ); public function getDiscount($amount = '0.00'){ if(is_null($amount) || !is_string($amount) || !is_numeric($amount)){ return '0.00'; } if(!isset($this->results[$amount])){ return '0.00'; } return $this->results[$amount]; } }
まとめ
FITについては、沢山の資料があるんで省略。
ちなみに、Acceptance Test Driven Developmentなんて言葉もあるようで、AcceptanceTestについてはもう少し知ることが多そう。
ってか、こういったことを便利(簡単)に行えるツールを誰か作って(とかいう
TODO
- wiki 形式とか csv 形式、excel 形式などで使えるようにすると便利かと
- Fixture クラスの再利用ができないので、もう少し利用方法を考える
- 日本語でテストケースを書いてもいいような方法を考える
- コンテナとかと連携できるように考える
wiki形式とかで書けるようになれば、あっち側の学習コストが下がって良いかも。
暇な時に挑戦しよう
- Fit For Developing Software: Framework For Integrated Tests (Robert C. Martin)
-
- 発売元: Prentice Hall
- レーベル: Prentice Hall
- スタジオ: Prentice Hall
- メーカー: Prentice Hall
- 価格: ¥ 6,659
- 発売日: 2005/07/08
- 売上ランキング: 34095
- おすすめ度
2008/08/10
php.ini をもう少し活用する
phpのコンパイルオプションに--disable-allとかして、extensionで管理している人向け。自分の整理メモ
php の configure オプションで、--with-config-file-scan-dir=/etc/php.dと指定していると、/etc/php.d/xml.iniとかの ini ファイルとかを読み込んでくれるので、これつかって少し php.ini を整理します。
例えば、php.ini-recommended には沢山の設定値が記述されていて、正直読みづらいです。
なので、必要なモジュール単位に分けて管理。
mbstringだけでいくとこんな感じ(/etc/php.d/mbstring.ini)
extension=mbstring.so [mbstring] ; language for internal character representation. mbstring.language = Japanese ; internal/script encoding. ; Some encoding cannot work as internal encoding. ; (e.g. SJIS, BIG5, ISO-2022-*) mbstring.internal_encoding = UTF-8
他にも、項目の多い session とか、xdebug なんかもこの方法で管理すると、少し楽ができるかと思います。
(php.iniには、error_reportingとかextension_dirの設定だけにして読むのを少なくする)
ちなみに、--with-config-file-scan-dir=/etc/php.activeとかって指定して、/etc/php.d自体には、各モジュールの ini ファイルを沢山配置し、/etc/php.activeには必要なファイルだけ ln -s で管理するとモジュールの読み込みとかの管理が少し楽になるかも。
というメモ
2008/08/01
就職しました。
2008年8月1日に、都内のベンチャーに就職しました。
長いこと(2年くらい ?) PHP から遠ざかっていましたが、また PHP 界隈に復帰します。
よろしくお願いします。
2008/07/31
ニートになりました。
報告(誰に?)が送れましたが
2008年6月30日付けでサラリーマンを辞め、ニートの仲間入りしました。
一人前のニートになるべく頑張ります。よろしくお願いします。
2008/07/26
Log4Io を作ってみた。
Log4Io を作ってみた。
ref http://coderepos.org/share/browser/lang/io/Log4Io/io
使い方は、Log4** とほぼ同じで、こんな感じ
Log4Io logger := Log4Io getLogger("sample") logger addAppender(Log4Io ConsoleAppender with) logger setLevel(Log4Io Level INFO) if(logger isDebugEnabled) then ( logger debug("debug") ) if(logger isInfoEnabled) then ( logger info("info") ) if(logger isWarnEnabled) then ( logger warn("warn") )
これを実行するとこんな感じ。
==> 2008-07-26 20:48:50 JST INFO sample - info ==> 2008-07-26 20:48:50 JST WARN sample - warn
また、Lobby に log4ioLogger を設置しているので、こちらはそのまま使えます。
Log4Io // lobby log4ioLogger log4ioLogger error("hoge") log4ioLogger info("foo") log4ioLogger warn("bar")
こっちは、こんな感じに出力される
==> 2008-07-26 20:48:50 JST ERROR Log4Io - hoge ==> 2008-07-26 20:48:50 JST INFO Log4Io - foo ==> 2008-07-26 20:48:50 JST WARN Log4Io - bar
ほとんど log4js からパクったけど、Log4J 並みの細かいことも作れそう。
ただ、パッケージとかが無い分だけ色々と面倒なのだけど。。。
Io の clone でハマる
clone したときは、init メソッドが呼ばれて、そこで必ず初期化しないとダメだということ
以下のコードだと、オブジェクトの値が clone されるんだね
Hoge := Object clone do( values := List clone with := method(name, c := self clone c values append(name) c ) ) a := Hoge with("a") b := Hoge with("b") a values println ==> list(a, b)
これってつまり js だと、以下のコードと同じってことか。
var Hoge = function (){}; Hoge.prototype = { values: [], with: function(name){ var c = new Hoge c.values.push(name) return c } }; var a = Hoge.prototype.with("a") var b = Hoge.prototype.with("b") print(a.values) ==> (a, b)
ということで、Io のコード及び、js のコードを直すなら、こんな感じになる。
# って基礎中の基礎か
Io
Hoge := Object clone do( init := method( self values := List clone ) with := method(name, c := self clone c values append(name) c ) ) a := Hoge with("a") b := Hoge with("b") a values println ==> a b values println ==> b
js
var Hoge = function (){return this.init.apply(this)}; Hoge.prototype = { init: function (){ this.values = [] }, with: function(name){ var c = new Hoge c.values.push(name) return c } }; var a = Hoge.prototype.with("a") var b = Hoge.prototype.with("b") alert(a.values) ==> a alert(b.values) ==> b
慣れは怖い
2008/07/25
メモリを4GBに増設した
増設したといっても、結構前に増設してたんだけど、メモリが 1GB から 4GB になると結構快適に過ごせてます。
購入は上海問屋 → 【楽天市場】[送料\210〜]【相性保証付】[個数限定特価] ノートパソコン用メモリ DDR2 PC2-5300 2GB:上海問屋セレクト SODIMM DDR2 PC2-5300 2GB [メ1]:上海問屋
安いっすね。もっと早くに買っておけば良かった。


S2Container/S2Dao.PHP5を使っていて困ったこと・質問などがありましたら、S2Container-PHP5のMLにてお願いします。