[PHP7]エクステンションで自作関数を作る

## はじめに
Swiftとかだと`extension`を使って、元から存在する型（String, arrayとか）に任意の関数を追加することができます。

```swift
extension Int {
    static var zero: Int { return 0 }
    mutating func inverse() {
        self = -self
    }
}
```

しかし、PHPのエクステンションはC言語で書かれています。そのため実装は大変なのですが、普通のPHPスクリプトと異なり高速で動作します。このエクステンションはPHP7 と PHP5 で大幅に変わったらしいのですが、基本的には変わっていないように感じました。

## 準備
### PHP本体のソースを取得

```bash
$ git clone git@github.com:php/php-src.git
$ cd php-src
```
### 指定したバージョンをチェックアウト
```bash
$ git tag --list

...

php-7.1.0
php-7.1.0RC1
php-7.1.0RC2
php-7.1.0RC3
php-7.1.0RC4
php-7.1.0RC5
php-7.1.0RC6
php-7.1.0alpha1
php-7.1.0alpha2
php-7.1.0alpha3
php-7.1.0beta1
php-7.1.0beta2
php-7.1.0beta3
php-7.1.1
php-7.1.1RC1
php-7.1.2
php-7.1.2RC1
php-7.1.3
php-7.1.3RC1

...
```

ここでは、PHP7.1.2 をチェックアウトしていきます。

```git
$ git checkout php-7.1.2
```

### 雛形の作成
```bash
$ cd ext
$ ./ext_skel --extname=myext

...

To use your new extension, you will have to execute the following steps:

1.  $ cd ..
2.  $ vi ext/myext/config.m4
3.  $ ./buildconf
4.  $ ./configure --[with|enable]-myext
5.  $ make
6.  $ ./sapi/cli/php -f ext/myext/myext.php
7.  $ vi ext/myext/myext.c
8.  $ make

...
```

### ビルド／動作確認
雛形をビルド／実行できるかを確認してみます

```
$ cd myext
```

config.m4 を編集します。10行目あたりの

```
PHP_ARG_WITH(myext, for myext support,
Make sure that the comment is aligned:
[  --with-myext             Include myext support])
```
という箇所のコメントを外します。ここファイルでのコメントは`dnl`で表されます。
なので、`dnl`を消してください。

```
$ phpize
$ ./configure
$ make
```
```
$ php -d extension=./modules/myext.so myext.php
Functions available in the test extension:
confirm_myext_compiled

Congratulations! You have successfully modified ext/myext/config.m4.
Module myext is now compiled into PHP.
```

`-d extension=./modueles/myext.so` で .so ファイルをロードして、 `Congratulations! You have successfully 〜 `という表示が出れば準備完了です。

## 自作関数の登録と実装
今回は簡単なadd関数を自作することにします。`add_extension`という関数名にしておきます。使用例としては

```php
$a = 5;
$b = 5;
pr(add_extension($a, $b));  // 10
```

### 関数の登録
今回は`const zend_function_entry add_extension_functions[]`に`add_extension`関数を追加して行きます。変数名は特に指定されていないのでわかりやすくしておきましょう。

```C:add_extension.c
// Reflectionなし
const zend_function_entry add_extension_functions[] = {
    PHP_FE(add_extension, NULL)
    PHP_FE_END
};

// Reflectionあり
ZEND_BEGIN_ARG_INFO_EX(arginfo_add_extension_functions, 0, 0, 2)
  ZEND_ARG_INFO(0, val1)
  ZEND_ARG_INFO(0, val2)
ZEND_END_ARG_INFO()

const zend_function_entry add_extension_functions[] = {
    PHP_FE(add_extension, arginfo_add_extension_functions)
    PHP_FE_END
};
```
`PHP_FE(add_extension, NULL)`の第２引数に`arg_info`という構造体を渡すとタイプヒンティングやリフレクション等が利用できるようになります。この説明については付録で書きます。

### 関数本体の実装
関数本体を実装するときには`PHP_FUNCTION()`というマクロを利用します。

```c:add_extension.c
PHP_FUNCTION(add_extension) {
    // 引数の格納先
    zend_long val1, val2;

    // 引数をパースして格納先に代入
    ZEND_PARSE_PARAMETERS_START(2, 2)
        Z_PARAM_LONG(val1)
        Z_PARAM_LONG(val1)
    ZEND_PARSE_PARAMETERS_END();

    // 足し算
    zend_long res = val1 + val2;

    // 結果を return する
    RETURN_LONG(res);
}
```

#### ZEND_PARSE_PARAMETERS_START(MIN, MAX)
第１引数は最小引数、第２引数は最大引数の個数を表します。

#### Z_PARAM
受け取る引数の型を指定して、どの変数に代入するかを指定します。`型指定子`とよばれ以下のように定義されています。

specifier|Fast ZPP API macro|args
|:---|:-----------|:--|
\||Z_PARAM_OPTIONAL|
a|Z_PARAM_ARRAY(dest)|dest - zval*
A|Z_PARAM_ARRAY_OR_OBJECT(dest)|dest - zval*
b|Z_PARAM_BOOL(dest)|dest - zend_bool
C|Z_PARAM_CLASS(dest)|dest - zend_class_entry*
d|Z_PARAM_DOUBLE(dest)|dest - double
f|Z_PARAM_FUNC(fci, fcc)|fci - zend_fcall_info, fcc - zend_fcall_info_cache
h|Z_PARAM_ARRAY_HT(dest)	|dest - HashTable*
H|Z_PARAM_ARRAY_OR_OBJECT_HT(dest)|dest - HashTable*
l|Z_PARAM_LONG(dest)|dest - long
L|Z_PARAM_STRICT_LONG(dest)|dest - long
o|Z_PARAM_OBJECT(dest)|dest - zval*
O|Z_PARAM_OBJECT_OF_CLASS(dest, ce)|dest - zval*
p|Z_PARAM_PATH(dest, dest_len)|dest - char*, dest_len - int
P|Z_PARAM_PATH_STR(dest)	|dest - zend_string*
r|Z_PARAM_RESOURCE(dest)|dest - zval*
s|Z_PARAM_STRING(dest, dest_len)|dest - char*, dest_len - int
S|Z_PARAM_STR(dest)|dest - zend_string*
z|Z_PARAM_ZVAL(dest)|dest - zval*
 |Z_PARAM_ZVAL_DEREF(dest)|dest - zval*
+|Z_PARAM_VARIADIC('+', dest, num)|dest - zval*, num int
*|Z_PARAM_VARIADIC('*', dest, num)|dest - zval*, num int

https://wiki.php.net/rfc/fast_zpp

### 実際に呼び出してみる
```php
 <?php
$val1 = 10;
$val2 = 10;

$res = add_extension($val1, $val2);
echo "$res\n";
```

```
$ php -d extension=./modules/myext.so myext_test.php
20
```

とりあえず関数のエクステンションを作り上げることまではできるようになりました。

記事を書く気力とプログラムを組む気力があれば、エクステンションでクラス作成もしてみたいと思います。

## 付録
### PHP_FE
#### arg_info構造体
`PHP_FE`マクロの第２引数は[Reflection API](http://php.net/manual/ja/book.reflection.php)に対して関数の引数情報を提供するために使用されます。つまりは指定しなくても（`NULL`でも）動きますが、実装したExtensionを後悔する場合には必ずと言っていいほど必要になるので、実装しておく方が無難です。以下に`mb_convert_encoding`関数を例に示します。

#### mb_convert_encoding
```php
// 文字列 strの文字エンコーディングを
// オプションで指定した from_encoding から to_encoding に変換します。
string mb_convert_encoding (
  string $str, string $to_encoding [, mixed $from_encoding = mb_internal_encoding() ]
)
```
この定義がされている`php/ext/mbstring/mbstring.c`には以下のように記載されています

```c
ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_convert_encoding, 0, 0, 2)
    ZEND_ARG_INFO(0, str)
    ZEND_ARG_INFO(0, to)
    ZEND_ARG_INFO(0, from)
ZEND_END_ARG_INFO()

PHP_FE(mb_convert_encoding,     arginfo_mb_convert_encoding)
```
#### ZEND_BEGIN_ARG_INFO_EX
以下の4つの引数を指定します。

|引数番号|解説|
|:----|:------|
|1|定義名（PHP_FEマクロの第２引数として指定する文字列）|
|2|未使用|
|3|返却フラグ（1: 参照を返す, 0: 受け取るだけ）|
|4|必須引数の個数|

#### ZEND_ARG_INFO
関数の引数の数だけ記述する必要があります。それぞれ以下の２つの引数を指定します。

|引数番号|解説|
|:----|:------|
|1|1: 参照渡し, 0: 値渡し|
|2|仮引数名|

#### ZEND_END_ARG_INFO
引数定義の終了を示します。

先ほどのを見返すと

```c
ZEND_BEGIN_ARG_INFO_EX(arginfo_mb_convert_encoding, 0, 0, 2)
    ZEND_ARG_INFO(0, str)
    ZEND_ARG_INFO(0, to)
    ZEND_ARG_INFO(0, from)
ZEND_END_ARG_INFO()

PHP_FE(mb_convert_encoding,     arginfo_mb_convert_encoding)
```
となっています。必須引数が２となっていますが、これは`ZEND_ARG_INFO`の定義順で必須になっていくので注意してください。また、`ZEND_ARG_INFO`の第１引数が0なので全て値渡しになります。

この`PHP_FE`や`ZEND_ARG_INFO`で行なった引数定義は`ReflectionFunction`クラスを用いて参照することができます

```php:reflection_exec.php
// $ cat reflection_exec.php
$reffunc = new ReflectionFunction('mb_convert_encoding');
foreach ($reffunc->getParameters() as $arg) {
  print $arg . PHP_EOL;
}
```
```
// $ php reflection_exec.php
Parameter #0 [ <required> $str ]
Parameter #1 [ <required> $to ]
Parameter #2 [ <optional> $from ]
```