前書き
@Hiraku さんの SplFixedArrayで"純粋な配列"を作る に感化されて衝動的に作ってしまった。
個人的により強く縛りたいと感じるのは、配列のサイズというよりは格納される 要素の型 なので。それならPHPなんか使うなって言われそうだけどまぁうんそうだねはい(適当)
Collection
ArrayObject を継承して作ってみました。使い方はほとんど ArrayObject と同じです。
クラス定義
class Collection extends ArrayObject {
const TYPE_BOOL = 1;
const TYPE_INT = 2;
const TYPE_FLOAT = 4;
const TYPE_STRING = 8;
const TYPE_ARRAY = 16;
const TYPE_OBJECT = 32;
const TYPE_NULL = 64;
const TYPE_RESOURCE = 128;
const TYPE_CALLABLE = 256;
const TYPE_STRINGABLE = 512;
const TYPE_NUMBER = 7;
const TYPE_TRAVERSABLE = 48;
const TYPE_ALL = 1023;
private $type;
private $object;
private $resource;
public function __construct($type = self::TYPE_ALL, $object = null, $resource = null) {
if (!is_int($type) or !($type & self::TYPE_ALL)) {
throw new InvalidArgumentException('Unknown type definition.');
}
$this->type = $type;
if ($object !== null) {
switch (true) {
case is_object($object):
$this->object = get_class($object);
break;
case is_string($object):
$this->object = $object;
break;
default:
throw new InvalidArgumentException('Illegal object type definition.');
}
}
if ($resource !== null) {
switch (true) {
case is_resource($resource):
$this->resource = get_resource_type($resource);
break;
case is_string($resource):
$this->resource = $resource;
break;
default:
throw new InvalidArgumentException('Illegal resource type definition.');
}
}
parent::__construct();
}
public function offsetSet($offset, $value) {
if (is_array($offset)) {
throw new DomainException('Illegal offset type.');
}
switch (true) {
case $this->type & self::TYPE_BOOL and is_bool($value):
case $this->type & self::TYPE_INT and is_int($value):
case $this->type & self::TYPE_FLOAT and is_float($value):
case $this->type & self::TYPE_STRING and is_string($value):
case $this->type & self::TYPE_ARRAY and is_array($value):
case $this->type & self::TYPE_NULL and is_null($value):
case $this->type & self::TYPE_CALLABLE and is_callable($value):
case $this->type & self::TYPE_OBJECT and is_object($value):
case $this->type & self::TYPE_STRINGABLE and self::isStringable($value):
break;
default:
throw new DomainException('Illegal value type.');
}
switch (true) {
case
$this->object !== null and
is_object($value) and
!($value instanceof $this->object):
throw new DomainException('Illegal object type.');
case
$this->resource !== null and
is_resource($value) and
get_resource_type($value) !== $this->resource:
throw new DomainException('Illegal resource type.');
}
parent::offsetSet($offset, $value);
}
private static function isStringable($value) {
switch (true) {
case is_array($value):
case is_object($value) and !method_exists($value, '__toString'):
return false;
default:
return true;
}
}
}
基本的な使い方
型が適合しなかったときには DomainException がスローされます。
整数
$array = new Collection(Collection::TYPE_INT);
$array[] = 1;
数値(整数と浮動小数点数と論理値)
$array = new Collection(Collection::TYPE_NUMBER);
$array[] = 1;
$array[] = 1.1;
$array[] = true;
コーラブル
$array = new Collection(Collection::TYPE_CALLABLE);
$array[] = 'strlen';
$array[] = function () { echo 'こんにちは世界'; };
$array[] = array(new SimpleXMLElement('<xml></xml>'), 'addChild');
オブジェクト
$array = new Collection(Collection::TYPE_OBJECT);
$array[] = new SimpleXMLElement('<xml></xml>');
$array[] = new stdClass;
特定のオブジェクト
$array = new Collection(Collection::TYPE_OBJECT, 'stdClass');
$array[] = new stdClass;
特定のリソース
$array = new Collection(Collection::TYPE_RESOURCE, null, 'stream');
$array[] = fopen('test.txt', 'wb');
文字列にキャスト可能なもの
$array = new Collection(Collection::TYPE_STRINGABLE);
$array[] = 1;
$array[] = 1.1;
$array[] = true;
$array[] = null;
$array[] = fopen('test.txt', 'wb');
$array[] = new SimpleXMLElement('<xml></xml>');
再帰的な定義
Collection を再帰的に代入していけば、Javaの ジェネリクス みたいなことがPHPでも出来るように!
$root = new Collection(Collection::TYPE_OBJECT, 'Collection');
$root['int'] = new Collection(Collection::TYPE_INT);
$root['bool'] = new Collection(Collection::TYPE_BOOL);
$root['int'][] = 1;
$root['bool'][] = true;
…よく考えたら new でインスタンス生成するタイミングで全ての再帰的な定義が終わってないとだめだよね……うーん………所詮はPHPか…………