PHP7調査(36)スカラ型のタイプヒントが書けるようになった


PHP7から、スカラ型（`int`、`float`、`string`、`bool`）のタイプヒントが書けるようになります。関数の引数だけでなく、返り値にも書けます（参照：「[PHP7調査(35)関数の返り値のタイプヒントが書けるようになった - Qiita](http://qiita.com/hnw/items/347872ad8a05fb0df121)」）。

話がややこしくなるので本稿ではstrict modeには触れず、デフォルトの挙動であるweak modeの話題のみを扱います。

# スカラ型のタイプヒントの記法

スカラ型のタイプヒントは他の型のタイプヒントと同じように記述することができます。

```php
<?php
function foo(int $i) { var_dump($i); }
foo(3); /* int(3) */
```

タイプヒントで新たに書けるようになった型は`int`、`float`、`string`、`bool`の4つだけです。`mixed`のような指定はできません。

# スカラ型のタイプヒントと型キャスト

スカラ型のタイプヒントはクラスなどのタイプヒントとは異なり、必要に応じてキャストされます。

```php
<?php
function foo(int $i) { var_dump($i); }
foo(10.0); /* int(10) */
foo("1e3"); /* int(1000) */
```

`int`型のタイプヒントに対して`float`型や`double`型の値を渡すとキャストされることがわかります。

この型キャストのルールは組み込み関数での型キャストとほとんど同じということになっています。型キャストの詳細は次のような表にまとめることができます。


|タイプヒント＼実際の型| int | float | string | bool | object |
|-----------------|-----|-------|--------|------|--------|
|**int**          | yes | yes★  |  yes★† |   yes |     no |
|**float**        | yes☆ | yes   |  yes† |   yes |     no |
|**string**       | yes | yes   |  yes   |   yes |    yes‡ |
|**bool**         | yes | yes   |  yes   |   yes |    no |

* ☆：64bit環境で53bit以上の整数を渡した場合に精度が落ちることがある、エラーは特に出ない
* ★：浮動小数点数の値が整数の範囲内のときはキャストされる。そうでなければ`TypeException`がスローされる（参照：[PHP7調査(5)整数型の引数のオーバーフローがエラーになった - Qiita](http://qiita.com/hnw/items/1edda557d8c144f0cd20)）
* †：数値文字列であればキャストされる。文字列の先頭が数値文字列として解釈できる場合は、noticeを出した上で残りを無視してキャストされる。数値文字列でない場合は`TypeException`がスローされる。
* ‡：オブジェクトが`__toString`メソッドを持っているときのみキャストされる。それ以外の場合`TypeException`がスローされる。

## nullの扱いの違い

実は、ユーザー関数の型キャストのルールは組み込み関数での型キャストとで`null`に関する挙動だけが異なっています。次の例を見てみましょう。


```php:cast-with-sysfunc.php
<?php
var_dump(array_fill(null, 3, "foo"));
/*
array(3) {
  [0]=>
  string(3) "foo"
  [1]=>
  string(3) "foo"
  [2]=>
  string(3) "foo"
}
*/
```

`array_fill()`の第1引数は`int`型ですが、`null`を渡してもエラーにはならず、0として解釈されていることがわかります。一方でユーザー関数の場合は挙動が異なります。


```php:cast-with-userfunc.php
<?php
function my_array_fill(int $start_index, int $num, $value) {
    return array_fill($start_index, $num, $value);
}
var_dump(my_array_fill(null, 3, "foo"));
/*
PHP Fatal error:  Uncaught TypeException: Argument 1 passed to my_array_fill() must be of the type integer, null given, called in /tmp/cast-with-userfunc.php on line 5 and defined in /tmp/cast-with-userfunc.php:2
Stack trace:
#0 /tmp/cast-with-userfunc.php(5): my_array_fill(NULL, 3, 'foo')
#1 {main}
  thrown in /tmp/cast-with-userfunc.php on line 2
*/
```

このように、ユーザー関数のタイプヒントでは`null`は勝手にはキャストされず、エラーになります。引数のデフォルト値を`null`に指定すると`null`を許容しますが、当然ながらキャストはされません。クラスなどのタイプヒントの挙動と整合性を取るため、このような仕様になっています。

# 型のエイリアス名はタイプヒントに使えない

経緯は追っていませんが、`integer`・`double`・`boolean`の3つの型名はタイプヒントに利用できません。これらをタイプヒントに指定すると、PHP5までと同様にクラス名として解釈されます。このままの仕様だとPHPプログラマが混乱しかねないので、エラーメッセージを整理すべきかもしれません。

```
PHP Fatal error:  Uncaught TypeException: Argument 1 passed to foo() must be an instance of integer, integer given, called in /tmp/foo.php on line 3 and defined in /tmp/foo.php:2
Stack trace:
#0 /tmp/foo.php(3): foo(3)
#1 {main}
  thrown in /tmp/foo.php on line 2
```

「第1引数は`integer`のインスタンスであるべきなのに`integer`が渡された！」「お、おう…？」

# 参照

* [PHP RFC: Scalar Type Declarations](https://wiki.php.net/rfc/scalar_type_hints_v5)
* [【導入決定！】PHP7で実装されるスカラー型宣言とは？ | 東北ギーク](http://tech.respect-pal.jp/php7_scalar_type_hinting/) 長文ですが、超まとまっていて素晴らしいと思います。
* [PHP7調査(5)整数型の引数のオーバーフローがエラーになった - Qiita](http://qiita.com/hnw/items/1edda557d8c144f0cd20)
* [PHP7調査(35)関数の返り値のタイプヒントが書けるようになった - Qiita](http://qiita.com/hnw/items/347872ad8a05fb0df121)
* [PHP7調査(34)型名と同じクラス名が作れないようになった - Qiita](http://qiita.com/hnw/items/f815ce15f795620b5133)