【PHP8】プリミティブ型にメソッドを生やそうスカラーオブジェクト

PHPの第一人者Nikitaの[GitHub](https://github.com/nikic)を覗いていたら[なんか面白いもの](https://github.com/nikic/scalar_objects)を見つけました。

わりと前から存在しているみたいで、[最古のコミット](https://github.com/nikic/scalar_objects/commit/92c16762f742bc2a66db3d5bd17aaa63f3464a94)は2013/01/25です。
積極的な機能追加こそほとんどないものの、メンテナンスは続いていて、[PHPに合わせて着実にバージョンアップ](https://github.com/nikic/scalar_objects/commits/master)されています。
2020年11月にもPHP8対応のアプデがされています。

残念ながら[PHPの機能として取り込むつもりはない](https://github.com/nikic/scalar_objects/issues/20)ということで、あくまでエクステンションの立場に留まるようです。

さて、ではどんな機能なのかというとこんなのです。

```php
class StringHandler {
    public static function length($self) {
        return strlen($self);
    }

    public static function startsWith($self, $other) {
        return strpos($self, $other) === 0;
    }
}

register_primitive_type_handler('string', 'StringHandler');

$string = "abc";
var_dump($string->length()); // int(3)
var_dump($string->startsWith("a")); // bool(true)
```

文字列にメソッドが生えました。

# Add support for method calls on primitive types in PHP

この拡張モジュールは、一部のプリミティブ型にメソッド呼び出しを可能にする機能を実装します。
これにより、`$str->length()`のように新しいAPIを実装することが可能になります。

このリポジトリの主な目的は、新しいAPIを設計するための概念実証を行うことです。
プリミティブ型に対する操作をオブジェクト構文に切り替えることは、PHPに多数存在する一貫性のないコアAPIを再設計するための、またとない機会となります。
ユーザランドのコードとして新しいAPIを素早くプロトタイピングしてテストするための手段を提供します。
一旦APIの形が出来あがったら、それをPHPへ組み込むためにRFCとして提出されます。

注意：この機能はプロトタイピングのために存在しており、実際に私がPHPに求めているものではありません。


## Registering type handlers

型ハンドラの登録は`register_primitive_type_handler`関数で行います。
この関数は型名(`string`や`array`等)とクラス名を受け取ります。
メソッドはstaticに実装し、第一引数でプリミティブ型を受け取ります。

```php
class StringHandler {
    public static function length($self) {
        return strlen($self);
    }

    public static function startsWith($self, $other) {
        return strpos($self, $other) === 0;
    }
}

register_primitive_type_handler('string', 'StringHandler');

$string = "abc";
var_dump($string->length()); // int(3)
var_dump($string->startsWith("a")); // bool(true)
```

現在サポートされている型名は`null`・`bool`・`int`・`float`・`string`・`array`・`resource`です。
全てをサポートする意味があるかは別として、今のところは全てサポートしています。

## Implemented APIs

先述したとおり、このリポジトリの主目的は、プリミティブ型のための良いAPIを設計することです。
実際に作成したAPIは`handlers/`フォルダ内に存在します。(そして明らかに作業中です)
実際に使用したい場合は`handlers/bootstrap.php`をincludeすると使えます。

## Installation

masterはPHP7.0から8.0までをサポートしています。
PHP5は[0.2ブランチ](https://github.com/nikic/scalar_objects/tree/0.2)を使用してください。

### Unix

以下のコマンドでコンパイル・インストールしてください。

```bash
phpize
./configure
make
sudo make install
```

### Windows

[prebuilt Windows DLL](https://windows.php.net/downloads/pecl/snaps/scalar_objects/)から使用しているPHPバージョンに合ったDLLをダウンロードし、PHPのエクステンションディレクトリに配置します。
そして`php.ini`に`extension=php_scalar_objects.dll`を追加してください。

## Testing the extension

この拡張機能には、動作テストを行うための`run-tests.php`が付属しています。
以下のコマンドで実行してください。

```bash
php run-tests.php -q -p php
```


## Limitations

この拡張機能にはいくつかの制限があります。

技術的制約のため、ミュータブルなAPIを作成することはできません。
メソッド内で`$self`を変更することはできません。
というかコピーが変更されるだけなので、元に影響することはありません。


# stringハンドラ

どのようなことができるかのサンプルとして[stringハンドラ](https://github.com/nikic/scalar_objects/blob/master/handlers/string.php)が用意されていますので、その一部を紹介してみます。

## length

文字列長を取得します。
[strlen](https://www.php.net/manual/ja/function.strlen.php)です。

```php
echo "foobar"->length(); // 6
```

## slice

文字列の一部を返します。
[substr](https://www.php.net/manual/ja/function.substr)です。

```php
echo "foobar"->slice(1, 4); // "ooba"
```

## replaceSlice

文字列の一部を置換します。
[substr_replace](https://www.php.net/manual/ja/function.substr-replace)です。

```php
echo "abcde"->replaceSlice("aboo", 1, 4); // "faboor"
```

## indexOf

文字の現れる位置を返します。
[strpos](https://www.php.net/manual/ja/function.strpos)です。

```php
echo "abc def abc def abc"->indexOf("abc", 1); // 8
```

## lastIndexOf

文字が最後に現れる位置を返します。
[strrpos](https://www.php.net/manual/ja/function.strrpos.php)です。

```php
echo "abc def abc def abc"->lastIndexOf("abc"); // 16
```

## contains

文字が含まれるかどうかを判定します。
[str_contains](https://www.php.net/manual/ja/function.str-contains.php)です。

```php
echo "abc def abc def abc"->contains("abc"); // true
```

## repeat

指定回数繰り返します。
[str_repeat](https://www.php.net/manual/ja/function.str-repeat.php)です。

```php
echo "foobar"->repeat(3); // "foobarfoobarfoobar"
```



## その他

サンプルは他にもtrim、toLower、toUpperなど色々用意されています。
ほとんどは1行で書けるような中身ばっかりですが、それを関数呼び出しではなく直接メソッド形式で書けるのは文法的に楽ですね。
また何か複雑な機能が欲しいほしいとなったときにも、このハンドラはPHPで書くことができるため、簡単に自由に追加することができます。


# 感想

PHPの機能として使えればたしかに便利そうではあります。

```php
// 同じ。コード自体は特に意味ない
echo str_contains(substr(str_repeat('foo', 3), 0, 1), 'foo');
echo 'foo'->repeat(3)->substr(0, 1)->contains('foo');
```

どちらが見やすいかといえば意見が分かれるかもしれませんが、どちらが書きやすいかといえば間違いなく後者でしょう。
作者の言う、プロトタイピングという目的に実に合致した記述だと言えるでしょう。

とはいえ本人が望んでいないこともあり、PHP本体に取り込まれることはまずないと思われます。
半端に入ってしまうと却って絶望的なコードになってしまうので、PHP本体に入れるならこれを防ぐ方法も考えないといけないでしょうしね。

```php:死
echo substr('foo'->repeat(3), 0, 1)->contains('foo');
```


# 試せなかった

[Windows版ビルド](https://windows.php.net/downloads/pecl/snaps/scalar_objects/)が2017/04/14のPHP7.1で止まっているかなしみ。