spindle / types
Basic types for PHP object system
Requires
- php: >=5.3.2
Requires (Dev)
- ext-pdo_sqlite: *
- pdepend/pdepend: *
- phpunit/phpunit: *
This package is not auto-updated.
Last update: 2024-11-09 16:11:45 UTC
README
PHPにより強い型付けを提供する基底クラス群です。
$ composer require 'spindle/types:*'
基本の型
Spindle\Types\Enum
Enumを継承すると列挙型になります。インスタンスは、クラスに定義したconstのいずれかの値であることを型から保証することができます。
<?php final class Suit extends Spindle\Types\Enum { const SPADE = 'spade' , CLUB = 'club' , HEART = 'heart' , DIAMOND = 'diamond' } $spade = new Suit(Suit::SPADE); $spade = Suit::SPADE(); //syntax sugar echo $spade, PHP_EOL; echo $spade->valueOf(), PHP_EOL; function doSomething(Suit $suit) { //$suitは必ず4種類のうちのどれかである }
Spindle\Types\TypedObject
TypedObjectを継承すると、プロパティの型を固定化したクラスを作ることができます。複雑なデータをより確実に扱うことができます。Domain Driven Designにおける"Entity"や"ValueObject"の実装に使えます。
型はschema()
というstaticメソッドで定義します。
schemaは配列を返す必要があり、その配列はプロパティ名 => 型, デフォルト値,
を繰り返したものになります。デフォルト値は省略でき、その場合はnullがセットされます。
<?php class User extends Spindle\Types\TypedObject { static function schema() { return array( 'firstName' => self::STR, 'lastName' => self::STR, 'age' => self::INT, 'birthday' => 'DateTime', new DateTime('1990-01-01'), ); } function checkErrors() { $errors = array(); if ($this->age < 0) { $errors['age'] = 'ageは0以上である必要があります'; } return $errors; } } $taro = new User; $taro->firstName = 'Taro'; $taro->lastName = 'Tanaka'; $taro->age = 20; //$taro->age = '20'; とすると、InvalidArgumentExceptionが発生して停止する
型として指定できる値には以下のものがあります。
- self::BOOL (真偽値)
- self::INT (整数)
- self::DBL (浮動小数点数)
- self::STR (文字列)
- self::ARR (配列)
- self::OBJ (オブジェクト)
- self::RES (リソース)
- self::CALL (コールバック関数)
- self::MIX (型を指定なし)
- className クラス名/インターフェース名。完全修飾名で指定します。クラス名の場合、instanceofで判定を行います。
__get()
や__set()
をfinalで固定化してしまうため、TypedObjectを継承するとクラスが持つ能力を一部奪うことになります。全てのクラスをTypedObjectから派生させて作るような設計は推奨しません。
TypedObjectはforeachに対応しています。(IteratorAggregate) TypedObjectはcount()関数で要素数を数えることができます。(Countable)
TypedObjectはcheckErrors()
というメソッドを実装することを推奨します。これは、型だけではチェックしきれないバリデーションを行うためのメソッドです。
デフォルト実装ではすべてのプロパティがnot nullであることをチェックします。
TypedObject::$preventExtensions
TypedObjectはデフォルト状態ではschema()で定義されていないプロパティへの代入・参照を拒否します。これはプロパティのtypoを発見しやすくする効果がありますが、不便に感じることもあるでしょう。
TypedObject::$preventExtensionsをfalseにすると、未定義のプロパティを拒否せず、自動で拡張するようになります。(デフォルトはtrue)
なお、拡張したプロパティは自動的にmixed(型検査しない)として扱われます。
<?php use Spindle\Types; class MyObj extends Types\TypedObject { static function schema() { return array( 'a' => self::INT, 'b' => self::BOOL, ); } } $obj = new MyObj; Types\TypedObject::$preventExtensions = false; $obj->c = 'str'; //エラーは起きない Types\TypedObject::$preventExtensions = true; $obj->c = 'str'; //例外発生
TypedObject::$casting
TypedObjectはプロパティに代入時、schemaと型が違えば例外を発生させます。 しかしPHPの標準的な挙動のように、違う型を代入しようとしたら型キャストを行ってほしい場合もあるでしょう。例えばデータベースから取り出した文字列からオブジェクトを復元したい場合などです。
TypedObject::$castingをtrueにすると、型が違う代入をしようとしても、なるべくキャストを行おうとします。
PDO::FETCH_CLASSとの組み合わせ
DBからSELECTしてきた結果をTypedObjectへ流し込むことができます。PDOの標準機能として、直接クラスをnewして流し込むPDO::FETCH_CLASS
というモードがあるので、これを使うとよいでしょう。
通常、PDOから渡ってくるデータはstring型ですので、$casting
オプションを有効にしておいてください。
<?php use Spindle\Types; class User extends Types\TypedObject { static $casting = true; static function schema() { return array( 'userId' => self::INT, 'name' => self::STR, 'age' => self::INT ); } } $pdo = new PDO('sqlite::memory:', null, null, array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, )); $pdo->exec('CREATE TABLE User(userId INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)'); $pdo->exec('INSERT INTO User(name, age) VALUES("taro", 20)'); $pdo->exec('INSERT INTO User(name, age) VALUES("hanako", 21)'); $stmt = $pdo->prepare('SELECT * FROM User'); $stmt->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, __NAMESPACE__ . '\\RowModel'); $stmt->execute(); foreach ($stmt as $row) { self::assertInternalType('integer', $row->userId); self::assertInternalType('string', $row->name); self::assertInternalType('integer', $row->age); }
注意点として、PDO::FETCH_CLASS
は通常のオブジェクト初期化と挙動が違い、 先にセッターを実行してから、コンストラクタを起動 します。TypedObjectはコンストラクタでオブジェクトを初期化しているため、この挙動ではうまく動作しません。
PDO::FETCH_CLASS
を用いる場合、必ずPDO::FETCH_PROPS_LATE
を同時に指定してください。
このオプションを指定すると、コンストラクタが先に起動するようになり、正常に動作します。
TypedObjectの継承
TypedObjectで作られたクラスを継承する場合、親クラスのschemaは自動的には引き継がれません。extendメソッドを使って明示的に拡張する必要があります。
<?php class Employee extends Spindle\Types\TypedObject { static function schema() { return array( 'id' => self::INT, 0, 'name' => self::STR, ); } } class Boss extends Employee { static function schema() { return self::extend(parent::schema(), array( 'room' => self::INT, )); } }
Spindle\Types\ConstObject
ConstObjectはTypedObjectを変更不可のオブジェクトにするDecoratorです。
<?php $const = new ConstObject($typedObject); echo $const->foo; //参照は透過的に可能 //$const->foo = 'moo'; どのプロパティに対しても代入操作は常に例外を発生させる
Spindle\Types\Collection
array()からいくつか制限を追加した配列です。
- 数値の添え字しか許容しない
- 順番が保証される
- 必要に応じて、要素の型も固定できる
Spindle\Types\ConstCollection
Collectionを読み取り専用にするDecoratorです。
Polyfill
PHPは5.4や5.5から使えるようになった標準インターフェースがいくつか存在します。 それらをPHP5.3においても使えるようにする目的で、Polyfillを用意しています。
DateTime implements DateTimeInterface
などの状態を保証するため、独自の名前空間上に配置しています。
Spindle\Types\Polyfill\JsonSerializable
JsonSerializable
インターフェース(PHP5.4以降)に相当します。
Spindle\Types\Polyfill\DateTimeInterface
DateTimeInterface
インターフェース(PHP5.5以降)に相当します。
Spindle\Types\Polyfill\DateTime
DateTimeInterface
をimplementsしたDateTimeです。
Spindle\Types\Polyfill\DateTimeImmutable
DateTimeImmutable
(PHP5.5以降)に相当します。状態を変更することができず、modifyやsetTimestampなどのメソッドを作用させると、別のインスタンスを返します。
License
spindle/typesの著作権は放棄するものとします。 利用に際して制限はありませんし、作者への連絡や著作権表示なども必要ありません。 スニペット的にコードをコピーして使っても問題ありません。
CC0-1.0 (No Rights Reserved)