Unknown::Programming このページをアンテナに追加 RSSフィード

2011-12-18 不逞

MDB2で複数のDB接続を行う場合の恐ろしい罠

MDB2を使って複数のDB接続したいなんてことあるよね。

ところがどっこい、実際にコードを書いてみると恐ろしいことが起きた。それは二つ目のDB接続をすると一つ目のDB接続が二つ目のDB接続に乗っ取られてしまうという!

<?php
// fooというデータベースへ接続し、hoge_tblのカウントする
$dbh = MDB2::factory('mysql://user:pass@localhost/foo?charset=utf8');
$res = $dbh->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 5件

// で次にbarへ接続し同様の処理
$dbh2 = MDB2::factory('mysql://user:pass@localhost/bar?charset=utf8');
$res = $dbh2->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 2件

// そして、fooに接続してるDBハンドルでもういちどカウントしてみると・・・
$res = $dbh->query('SELECT COUNT(*) FROM hoge_tbl');
$row = $res->fetchRow();
$res->free();
print $row[0]; // 2件

このコードが5、2、2と表示されるわけです。なんでこんなことに・・・、と思って色々調べてみると、どうやら同一ホストへの接続の場合、いろいろキャッシュ的な感じでごにょごにょむにょむにょして同一の接続扱いになってしまうようです。

でまぁ原因はわかったけど、じゃあどうやって複数接続すればいいんだよ!ということで、これを明示的に別の接続として扱うためのnew_linkってオプションを発見。

<?php
// new_link=trueをつけましょう!
$dbh2 = MDB2::factory('mysql://user:pass@localhost/bar?charset=utf8&new_link=true');

こうすることで二つ目のDB接続は新しい接続だよ!と明示され、晴れて同一の接続じゃなくなりました。

いやぁnew_linkなんていう便利なオプションがあるんですね() # デフォでtrueにしとけよ!って感じですが。

ということで今頃気付いたのかよって思う人もいるかもしれませんが、知らなかった人はもしかしたら気付かない間に別のDBに接続されててバグだらけになってた、なんてことになってるかもしれないのでご愁傷様です。

2011-06-16 脱却

クラス内で定義した定数の一覧を得る方法

<?php

class Foo {
    const BAR = 1;
    const BAZ = 2;
}

get_defined_constantsを使えば全ての定数が取れると思いきや、上記のようなクラス内で定義した定数は取れなかった。

どうやったら取れるのか。PHPお得意の大量の関数の中に一覧取れるヤツが必ずあるはず!と思いきや、なかった。

諦めかけたその時、ReflectionClassというクラスを使えば取れることがわかったのだ!

<?php

class Foo {
    const BAR = 1;
    const BAZ = 2;
}
$reflect = new ReflectionClass('Foo');
print_r( $reflect->getConstants() );
$ php test.php
Array
(
    [BAR] => 1
    [BAZ] => 2
)

PHP5になってから、PHPさんは関数からの脱却を図っている模様だ。

目的のものが標準関数に存在しなかったら、標準クラスを調べてみようというお話でした。

2011-03-11 精緻

PHP5.2とPHP5.3でArrayObjectの挙動が違う話

ハマッタのでメモ。

<?php

$a = new ArrayObject(array('test' => 1));

print_r($a);

これをPHP5.2とPHP5.3で実行すると以下のようになる

# PHP5.2
ArrayObject Object
(
    [test] => 1
)

# PHP5.3
ArrayObject Object
(
    [storage:ArrayObject:private] => Array
        (
            [test] => 1
        )

)

PHP5.3の場合、なにやらデータの持ち方が新しくなったのかstorage:ArrayObject:privateとか言うのが増えてる。

これはおそらくstorageというprivate変数に配列のデータを持つようになったという感じか。まぁそれ自体は問題ない。

問題は以下のコード

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject($a);

print_r($b);

実行結果

# PHP5.2
ArrayObject Object
(
    [test] => 1
)

# PHP5.3
ArrayObject Object
(
    [storage:ArrayObject:private] => ArrayObject Object
        (
            [storage:ArrayObject:private] => Array
                (
                    [test] => 1
                )

        )

)

PHP5.2のときにはArrayObjectにArrayObjectを渡しても配列として処理されていたが、PHP5.3からはArrayObjectがネストされてしまう。

正直ArrayObjectってのは配列をシュミレートするもののはずなのでArrayObjectをオブジェクトとして保存してしまうPHP5.3の仕様には聊か首を捻らざるを得ない。

さらに怖いのが、この状態でもちゃんと配列として動いてしまうのでネストされてることに気付かないということだ。

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject($a);

print($b['test']); # ちゃんと「1」と表示される

つまり普通に扱ってる分には配列として動いてくれるので、めちゃくちゃネストしちゃっててもぜんぜん気付かない。

気付いたときには

ArrayObject Object
(
    [storage:ArrayObject:private] => ArrayObject Object
        (
            [storage:ArrayObject:private] => ArrayObject Object
                (
                    [storage:ArrayObject:private] => ArrayObject Object
                        (
                            [storage:ArrayObject:private] => ArrayObject Object
                                (
                                    [storage:ArrayObject:private] => ArrayObject Object
                                        (
                                            [storage:ArrayObject:private] => ArrayObject Object
                                                (
                                                    [storage:ArrayObject:private] => ArrayObject Object
                                                        (
                                                            [storage:ArrayObject:private] => Array
                                                                (
                                                                    [test] => 1
                                                                )

                                                        )

                                                )

                                        )

                                )

                        )

                )

        )

)

こんなことになってるかもしれない。(ってかなってたw)

ArrayObjectを良く使う人はPHP5.3では注意しましょう!

ちなみに解決方法としては色々あるだろうけど、ArrayObjectに渡すときにarrayにキャストするとかしましょう。

<?php

$a = new ArrayObject(array('test' => 1));
$b = new ArrayObject((array)$a); // array型へキャスト

2010-09-28 落語

Smartyのテンプレートで配列や連想配列を定義する方法

Smartyのテンプレートでは配列や連想配列を定義できない。そんな風に考えていた時期が俺にもありました。

さてググってみるとプラグインを利用する方法を発見しました。

前者は自作プラグインを作る方法、後者はsplitプラグインを利用する方法ですね。

しかしプラグインを利用する方法には以下の三つの問題点があります。

  1. 文字列の配列しか生成できない
  2. 多次元配列を生成できない
  3. メソッドに引数にそのまま渡せない

あくまで文字列を分割して配列を生成してるだけなのでオブジェクトの配列や多次元配列を生成できません。

またメソッドに渡せないというのは以下のようなケース

エラー。メソッドの引数にプラグインを使用できない
{$foo->bar(','|split:'a,b,c')}

一旦一時変数に入れないと渡せない。
{assign var='hoge' value=','|split:'a,b,c')}
{$foo->bar($hoge)}

さてこれらの問題を超無理やり解決するために僕が考えた方法はこれ。

<?php
class SmartyUtil {
    function ary () {
        $param = func_get_args();
        return $param;
    }
}

$smarty->assign('u',new SmartyUtil);
$smarty->display('test.tpl');

テンプレート側はこう。

{$u->ary('a','b')}

Smartyに常に渡すユーティリティクラスを用意しておいて、そこにaryというメソッドを用意しておく。

aryの実装は引数をそのまま配列にして返すだけのシンプルなもの。

これで文字でもオブジェクトでも何でも配列化が可能となる。

またメソッド呼び出しはネストが可能なので多次元配列も簡単に作れる

{$u->ary($u->ary('a','b'),$u->ary('c','d'))}

お次は連想配列。これもaryと同じく、ユーティリティクラスにメソッドを追加する。

<?php
class SmartyUtil {
    function ary_assoc () {
        $ret = array();
        $param = func_get_args();
        $count = count($param);
        if ($count  % 2 ) $param []= null;
        for($i=0;$i<$count;$i+=2) $ret[$param[$i]] = $param[$i+1];
        return $ret;
    }
}
{$u->ary_assoc('A','1','B','2')}

お後がよろしいようで。

2010-09-15 流麗

PHPでスクリプト実行ファイルのディレクトリを得る方法

<?php

print dirname(__FILE__); // PHP5.3なら「__DIR__」でもOK

これだけで取ってこれる。

が、__FILE__定数はこの定数が書かれているファイルの絶対パスを取得するだけのものなので、この処理を別ファイルで関数化してもうまくいかない。

なのでrealpathと$argvの組み合わせを利用して関数化を実現する。

<?php

function get_script_dir () {
    return dirname(realpath($GLOBALS['argv'][0]));
}

これでOK。