「例えば、PHPを使う」は終わりました。長年のご愛顧ありがとうございました。

ほんとにPHP使い始めたら止めようと初めたときから思ってたのでここでの更新やめます。
(あとはてなダイアリーEUC-JPとかだったりするので)
移転先はとくにありません。


散文的なものは主に以下を更新してるようです。
- https://gist.github.com/sasezaki
- jottit

悪用厳禁! ZF2のPluginClassLoader::addStaticMap

こんな場末の日記をご覧の方は、ZF2のプラグイン機構では、クラス定義の読み込みをPluginClassLoaderが、インスタンス化をPluginBrokerが行うということはご存知ですよね!え、ご存知ない?Wikipedia創設者ZFリードデペロッパーMatthew Weier O'Phinneyからのメッセージをお読みください。

Introducing the ZF2 Plugin Broker
http://weierophinney.net/matthew/archives/248-Introducing-the-ZF2-Plugin-Broker.html


で、LazyLoadingをする場合は、PlginBrokerの拡張PluginSpecBrokerというのが存在します。
(Controller\Actionでは、Loader\PluginSpecBrokerの拡張のController\Action\HelperBrokerを
ヘルパー読み込みの際に使用。View\PhpRendererでは、Loader\PluginBrokerの拡張のView\HelperBrokerを使用)


ということで、プラグイン機構を自前のライブラリで使う場合に以下のように使う場合に以下のような構造を考えるわけですが、

<?php
namespace Diggin\Service\ShortUrl;

class ShortUrl
{
    protected $shortUrlBroker;
   
    // host名=$plugin名としてpreg_replace_callbackに渡すなどする包括的なフィルタ用
    public function unshorten($plugin, $shortenedUrl)
    {       
        if ($shortener = $this->getShortUrlBroker()->load($plugin)) {
                
            if ($shortener instanceof \Zend_Service_ShortUrl_Shortener) {
                return $shortener->unshorten($shortenUrl);
            } else if ($shortener instanceof \Services_ShortUrl) {
            }

            return $shortenedUrl;
        }

        return $shortenedUrl;
    }   
        
    public function getShortUrlBroker() 
    {   
        if (!$this->shortUrlBroker instanceof ShortUrlBroker) {
            $this->shortUrlBroker = new ShortUrlBroker;
        }
        return $this->shortUrlBroker;
    }

}
<?php
namespace Diggin\Service\ShortUrl;
use Zend\Loader\PluginSpecBroker;

class ShortUrlBroker extends PluginSpecBroker
{
    protected $defaultClassLoader = 'Diggin\Service\ShortUrl\ShortUrlClassLoader';
}
<?php
namespace Diggin\Service\ShortUrl;
use Zend\Loader\PluginClassLoader;

class ShortUrlClassLoader extends PluginClassLoader
{
    protected $plugins = array(
        'tinyurl' => 'Zend_Service_ShortUrl_TinyUrlCom',
        'isgd' => 'Zend_Service_ShortUrl_IsGd',
        'metamark' => 'Zend_Service_ShortUrl_MetamarkNet',
    );
}


ここで、メンドくさがって、BrokerにvalidatePlugin()の個別処理を書かないと、以下のような呼び出しをしたときに

<?php
set_include_path(__DIR__.'/library/'. PATH_SEPARATOR .get_include_path());
require_once 'Zend/Loader/StandardAutoloader.php';
$loader = new Zend\Loader\StandardAutoloader;
//$loader->registerNamespace('Diggin');
$loader->setFallbackAutoloader(true);
$loader->register();

//bootstrapなどで
Zend\Loader\PluginClassLoader::addStaticMap(array('tinyurl' => 'Zend\Version'));

//bootstrapなででtypoした場合
Diggin\Service\ShortUrl\ShortUrlClassLoader::addStaticMap(array('tonyurl' => 'User\ShortUrl\Type'));

use Diggin\Service\ShortUrl\ShortUrl;
$shorturl = new ShortUrl;
var_dump($shorturl->getShortUrlBroker()->load('tinyurl')); //Zend\Version

という感じで処理がインスタンス生成後もヘルパーコールの場合処理がそのまま起動されちゃいます。なので、読み込み後インスタンスが適切か確認するvalidatePluginはちゃんと書かないとだめですね。

<?php
namespace Diggin\Service\ShortUrl;
use Zend\Loader\PluginSpecBroker;

class ShortUrlBroker extends PluginSpecBroker
{
    protected $defaultClassLoader = 'Diggin\Service\ShortUrl\ShortUrlClassLoader';

    protected function validatePlugin($plugin)
    {
        if (!$plugin instanceof \Zend_Service_ShortUrl_Shortener) {
            throw new Exception\RuntimeException('Serializer adapters must implement Zend\Serializer\Adapter');
        }
        return true;
    }
}

まあ、この自前コンポネーント設計どうなのよって話もありますが。

AjaxなURL(#!)を変換する奴を書かれたものをPHP用にポーティング

http://github.com/sasezaki/Zend_Uri_HttpCrawlableHash

@see http://code.google.com/intl/ja/web/ajaxcrawling/docs/specification.html
@see http://subtech.g.hatena.ne.jp/mala/20101018/1287419036

とりあえずテストコードに書かれた分はパスしてますって程度ですね。

<?php
require_once 'Diggin/Uri/HttpCrawlableHashTest.php';

$uri = Zend_Uri::factory("http://twitter.com/#!/wozozo", 'Diggin_Uri_HttpCrawlableHash');

// 変換したURIオブジェクトが欲しいとき(cloneして返します)
echo $uri->getFragmentToQuery(); // http://twitter.com/?_escaped_fragment_=/wozozo

// uriオブジェクトを変換して保持する場合
$uri->filterFragmentToQuery();
echo $uri; // http://twitter.com/?_escaped_fragment_=/wozozo

// 常に変換を掛けたい場合
//(zf2だとスタティックコンフィグじゃなくなるんで結構いまさらできても意味ない)
Zend_Uri::setConfig(array('convert_always' => 'query'));
$uri2 = Zend_Uri::factory("http://twitter.com/#!/gusagi", 'Diggin_Uri_HttpCrawlableHash');
echo $uri2; // http://twitter.com/?_escaped_fragment_=/gusagi


Zend_Uri、コンストラクタは置き換えられませんが、factoryでのクラスは置き換えられます。
(Zend Framework 1.10.5以上)
http://framework.zend.com/issues/browse/ZF-9925
http://framework.zend.com/manual/ja/zend.uri.chapter.html (Creating a New Custom-Class URI)

Zend_Http_UserAgentでの個別デバイスクラスの記述

Zend Framework1.11では、Zend_Http_UserAgentというブラウザデバイスの各種情報取得判定のためのコンポーネントが登場しますが、現状デフォルトで用意されているデバイスクラス(Zend_Http_UserAgent_Deviceインターフェイスの実装)以外のクラスを用意したい場合の利用メモです。ここでは、ガラパゴスというiPhoneとかBlackべりーとかのとはちょっと世界の世間とは隔離されたデバイスに関して処理したい場合のクラスを用意してみます。


サーバー情報は、(ZFの他のコンポーネント同様に)Zend_Http_UserAgentのインスタンス生成時でのコンフィグ用引数にあればそれを使用し、ない場合は$_SERVERグローバル変数です。


バイス取得情報などは一旦storageに格納したものを使い回すようです。デフォルトはSessionです。

ユーザ定義のクラスを利用するにはいくつか手段があるっぽいですが、

$config['mobile']['device']['classname'] = 'Test_Galapagos';

のように設定すると単純にロードされます。

<?php
class BarContoller extends Zend_Contoller_Action
{
    public function fooAction()
    {
        $this->getHelper('ViewRenderer')->setNoRender(true);

        //デバッグ用設定
        $config['server'] = $this->getRequest()->getServer();
        $config['server']['http_host'] = '192.168.0.1';
        $config['server']['http_user_agent'] = 'willcom';


        $config['storage']['adapter'] = 'NonPersistent';

        $config['mobile']['device']['classname'] = 'Test_Galapagos';

        $useragent = new Zend_Http_UserAgent($config);
        
        if ($useragent->getDevice()->getType() == 'galapagos') {
            echo '192.168.0.1はわたしが所有しているIPアドレスです';
        } else {
            echo 'sorry, Japanese Only';
        }  
        
    }   
        
}

class Test_Galapagos extends Zend_Http_UserAgent_AbstractDevice
{   
    public static function match($userAgent, $server)
    {   
        if ('192.168.0.1' === $server['http_host'] && 
            preg_match('/^willcom/', $userAgent) ) {
            return true;
        }
        
        return false;
    }

    public function getType()
    {
        return 'galapagos';
    }
}


ユーザ定義クラスでは上記matchメソッドでの判定によってそのクラスをデバイスクラスとして利用するか決定されます。

Zend Framework 2 - SignalSlotの利用メモ

Zend Framwork2で使われそうなSignalSlotの利用メモ
(元々はPhly_PubSubとしてマシューが開発を進めてたもの)。
もうZend\Sessionでは使われてますけど。


利用したZF2のソースは
http://github.com/zendframework/zf2からpullしたものです。
(今InvalidArgumentExceptionのときrequireがないとかでるけど多分気のせい。きっと)


その1 単純なロガーなどのオンオフに利用できそうなあれ

emitの第一引数には"ポイント名"となる信号キー文字列を設定します

<?php
set_include_path(__DIR__.'/library/');
require_once 'Zend/Loader/Autoloader.php';
Zend\Loader\Autoloader::getInstance();

use Zend\SignalSlot\GlobalSignals;

class TestClass
{  
    public function request()
    {
        GlobalSignals::emit('before', 'http://test');
        echo 'request!!', PHP_EOL;
        GlobalSignals::emit('after', 'http://test', 'world');
    }
}

function setUp()
{  
    global $argv;
    if (isset($argv[1]) && $argv[1] == 'dev') {
        //logger
        GlobalSignals::connect('before', function() {echo 'hello', PHP_EOL;});
        GlobalSignals::connect('after', function($url, $response) {echo $url, '--', $response,PHP_EOL;});
    }
}

// your action
setUp();
$test = new TestClass;
$test->request();


実行

$ php sample1.php
request!!
$ php sample1.php dev
hello
request!!
http://test--world


その2 同じ信号キー文字列に登録したものでもdetachが可能

<?php
function setUp()
{       
    global $argv;
    if (isset($argv[1]) && strtolower($argv[1]) == 'dev') {
        //logger
        $before1 = GlobalSignals::connect('before', function() {echo 'hello', PHP_EOL;});
        $before2 = GlobalSignals::connect('before', function($url) {echo $url, PHP_EOL;});
    
        if ($argv[1] === 'DEV') {
            GlobalSignals::detach($before1);
        }
    }
}


実行

$ php sample2.php dev
hello
http://test
request!!
$ php sample2.php DEV
http://test
request!!


その3 emitUntilを利用した場合、特定の条件にマッチしたとき(そしてtrueを返す)ときまで順にコネクトの内容を実行可能
実行順序はFIFO

<?php
class TestClass2
{  
    public function request2($uri = null)
    {
        $uri = 'http://test';
        GlobalSignals::emitUntil(function($value) {var_dump($value); return (boolean)$value;}, 'before', $uri);
        echo 'request!!', PHP_EOL;
    }
}   
    
function setUp()
{   
    $before1 = GlobalSignals::connect('before', function($url) {return ($url == 'http://te') ? true : null;});
    $before1 = GlobalSignals::connect('before', function($url) {return ($url == 'http://tes') ? true : 0;});
    $before1 = GlobalSignals::connect('before', function($url) {return ($url == 'http://test') ? true : false;});
    $before1 = GlobalSignals::connect('before', function($url) {return ($url == 'http://test') ? true: false;});
    $before1 = GlobalSignals::connect('before', function($url) {return ($url == 'http://tests_') ? true: false;});
}

// your action
setUp();
$test = new TestClass2;
$test->request2();


実行

$  php sample3.php
NULL
int(0)
bool(true)
request!!

PHPカンファレンス2010 テックデイ

http://phpcon.php.gr.jp/2010/
http://gihyo.jp/news/report/01/phpcon2010/0002


参加しました。僭越ながら壇上にも上がらせていただきました。

フレームワークアップーデートというテーマで話を受けた際に、「基本方針は立ち上げ時から変わってないから、追加かバグ改修しかないです(キリッ!」てことで何を話すべきか、またPHPカンファレンスに来られる多様な方々にZFについての概観を伝えられるか非常に悩んでました。


ですけど、やはり他のMVCとしてのフレームワークと比較される形での発表となるであろうから、なぜZFがフレームワークと冠しているのかてことを伝えつつ、利用するユーザはどうアクションを起こすべきかを伝えることを主題においてます。ZFの立ち上げ参加/zeta/PHPUnitの、Sebastian Bergmann最近のスライドで疎結合に考えろよと改めて話しておりまして、今のZFリードのマシューは、Symfony Liveに参加したさいに、symfonyもez componentも他のライブラリもそれぞれ使えば良いだろ常識的に考えて。。とも話してる時勢なので、どのフレームが良いかとかは割とFUDPHP全体における協調としての側面から見るために、ライブラリーとして何が加わった(加わる)とコンポーネント名を並べつつ、Zend_Serializarのアダプターを引き合いに柔軟性を強調したかったのですが、あまり魅力は伝えられてなかったかもしれないですね。。今回キャッチーさはないものとなってしまい、受けは良くなかったようで、、、、落ちこんだりもしたけれど、私は元気です。


発表時のスライドは以下です。


ZF2について聞かれることもあったんですが、ZF2については11月のZendConでマシューからまとまった発表があるでしょうから、話すのは時期早々かなと思います。(MLの方では色々話が進んでますし、いくつか新規のも出てますが)
http://zendcon.com/tracks?tid=1698#session-15115
マシューはAOPな方向に持っていきたいようですが、今回のPHPカンファレンス全体の感慨も含めるとPHPとしてのフレームワークとして必然的なものか、個人的には考えがまとまってません。各フレームワークは、ちょっとオーバーアーキテクチャな方向に進みそうなんですが、私は経緯的な事情があり結果的に中庸になった今のZFが好みですね。


AgaviHiPHoP対応はとても魅力的です。HiPHoP対応は、facebookの方の話を受けるとfunction_existsを除かなけばいけないのが割とウゲゲて感じです。例えば今のZend_Http_Clientのコードには、、、

<?php
        // If mbstring overloads substr and strlen functions, we have to
        // override it's internal encoding
        if (function_exists('mb_internal_encoding') &&
           ((int) ini_get('mbstring.func_overload')) & 2) {

            $mbIntEnc = mb_internal_encoding();
            mb_internal_encoding('ASCII');
        }


P1040084


PHPの中の人によるパネルディスカッション」のときは、Rasmusに質問が集中する形でしたが、mbstringの方々にも聞けたら良かったかなーと思います。パネルディスカッションは時間短すぎでしたけれども。


今回は懇親会にも参加しました。
P1040092
14時のときに懇親会LTの応募の張り紙はriafさんのしかなくて、
他いないならってことで急いでスライド作って話ました。
http://handsout.jp/slide/3075

てか、皆さんちゃんと懇親会LT用仕込んでたんじゃないですか。。
(去年の即興ぽかったのに。。
http://gihyo.jp/news/report/01/phpcon2009/0002?page=5##vae1b14f)


後方付けぐらいしか手伝えませんでしたが、
来年はスタッフとして協力できたらなと思います。
発表者・参加者・スタッフの方々お疲れ様でした。