PublicプロパティとPublishedプロパティ
ところで、Delphi for PHP 2.0のIDEには、メインメニューの「編集」の中に「Publicプロパティの追加」と「Publishedプロパティの追加」に加えて、「PublicプロパティをPublishする」という3つのプロパティ関連のメニューがあります。
PublicプロパティとPublishedプロパティの違いは、後者は値が保持されるのだろうなと想像できるのですが、IDEのヘルプは日本語されていない上、実はここにはほとんど何も書かれていません。
まず「Publicプロパティを追加」を実行すると、コードエディタ上に以下のようなスケルトンが生成されます。プロパティ名はtestとして、デフォルト値はnullとしてあります。
protected $_test=null;
function readtest() { return $this->_test; }
function writetest($value) { $this->_test=$value; }
function defaulttest() { return null; }
同様に「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()で登録されています。
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
)3/4 |
| Index | |
| Delphi for PHPでExcel帳票を作ろう | |
| Page1 PHPExcelから帳票出力 コンポーネントの作成 |
|
| Page2 PHPExcelをコンポーネントに組み込む |
|
| Page3 PublicプロパティとPublishedプロパティ オブジェクトのシリアライズとアンシリアライズ |
|
| Page4 PHPExcel使用時の注意点 |
|
| 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の世界を体験してみよう |
|
- プログラムの実行はどのようにして行われるのか、Linuxカーネルのコードから探る (2017/7/20)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。最終回は、Linuxカーネルの中では、プログラムの起動時にはどのような処理が行われているのかを探る - エンジニアならC言語プログラムの終わりに呼び出されるexit()の中身分かってますよね? (2017/7/13)
C言語の「Hello World!」プログラムで使われる、「printf()」「main()」関数の中身を、デバッガによる解析と逆アセンブル、ソースコード読解などのさまざまな側面から探る連載。今回は、プログラムの終わりに呼び出されるexit()の中身を探る - VBAにおけるFileDialog操作の基本&ドライブの空き容量、ファイルのサイズやタイムスタンプの取得方法 (2017/7/10)
指定したドライブの空き容量、ファイルのタイムスタンプや属性を取得する方法、FileDialog/エクスプローラー操作の基本を紹介します - さらば残業! 面倒くさいエクセル業務を楽にする「Excel VBA」とは (2017/7/6)
日頃発生する“面倒くさい業務”。簡単なプログラミングで効率化できる可能性がある。本稿では、業務で使うことが多い「Microsoft Excel」で使えるVBAを紹介する。※ショートカットキー、アクセスキーの解説あり
|
|




