Do You PHP はてブロ

Do You PHPはてなからはてブロに移動しました

OCL(Object Constraint Language:オブジェクト制約言語)

今頃、目から鱗。やっぱりこの辺は全然追いつけてないというか、やってないというか。。。
OCL(Object Constraint Language:オブジェクト制約言語)を簡単に言うと、「インスタンスレベルでの具体的な数量的関係(制約)を補足するための言語」という感じでしょうか。オブジェクト/オブジェクト同士の制約を表すのに使われる言語のようです。また、@ITに非常に分かりやすい記事がありますので、是非一読を(2004年末の記事!)。

見てみると、かなりプログラムそのものに近いですね。それなら、実際のプログラムに書いちゃえるといいよね?ということで、実装しちゃった人がいるようです。


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.)

実装の中身はマジックメソッド(__call、__get、__set)+Reflectionで、annotationで制約を定義できるようになっています。
サイトに掲載されているサンプルでは、以下のような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行ちょっとのコードで、クラスやクライアントコードをシンプルに書けるようになる事の方がホントにスバラシイ。