PHP7 の関数の戻り値の型メモ

PHP7 で追加された関数の戻り値の型指定について、ちょっと気になったので調べてみました。
http://php.net/manual/ja/functions.returning-values.php

# 関数の返却値の型指定

## 基本形

PHPだと、何もしなければ関数から返却される値の型は、定まっていません。
例えば、以下のコードは返却値の型が引数によって全部違います

```test1.php
<?php

function test ($val)
{
    if ($val == 1) {
        return 0;
    } else if ($val == 2) {
        return '0';
    } else {
        return false;
    }
}

var_dump(test(1));// int(0)
var_dump(test(2));// string(1) "0"
var_dump(test(3));// bool(false)
```

しかし、以下のように、関数の戻り値の型を指定することができます

```test2.php
function test ($val): int
{
    if ($val == 1) {
        return 0;
    } else if ($val == 2) {
        return '0';
    } else {
        return false;
    }
}

var_dump(test(1));// int(0)
var_dump(test(2));// int(0)
var_dump(test(3));// int(0)
```
関数定義内の返却値が戻り値の宣言型と異なっている場合は、宣言型に合わせた形で返却されます。
以下のように型変換でうまくいかない場合は、TypeErrorを出します

```test3.php
<?php

function test (): int
{
    return 'a';

}

var_dump(test());// Fatal error: Uncaught TypeError: Return value of test() must be of the type integer, string returned in /var/dev/test1.php:5
```
厳密な型宣言がある場合は、型変換でうまくいく場合であっても、エラー落ちします

```test4.php
declare(strict_types=1);// 厳密な型チェック

function test (): int
{
    return '0';

}

var_dump(test());// atal error: Uncaught TypeError: Return value of test() must be of the type integer, string returned in /var/dev/test1.php:5
```
`declare(strict_types=1);`がなければ上のコードはint(0)となりますが、厳密な型チェックが宣言されていると、これもだめです。

## メソッドの型指定

当然クラスのメソッドにも型指定はできます

```test5.php
<?php
class A
{
    public function some(): int
    {
        return 1;
    }
}

$a = new A();
var_dump($a->some()); // int(1)
```
しかし、継承時に型指定を引き継がなければならないという制約がつきます。
例えば、以下の様なコードは動作する以前に、読んだ瞬間にアウトです

```test6.php
<?php
class A
{
    public function some(): int
    {
        return 1;
    }
}

class B extends A
{
    public function some(): string// Declaration of B::some(): string must be compatible with A::some(): int in /var/dev/test1.php on line 16
    {
        return 'hoge';
    }
}
```

## オブジェクトの型

クラスも型といえば型なので、次のような書き方が可能です

```test6.php
<?php
class A
{
    public static function some(): self
    {
        return new static;
    }
}

class B extends A {}

$a = A::some();
echo get_class($a);// A
$b = B::some();
echo get_class($b);// B
```
クラスBもちゃんと機能しているのは、継承したクラスのインスタンスは継承元のインスタンスでもあるってやつですね。
ちなみに、上記コードのselfをstaticに変えると、エラーになります。これは解析時点でstaticは型が決まっていないので、型指定になっていないからですね。
インターフェースも同じように型指定できます。

```test8.php
<?php
interface A
{
    public static function some(): self;
}

class B implements A
{
    public static function some(): A
    {
        return new self;
    }
}

$b = B::some();
echo get_class($b);// B
```
ここでクラスBの関数定義で改めて型指定をしていますが、これをしておかないとコンパチブルエラーを出して落ちます

## __toString
> **@DQNEO さんより指摘いただきましたが、このマジックメソッドは型宣言に合わせて文字列型に直しているわけではなく、暗黙の型変換によって、文字列になっただけですので、この項目は誤りとなります**

なんの価値が有るか不明ですが、オブジェクトのマジックメソッドである`__toString`を定義することで、文字列型への宣言に対応することができます。
例えば、以下のコードのようなものです

```test9.php
<?php
class A
{
    private $name;
    public function __construct($name)
    {
        $this->name = $name;
    }

    public function __toString()
    {
        return $this->name;
    }
}

function test($name): string
{
    return new A($name);
}

var_dump(test('niisan'));// string(6) "niisan"
```
とりあえず、戻り値の宣言型に合わせるよう、オブジェクトを文字列に変換しようとしている、ということが実感できるかなぁと思います

# まとめ

型安全にしておくと、動作中に不気味なnoticeに見舞われることが少なくなるので、推奨していきたいです。