Mozo.PHP (その2)

(追記: タイトルの一部にタグを入れているところが正しくタグとして認識されなかったので直しました。重複RSSエントリが発生したらすみません。)
前日の続き。

ダウンロードはここ

主な変更点:

  • std::string を戻り値にできないバグの修正。
  • HashTable の変換をサポート。
  • HashTable のラッパーに STL ライクなイテレータを実装。
  • const_value_ptr を廃止、value_ptr に統一。

mozo::php::value について

mozo::php::value は zval 構造体を継承したクラスです。zval と全く同じフィールドを持ち、いつでも static_cast で zval にキャストして使うことができます。long, double, mozo::php::string など zval のサポートする型を受け取るコンストラクタを持っているので、マクロがなくとも簡単に zval を構築することができます。

例えば

zval val;
ZVAL_DOUBLE(&val, 12.345);

と、

mozo::php::value val(12.345);

とはほぼ同じ効果があります。

通常、zval を Zend Engine の ALLOC_INIT_ZVAL() や MAKE_STD_ZVAL() マクロで初期化すると参照カウント (refcount) は 1 にセットされた状態になりますが、mozo::php::value では mozo::php::value_ptr との連携の都合で refcount=0 となるように初期化しています。

mozo::php::value_ptr について

mozo::php::value_ptr は、mozo::php::value の intrusive なスマートポインタとして動作します。zval のポインタ (zval*) の代わりに使うことで、自動的に参照カウントを行い、いらなくなったところで破棄してくれます。コンストラクタ引数の問題や、演算子のオーバーロードの問題から、もはや boost::intrusive_ptr を使うメリットがなくなったので独自の実装となっています。

基本的に zval** が出現する場所では zval が共有されており、参照カウントされているとみてほぼ間違いないですが、このようなケースでは reinterpret_cast で mozo::php::value_ptr にキャストして利用すると便利です。単に zval * が出現する場所では、これが C++ 的な参照のセマンティクスを持つポインタなのか、純粋に所有権が曖昧な zval を指すポインタなのか、よく見極める必要があるでしょう。

mozo::php::value_ptr は、非 const ポインタとして "*" や "->" を用いて参照した場合に Copy-on-Write の処理 (SEPARATE_ZVAL_IF_NOT_REF) を行います。これに留意しないと思わぬ速度低下を引き起こす可能性があります。ZendEngine の関数はイミュータブルな操作でも非 const ポインタを受け取るような API になっているので *1 適宜 const_cast を行うとよいでしょう。

using namespace mozo;

void f1()
{
    // 最初の呼び出しでリファレンスカウントは 1 にセットされる
    php::value_ptr s(new php::value("abc"));
    f2(s);
    // f1() の終了時にリファレンスカウントは -1 され、0 になるので
    // new で確保された php::value オブジェクトは解放される
}

// f2() が呼び出された時点でリファレンスカウントが +1 され、終了時に -1 される。
void f2(php::value_ptr s)
{
    std::cout << *s << std::endl;
}

mozo::php::to_value_ptr()

zval を使うほとんどの機会で mozo::php::value_ptr が活躍しますが、mozo::php::value_ptr はあくまでポインタなので、コンストラクタで long や double など mozo::php::value 以外の引数の型を受け取ることはできません。そこで、引数が mozo::php::value_ptr ならそのままスルー、それ以外の型なら new mozo::php::value(...) を行ってそれを mozo::php::value_ptr でラップしたものを返すというユーティリティ関数 mozo::php::to_value_ptr() が用意されています。

using namespace mozo;
php::value::value_ptr v(php::to_value_ptr("test"));

mozo::php::hashtable

zend_hash.h の HashTable をラップするクラスです。hash"table" ですが、STL の pair associative container に近い使い方ができるように設計されています。ただし、いくつかのメソッドは concept で要求されているものと同じ名前を持つもののシグニチャの互換性がまったくありませんのであしからず。
php連想配列に相当する型は mozo::php::hashtable で、よく使われるので、これは mozo::php::array に typedef されています。

次の例は、a という hashtable にキーが整数の1で値がabcであるようなエントリを追加し、そのキーが含まれているかどうかをチェックして結果を表示します。

php::array a;
a.insert(php::array::value_type(1, php::to_value_ptr("abc")));
std::cout << a.contains(1) << std::endl;

なお、エントリを参照するには次のようにします。

std::cout << *a[1] << std::endl;

次のようにしても値を更新 / 追加できますが、おすすめできません。

php::array a;
a[1] = php::to_value_ptr("abc")));

完全なサンプルを以下に示します。

#include <iostream>
#include <algorithm>
#include "mozo/php/module.hpp"
#include "mozo/php/function.hpp"

using namespace mozo;

class m005_module
    : public php::module,
      public php::function_container<m005_module> {
public:
    class handler
        : public php::module::handler {
    public:
        handler(m005_module* mod)
            :php::module::handler(mod) {}

        struct print {
            void operator()(php::array::const_reference r) {
                std::cout << r.second << std::endl;
            }
        };

        static void array_iter(const php::array& a) {
            for (php::array::const_iterator i(a.begin()), e(a.end());
                    i != e; ++i) {
                std::cout << (::std::string)(*i).first << std::endl;
            }
            std::for_each(a.begin(), a.end(), print());
        }
    };

public:
    m005_module(zend_module_entry* entry)
        : php::module(entry) {
        entry->functions =
             defun("array_iter", &handler::array_iter);
    }
};

#define MOZO_PHP_MODULE_NAME m005
#define MOZO_PHP_MODULE_CAPITALIZED_NAME M005
#define MOZO_PHP_MODULE_VERSION "0.1"
#define MOZO_PHP_MODULE_CLASS_NAME m005_module

#include "mozo/php/module_def.hpp"

このサンプルで定義している array_iter() 関数は、渡された配列の要素を列挙する関数です。

<?php
array_iter(array(1, 2, "abc", 4, 5));
?>

を実行すると、次のような結果が出力されます。

0
1
2
3
4
1
2
abc
4
5

*1:当初は const を持たないコンパイラとの互換性を考慮してのことだったようだが、現在ではあまり気にされていない。