Rinda どうなの
永続化と、トランザクション的なものがないとつらい気がする
Rinda::TupleSpace を公開したくない時に不都合が
class Server def initialize @ts = Rinda::TupleSpace.new end def get(name) @ts.take([name]) end end DRb.start_service(nil, Server.new)
きれいなインターフェースのために、上記のようなTupleSpaceを直接公開しないコードを書きたいのだが、接続切れ対策のためTupleSpaceProxyと同じことをしようとすると困る。TupleSpaceProxyは
class TupleSpaceProxy def take(tuple, sec=nil, &block) port = [] @ts.move(DRbObject.new(port), tuple, sec, &block) port[0] end end
このようなことをやっていて、元のTupleSpaceとportが別々のサーバにあるので接続が切れた場合moveが失敗してくれるわけなんだけど、最初のコードのようにローカルでTupleSpaceを保持しているとそうはできない。
module Client def port DRbObject.new([]) end end class Server def initialize @ts = Rinda::TupleSpace.new end def get(client, name) port = client.port @ts.move(port, [name]) port[0] end end
みたいに、takeをつかう時は必ずリモートのオブジェクトを引数に渡すように強制する、というのを考えたんだけど、ちょっと汚い感じ。引数で渡さなくても呼び出し元を特定する方法がありそうなものだけど、よくわからない。良い案はありませんか。
PHPに触れて早3ヶ月
アセンブリに比べたら革新的な書きやすさ、とか思ってたけど、いい加減うんざりしてきたね。要所要所にいらっとくるのだよ。javascriptのようなサーバーサイドらしからぬ不安定感もあるし。だいたい他のメンバーが触れるようにってことでこうなったのに俺しかコード書いてないじゃん。うにゃー
Zend_Filter_Input で filter に複数の field を渡す
- ZendFramework 1.7.5
<?php class My_Filter_Input extends Zend_Filter_Input { // 複数 fields の filter に対応。 // fields が array で指定された時は // filter メソッドに { field名 => value } の連想配列を渡す protected function _filterRule(array $filterRule) { $fields = $filterRule[self::FIELDS]; if (!is_array($fields)) return parent::_filterRule($filterRule); $data = array(); foreach ((array) $filterRule[self::FIELDS] as $field) { if (array_key_exists($field, $this->_data)) { $data[$field] = $this->_data[$field]; } else if (array_key_exists(self::DEFAULT_VALUE, $filterRule)) { if (is_array($filterRule[self::DEFAULT_VALUE])) { $key = array_search($field, (array) $filterRule[self::FIELDS]); if (array_key_exists($key, $filterRule[self::DEFAULT_VALUE])) { $data[$field] = $filterRule[self::DEFAULT_VALUE][$key]; } } else { $data[$field] = $filterRule[self::DEFAULT_VALUE]; } } } $this->_data[$filterRule[self::RULE]] = $filterRule[self::FILTER_CHAIN]->filter($data); } }
こんな感じで
Zend_Filter_Input で重複のチェック
unique なカラムのチェックをバリデート時に行いたい。
Table scheme
CREATE TABLE `page` ( `id` int PRIMARY KEY, `user_id` int, `url` varchar(255), UNIQUE (`user_id`, `url`) );
My/Validate/Unique.php
<?php class My_Validate_Unique extends Zend_Validate_Abstract { const DUPLICATE = 'duplicate'; protected $_messageTemplates = array( self::DUPLICATE => "already exists" ); protected $_messageVariables = array(); private $table; public function __construct($table) { $this->table = $table; } /** * @param $fields array field の連想配列。 * primary key, 重複チェック対象 field ... */ public function isValid($fields) { $table = new $this->table; $select = $table->select(); reset($fields); $select = $this->addWhere($select, key($fields), current($fields), false); // 自身は除外 array_shift($fields); foreach ($fields as $field => $value) { $select = $this->addWhere($select, $field, $value); // 重複チェック対象 $this->_messageVariables[$field] = $field; $this->$field = $value; } if ($row = $table->fetchRow($select)) { $this->_error(self::DUPLICATE); return false; } return true; } private function addWhere($select, $field, $value, $match = true) { if (!is_null($value)) $select = $select->where($field . ($match ? ' = ?' : ' != ?'), $value); return $select; } }
My/Filter/Input.php
<?php class My_Filter_Input extends Zend_Filter_Input { public function __construct($filterRules, $validatorRules, array $data = null, array $options = null) { parent::__construct($filterRules, $validatorRules, $data, $options); $this->addValidatorPrefixPath('My_Validate', 'My/Validate'); $this->setDefaultEscapeFilter(new My_Filter_Ident()); // DB にいれるのでエスケープはしない } public function getMessages() { // <field>-n は <field> とみなす // (fields 指定ありなしを混在させるために) $messages = parent::getMessages(); foreach ($messages as $field => $strs) { if (preg_match('/^(.*)-\d+$/', $field, $matches)) { $originField = $matches[1]; if (array_key_exists($originField, $messages)) $strs = array_merge($message[$originField], $strs); $messages[$originField] = $strs; unset($messages[$field]); } } return $messages; } } // 何もしない filter class My_Filter_Ident implements Zend_Filter_Interface { public function filter($value) { return $value; } }
以上のものを使う。
Filter_Input 設定ファイルは ini だと表現力が足りないし XML だと面倒なので http://wadslab.net/2008/05/zend_config-3/ を使って YAML で。
conf/page_filter.yml
filters: *: StringTrim validators: url: - - StringLength - 1 - 255 # url のエイリアス # fields なしの validator も利用するためにやむなく url-2: - - Unique - Page - fields: - id - user_id - url - messages: - URL "%url%" は既に使用されています。
ActionController でみせているけど rails みたいにモデルでやるほうが楽でいい気がする。
<?php class PageController extends Zend_Controller_Action { const FILTER_INPUT_CONFIG = 'page_filter.yml'; private function getFilterInput() { $config = new My_Config_Yaml(APPLICATION_ROOT . '/conf/' . self::FILTER_INPUT_CONFIG); return new My_Filter_Input($config->filters->toArray(), $config->validators->toArray()); } public function createAction() { $req = $this->getRequest(); $params = $req->getPost(); unset($params['id']); // id をセットされるとチェックを抜けられる $params['user_id'] = $req->getParam('id'); // どこかから $input = $this->getFilterInput(); $input->setData($params); if ($input->isValid()) { ...
うーん。Zend_Form だともっと簡単にできたりするんだろうか。あれもなんかいまいちな雰囲気が。Zend Framework って基本の構成はできてるけど使いやすくするための部分がまだまだという感じがしますねぇ。ライブラリーの疎結合を謳っているのでそこが阻害要因になっているというのもあるかもしれません。
組み込み関数の引数の順序を全く覚えられない
関数名にも一貫性がないけど、頻繁に使うものなら名前くらいは覚えられる。が引数の順序の一貫性のなさにはまいるね。
event を変数として受け渡す
public void AddEventHandler(object target, string eventName) { EventInfo e = target.GetType().GetEvent(eventName); EventHandler handler = (sender, args) => {}; e.AddEventHandler(target, handler); }
リフレクションを使えばとりあえずできるが気持ちは悪い。add/remove を wrap した delegate を渡すというのもできるけどいちいちそれをやるのは面倒だし。