Delphi for PHPを使い倒す!

Delphi for PHPを使い倒す!(後編)

Delphi for PHPでExcel帳票を作ろう

はやしつとむ
アナハイムテクノロジー株式会社

2009/11/25

PublicプロパティとPublishedプロパティ

 ところで、Delphi for PHP 2.0のIDEには、メインメニューの「編集」の中に「Publicプロパティの追加」と「Publishedプロパティの追加」に加えて、「PublicプロパティをPublishする」という3つのプロパティ関連のメニューがあります。

 PublicプロパティとPublishedプロパティの違いは、後者は値が保持されるのだろうなと想像できるのですが、IDEのヘルプは日本語されていない上、実はここにはほとんど何も書かれていません。

 まず「Publicプロパティを追加」を実行すると、コードエディタ上に以下のようなスケルトンが生成されます。プロパティ名はtestとして、デフォルト値はnullとしてあります。

●Publicプロパティのスケルトン
    protected $_test=null;

    function readtest() { return $this->_test; }
    function writetest($value) { $this->_test=$value; }
    function defaulttest() { return null; }

 同様に「Publishedプロパティの追加」を実行すると、コードエディタ上に以下のようなスケルトンが生成されます。

●Publishedプロパティのスケルトン
    protected $_test=null;

    function gettest() { return $this->_test; }
    function settest($value) { $this->_test=$value; }
    function defaulttest() { return null; }

 両者共に、protectedなクラスメンバ$_testをnullで初期化して。read/writeとget/setのアクセスメソッドとデフォルト値を返すメソッドを生成しているだけです。

 これだけでは、いったい何が違うのかがまるで分かりません。実は、ここにVCL for PHPがコンポーネント指向を実現できた最大の謎が隠されています。ユーザーが上記のプロパティにアクセスしようとする場合、どのようなコードを記述するでしょうか。以下のようになるはずです。

  $this->PHPExcelSimpleReport1->test = 'hogehoge';

 しかし、コードのどこを見てもそのままの「test」という要素は見当たりません。VCL for PHPでのプロパティアクセスは、PHP5で導入された「プロパティのオーバーロード」を利用しています。

 具体的には、PHPのマジックメソッドである__getと__setを利用してObjectクラスに実装されていますが、プロパティAnameからの読み出しに対しては'get'.Anameメソッドを探し、なければ'read'.Anameメソッドを探して、それでもない場合には、自分がComponentクラスを継承している場合にのみ、子コンポーネントを探して、それでもなければ例外を生成します。プロパティへの書き込みはこの逆になります。

オブジェクトのシリアライズとアンシリアライズ

 set/getとread/writeの違いが現れるのが、オブジェクトをセッションへSerializeする時とセッションからUnSerializeする時です。

 オブジェクトのSerializeは、forms.inc.php(58)からのVCLShutdown()関数で定義されています。アプリケーションオブジェクトに対して、全部の子要素をSerializeするように定義していて、この関数がPHPのファイナイライザとしてregister_shutdown_function()で登録されています。

●forms.inc.php(58)
function VCLShutdown()
{
        global $application;

        //This is the moment to store all properties in the session to retrieve them later
        $application->serializeChildren();

        //Uncomment this to get what is stored on the session at the last step of your scripts
        /*
        echo "<pre>";
        print_r($_SESSION);
        echo "<pre>";
        */
}

register_shutdown_function("VCLShutdown");

 それでは、Serializeはどのように行われるのでしょうか。Serializeが実装されているのは、Persitentクラスですが、この中で'set'の付いているメソッドがあるプロパティはセッションに保存するという判定を行っているのです。

 つまり、Publishedプロパティで追加されたset/getメソッド付きのプロパティは保存されますが、read/writeメソッド付きのPublicプロパティは保存されないということです。ちょっと長いですが、引用します。

        /**
         * Stores this object into the session.
         *
         * It uses PHP reflection
         * to get the published properties that will be stored. Component
         * persistance is achieved by iterating through all published properties
         * (the ones starting with get) and storing that value into the session.
         *
         * Those values are retrieved at the right time to recover the component
         * state.
         *
         * To be serialized, components need an owner to be unique on the session array.
         * If an owner is not assigned, an exception will be raised.
         *
         * @see readOwner(), readNamePath()
         * @link http://www.php.net/manual/en/language.oop5.reflection.php
         * @link http://www.php.net/manual/en/ref.session.php
         *
         */
        function serialize()
        {
                $owner=$this->readOwner();
 
        if ($owner!=null)
        {
 
                $_SESSION['insession.'.$this->readNamePath()]=1;
 
                $refclass=new ReflectionClass($this->ClassName());
                $methods=$refclass->getMethods();
 
                reset($methods);
 
                while (list($k,$method)=each($methods))
                {
                        $methodname=$method->name;
                        if ($methodname[0] == 's' && $methodname[1] == 'e' && $methodname[2] == 't')   // fast check of: substr($methodname,0,3)=='set'
                        {
                                $propname=substr($methodname, 3);
 
                                if($propname=='Name')
                                    $propvalue = $this->_name;
                                else
                                    $propvalue=$this->$propname;
 
                                if (is_object($propvalue))
                                {
                                    if ($propvalue->inheritsFrom('Component'))
                                    {
                                        $apropvalue='';
                                        $aowner=$propvalue->readOwner();
                                        if ($aowner!=null) $apropvalue=$aowner->getName().'.';
                                        $apropvalue.=$propvalue->getName();
                                        $propvalue=$apropvalue;
                                    }
                                    else if ($propvalue->inheritsFrom('Persistent'))
                                    {
                                       $propvalue->serialize();
                                    }
                                }
 
                                if ((!is_object($propvalue))  && ($this->allowserialize($propname)))
                                {
                                        $defmethod='default'.$propname;
 
                                        if (method_exists($this,$defmethod))
                                        {
                                            $defvalue=$this->$defmethod();
 
                                            if (typesafeequal($defvalue,$propvalue))
                                            {
                                                unset($_SESSION[$this->readNamePath().".".$propname]);
                                                continue;
                                            }
                                        }
 
                                        //TODO: Optimize this
                                        $_SESSION[$this->readNamePath().".".$propname]=$propvalue;
                                }
                        }
                }
 
                if ($this->inheritsFrom('Component')) $this->serializeChildren();
        }
        else
        {
                global $exceptions_enabled;
 
                if ($exceptions_enabled)
                {
                        throw new Exception('Cannot serialize a component without an owner');
                }
        }
  }

 serializeの中では、PHP5で実装されたリフレクションクラスの機能を使ってメソッドの存在をチェックしています。set付きのメソッドからプロパティ名を割り出して、プロパティ名がNameの時だけ内部フィールドの値を取得して、それ以外はプロパティインターフェイスであるgetメソッドを通してプロパティ値を取得しています。こうしないと、自分自身をオブジェクトとして取得してしまって循環参照になってしまうからです。

 プロパティがコンポーネントの場合は、最後にSerializeChildrenメソッドで一括してシリアライズするので、ここではオーナー名+コンポーネント名を値として保存するように変換しています。

 また、ComponentになっていないPersistent由来のオブジェクトがあった場合には、直接シリアライズするようにしています。あまりないとは思いますが、こういうものを作っても対応できるようになっています。

 逆に、Persistent由来でなければ当然Serializeメソッドもないし、シリアライズの手続きにも入らないわけです。

 その後、デフォルト値のチェックをしています。メニューからプロパティを追加したときに生成される3つのメソッドの内の1つですが、'default'+プロパティ名のメソッドがある場合には、デフォルト値と現在の値を比較し、同じであればシリアライズをスキップします。

 こうすることでセッションへの負荷を減らす工夫をしているわけですね。最後に、子のコンポーネントに対してすべてシリアライズする指令を発して終わります。

 実際にセッションに保存されている内容は、以下のようなものになっています。先ほどのVCLShutdown関数の中でコメントアウトされているところを外すと、簡単に内容を確認できます。

Array
(
    [restore_session] => 1
    [DBGSESSID] => 1@clienthost:7869
    [comps.vclapp.Unit1] => Array
        (
            [Button1] => Array
                (
                    [0] => Unit1
                    [1] => Button
                )

            [Label1] => Array
                (
                    [0] => Unit1
                    [1] => Label
                )

            [Button2] => Array
                (
                    [0] => Unit1
                    [1] => Button
                )

            [Button3] => Array
                (
                    [0] => Unit1
                    [1] => Button
                )

        )

    [vclapp.Unit1._reallastresourceread] => Array
        (
            [0] => C:\Users\tomneko\Documents\Delphi for PHP Projects\test3\unit1.xml.php
        )

    [insession.vclapp.Unit1] => 1
    [insession.vclapp.Unit1.Layout] => 1
    [vclapp.Unit1.DocType] => dtNone
    [vclapp.Unit1.Caption] => Unit1
    [insession.vclapp.Unit1.Font] => 1
    [vclapp.Unit1.Background] => 
    [vclapp.Unit1.IsMaster] => 0
    [vclapp.Unit1.Width] => 800
    [vclapp.Unit1.Height] => 600
    [vclapp.Unit1.Name] => Unit1
    [insession.vclapp.Unit1.Button1] => 1
    [vclapp.Unit1.Button1.OnClick] => Button1Click
    [vclapp.Unit1.Button1.Caption] => ラベル追加
    [insession.vclapp.Unit1.Button1.Font] => 1
    [vclapp.Unit1.Button1.Left] => 16
    [vclapp.Unit1.Button1.Top] => 64
    [vclapp.Unit1.Button1.Width] => 75
    [vclapp.Unit1.Button1.Height] => 25
    [vclapp.Unit1.Button1.Name] => Button1
    [insession.vclapp.Unit1.Label1] => 1
    [vclapp.Unit1.Label1.Caption] => Label1
    [insession.vclapp.Unit1.Label1.Font] => 1
    [vclapp.Unit1.Label1.Left] => 16
    [vclapp.Unit1.Label1.Top] => 104
    [vclapp.Unit1.Label1.Width] => 75
    [vclapp.Unit1.Label1.Height] => 13
    [vclapp.Unit1.Label1.Name] => Label1
    [insession.vclapp.Unit1.Button2] => 1
    [vclapp.Unit1.Button2.OnClick] => Button2Click
    [vclapp.Unit1.Button2.Caption] => ラベル削除
    [insession.vclapp.Unit1.Button2.Font] => 1
    [vclapp.Unit1.Button2.Left] => 112
    [vclapp.Unit1.Button2.Top] => 64
    [vclapp.Unit1.Button2.Width] => 75
    [vclapp.Unit1.Button2.Height] => 25
    [vclapp.Unit1.Button2.Name] => Button2
    [insession.vclapp.Unit1.Button3] => 1
    [vclapp.Unit1.Button3.OnClick] => Button3Click
    [vclapp.Unit1.Button3.Caption] => 別ページ
    [insession.vclapp.Unit1.Button3.Font] => 1
    [vclapp.Unit1.Button3.Left] => 16
    [vclapp.Unit1.Button3.Top] => 16
    [vclapp.Unit1.Button3.Width] => 75
    [vclapp.Unit1.Button3.Height] => 25
    [vclapp.Unit1.Button3.Name] => Button3
)
prev
3/4
next

Index
Delphi for PHPでExcel帳票を作ろう
  Page1
PHPExcelから帳票出力
コンポーネントの作成
  Page2
PHPExcelをコンポーネントに組み込む
Page3
PublicプロパティとPublishedプロパティ
オブジェクトのシリアライズとアンシリアライズ
  Page4
PHPExcel使用時の注意点

index Delphi for PHPを使い倒す!

 Coding Edgeお勧め記事
いまさらアルゴリズムを学ぶ意味
コーディングに役立つ! アルゴリズムの基本(1)
 コンピュータに「3の倍数と3の付く数字」を判断させるにはどうしたらいいか。発想力を鍛えよう
Zope 3の魅力に迫る
Zope 3とは何ぞや?(1)
 Pythonで書かれたWebアプリケーションフレームワーク「Zope 3」。ほかのソフトウェアとは一体何が違っているのか?
貧弱環境プログラミングのススメ
柴田 淳のコーディング天国
 高性能なIT機器に囲まれた環境でコンピュータの動作原理に触れることは可能だろうか。貧弱なPC上にビットマップの直線をどうやって引く?
Haskellプログラミングの楽しみ方
のんびりHaskell(1)
 関数型言語に分類されるHaskell。C言語などの手続き型言語とまったく異なるプログラミングの世界に踏み出してみよう
ちょっと変わったLisp入門
Gaucheでメタプログラミング(1)
 Lispの一種であるScheme。いくつかある処理系の中でも気軽にスクリプトを書けるGaucheでLispの世界を体験してみよう
  Coding Edgeフォーラムフィード  2.01.00.91


Coding Edge フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

>

Coding Edge 記事ランキング

本日 月間