WordPress3.0にして変わったこと

wordpress3.0にアップグレードしたら挙動が変わった部分を発見したのでメモ。

wordpressの場合、category-xx.php (xxはカテゴリID)というテンプレートを作成していると、該当のカテゴリIDを指定して記事一覧画面を出す場合(http://hoge.com/?cat=xx など)に、優先的にIDまで名前に入ったテンプレートを選択してくれていた。

基本的には、3.0でも一緒だが、変わっていたのは、指定されたカテゴリIDに対応する記事が1件もなかった場合。2.9までは、記事がなくてもcategory-xx.phpが呼び出されていたが、3.0からは、index.phpが呼び出されるようになっていた。archive.phpも置いてみたけど、archive.phpではなくindex.phpが呼び出された。

カテゴリID指定のテンプレートはあまり作る機会は無いが、複数の指定カテゴリの記事一覧を表示するとき等に、架空のカテゴリIDを指定して、テンプレート内にて複数カテゴリIDを指定したり、特定カテゴリIDを除外した記事一覧を取得して表示したりしていた。

今後は、index.phpを使うってことで。架空のカテゴリIDを複数使いたい場合は、index.phpの中で、$_GETの中身を見て切り替えることになりそうです。

この挙動は、タグ指定とかでも一緒だと思われます。

ZendFrameworkを使ってBasic認証を行う

ZendFrameworkで構築したサイトの一部にbasic認証をかけたいけど、どうしたらできるか知ってる?と聞かれたのでやってみました。
考えたことなかったですが、.htaccess使うとindex.phpと同じ階層に置くことになって、サイト全体にしかかけれないのかもしれません。試してはないので推測です。

やりたかった内容は、特定のURLにbasic認証をかけて、認証NGな場合は他の画面を表示し、認証OKな場合は、そのまま指定されたURLの画面を表示するというものです。

サンプルソースは、以下になります。
今回は、controller内に全部書きました。

    public function testAction()
    {

        $config = array(
            'accept_schemes'  => 'basic',    // basic認証指定
            'realm'           => 'aaaaa',    // realm(passwd.txtの内容と一致させる必要あり)
            'digest_domains'  => '/login/',  // このURL以下には全てbasic認証要
            'nonce_timeout'   => 3600
        );

        $resolver = new Zend_Auth_Adapter_Http_Resolver_File();
        $resolver->setFile('passwd.txt');  // index.phpがある階層と同じところに置いてます。

        $adapter  = new Zend_Auth_Adapter_Http($config);
        $adapter->setBasicResolver($resolver)
                ->setRequest($this->getRequest())
                ->setResponse($this->getResponse());


        $result = $adapter->authenticate();
        if (!$result->isValid()) {
            // 認証エラーの場合
            return $this->_forward('test1');
        }

        // 認証OK
    }

passwd.txtの中身はこちら

test:aaaaa:hogehoge

左から、アカウント、realm、パスワードです。パスワードは生パスワードでOKです。
realmは、はプログラム内で指定したものと一致する必要があります。
アカウントとrealmが一対となっていて、それに対してパスワードが存在するイメージです。

basic認証の画面は、authenticate()の部分で生成され、Zend_Controller_Response_Httpに設定されています。
authenticate()の部分で実行されるということは、認証画面が表示される際は、必ず “認証エラー” のルートを通ることになります。
なので、このルートを単なるエラールートと捉えない方がいいです。例えば、Exceptionをthrowしたりしたら、必ずthrowされます。

あと、はまった点が1つありました。”認証エラー”のルートに $this->_redirect()を利用すると、期待する動きはしません。
basic認証画面の出力より、リダイレクト処理が優先されてしまい、常にリダイレクトされることになります。
そのため、上記サンプルでは $this->_forward()を利用しています。これなら、認証画面にてキャンセルを押した場合にforward先の画面が出力されます。

Zendのソースを見つつ、認証画面にてキャンセルを押された場合を検出しようとしたんですが、判別の方法がわかりませんでした。
認証画面をキャンセルした際に、サーバ側にリクエストがきてないようだったので、サーバ側で判別するのは無理と判断しました。
今回は、Responseにbasic認証の情報と、forwardの情報を両方設定し、basic認証がキャンセルされたら、forwardが実行される形になりました。

サイトで利用されているツールがわかるfirefoxのアドオン

twitterのタイムラインを見てて発見した、このアドオン。
サイトで使わているツールやCMSが何かを表示してくれます。

僕のサイトを見ると、google analyticsとwordpressのアイコンが出ました。
wordpressでここまでできるんだとか、そういう発見ができそうです。

インストールはこちらからどうぞ。
http://wappalyzer.com/

情報元はこちらです。
http://twitter.com/evian/statuses/15088794173

chromeにも同様のものがあるようです。
アドオンはあまり利用しないので、今まで知りませんでした。便利ですね。

iPadのDJアプリ

iPadの画面の広さがあれば、より本格的なDJアプリが作成されそうだなと思ってたんですが、すでにありました。

スクラッチとかもできているのがすごい。
多少ラグはあるだろうけど、この程度に収まっているのはすごいなと思います。

4つ打ちのDJアプリをやるなら、ピチコンをもっと大きくして操作性を上げて、インタフェースもターンテーブルよりもCDJにして、キューの位置とか決めれるようにして、波形とかだせちゃったらいいだろうなー。

そのうち誰かが作ってくれることに期待。

Zendを使ってZip圧縮

ZendFramework使って、ディレクトリのzipってできますか?
と聞かれたので調べたらでてきた。しかも、filterのところで。

filterは、validateが不正データを検出するのに対して、フィルタリングするってイメージで、入力チェックのイメージが強かったから意外だった。

filterを通すことで、データを変換するってイメージならファイル圧縮がfilterにあるのも納得できる。あー、なるほどねーって思わされた。

使い方は、こんな感じ。

$filter = new Zend_Filter_Compress('Zip');
$filter->setArchive(作成したい圧縮ファイルのパス付ファイル名);
$filter->filter(圧縮したいファイルのパス付ファイル名);

他にもこんな書き方もできそう。

$filter = new Zend_Filter_Compress(array(
     'adapter' => 'Zip', 
     'options' => array('archive' => '作成したい圧縮ファイルのパス付ファイル名')
));
$filter->filter(圧縮したいファイルのパス付ファイル名);

中身は、ZipArchiveクラス使ってました。

mac の RAID1(ミラーリング)機能を使った

先日、今までメインマシントして利用していた、Windowsマシンが立ち上がらなくなりました。
症状を友人に話したところ、恐らくマザーボードが壊れたぽいということで、しばらく使えないことに。

そこで、今までプログラム作成用途に使っていたmacbook proを急遽メインマシンにすることに。
重要なのはデータのバックアップ。趣味でDJをやっているので、時間とお金(特に時間)をかけて収集した音源が消えてなくなるのは困る。
しかし、macbook proは、ハードディスクの容量が200G程度。音源が入りきりません。
というわけで、外付けハードディスクを2台買ってきて、両方にコピーしてたところ、以下の記事を見つけました。

【Mac OSの機能だけでUSB接続の外付けHDDをミラーリングする】
http://d.hatena.ne.jp/festango/20090913/1252854323

これなら、2箇所にコピーする手間も省けるし、片方にしかコピーしてなかったという手間も発生しない。
1台に書き込むより多少時間はかかるかもしれませんが、結局2台に書き込むんであれば、それよりかは早いはず。

2台同時に壊れない限りデータもなくならないし、安心です。
今回、データが運良く取り出せたけど、今後も怖いので、音源データでしかもってないものは、ちゃんとバックアップをDVDとかにもとっておこうと思いました。こつこつやろう。

Zend_Db_SelectでUNIONとHAVINGを同時に使えない

訂正:2010/04/09

通りすがりさんにコメントを頂き、UNIONで連結した全体にhavingをかけるのはできないということで正しいということが分かりました。

たまたま、$select2の方にだけhavingでフィルターされるデータがあったため、結果的に意図通りのデータが出力されただけで、全体にhavingが掛かっているわけではありませんでした。

サブクエリを使う方法が適切です。

    // $adapterはZend_Db_Adapterのクラス。MySQLを利用。
    $select1 = $adapter->select()->from(....)->where(....);
    $select2 = $adapter->select()->from(....)->where(....);

    $select = $adapter->select()->union(array($select1, $select2));

   echo $adapter->select()->from($select)->where(....); ←havingで書いてた条件

=========

UNIONとHAVINGを同時に使うSQLを組んでいたときに、Zend_Db_Selectから生成されたSQLからHAVING句が抜け落ちていました。

プログラムは以下のようなものです。

    // $adapterはZend_Db_Adapterのクラス。MySQLを利用。
    $select1 = $adapter->select()->from(....)->where(....);
    $select2 = $adapter->select()->from(....)->where(....);

    $select = $adapter->select()->union($select1, $select2)->having(.....);
    echo $select;   ← ここで HAVING句がなくなってる

上記でできるSQLのイメージは、これです。

   select ...... from .... where ....
   union
   select ...... from .... where ....
   having .....

このSQLをDBサーバで実行すると、動くので構文はあってます。

そこで、Zend_Db_Selectクラスのhaving句を出力するところを見たら、こうなってました。(バージョンは10.2)

    /**
     * Render HAVING clause
     *
     * @param string   $sql SQL query
     * @return string
     */
    protected function _renderHaving($sql)
    {
        if ($this->_parts[self::FROM] && $this->_parts[self::HAVING]) {
            $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
        }

        Zend_Debug::dump($sql);
        return $sql;
    }

FROM句とHAVING句が両方ある場合に、HAVINGをつけるって処理ですね。確かにこれだと、UNIONと同時に使ったときは、FROM句がないので、HAVING句がつかないのは納得です。

というわけで、以下のように修正して、UNIONとHAVINGが同時に使えることを確認しました。

    /**
     * Render HAVING clause
     *
     * @param string   $sql SQL query
     * @return string
     */
    protected function _renderHaving($sql)
    {
        // FROMかUNIONがあればOKに変更
        if (($this->_parts[self::FROM] || $this->_parts[self::UNION]) && $this->_parts[self::HAVING]) {
            $sql .= ' ' . self::SQL_HAVING . ' ' . implode(' ', $this->_parts[self::HAVING]);
        }

        Zend_Debug::dump($sql);
        return $sql;
    }

そのうちZend本体でも実装してくれることに期待します。

phpで子クラスへ__constructのパラメタを渡す

今までは、配列で渡してたんですが、違和感があるので、どうにか__constructに渡した形と同じように _initにも渡せないかなと考えて、以下のようにしました。

意図通りに動いてくれています。5個以上パラメタがあるときも稀だと思うので、5個までにしています。増えたら増やせばいいかなと。これで違和感なく使えるようになってすっきりです。

abstract class Hoge_Model_Abstract
{
    final public function __construct()
    {
        // ここに共通初期化処理
 
        if (func_num_args() != 0) {
            list($param1, $param2, $param3, $param4, $param5) = func_get_args();
        }
        $this->_init($param1, $param2, $param3, $param4, $param5);
    }

    protected function _init()
    {
        // 初期化処理するときはこちらで
    }
}

filterに使えるarray_intersect_key

第一パラメタの配列の中で、第2パラメタ以降の配列全てに存在するキーのものだけ取得してくれる。

詳しい説明はこちらで。
http://php.net/manual/ja/function.array-intersect-key.php

今まで、Zend_Db_Table_RowのsetFromArrayの存在を知らなかったので、自分でこんなやりかたでfilterしてました。

$tmp = array();     
$table = new Zend_Db_Table_Abstractを継承したクラス();
$cols = $table->info('cols');
foreach ($params as $key => $value) {
    if (in_array($key, $cols)) { $columns[$key] = $value; }
}

これが、

$table = new Zend_Db_Table_Abstractを継承したクラス();
$cols = $table->info('cols');
$tmp = array_intersect_key($params, array_flip($cols));

と書けます。これ便利ですね。

ZendFrameworkのソースに書いてありました。
なんで今まで見つけれなかったんだろう。必要としてなかったからなのか。
もっと楽な方法がないのかって思えてなかったことに反省。