This project is no longer maintained and has been archived.

はじめに

[http://www.postgresql.jp/document/pg820doc/html/ddl-constraints.html PostgreSQLのドキュメント]より引用: > データ型は、テーブルに格納するデータの種類を限定するための方法です。しかし、多くのアプリケーションでは、型が提供する制約では精密さに欠けます。例えば、製品の価格が入る列には、おそらく正数のみを受け入れるようにする必要があります。しかし、正数のみを受け入れるという標準のデータ型はありません。また、他の列や行に関連して列データを制約したい場合もあります。例えば、製品の情報が入っているテーブルでは、1つの製品番号についての行が2行以上あってはなりません。

Doctrineによってカラムとテーブルでポータブルな制約を定義できます。制約によってテーブルのデータを望むままにコントロールできます。ユーザーがカラムの制約に違反するデータを保存しようとすると、エラーが起動します。値がデフォルトの値の定義から来る場合でもこれが適用されます。

アプリケーションレベルのバリデータと同じようにDoctrineの制約はデータベースレベルの制約として振る舞います。このことは二重のセキュリティを意味します: 間違った種類の値とアプリケーションを許容しません。

Doctrineの範囲内で利用可能なバリデーションの全リストは次の通りです:

||  バリデータ(引数) ||  制約 ||  説明 || || notnull || NOT NULL || アプリケーションとデータベースの両方のレベルで'not null'制約を確認する || || email || || 値が有効なEメールであるかチェックする || || notblank || `NOT NULL`` \|\| not blankであることをチェックする \|\| \|\| ``nospace` || || スペースがないことをチェックする || || past || CHECK制約 || 値が過去の日付がチェックする || || future || || 値が未来の日付かチェックする || || minlength(length) || || 値が最小長を満たすかチェックする || || country || || 値が有効な国コードであるかチェックする || || ip || || 値が有効なIP(internet protocol)アドレスかチェックする || || htmlcolor || || 値が妥当なhtmlの色であるかチェックする || || range(min, max) || CHECK制約 || 値が引数で指定された範囲に収まるかチェックする || || unique || UNIQUE制約 || 値がデータベーステーブルでユニークかチェックする || || regexp(expression)|| || 値が正規表現にマッチするかチェックする || || creditcard || || 文字列が適正なクレジットカード番号であるかチェックする || || digits(int, frac) || 精度とスケール || 値が整数の//int//の桁数を持ち分数の//frac//の桁数を持つかチェックする || || date || || 値が有効な日付であるかチェックする || readonly || || フィールドが修正され読み込み限定のフィールドに強制するようにfalseを返すかチェックする || || unsigned || || 整数値が符号無しであるかチェックする || || usstate || || 値が有効なUSの州コードであるかチェックする ||

下記のコードはバリデータの使い方とカラムでバリデータ用の引数を指定する方法の例です。

minlengthバリデータを使う例です。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'minlength' => 12
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: username: type: string(255) minlength: 12 # ...

= 例 =

Not Null

not-null制約はカラムがnullの値を想定してはならないことを指定します。not-null制約は常にカラムの制約として記述されます。

次の定義はカラム名用にnotnull制約を使います。このことは指定されたカラムはnullの値を受け取らないことを意味します。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'notnull' => true,
            'primary' => true,
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます。:

# schema.yml

User: columns: username: type: string(255) notnull: true primary: true #

このクラスがデータベースにエクスポートされるとき次のSQL文が実行されます(MySQLにて):

CREATE TABLE user (username VARCHAR(255) NOT NULL, PRIMARY

KEY(username))

not-null制約はアプリケーションレベルのバリデータとして振る舞います。このことはDoctrineのバリデータが有効な場合、Doctrineは指定されたカラムを保存するときにnullの値が含まれないことを自動的にチェックします。

これらのカラムがnullの値を含む場合Doctrine\_Validator_Exceptionが起動します。

Eメール

Eメールバリデータは入力された値が本当に有効なEメールアドレスでありアドレスドメイン用のMXレコードがEメールアドレスとして解決することをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('email', 'string', 255, array(
            'email'   => true
        )
    );
}

}

YAMLフォーマットでの同じサンプルは次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... email: type: string(255) email: true # ...

無効なEメールアドレスを持つユーザーを作成しようとするとバリデートは行われません:

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->email = 'jonwage';

if ( ! $user->isValid()) { echo 'User is invalid!'; }

jonwageは有効なEメールアドレスではないので上記のコードは例外を投げます。これをさらに推し進めてEメールアドレスは有効であるがドメイン名が無効な例は次の通りです:

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->email = '[email protected]';

if ( ! $user->isValid()) { echo 'User is invalid!'; }

ドメインのsomefakedomainiknowdoesntexist.comが存在せずPHPの`[http://www.php.net/checkdnsrr checkdnsrr()]`関数はfalseを返すので上記のコードはエラーになります。

Not Blank

not blankバリデータはnot nullバリデートと似ていますが空の文字列もしくは空白文字が含まれる場合はエラーになります。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'notblank'   => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: username: type: string(255) notblank: true # ...

1つの空白スペースを含むusernameを持つUserレコードを保存しようとすると、バリデーションはエラーになります:

// test.php

// ... $user = new User(); $user->username = ' ';

if ( ! $user->isValid()) { echo 'User is invalid!'; }

No Space

no spaceバリデータは単純です。値にスペースが含まれないことをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'nospace'   => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: username: type: string(255) nospace: true # ...

スペースを含むusernameを持つUserを保存しようとするとバリデーションが失敗します:

$user = new User(); $user->username = 'jon wage';

if ( ! $user->isValid()) { echo 'User is invalid!'; }

Past

pastバリデータは値が過去の有効な日付であるかをチェックします。この例ではbirthdayカラムを持つUserモデルがあり日付が過去のものであることバリデートします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('birthday', 'timestamp', null, array(
            'past' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... birthday: type: timestamp past: true # ...

過去にはない誕生日を設定しようとするとバリデーションエラーになります。

Future

futureバリデータはpastバリデータの反対でデータが未来の有効な日付であることをチェックします。この例ではnext\_appointment_dateカラムを持つUserモデルがあり日付が未来のものであることをバリデートします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('next_appointment_date', 'timestamp', null, array(
            'future' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... next_appointment_date: type: timestamp future: true # ...

予約日が未来のものでなければ、バリデーションエラーになります。

最小長

最小長は正確な表現ではありません。文字列の長さが最小の長さよりも大きいことをチェックします。この例ではpasswordカラムを持つUserモデルがありpasswordの長さが少なくとも5文字であることを確認します。

// models/User.php

class User extends BaseUser { public function setTableDefinition() { parent::setTableDefinition();

    // ...

    $this->hasColumn('password', 'timestamp', null, array(
            'minlength' => 5
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... password: type: timestamp minlength: 5 # ...

5文字より短いpasswordを持つUserを保存しようとすると、バリデーションはエラーになります。

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->password = 'test';

if ( ! $user->isValid()) { echo 'User is invalid because "test" is only 4 characters long!'; }

Country

countryバリデータは値が有効なcountryコードであるかチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('country', 'string', 2, array(
            'country' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... country: type: string(2) country: true # ...

無効な国コードを持つUserを保存しようとするとバリデーションがエラーになります。

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->country_code = 'zz';

if ( ! $user->isValid()) { echo 'User is invalid because "zz" is not a valid country code!'; }

IPアドレス

IPアドレスバリデータは値が有効なIPアドレスであることをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('ip_address', 'string', 15, array(
            'ip' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... ip_address: type: string(15) ip: true # ...

無効なIPアドレスを持つUserを保存しようとするとバリデーションはエラーになります。

$user = new User(); $user->username = 'jwage'; $user->ip_address =

'123.123';

if ( ! $user->isValid()) { echo 'User is invalid because "123.123" is not a valid ip address }

HTML Color

htmlcolorバリデータは値が有効な16進法のhtmlカラーであることをチェックします。

// models/User.php

class User extends BaseUser { public function setTableDefinition() { parent::setTableDefinition();

    // ...

    $this->hasColumn('favorite_color', 'string', 7, array(
            'htmlcolor' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... favorite_color: type: string(7) htmlcolor: true #

favorite_colorカラム用の無効なhtmlカラーの値を持つUserを保存しようとするとバリデーションはエラーになります。

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->favorite_color = 'red';

if ( ! $user->isValid()) { echo 'User is invalid because "red" is not a valid hex color'; }

Range

rangeバリデータは値が与えられた数の範囲にあることをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('age', 'integer', 3, array(
            'range' => array(10, 100)
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... age: type: integer(3) range: [10, 100] # ...

10才未満もしくは100才を越えるUserを保存しようとすると、バリデーションはエラーになります。

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->age = '3';

if ( ! $user->isValid()) { echo 'User is invalid because "3" is less than the minimum of "10"'; }

範囲配列の0もしくは1キーのどちらかを省略することで最大と最小の値をバリデートするためにrangeバリデータを使うことができます:

// models/User.php

class User extends BaseUser { public function setTableDefinition() { parent::setTableDefinition();

    // ...

    $this->hasColumn('age', 'integer', 3, array(
            'range' => array(1 => 100)
        )
    );
}

}

上記の例では最大年齢は100才になります。最小値を指定するには、範囲配列で1の代わりに0を指定します。

YAML構文の例は次のようになります:

# schema.yml

User: columns: # ... age: type: integer(3) range: 1: 100 # ...

Unique

unique制約は1つのカラムもしくはカラムのグループに含まれるデータがテーブルのすべての列に関してユニークであること保証します。

一般的に、制約に含まれるカラムのすべての値が等しい複数の列が存在するときにunique制約は破られます。しかしながら、この比較では2つのnull値は等しいとはみなされません。このことはunique制約の下で制約の課された少なくとも1つのカラムでnull値を含む重複列を保存することが可能であることを意味します。このビヘイビアはSQL標準に準拠しますが、一部のデータベースはこのルールに従いません。ですのでポータルなアプリケーションを開発するときは注意してください。

次の定義はカラム名に対してunique制約を使います。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'unique' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: username: type: string(255) unique: true # ....

NOTE 主キーは既にuniqueなので主キー以外のカラムに対してのみunique制約を使うべきです。

正規表現

正規表現バリデータは独自の正規表現に対してカラムの値をバリデートするシンプルな方法です。この例ではユーザー名は有効な文字もしくは数字だけを含むことを確認します。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('username', 'string', 255, array(
            'regexp' => '/[a-zA-Z0-9]/'
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: username: type: string(255) regexp: '/ [1]+$/' # ...

文字か数字以外の文字を含むusernameを持つUserを保存しようとすると、バリデーションはエラーになります:

// test.php

// ... $user = new User(); $user->username = '[jwage';

if ( ! $user->isValid()) { echo 'User is invalid because the username contains a [ character'; }

クレジットカード

creditcardバリデータは値が本当に有効なクレジットカード番号であることをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('cc_number', 'integer', 16, array(
            'creditcard' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

Read Only

readonlyバリデータが有効なカラムを修正しようとするとバリデーションに失敗します。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('readonly_value', 'string', 255, array(
            'readonly' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... readonly_value: type: integer(16) readonly: true #

Userオブジェクトインスタンスからreadonly_valueという名前のカラムを修正しようとすると、バリデーションはエラーになります。

Unsigned

unsignedバリデータは整数が符号無しであることをチェックします。

// models/User.php

class User extends BaseUser { // ...

public function setTableDefinition()
{
    parent::setTableDefinition();

    // ...

    $this->hasColumn('age', 'integer', 3, array(
            'unsigned' => true
        )
    );
}

}

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

User: columns: # ... age: type: integer(3) unsigned: true # ...

マイナス年齢のUserを保存しようとするとバリデーションはエラーになります:

// test.php

// ... $user = new User(); $user->username = 'jwage'; $user->age = '-100';

if ( ! $user->isValid()) { echo 'User is invalid because -100 is signed'; }

US State

usstateバリデータは文字列が有効なUSの州コードであることをチェックします。

// models/State.php

class State extends Doctrine_Record { public function setTableDefinition() { $this->hasColumn('name', 'string', 255); $this->hasColumn('code', 'string', 2, array( 'usstate' => true ) ); } }

YAMLフォーマットでの例は次の通りです。[doc yaml-schema-files :name]の章でYAMLの詳細を読むことができます:

# schema.yml

State: columns: name: string(255) code: type: string(2) usstate: true

無効な州コードでStateを保存しようとするとバリデーションがエラーになります。

$state = new State(); $state->name = 'Tennessee'; $state->code = 'ZZ';

if ( ! $state->isValid()) { echo 'State is invalid because "ZZ" is not a valid state code'; }

まとめ

データを永続的にデータベースに保存する前にDoctrineにデータのバリデーションを行わせる方法を理解しDoctrineコアが提供する共通のバリデータを使うことができます。

[doc inheritance 次の章]では[doc inheritance :name]を検討するので重要です!継承は最小のコードで複雑な機能を実現するための偉大な方法です。継承を検討した後で[doc behaviors :name]と呼ばれる継承よりも優れた機能を提供するカスタム戦略に移ります。