PHPプリミティブ型(int,string,array...)にメソッドを生やそう

![example](http://gifzo.net/iyn1G67j1S.gif)

※PHP extension が導入できる環境(Webサーバ管理権限等)が必要です

# できること

JavaScript のプロトタイプ拡張や C# の拡張メソッドのように、string や array など、PHP のプリミティブ型にメソッドを **自由に追加** することができます。

```php:メソッドを生やすと華麗にキマる
// 単なる文字列
$str = "foo bar hoge";

// 文字列を空白で分割して配列に...
// 条件で絞り込みつつ要素数を取得
echo $str
    ->split(' ')
    ->filter(function($x) {
        return $x->contains('o');
    })
    ->length();
    // ... 2 
```

メソッドチェインができるのとできないのとでは、コードに込められる意図の密度が全く違いますよね。
ちなみに同じコードを素の PHP で書くとこうなります↓

```php:もうこんなコード書きたくない...
$str = "foo bar hoge";
$arr = explode(' ', $str);
$arr = array_filter(function($x) {
    return false !== strpos($x, 'o');
}, $arr);
echo count($arr);
```

字数増え、情報量減る。主語が見えづらい…。

# できないこと

PHP のシンタックスに反することはできないので、JavaScript のように文字列リテラルや配列リテラルをオブジェクトとして扱うことはできません。

```php:こうは書けない
$arr = "foo bar hoge"->split(' ');
$arr = ("foo bar hoge")->split(' ');
$str = ["foo","bar","hoge"]->join(',');
```

> 追記: PHP7 からは上記の記法も使えるようです。

# 方法

[scalar_objects](https://github.com/nikic/scalar_objects) という PHP extension を使います・

## インストール

### Unix

```bash
cd /usr/local/src
git clone https://github.com/nikic/scalar_objects.git
cd scalar_objects
phpize && ./configure && make && make install
```

php.ini に `extension=scalar_objects.so` と追記すればインストール完了です。
ちゃんとロードできてるかどうかは `phpinfo()` で確認できます。

### Windows

使っているPHPのバージョンに適した [Windows用DLL](http://windows.php.net/downloads/pecl/snaps/scalar_objects/20130301/) をダウンロードし、PHP インストールフォルダ内の `ext/` フォルダに置いてください。

php.ini に `extension=php_scalar_objects.dll` と追記すればインストール完了です。

## メソッドの追加

次のように疑似クラスにメソッドを実装し、`register_primitive_type_handler` によってプリミティブ型に登録します。

本家では Handler と表現していますが、C# の拡張メソッドと概念と書き方が近いので Extension として書いてみます。

```php:extensions.php
<?php

// string 型の拡張メソッド群
class StringExtension {
    
    public static function split($self, $delimiter)
    {
        // 文字列値は第一引数 $self として渡される
        return explode($delimiter, $self);
    }

    public static function contains($self, $value)
    {
        return false !== strpos($self, $value);
    }
}

// 配列の拡張メソッド群
class ArrayExtension {
    
    public static function filter($self, $callback)
    {
        // 配列は第一引数 $self として渡される
        return array_filter($callback, $self);
    }
    
    public static function length($self)
    {
        return count($self);
    }
}

// string に StringExtension を登録
register_primitive_type_handler('string', 'StringExtension');

// 配列に ArrayExtension を登録
register_primitive_type_handler('array', 'ArrayExtension');
```

これを include することで、冒頭のように書くことができるようになります。

Phalcon などのフレームワークで使う場合は、loader.php 等の適した場所で `register_primitive_type_handler` を呼び出してしまえばいいでしょう。

# 蛇足: 最強のIDE PhpStorm で拡張メソッドを補完してもらうには

（ @yamasemi さん提供情報を元に追記編集）
最初はプラグインを作る必要があると思っていましたが **そんなことはなかったようです。**

string, array の拡張メソッドを補完してもらうには、同プロジェクト内のどこかに次のように書いた PHP ファイルを置くか、または PHP 外部ライブラリとして読み込むだけでOKです。
もちろん StringExtension クラス等を記述したファイル内に書いても問題ないです。

```php:mixin_extensions.php
<?php

/**
 * Interface string
 * @mixin StringExtension
 */
interface string {}

/**
 * Interface array
 * @mixin ArrayExtension
 */
interface PS_UNRESERVE_PREFIX_array {}
```

mixin は PHP で言うトレイトに近い意味を持ちます。つまり「string は StringExtension の特徴を持ちます」と PhpStorm に教えるわけですね。
`array` は予約語ですが `PS_UNRESERVE_PREFIX_` をつけることで回避して定義できるようです。

今後の開発が捗りそうです。改めまして @yamasemi さん、ありがとうございます！