OCL(Object Constraint Language:オブジェクト制約言語)
今頃、目から鱗。やっぱりこの辺は全然追いつけてないというか、やってないというか。。。
OCL(Object Constraint Language:オブジェクト制約言語)を簡単に言うと、「インスタンスレベルでの具体的な数量的関係(制約)を補足するための言語」という感じでしょうか。オブジェクト/オブジェクト同士の制約を表すのに使われる言語のようです。また、@ITに非常に分かりやすい記事がありますので、是非一読を(2004年末の記事!)。
見てみると、かなりプログラムそのものに近いですね。それなら、実際のプログラムに書いちゃえるといいよね?ということで、実装しちゃった人がいるようです。
実装の中身はマジックメソッド(__call、__get、__set)+Reflectionで、annotationで制約を定義できるようになっています。
While the goal of the code camp was to do some nifty things with OCL and Ruby, I wanted to see if I could create a method to easily implement OCL constraints in arbitrary PHP objects. (Mainly triggered by one of the students, who discovered that a search for 'OCL and PHP' only resulted in pages that were written in PHP, and about OCL. There weren't any pages about how OCL can be used in PHP.)
サイトに掲載されているサンプルでは、以下のようなPersonクラス
<?php class Person { public $name; public function __construct($name) { $this->name = $name; } }
と、CreditCardクラス
<?php class CreditCard { public $limit; public $owner; public $expirationdate; public $name; public function __construct($limit) { $this->limit = $limit; } function setOwner($owner) { $this->owner = $owner; } function setName($name) { $this->name = $name; $this->validate($name); } function withdraw($amount) { $this->limit -= $amount; } }
を例に挙げています。
で、これらを使ったサンプルコードは、
<?php require_once 'CreditCard.class.php'; require_once 'Person.class.php'; /** * @see http://www.jansch.nl/2007/11/15/validating-ocl-constraints-in-php-objects/ */ $cc = new CreditCard(2000); $cc->owner = new Person("Hans"); $cc->name = "Hans"; $cc->expirationdate = "2006-08-12"; $cc->withdraw(100); $cc->withdraw(3000);
のようになりますが、クレジットカードの有効期限が切れています。また、最後の1行も本来エラーにしたい箇所です(2000 < 100 + 3000なので)。しかし、何も制約がないので、実行するとエラーは発生しません。そのため、制約を実現するための処理を実装する必要があります。その他、所有者(ownerプロパティ)と使用者(Personオブジェクトのnameプロパティ)が一致性チェックも同様です。
ここで、OCLを実現するためのクラスを導入し、制約をannotationとして付けるとどうなるか?
OCLクラスは、以下のURLからダウンロードできます(OCLWrapperクラス)。
そして、制約を付けたCreditCardクラスは以下の通りです。
<?php /** * @constraint self.limit > 0 */ class CreditCard { public $limit; public $owner; public $expirationdate; /** * @constraint self.name = owner.name */ public $name; public function __construct($limit) { $this->limit = $limit; } function setOwner($owner) { $this->owner = $owner; } function setName($name) { $this->name = $name; $this->validate($name); } /** * @constraint self.expirationdate > now */ function withdraw($amount) { $this->limit -= $amount; } }
サンプルコードは以下のようになります。OCLWrapperオブジェクトに色々突っ込んでいるのがちょっとナニですが。。。
<?php require_once 'CreditCard.class.php'; require_once 'Person.class.php'; require_once 'OCLWrapper.class.php'; /** * @see http://www.jansch.nl/2007/11/15/validating-ocl-constraints-in-php-objects/ */ try { $cc = new OCLWrapper(new CreditCard(2000)); $cc->owner = new OCLWrapper(new Person("Hans")); $cc->name = "Hans"; $cc->expirationdate = "2006-08-12"; $cc->withdraw(100); $cc->withdraw(3000); } catch (Exception $e) { echo $e->getMessage()."\n"; } echo "Owner's name is: ".$cc->owner->name."\n"; echo "Cardholder's name is: ".$cc->name."\n"; echo "Card balance: ".$cc->limit."\n";
で、実行してみると、
$ php sample02.php Constraint 'self.expirationdate > now' violated Owner's name is: Hans Cardholder's name is: Hans Card balance: 2000 $
と正しくチェックが入っています。また、
<?php : $cc->expirationdate = "2010-08-12"; :
として実行すると、
$ php sample02.php
Constraint 'self.limit > 0' violated
Owner's name is: Hans
Cardholder's name is: Hans
Card balance: 1900
$
となります。おお!スバラシー!
使った感じ、OCLWrapperという名前はどちらかというとOCLContainerのような気がしなくもないですね。
OCLWrapperクラスの具体的な実装はソース(コメント込みで222行)を読んでもらうと分かりますが、制約の種類を増やすのにコードに追記する必要があるのがネックでしょうか。validatorをうまく追加できるようになると、結構良い感じに使えそうです。
逆に200行ちょっとのコードで、クラスやクライアントコードをシンプルに書けるようになる事の方がホントにスバラシイ。