Rinda::TupleSpace を公開したくない時に不都合が

I like Ruby. - 8章 Rinda

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 を渡すというのもできるけどいちいちそれをやるのは面倒だし。