読者です 読者をやめる 読者になる 読者になる

Diggin_Scraperのフィルタに匿名関数を使いたい

Diggin_Scraperで自前のフィルタを用意する場合、2つの方法があります(たぶん)。

  1. 関数を定義する
  2. Zend_Filter_Interfaceを継承したクラスを定義する

でも例えばちょっとした置換をしたいためだけにクラスを定義するのは面倒だし、ライブラリやフレームワークの中で使うときに関数を定義するのはよろしくないので、現状は結果をforeachで回して一個ずつ処理してるんですが、なんともいけてないです。

これってフィルタに匿名関数使えたら解決しないかなーと思ったので、使えるようにする方法を考えてみました。

Diggin_Scraperでフィルタの呼び出し・実行をしてるのはDiggin_Scraper_Filter::run()で、ここでフィルタ名からどのフィルタを実行するかを決定してるもよう。

<?php
    public static function run($values, $filters, $filterParams = null)
    {
        foreach ($filters as $filter) {

            $return = array();

            if (preg_match('/^[0-9a-zA-Z]/', $filter)) {
                if (function_exists($filter)) {
                    foreach ($values as $value) {
                        $return[] = call_user_func($filter, $value);
                    }
                } elseif (!strstr($filter, '_')) {
                    require_once 'Zend/Filter.php';
                    foreach ($values as $value) {
                        $return[] = Zend_Filter::get($value, $filter);
                    }
                } else {
                    require_once 'Zend/Loader.php';
                    try {
                        Zend_Loader::loadClass($filter);
                    } catch (Zend_Exception $e) {
                        require_once 'Diggin/Scraper/Filter/Exception.php';
                        throw new Diggin_Scraper_Filter_Exception("Unable to load filter '$filter': {$e->getMessage()}");
                    }
                    $filter = new $filter();
                    foreach ($values as $value) {
                        $return[] = $filter->filter($value);
                    }
                }
            } else {
                $prefix = substr($filter, 0, 1);

                //have
                if ($prefix === '*') {
                    require_once 'Diggin/Scraper/Autofilter.php';
                    $filter = substr($filter, 1);
                    $filterds = new Diggin_Scraper_Autofilter(new ArrayIterator($values), $filter, true);
                //not have
                } elseif ($prefix === '!') {
                    $filter = substr($filter, 1);
                    $filterds = new Diggin_Scraper_Autofilter(new ArrayIterator($values), $filter, false);
                } elseif ($prefix === '/' or $prefix === '#') {
                    $filterds = new RegexIterator(new ArrayIterator($values), $filter);
                } else {
                    require_once 'Diggin/Scraper/Filter/Exception.php';
                    throw new Diggin_Scraper_Filter_Exception("Unkown prefix '$prefix' : {$e->getMessage()}");
                }

                foreach($filterds as $filterd) $return[] = $filterd;
            }

            $values = $return;
        }

        return $return;
    }
}

PHPで知っておきたいcreate_function5つのトリビア - komagataによると、create_functionで作った匿名関数はchr(0)(ヌル文字?)で始まる名前で管理されるらしい?ので、一番はじめの正規表現をヌル文字にもマッチするように書き換えてみます。

<?php
if (preg_match('/^[0-9a-zA-Z\0]/', $filter)) {

するとこんな風に書けるようになりました。

<?php
require_once 'Diggin/Scraper.php';

$func = create_function('$url', <<<FUNC
    return preg_replace('/^'.preg_quote('http://www.au.kddi.com', '/').'/', '', \$url);
FUNC
);

// サービスカテゴリのURLを取得
try {
    $url = 'http://www.au.kddi.com/service/index.html';

    $scraper = new Diggin_Scraper();
    $scraper->process('p.contentsBoxTitle a', "url[] => @href, $func")
            ->scrape($url);
    print_r($scraper->results);
} catch (Exception $e) {
    echo $e.PHP_EOL;
}

この例だとあんまりメリット無いですが…。

というわけで、こんなのいかがでしょーか。> sasezakiさん。