Tags: 技術日記

技術日記
ZF1.0RCのZend_Viewでlayout機能Edit

もうlayout機能がないビューは使いたくないんだけど、Zend Framework 1.0RCになっても、Zend_Viewには標準でlayout機能が搭載されていないらしい。昔layout機能のproposalが出ていた記憶があるけど、あれってどうなってるのかな? Zend Frameworkの開発Wikiにつながらないから、状況がわからん。

前はZend_Viewのラッパーを書いてlayout機能を追加していたけど、1.0RCのZend_Viewはもはやラッパーを書くのは無理っぽいな。テンプレートエンジン差し替えとかコントローラとの連携とかまで考えたラッパーはとても書けそうにない。一発こっきりだったら書けるかもしれないけど、とてもバージョンアップ対応できそうにないし。

しょうがないんで、Zend_Viewをラップする以外の方法でlayout機能を実現しようと思ったんだけど、今のところ思いついたのはアクションコントローラのpostDispatch()でlayout処理を実現するアプローチ。

class FooController extends Zend_Controller_Action
{
public function postDispatch()
{
$response = $this->getResponse();
$view = $this->initView();
$view->contents_for_layout = $respoinse->getBody();
$response->setBody($view->render('path/to/layout.phtml');
}
}
<!-- ヘッダとかごちゃごちゃ -->
<?php echo $this->contents_for_layout; ?>
<!-- フッタとかごちゃごちゃ -->

みたいな感じね。

これはこれで一応動きそうではあるけど、Zend Framework 1.0RCでlayoutしたい場合は、こんな感じでやる派が多数派でしょうか?

Published At2007-06-04 00:00Updated At2019-12-30 23:52

技術日記
mb_convert_kanaの'a'と'rn'は違うのかEdit

元は、

/ ↑全角の斜線は強制的に半角に変換しちゃうのか…。

http://1470.net/user/shingo/2007/05/31#m_134678

というメモで、メモのコメント欄はmb_convert_kana($var, 'KVas')がフィルタとして設定されていた。半角全角カナ英数のみをそろえるだけで、記号は変換かけていないつもりだったんだけど、試しに、mb_convert_kana('/', 'KVas')すると、見事に「/」が「/」に変換される。

結論としては、mb_convert_kana('/', 'a')でも変換がかかった。aオプションって、マニュアルには『「全角」英数字を「半角」に変換します。』と書かれているけど、その下のAオプションのところには『("a", "A" オプションに含まれる文字は、U+0022, U+0027, U+005C, U+007Eを除く U+0021 - U+007E の範囲です)。』という注釈がついている。んで、文字コード表で該当の範囲を見ると、がーん、'a'だと結構な数の記号が対象になっちゃってるじゃん。この記号の取捨選択の意図がよくわからん。

まあともかく、mb_convert_kana($var, 'KVas')じゃなくて、mb_convert_kana($var, 'KVrns')にしておいたほうがいいな。

Published At2007-05-31 00:00Updated At2019-12-30 23:57

技術日記
Zend Framework(1.0RC)もだいぶ複雑になったなーEdit

ようやくZend Framework 1.0RCのMVC周りの概要がつかめてきたんだけど、昔(0.6以前)読んだときと比べるとだいぶ複雑になっているなー。

MVC周りの結合度があがった

前は、シンプルなフロントコントローラ、ルーター、ディスパッチャー、アクションコントローラが疎結合して連携しつつ、それとは独立したシンプルなビューがある、って感じの構成で、ざっと関連ソースをながめれば全体像が把握できたんだけど、もうそんなシンプルな構成じゃなくなっている。

まず、以前は独立していたビューが、現在のバージョンではかなり密にコントローラ周りと結合している。といっても、それなりにインターフェースは整えられているんで、分離不可能になっている訳じゃないけれども、標準状態では一体化して動作する前提になっていて、古い(0.6以前の)知識で扱おうとするとかなりとまどう(特にViewRenderer周りの挙動がわかりにくかった)。

モジュールの導入

あと、以前はアクションコントローラやビューのディレクトリは、それぞれ一つだけ用意するような構造になっていて、そのターゲットとなるアプリケーションのスケールがそれほど大きくない感じだったんだけど、現在のバージョンでは、「モジュール」というアクションコントローラやビューディレクトリを複数管理するための仕組みが追加されている。おそらくは、一つのWebサイト(アプリケーション)で複数のサービス(モジュール)を実装するような、今までよりも大きなスケールに対応できる設計なんだろう。

モジュールの導入により、MVC周りの複雑さがかなり増している。今まではルーティングの結果は、コントローラ名+アクション名+パラメータに解決されていて、ソースコード的には、コントローラクラス+アクションメソッドを追えば良かったんだけど、現在はそれにプラスしてモジュール名という要素もオプショナルに解決されるようになった。モジュール名付きでルーティングが解決された場合は、ランタイムで(コントローラやビューの)ディレクトリのスコープが変わるような動作イメージになってしまうため、コントローラクラス+アクション名だけじゃなくて、収録ディレクトリレベルでソースを追う必要が出てくる。

標準でコントローラ周りと連携して動作するビューは、ランタイムで解決されるモジュールと自動的に連携するようになっているんで、標準で提供されるコントローラ+ルーター+ビューを使っているぶんには、「まあそういうもんだ」って感じで使えるだろう。

でも俺みたいに、Zend FrameworkのMVC周りをラップした自前のフレームワークを作っている人間には、非常に扱いにくい仕組みになってしまった。昔と違って、複数箇所でコントローラやビューが結合しているため、単純にコントローラ(あるいはビュー、あるいはルーター)クラスをラップしたクラスで置き換えるだけでは、うまく動かない可能性が高い。特にオプション設定+アクションヘルパーなんかを経由して、遠回りに結合している部分(たとえばViewRenderer)なんかは、扱いが面倒だ。

現在のMVC周りのまとめ

昔のZend Framework(のMVC)は「取り替え可能なコンポーネント群で構成された、シンプルな疎結合のMVCフレームワーク」と紹介できたけれども、現在のZend Framework(のMVC)は「充実した拡張性を持つMVCフレームワーク。単純な拡張(機能追加)は容易だが、各コンポーネント間の結合は密なので、大規模な拡張(コンポーネント入れ替え)は難易度が高い」って感じかなー。

その他目に付いた主な変更点

って概要レベルの話だけでなく、目について主な変更点も上げておこう。

モジュール

さっきも書いたけれども、モジュールという複数のアクションコントローラ+ビューを管理する概念が追加されている。これがおそらく一番大きな変更じゃなかろうか。互換性のためにモジュールがあってもなくてもそれなりに動作するような実装となっており、それがMVC周りのコンポーネントのコードの可読性を下げていて、ラッパー開発者にはつらいとこ。

アクションヘルパー

あと、新しくアクションヘルパーが追加されている。従来はヘルパーは、ビュースコープで使えるビューヘルパーだけだったけど、今回はアクションコントローラレベルのヘルパーが追加された。ちなみにアクションヘルパーはアクションコントローラ内のメソッドスコープだけでなく、グローバルな(アクションヘルパーブローカーのスタティックメソッド経由)スコープでアクセスできる。従来は、アクションコントローラよりも上のレベルで共用したいロジックは、グローバルなクラス(あるいはグローバルなファンクション)に実装するしかなかったけど、これでアプリケーションスコープで共用ロジックを置く場所ができた。

ビューエンジン

ビューは、新しくエンジンという概念が追加されて、従来のPHPコードを使って記述するビューだけでなく、Smartyとかのテンプレートエンジンを使えるようになった。らしい。というのは、レポジトリを見ても、いまだに他のテンプレートエンジンを使った実装が見あたらないから。MLとかを見ると公式のZend_View_Smarty実装があるみたいなのに。いったいどこにあるんだろう?(といっても、あまり真面目に探してないけど)

入出力ラッパー(Request/Response)

あと、だいぶ前からだけど、入出力にPHPの標準入出力をそのまま使わず、Request/Response系オブジェクトを経由するような仕組みになっている。この辺は(ラッパーを作ろうと)実装を追っていくと結構複雑で面倒くさい部分だけど、標準で使うぶんには従来とさほど変わらないかな? テストとかを作るときにはだいぶ楽になるだろう。

Zend_Db_Tableの拡張

そういやZend_Db_Tableは実装(およびその元となる規約)がだいぶ変わったっぽい。あと、リレーションをたどる機能も追加された模様。ソースをながめた限りでは、機能が少なくてそのままでは用途が限られていた昔のバージョンとはだいぶ変わって、かなり実用的に使えるような雰囲気になっている。ただ、相変わらずモデルクラスをロードする仕組みは特にないっぽいけど、Zend_Loaderでもつかえってことなのかな?

その他

今のところMVC周り以外はほとんど見ていないけれども、AuthとかAclとかの認証・権限管理系のコンポーネントも標準化されていたり、各種ユーティリティクラス系もだいぶ充実している模様。前に見たときにはなかったバリデーション系クラスもちゃんとできているし。っつーか、昔のフィルターとバリデーターがごっちゃになっている仕組みは良くなかったし、それと比べるとだいぶいい感じになっているように見える(ざっと見ただけだけど)。っつーか、なんかWEBXP_FilterとかWEBXP_Validatorと似たような実装になっているなー。

Published At2007-05-31 00:00Updated At2019-12-30 23:57

技術日記
PHPで__setを使ったプロパティに対する配列要素の追加Edit

PHP(5以降)で、

class Foo
{
protected $_vars = array();
public function __set($name, $value)
{
$this->_vars[$name] = $value;
}
public function __get($name)
{
return $this->_vars[$name];
}
}

みたいに、プロパティに対して__get()、__set()を使ってアクセスするようなクラスを作って、

$foo = new Foo();
$foo->bar[] = 'test';

みたいに、プロパティに対して配列要素追加の演算子を使った場合、昔は期待通りに動作していた($foo->bar == array(0 => 'test')になった)んだけど、最近のPHP(5.2.2)では期待したとおりには動作しなくなっていた(「Notice: Indirect modification of overloaded property Foo::$bar has no effect」になる)。確かに怪しい記法ではあったんだけど、これっていつから変更されていたのかな?

ググったら、関連情報としてPHP 5.2.0 で配列をオーバーロードするときの注意点 - PHPプロ!ニュースが見つかった。ArrayObjectって使ったことなかったけど、こういう使い道もあるのか。でも、なんかバッドノウハウ度が高くていやな書き方だなー。

あと、バグレポートでの関連議論「PHP Bugs: #39449: Overloaded array properties do not work correctly」。

まとめると、

  • PHP 5以降で__get()、__set()を使ってプロパティオーバーロードし、プロパティがarray型の値を持つ場合、それに対して利用できる演算子は制約を受ける($foo->bar[]= 〜や$foo->bar[$hoge] = 〜などは使えない)
  • プロパティオーバーロードを利用したarray型のプロパティを、従来の配列型プロパティと同じように操作したい場合、ArrayObject+ARRAY_AS_PROPSオプションを利用することで、ある程度対応できる(完全にネイティブ配列型プロパティ互換になるわけではない)
  • PHP 5.2以前に、プロパティオーバーロードを使って配列型プロパティを操作するコードを書いた記憶がある人は、関連コードの見直しをガンガレ! チョーガンガレ! Notice出力している開発環境だと気づきやすいけど、production環境だと結構気づきにくいバグになったりするぞ!

Published At2007-05-28 00:00Updated At2019-12-30 23:57

技術日記
PHPでクラスに特定のメソッドがあるかどうかを調べる方法Edit

っつーか普通に、

$foo = new Foo();
if (is_callable(array($foo, 'bar')) {echo "Foo::bar() exists"}

っていけるかなーと思っていたんだけど、問題は__callを使ったときにどうなるのか。試してみたところ、__callが存在するだけで、あらゆるメソッドが存在することになってしまうらしい(っつー言い方は不正確だな。callableかどうかを返す関数なんだから。正確には「あらゆるメソッドが呼べることになってしまう」ね)

class Foo
{
function __call($methodName, $args)
{
throw new Exception()
}
}

とかでも、Foo::bar()だろうがFoo::baz()だろうがis_callableはtrueを返してしまう。

試しにis_callableの第3引数を使って、

$methodName = null;
is_callable(array($foo, 'bar'), true, $methodName);

とかしてみたら、存在しないときには$methodNameで識別できるんじゃないかと期待してみたんだけど、期待も空しくFoo::barが返ってきてしまった。

Reflection APIを使って、

$reflection = new ReflectionClass('Foo');
$reflection->hasMedhot('bar');

とすればいけるんだけど、この程度のことにReflection APIを使うのはなんだか大げさなような気がしてならない。今のところそれしか方法が見つからないから、使うんだけど。

追記

以上の文章は、method_exists()を使えば簡単にできることなのに、その関数の存在をすっかり忘れて試行錯誤した記述です。メソッドの存在確認をしたい場合は、

method_exists($foo, 'bar')

を、そのメソッド名での呼び出しが可能かどうか(__callも含めて)が知りたい場合は、

is_callable($foo, 'bar')

を使うのが正解でした。

ご指摘ありがとうございました>wtnabe様。

Published At2007-04-12 00:00Updated At2019-12-31 00:00

技術日記
Zend_Config_Yaml野良版Edit

Zend Frameworkの最新状況に追随しようとして、コードを見ているんだけど、なぜかいまだにZend_Config_Yamlがないみたいなんで、試しに書いてみた(つっても、ほとんどのコードはZend_Config_Xml/Iniのものを流用)。

YAMLのパースにSpycかSyckを使っているんで、どちらかが必要。一応Zend_Config_Xml相当のテストは通るようにしておいたけど、それ以外のテストはしていない。

あと、テストパターンの中で、空の要素をarray_mergeしているんで、Warningが出ているんだけど、これはコードレベルで対応しておくべきなのか、それともWarningとして残しておくべきなのか、Zend_Configの思想がよく分かってないんで放置中。

純血主義のZend Framework的には、こういう外部ライブラリを利用したコンポーネントはダメで、たぶんまだproposal段階のZend_Yamlあたりができあがってから、正式版のZend_Config_Yamlが作られるんだろうなー。

Published At2007-04-06 00:00Updated At2019-12-31 00:00

技術日記
PHP 5.2.1のPerl互換正規表現関数の非互換性?Edit

$regex = '/^(?:\/(?:(?:(?:[^\W_]|[-_.!~*\'()])|(?:%[\da-fA-F]{2})|[:@&=+$,;])*))+$/';
$path = '/entry/http://anond.hatelabo.jp/20070223015821';
echo preg_match($regex, $path);

というコードをPHP 4.4.1/4.3.11/5.1.4で実行すると、preg_matchが1を返した。でも、PHP 5.2.1(複数環境)で実行したところ0を返した。

上記正規表現は、Zend_Uri_Httpの中でpath要素のバリデーションに使われているもの。この動作のせいで、1470.netに登録できないURIが発生しているというところまでは追いかけたんだけど、ここで追跡頓挫中。

PHP 5.2.0でPCREのバージョンを上げたらしいけど、そのせいで発生している問題なのか? 5.2.0で試せる環境がないんだよなー。何か情報持っている方いませんか。

PHPのバグレポートを調べたところ

Bug #40195 PCRE Version 6.7 04-Jul-06 seems to have a bugが怪しいかも。っつーか、PHP 5.2.2を待つかtrunkに上げるかの2択ですか。

Published At2007-03-09 00:00Updated At2019-12-31 00:01

技術日記
そろそろZend Frameworkの1.0も見えてきたことだしEdit

Zend Framework 0.1.3ベースで構築した環境を、Zend Frameworkのtrunkに対応させようかとちょっとだけ作業を始めてみたけど、これだけコントローラ周りが変わっていると、既存のコードを修正するよりも、インターフェース互換を意識しながら全部書き直した方が早い気がしてきた。Zend_Controller_Dispatcher_TokenをZend_Controller_Request_Httpに直していくくらいでいいかなーとか思っていたけど、それだけじゃすまないなー。っつーか、その部分だけでも結構重要な変更(入力値の取り扱いが全面的に変わってくる)だし。ポール・グレアムに怒られそうな全書き直しをやっちゃおうかなー。

Published At2007-03-08 00:00Updated At2019-12-31 00:01

技術日記
SSLで名前ベースのバーチャルホストEdit

Apacheのドキュメントの名前ベースのバーチャルホストの項に、

名前ベースのバーチャルホストは SSL プロトコルの特徴により、 SSL セキュアサーバには使えません。

とか書いてあるんで、SSLでは名前ベースのバーチャルホストは一切使えないものだと思いこんでいたんだけど、(証明書の確認がテキトーでいいテスト環境用ならば)SSL+名前ベースのバーチャルホストも使えるという話を聞いて試しに設定してみたところ、ちゃんと動いた。

今までテスト・開発環境で、1IPで複数のSSLサーバーを立てられないと信じて、いろいろ面倒くさいことをやってきたんだけど、それらはすべて徒労だったのか……。

いや、やっぱりなんかおかしい

NameVirtualHostを*:443で有効にして、https://foo/https://bar/にアクセスを振り分けることに成功はしているんだけど、サーバー側の環境変数を見てみたら、SERVER_PORTが443ではなく80になっているし、HTTPSもonになってないなー。でもブラウザの方はSSL接続の鍵マーク付きでアクセスしているっぽいんだけど。どういうことだ?

VirtualHostのなかでSetEnv HTTPS onしてごまかし

テストしているアプリケーションが、同一ホスト名+パスで、必要に応じてHTTP→HTTPS、HTTPS→HTTPへリダイレクトするようになっていて、その識別にHTTPS/HTTP_X_SSLを見ていたんだけど、テスト環境だと識別できる環境変数が見あたらなくなってしまったんで、VirtualHostの中でSetEnv HTTPS onとか強引につけて識別できるようにした。けど、なんか根本的なところから間違っている気がするな。まあテストには使えるんだけど。

Published At2007-02-22 00:00Updated At2019-12-31 00:02

技術日記
MySQLのクエリーキャッシュが効かない 解決編Edit

この間書いたZend_Db_Adapter_Pdo_Mysql経由でMySQLに接続しているとクエリーキャッシュが効かないという話、ひとまず解決方法を見つけた模様。結論から言うと「PHP 5.2.1にあげれ」。

ポイントとしては、PHP 5.2.1 Release Announcementの主要な拡張ポイントとして書かれている、「PDO_MySQL now uses buffered queries by default and emulates prepared statements to bypass limitations of MySQL's prepared statement API.」って部分。その辺のソースも読んでないし、MySQL API関連の詳しい事情もよく分からないけど、ともかくPHP 5.2.1では、PDO_MySQLの実装として、MySQLのプリペアードステートメントを使うのはやめ、エミュレーション実行することにしたらしい。で、試しにPHP 5.2.1に入れ替えてみたところ、ちゃんとクエリーキャッシュが効くようになってくれたよ。

一応自前で、Zend_Db_Adapter_Pdo_Mysqlのfetch系メソッドだけオーバーライドして、mysqli関数経由で非プリペアードステートメントを投げる実装に置き換えたクラスとか書いてみていたんだけど、そういう中途半端な対策をするよりもすぱっとPHP 5.2.1に置き換えた方がましそうだ。

ちなみにZend Frameworkのincubatorの中には、Zend_Db_Adapter_Mysqliクラスとかがあって、ちょっと期待したんだけど、どうやらこれはろくにメンテナンスされなかったために、コアからincubatorに格下げになった代物だったらしい。試しに使ってみたけど、完成度が低くて使い物にならなかった。っつーか、拡張して使う気にもなれなかった。

Published At2007-02-09 00:00Updated At2019-12-31 00:02