PHPの配列要素の型を縛る

# 前書き

**[@Hiraku](http://qiita.com/Hiraku)** さんの **[SplFixedArrayで"純粋な配列"を作る](http://qiita.com/Hiraku/items/471747cacda58070f3a6)** に感化されて衝動的に作ってしまった。

個人的により強く縛りたいと感じるのは、配列のサイズというよりは格納される **要素の型** なので。それならPHPなんか使うなって言われそうだけどまぁうんそうだねはい（適当）

# **Collection**

**ArrayObject** を継承して作ってみました。使い方はほとんど **ArrayObject** と同じです。

## クラス定義

```php
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** がスローされます。

### 整数

```php
$array = new Collection(Collection::TYPE_INT);
$array[] = 1;
```

### 数値（整数と浮動小数点数と論理値）

```php
$array = new Collection(Collection::TYPE_NUMBER);
$array[] = 1;
$array[] = 1.1;
$array[] = true;
```

### コーラブル

```php
$array = new Collection(Collection::TYPE_CALLABLE);
$array[] = 'strlen';
$array[] = function () { echo 'こんにちは世界'; };
$array[] = array(new SimpleXMLElement('<xml></xml>'), 'addChild');
```

### オブジェクト

```php
$array = new Collection(Collection::TYPE_OBJECT);
$array[] = new SimpleXMLElement('<xml></xml>');
$array[] = new stdClass;
```

### 特定のオブジェクト

```php
$array = new Collection(Collection::TYPE_OBJECT, 'stdClass');
$array[] = new stdClass;
```

### 特定のリソース

```php
$array = new Collection(Collection::TYPE_RESOURCE, null, 'stream');
$array[] = fopen('test.txt', 'wb');
```

### 文字列にキャスト可能なもの

```php
$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でも出来るように！

```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か…………