Home > PHP

PHP Archive

Zend_ApplicationでZend_Session_SaveHandler_DbTableを使うとエラーになってしまう

Zend_Applicationが便利そうなので使ってみたら一つハマりましたので報告(Zend Frameword 1.9.0にて)。

Zend_Application_Resource_Sessionを使って設定ファイルからZend_Session_SaveHandler_DbTableの設定を行なおうとすると

Zend_Db_Table_Exception: No adapter found for Zend_Session_SaveHandler_DbTable in /library/Zend/Db/Table/Abstract.php on line 754

というエラーメッセージを吐いて止まってしまいます。

対処法としては”全体のブートストラップを行なう前にDBリソースだけ先にブートストラップを行う”という方法をとってみましたが、あまり美しい方法では無いので次のヴァージョンアップで直っていることを願います。

$application->getBootstrap()->bootstrap('db');
$application->bootstrap()
            ->run();

del.icio.us cached++を使って最近deliciousに追加したブックマークをWordpress上で公開

あまり興味ある人もいないでしょうがdeliciousに登録している私のブックマークをblog上で公開してみることにしました。

del.icio.us cached++のインストールは他のプラグインと同様に、非常に簡単。

  1. del.icio.us cached++からzipファイルをダウンロード
  2. ダウンロードしてきたファイルを解凍すると中にdelicious_cached_pp.phpというファイルがありますのでこれをwp-content/pluginsに格納
  3. プラグイン管理画面でdel.icio.us cached++の使用を許可
  4. 後はテンプレートに
    <ul>
    	<?php delicious_pp('accountname'); ?>
    </ul>

    を記述

以上で最近deliciousに追加したブックマークをWordpress上で公開できます。

テンプレートに記述するdelicious_pp()を使ってもう少し詳細に表示をカスタマイズすることもできます。

function delicious_pp(
    $username, //ブックマークを表示するdeliciousのアカウント名
    $count=15, // 表示をさせたいブックマーク数
    $extended=1, //NOTES表示の有無
    $tags=0, //表示させるタグの数
    $before='<li>', //各々のブックマークの前に追加する文字列
    $after='</li>', //各々のブックマークの後に追加する文字列
    $beforeExtended='<p>', //各々のNOTESの前に追加する文字列
    $afterExtended='</p>', //各々のNOTESの後に追加する文字列
    $beforeTags='<p>', //ブックマークごとのタグの前に追加する文字列
    $betweenTags=' ', //ブックマークに対するタグが複数の場合にタグ同士の間に追加する文字列
    $afterTags='</p>' //ブックマークごとのタグの後に追加する文字列
)

しかし$tagsを設定してもどうにもタグが表示されません。
原因を調べてみるとどうもdeliciousのRSSの仕様が変わったらしい。現在、deliciousのRSSでは<category>にタグが記述されているのですが、下記サイトによると以前は<dc:subject>に記述されていたようです。ソースを覗いてみると

$linkTagsRaw = $feedItems[$iter]['dc']['subject'];

という処理があるので新しい仕様には対応していないみたいです。まぁ、どちらにしろ私はタグを表示させる気はないので良い事なんですが、タグを表示したい人はソース書き換えて下さい。ちなみにdel.icio.us cached++のヴァージョンは1.3a4。

Big Sky :: はてなブックマークをPlaggerで同期する際の注意点

そういえばdel.icio.usってdeliciousに変わってたんですね。今回初めて気付きました。

Zend_Sessionでセッションデータの検証

Zend_Session_Validateなる存在をみなさんご存知ですか?Zend Frameworkのマニュアルには載っていないのですがZend_Session_Validateを使用する事でZend_Session::start()時にセッションデータの検証を行う事が出来るのです。Zend_Sessionのソースを覗いている時に偶然発見してしまいました。たまにはソースも覗いてみるべきですね!

Zend_Session_Validatorの処理としてはZend_Session::registerValidator()で検証すべきデータをセッションに格納し、Zend_Session::start()の時にセッションデータの検証を行います。で、その検証に失敗するとZend_Session_Exceptionが返されますのでtry~catchで受け取り適切な処理を記述する形になります。

最新版Zned Framework(1.7.1)で組み込まれているZend_Session_ValidateはZend_Session_Validator_HttpUserAgentだけですので、とりあえずこれを実装してみます。


try {
	Zend_Session::start();
} catch (Zend_Session_Exception $e) {
	throw $e;
}
Zend_Session::registerValidator(new Zend_Session_Validator_HttpUserAgent());

これで前回セッションを使用した時とUserAgentの変更があればセッションデータを破棄することができます。携帯サイト等でこのコードを実装すると多少問題が出ることもありそうですが、PCサイトであれば多少なりとも安全になりますのでオススメです。

あと注意点として”Zend_Session::start()の後にZend_Session::registerValidator()を行う事”を挙げておきます。処理を理解すると当然なんですが、何となく逆にしてしまいがちなので。

2008/12/15 追記:ZJ吉田さんがコメントされた内容

早速バグとして報告をあげておきました。

がどうしても気になったのでメールで確認してみたところ、『マニュアルにZend_Session_Validatorの記述が無い』というバグでした。バグはプログラムの方だと思い込んでいたのでマニュアルのバグとは気付きませんでした。ZJ吉田さんのおかげでZend Frameworkに貢献でき大変感謝です!!

2009/02/23 追記

サンプルソースに間違いがありましたので訂正いたしました。
Zend_Session::start()に失敗した場合、Zend_Session::destroy()を行なってsessionを破棄しようとしていましたが、startに失敗しているのに破棄できる訳がなくWarningメッセージ(Trying to destroy uninitialized session)をはいておりました。

Zend Framework(Zend_Validate)におけるZend_Translateを用いたエラーメッセージの日本語化

Zend Frameworkではデータ検証用クラスとしてZend_Validateが用意されています。日ごろ使いそうな検証はほぼ揃っているのでありがたく使わせてもらっているのですが、残念ながらこのZend_Validateには日本語のエラーメッセージが実装されていません。なのでエラーメッセージの日本語化を自分で行う必要があります。 Zend Frameworkではメッセージの多言語化を行うためにZend_Translateというクラスが用意されているので、今回はこのZend_Translateを用いてZend_Validateの日本語化を行ってみます。

まずZend_Translateを用いてメッセージの他言語化を行う場合、メッセージの翻訳データを用意する必要があります。そしてZend_Translateではその翻訳データの種類に合わせて多くのアダプターが用意されています。私は今までプログラムの翻訳作業等やったことがないので、とりあえず一番使われていそうなGettextアダプタを使用してみることにします。gettextについての解説は下記サイトが大変参考になりました。

GNU gettextユーティリティ

gettextで翻訳作業を行うために、まずPOファイルと言うものが必要になります。gettext関数を用いて書かれたプログラムであればxgettextなどを用いて自動で翻訳のもとになるメッセージを抽出できるようなのですが、今回はクラスで定義されたプロパティからメッセージを抽出する必要があるので、自作でZend_Validateからメッセージを抽出するプログラムを作ってみました。POファイルはテキストファイルなので手作業で作る事も出来るのですが、今後Zend_Translateのクラスが増えたり、メッセージが増えたりする事があるかもしれませんのでこのようにプログラムを組んでおくと楽なんじゃなかろうかと思います。

xgettext.pl

#!/usr/bin/perl
use File::Basename;

my $start;
my $basename;
my %texts;

while (<>) {
    chomp;
    if (/\$_messageTemplates = array\($/) {
        $start = 1;
        next;
    } elsif ($start) {
        if (/\);$/) {
            $start = 0;
            next;
        }
        s/^.*=> ('|")([^\1]*)\1,?$/$2/;
        $basename = basename(${ARGV});
        push @{$texts{$_}}, $basename . ':' . $.;
    }
    if (eof) {
        close(ARGV);
    }
}

print "msgid \"\"\n";
print "msgstr \"\"\n";
print "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
print "\"Content-Transfer-Encoding: 8bit\\n\"\n\n";

foreach $key (sort keys %texts) {
    foreach my $file_name (@{$texts{$key}}) {
        print "#: ${file_name}\n";
    }
    print "msgid \"${key}\"\n";
    print "msgstr \"\"\n\n";
}

PHPのフレームワークの解説なのにperlで実装してしまいました。私の中でコマンドラインのプログラムを書く際はシェルスクリプトかperlで書くという掟があるのです。許して下さい…。

./xgettext.pl lib/Zend/Validate/* > validate.po

とコマンドラインでやってもらえるとPOファイルが作成できます。
ちなみに現在のZend Frameworkの最新版(1.7.1)でのPOファイルはこんな感じです。

msgid ""
msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: EmailAddress.php:58
msgid "'%hostname%' does not appear to have a valid MX record for the email address '%value%'"
msgstr ""

#: EmailAddress.php:57
msgid "'%hostname%' is not a valid hostname for email address '%value%'"
msgstr ""

#: EmailAddress.php:61
msgid "'%localPart%' is not a valid local part for email address '%value%'"
msgstr ""

#: EmailAddress.php:59
msgid "'%localPart%' not matched against dot-atom format"
msgstr ""

#: EmailAddress.php:60
msgid "'%localPart%' not matched against quoted-string format"
msgstr ""

#: Hostname.php:74
msgid "'%value%' appears to be a DNS hostname but cannot extract TLD part"
msgstr ""

#: Hostname.php:71
msgid "'%value%' appears to be a DNS hostname but cannot match TLD against known list"
msgstr ""

#: Hostname.php:73
msgid "'%value%' appears to be a DNS hostname but cannot match against hostname schema for TLD '%tld%'"
msgstr ""

#: Hostname.php:72
msgid "'%value%' appears to be a DNS hostname but contains a dash (-) in an invalid position"
msgstr ""

#: Hostname.php:77
msgid "'%value%' appears to be a local network name but local network names are not allowed"
msgstr ""

#: Hostname.php:70
msgid "'%value%' appears to be an IP address, but IP addresses are not allowed"
msgstr ""

#: Digits.php:61
msgid "'%value%' contains not only digit characters"
msgstr ""

#: Float.php:45
msgid "'%value%' does not appear to be a float"
msgstr ""

#: Ip.php:41
msgid "'%value%' does not appear to be a valid IP address"
msgstr ""

#: Date.php:60
msgid "'%value%' does not appear to be a valid date"
msgstr ""

#: Hostname.php:76
msgid "'%value%' does not appear to be a valid local network name"
msgstr ""

#: Int.php:41
msgid "'%value%' does not appear to be an integer"
msgstr ""

#: Date.php:61
msgid "'%value%' does not fit given date format"
msgstr ""

#: Regex.php:45
msgid "'%value%' does not match against pattern '%pattern%'"
msgstr ""

#: Hostname.php:75
msgid "'%value%' does not match the expected structure for a DNS hostname"
msgstr ""

#: Alnum.php:68
msgid "'%value%' has not only alphabetic and digit characters"
msgstr ""

#: Alpha.php:68
msgid "'%value%' has not only alphabetic characters"
msgstr ""

#: Hex.php:49
msgid "'%value%' has not only hexadecimal digit characters"
msgstr ""

#: Alnum.php:69
#: Alpha.php:69
#: Digits.php:62
msgid "'%value%' is an empty string"
msgstr ""

#: StringLength.php:47
msgid "'%value%' is greater than %max% characters long"
msgstr ""

#: StringLength.php:46
msgid "'%value%' is less than %min% characters long"
msgstr ""

#: EmailAddress.php:56
msgid "'%value%' is not a valid email address in the basic format local-part@hostname"
msgstr ""

#: Between.php:54
msgid "'%value%' is not between '%min%' and '%max%', inclusively"
msgstr ""

#: GreaterThan.php:45
msgid "'%value%' is not greater than '%min%'"
msgstr ""

#: LessThan.php:45
msgid "'%value%' is not less than '%max%'"
msgstr ""

#: Date.php:59
msgid "'%value%' is not of the format YYYY-MM-DD"
msgstr ""

#: Between.php:55
msgid "'%value%' is not strictly between '%min%' and '%max%'"
msgstr ""

#: Ccnum.php:61
msgid "'%value%' must contain between 13 and 19 digits"
msgstr ""

#: InArray.php:45
msgid "'%value%' was not found in the haystack"
msgstr ""

#: Ccnum.php:62
msgid "Luhn algorithm (mod-10 checksum) failed on '%value%'"
msgstr ""

#: Identical.php:47
msgid "No token was provided to match against"
msgstr ""

#: Identical.php:46
msgid "Tokens do not match"
msgstr ""

#: NotEmpty.php:45
msgid "Value is empty, but a non-empty value is required"
msgstr ""

この作成したPOファイルのmsgidに対する日本語訳を下記のようにmsgstrに記述していきます。

#: StringLength.php:47
msgid "'%value%' is greater than %max% characters long"
msgstr "%max%文字以下で入力して下さい"

全ての日本語訳が完成したらmsgfmt等を用いてMOファイルを作成します。

msgfmt -o validate.mo validate.po

msgfmtに関しても詳しい事は上記『GNU gettextユーティリティ』にて解説してありますので参考にしてみて下さい。

以上で翻訳データの準備は完了です。後はこの翻訳データを用いてZend Frameworkでのエラーメッセージの日本語化を行います。

まず翻訳データを格納するディレクトリの構造を決める必要があります。
Zend Frameworkでは次の5つの構造を推奨しています。

  • 単一構造のソース
  • 言語ごとに分けた構造
  • アプリケーションごとに分けた構造
  • Gettext 形式の構造
  • ファイル構造のソース

今回アダプターとしてGettextを選びましたので『Gettext 形式の構造』を選ぼうかと思ったのですが”LC_MESSAGES”の存在意義が見いだせなかったので『 言語ごとに分けた構造』にしてみました。

/languages
  /ja
    validate.mo

あとはindex.phpなどでZend_translateオブジェクトを作りZend_Validate_AbstractにDefautlTranslatorとして登録するだけです。

$translate = new Zend_Translate('gettext', '/var/www/html/languages', null, array('scan' => Zend_Translate::LOCALE_DIRECTORY));
Zend_Validate_Abstract::setDefaultTranslator($translate);

注意事項としてZend_Translateの第二引数として渡しているパスは翻訳データを格納しているディレクトリまでの絶対パスで指定して下さい。

以上でZend_Validateのエラーメッセージの日本語化は終了です。
記述した内容でおかしなところ等あればコメント残して頂けると喜びます!!

XREA DOMXPathのquery()で真っ白に

比較的新しいバージョンのプログラムを使えるのと、サーバー代が安いのでXREAのサーバーをよく使わせてもらっているのですが、そのXREAで今回一つハマってしまったことがありましたので報告です。

Zend FrameworkのZend_Service_Yahooを使用してちょっとしたマッシュアップサイトを作ったのでXREA(s341.xrea.com)に上げて動かそうとしたら、画面が真っ白になって反応なし・・・。当然display_errorsはon。原因を調べてみるとZend_Service_Yahooの_checkErrors()で使用されているDOMXPathのquery()を実行するところでプログラムが止まっていることが分かりました。これ以上は自分の力で原因を追うことが出来ないのでネットで検索。すると次のサイトで同じような状況が報告されていました。

XUGJ「OpenID モジュール」

$doc = new DOMDocument;$doc->loadXML($xml);

$xpath = new DOMXPath($doc);

$result = $xpath->query('/xrds:XRDS');//ここでプロセス落ちる

不思議な物ショップ 「OpenID」

・原因
xrea、CORESERVER.JPのレンタルサーバーで使用されているlibxml2のバージョンに問題があり、xmlファイルの解析に失敗する。
libxml Version 2.6.19の不具合が原因のようです。

さっそくphpinfo()で使用しているサーバーのlibxml2のバージョンを確認してみると

libXML Version 2.6.19

やはりlibxml2のバージョンが原因のようです。

libxml2を使用するプログラムはXREA以外のサーバーで動かす必要がありそうですね。ちなみにZend Framework内をxpath->queryで検索したところ次に列挙されたクラスがヒットしましたので使用の際は注意しておいてください。

  • Zend_Search_Lucene_Document_Html
  • Zend_Service_Amazon(Zend_Service_Amazon_xxx色々)
  • Zend_Service_Flickr(Zend_Service_Flickr_xxx色々)
  • Zend_Service_Simpy(Zend_Service_Simpy_xxx色々)
  • Zend_Service_Technorati(Zend_Service_Technorati_xxx色々)
  • Zend_Service_Yahoo(Zend_Service_Yahoo_xxx色々)

PHP マジックメソッド __toString()

  • 2008-05-02 (金)
  • PHP

__toString()はPHP5.2.0以前ではecho()もしくはprint()でしかコールされないみたいです。知らなくてしばらくハマってしまいました。exceptionをlogに書き込む際など注意する必要がありますね。

Zend Framework日本語訳マニュアル

m-takagiの実験室さんで、Zend Frameworkの日本語訳マニュアルを本家よりはやく見ることが出来ます。有り難い限りです!!

SAKURA internetでZend Framework(Zend_Db)とMySQLを使う

去年一杯でPHP4のサポートが終わったことにより、共有ホスティング界でもPHP5化が急速に進んでいるように感じられます。その流れにまかせて私も仕事で使用するメインフレームワークを変更することにしてみました。今まではEthnaをメインにしていたのですが、開発が止まってしまってるみたいですし、クラス間の依存度も高いので、個人的に結構扱いづらいです。そこで趣味で使っていたZend Frameworkをメインにしてみることにしました。このZend Frameworkは他のPHPフレームワークに比べるとオブジェクト指向へのこだわりが強く感じられ拡張が非常にやりやすいように感じます。CakePHPも多少使ってみたのですがZend Frameworkの方に惹かれる私はJava向きなのかもしれないですね。

そのZend FrameworkをSAKURA internetで使用するとZend_DbとMySQLで問題がありましたので解決法を提供します。

Zend_Dbを使用する場合、使用するデータベース用のZend_Db_Adapterを用意する必要があるのですがZend Frameworkに最初から用意されているZend_Db_Adapterは下記に挙げられている物になります。

  • IBM DB2 および Informix Dynamic Server (IDS) (pdo_ibm PHP 拡張モジュールを使用)
  • MySQL (pdo_mysql PHP 拡張モジュールを使用)
  • Microsoft SQL Server (pdo_mssql PHP 拡張モジュールを使用)
  • Oracle (pdo_oci PHP 拡張モジュールを使用)
  • PostgreSQL (pdo_pgsql PHP 拡張モジュールを使用)
  • SQLite (pdo_sqlite PHP 拡張モジュールを使用)
  • MySQL (mysqli を使用します)
  • Oracle (oci8 を使用します)
  • IBM DB2 (ibm_db2 を使用します)
  • Firebird/Interbase (php_interbase を使用します)

そこでMySQLを使用する場合、ドライバとしてpdo_mysqlもしくはmysqliを組み込む必要があるのですが、SAKURA internet組み込まれているMySQLドライバはmysqlのみです。そこで考えられる解決策は下記の3つ。

  1. 使用するデータベースを変更する
  2. pdo_mysqlドライバをインストールする
  3. mysqlドライバ用のZend_Db_Adapterを用意する

1.「使用するデータベースを変更する」はSAKURA internetでMySQL以外のデータベースの選択肢としてはSQLiteのみ。漏洩しても問題ないデータを扱うなら良いのですが共有サーバーでSQLiteはちょっと怖い気がするので無し。

2. 「pdo_mysqlドライバをインストールする」はPHP5を入れ直す必要がありますよね??その辺がまだ良くわかっていない・・・。とにかく面倒な気がするので無し。

そのような訳で私は3.「mysqlドライバ用のZend_Db_Adapterを用意する」を選択。しかしわざわざ自分でmysqlドライバ用のZend_Db_Adapterを作るのも面倒くさいのでネットで探してみたら、やはりありました。

Tutorial : Using Zend Framework Without PDO

このサイトにPDOを使わないZend_Db_Adapterの使い方とライブラリ(現在の最新Php-0.3.0.tar.gz)が置いてあるのでこれを使います。しかしこのサイトの情報が古いので最新のZend Framework(1.5.0 RC1)ではここに書かれているようにしても動きませんので修正が必要です。

まずリンク先のライブラリをダウンロード。

その中にZend_Db_Adapter_Php_Dbphpというクラスがあるのですが、このクラスに下記のようにZend_Db_Profilerをrequierする記述を追加

require_once 'Zend/Db/Profiler.php';

次にZend_Db_Adapter_Php_Mysqlに下記のgetQuoteIdentifierSymbol()を追加

public function getQuoteIdentifierSymbol()
{
	return "`";
}

後はそのダウンロードしてきたライブラリの中にあるPhpフォルダをZend FrameworkのZend_Db_Adapterフォルダの中に突っ込むだけです。

リンク先に「Zend_Dbを用意されている物と置き換えないといけないですよ〜。」的なことが書いてあるのですが現在のZend Frameworkはその必要はありませんので無視です。

後Zend_DB::factory()を使う場合はアダプター名が「PHP_MYSQL」 になるのでご注意を!!

とここまで色々書いて参りましたがPHP5対応させるならpdo_mysqlは入れるだろう、普通・・・。お願いしますよ、SAKURAさん。

Ethna_ActionForm エスケープの罠

Ethnaではテンプレートで使う値を$form、$app、$app_neの3つの変数に格納してから取得します。このうち$form、$appの2つの変数から値を取得すると、htmlの特殊文字を自動でエスケープしてくれる。私はこのエスケープがViewClassの内部で行っているとばかり思っていたら違いました。実際はActionFormのgetArray()、getAppNEArray()を使った時にエスケープは行われてます。よって入力されたままのフォームの値をまとめて取得したい場合には、getArray(false)のようにgetArray()に引数’false’を渡して取得しましょう。今日はこれで1時間ぐらいハマってしまった。

Dreamweaver のテンプレートによるパスの書き換え対策 Smarty Plugin その2

  • 2007-09-07 (金)
  • PHP

前回紹介したDreamweaver パス書き換え対策用Smarty Pluginですが、色々問題有りだったので修正です。

*WordPressのコードタグ内で”\”(バックスラッシュ)を受け付けてくれないので代わりに”¥”(円マーク)で記述しています。

function smarty_outputfilter_url($_src, &$smarty)
{
  $base_url = '/app/';//http://www.example.com/appがサイトトップの場合
  $escape_base_url = '/^' . str_replace('/', '\/', $base_url) . '/';
  $site_path = preg_replace($escape_base_url, "",$_SERVER["REQUEST_URI"]);
  $slash_count = substr_count($site_path, '/');
  $src = preg_replace('/< (script|img|link)(.*)(href|src)=("|¥')(¥.¥.¥/)+/', '<${1}${2}${3}=${4}' . str_repeat('../', $slash_count), $_src);
  return $src;
}

正規表現を修正したのと、プラグインの処理をprefilterからoutputfilterに変更しました。

”.”(ドット)を正規表現内でエスケープせずに記述するとは何とも情けないです。やはりパスをフィルタリングして書き換える力技じゃ安心して使用できないですね〜。他の Dreamweaver パス書き換え対策が思いつくまでは、この自作Smarty Pluginを使い続けようと思いますが、もっとスマートな解決策があればそちらに移行したいです・・・。

Home > PHP

Search
ezzy's latest bookmarks (delicious)
Feeds

Page Top