HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

type hintingの改善(Type declarations/型宣言)

だいぶ昔に以下の記事を書いた。

hhelibex.hatenablog.jp

それからだいぶ時が経ったものの、PHP 5.xの世界でずっと生きてきたので気付かなかった。

これによると、

英語

Type declarations can be added to function arguments, return values, and, as of PHP 7.4.0, class properties. They ensure that the value is of the specified type at call time, otherwise a TypeError is thrown.

日本語

関数のパラメータや戻り値、 クラスのプロパティ (PHP 7.4.0 以降) に対して型を宣言することができます。 これによって、その値が特定の型であることを保証できます。 その型でない場合は、TypeError がスローされます。

というわけでさわりだけ試してみる。

パラメータ

Main1.php

<?php

function hoge(int $val) {
    var_dump($val);
}

class Hoge {
    public function foo(int $val) {
        var_dump(__FUNCTION__, $val);
    }
    public function bar(string $val) {
        var_dump(__FUNCTION__, $val);
    }
    public function piyo(Hoge $val) {
        var_dump(__FUNCTION__, $val);
    }
    public function __toString() {
        return var_export($this, true);
    }
}

print "===function hoge(int)===" . PHP_EOL;
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        hoge($val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}

print "===method foo(int)===" . PHP_EOL;
$hoge = new Hoge();
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        $hoge->foo($val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}
print "===method bar(string)===" . PHP_EOL;
$hoge = new Hoge();
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        $hoge->bar($val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}
print "===method piyo(Hoge)===" . PHP_EOL;
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        $hoge->piyo($val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}

実行結果。長くなるので、代表してPHP 8.1に出てもらう。

$ php81 Main1.php
===function hoge(int)===
===    1    ===
int(1)
===    '2'    ===
int(2)
===    true    ===
int(1)
===    '00'    ===
int(0)
===    'hello'    ===
hoge(): Argument #1 ($val) must be of type int, string given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 26
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(26): hoge()
#1 {main}
===    Hoge::__set_state(array(
))    ===
hoge(): Argument #1 ($val) must be of type int, Hoge given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 26
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(26): hoge()
#1 {main}
===method foo(int)===
===    1    ===
string(3) "foo"
int(1)
===    '2'    ===
string(3) "foo"
int(2)
===    true    ===
string(3) "foo"
int(1)
===    '00'    ===
string(3) "foo"
int(0)
===    'hello'    ===
Hoge::foo(): Argument #1 ($val) must be of type int, string given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 38
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(38): Hoge->foo()
#1 {main}
===    Hoge::__set_state(array(
))    ===
Hoge::foo(): Argument #1 ($val) must be of type int, Hoge given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 38
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(38): Hoge->foo()
#1 {main}
===method bar(string)===
===    1    ===
string(3) "bar"
string(1) "1"
===    '2'    ===
string(3) "bar"
string(1) "2"
===    true    ===
string(3) "bar"
string(1) "1"
===    '00'    ===
string(3) "bar"
string(2) "00"
===    'hello'    ===
string(3) "bar"
string(5) "hello"
===    Hoge::__set_state(array(
))    ===
string(3) "bar"
string(27) "Hoge::__set_state(array(
))"
===method piyo(Hoge)===
===    1    ===
Hoge::piyo(): Argument #1 ($val) must be of type Hoge, int given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 59
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(59): Hoge->piyo()
#1 {main}
===    '2'    ===
Hoge::piyo(): Argument #1 ($val) must be of type Hoge, string given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 59
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(59): Hoge->piyo()
#1 {main}
===    true    ===
Hoge::piyo(): Argument #1 ($val) must be of type Hoge, bool given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 59
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(59): Hoge->piyo()
#1 {main}
===    '00'    ===
Hoge::piyo(): Argument #1 ($val) must be of type Hoge, string given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 59
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(59): Hoge->piyo()
#1 {main}
===    'hello'    ===
Hoge::piyo(): Argument #1 ($val) must be of type Hoge, string given, called in /home/hhelibex/blog/2022-0615-01/Main1.php on line 59
#0 /home/hhelibex/blog/2022-0615-01/Main1.php(59): Hoge->piyo()
#1 {main}
===    Hoge::__set_state(array(
))    ===
string(4) "piyo"
object(Hoge)#4 (0) {
}
$

ある程度なら(string(1) "2"⇒int(2)/bool(true)⇒int(1)/string(2) "00"⇒int(0)/ ⇒string() "*")キャストして扱ってくれるらしい。 オブジェクトも、__toString()メソッドを実装するとstringにキャストして渡される。

戻り値

Main2.php

<?php

function hoge($val):int {
    return $val;
}

class Hoge {
    public function foo($val):int {
        return $val;
    }
    public function bar($val):string {
        return $val;
    }
    public function piyo($val):Hoge {
        return $val;
    }
    public function __toString() {
        return var_export($this, true);
    }
}

print "===function hoge():int===" . PHP_EOL;
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        var_dump($val, hoge($val));
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}

print "===method foo():int===" . PHP_EOL;
$hoge = new Hoge();
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        var_dump($val, $hoge->foo($val));
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}
print "===method bar():string===" . PHP_EOL;
$hoge = new Hoge();
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        var_dump($val, $hoge->bar($val));
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}
print "===method piyo():Hoge===" . PHP_EOL;
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        var_dump($val, $hoge->piyo($val));
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}

実行結果。

$ php81 Main2.php
===function hoge():int===
===    1    ===
int(1)
int(1)
===    '2'    ===
string(1) "2"
int(2)
===    true    ===
bool(true)
int(1)
===    '00'    ===
string(2) "00"
int(0)
===    'hello'    ===
hoge(): Return value must be of type int, string returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(26): hoge()
#1 {main}
===    Hoge::__set_state(array(
))    ===
hoge(): Return value must be of type int, Hoge returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(26): hoge()
#1 {main}
===method foo():int===
===    1    ===
int(1)
int(1)
===    '2'    ===
string(1) "2"
int(2)
===    true    ===
bool(true)
int(1)
===    '00'    ===
string(2) "00"
int(0)
===    'hello'    ===
Hoge::foo(): Return value must be of type int, string returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(38): Hoge->foo()
#1 {main}
===    Hoge::__set_state(array(
))    ===
Hoge::foo(): Return value must be of type int, Hoge returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(38): Hoge->foo()
#1 {main}
===method bar():string===
===    1    ===
int(1)
string(1) "1"
===    '2'    ===
string(1) "2"
string(1) "2"
===    true    ===
bool(true)
string(1) "1"
===    '00'    ===
string(2) "00"
string(2) "00"
===    'hello'    ===
string(5) "hello"
string(5) "hello"
===    Hoge::__set_state(array(
))    ===
object(Hoge)#2 (0) {
}
string(27) "Hoge::__set_state(array(
))"
===method piyo():Hoge===
===    1    ===
Hoge::piyo(): Return value must be of type Hoge, int returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(59): Hoge->piyo()
#1 {main}
===    '2'    ===
Hoge::piyo(): Return value must be of type Hoge, string returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(59): Hoge->piyo()
#1 {main}
===    true    ===
Hoge::piyo(): Return value must be of type Hoge, bool returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(59): Hoge->piyo()
#1 {main}
===    '00'    ===
Hoge::piyo(): Return value must be of type Hoge, string returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(59): Hoge->piyo()
#1 {main}
===    'hello'    ===
Hoge::piyo(): Return value must be of type Hoge, string returned
#0 /home/hhelibex/blog/2022-0615-01/Main2.php(59): Hoge->piyo()
#1 {main}
===    Hoge::__set_state(array(
))    ===
object(Hoge)#4 (0) {
}
object(Hoge)#4 (0) {
}
$ 

クラスのプロパティ(PHP 7.4.0以降)

Main3.php

<?php

class Hoge {
    public int $int_val;
    public string $str_val;
    public Hoge $hoge_val;
    public function __toString() {
        return var_export($this, true);
    }
}

$hoge = new Hoge();
foreach (array(1, "2", true, "00", "hello", new Hoge()) as $val) {
    print "===    " . var_export($val, true) . "    ===" . PHP_EOL;
    try {
        $hoge->int_val = $val;
        var_dump($hoge->int_val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
    try {
        $hoge->str_val = $val;
        var_dump($hoge->str_val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
    try {
        $hoge->hoge_val = $val;
        var_dump($hoge->hoge_val);
    } catch (TypeError $e) {
        print $e->getMessage() . PHP_EOL;
        print $e->getTraceAsString() . PHP_EOL;
    }
}

実行結果。

$ php81 Main3.php
===    1    ===
int(1)
string(1) "1"
Cannot assign int to property Hoge::$hoge_val of type Hoge
#0 {main}
===    '2'    ===
int(2)
string(1) "2"
Cannot assign string to property Hoge::$hoge_val of type Hoge
#0 {main}
===    true    ===
int(1)
string(1) "1"
Cannot assign bool to property Hoge::$hoge_val of type Hoge
#0 {main}
===    '00'    ===
int(0)
string(2) "00"
Cannot assign string to property Hoge::$hoge_val of type Hoge
#0 {main}
===    'hello'    ===
Cannot assign string to property Hoge::$int_val of type int
#0 {main}
string(5) "hello"
Cannot assign string to property Hoge::$hoge_val of type Hoge
#0 {main}
===    Hoge::__set_state(array(
))    ===
Cannot assign Hoge to property Hoge::$int_val of type int
#0 {main}
string(27) "Hoge::__set_state(array(
))"
object(Hoge)#2 (0) {
  ["int_val"]=>
  uninitialized(int)
  ["str_val"]=>
  uninitialized(string)
  ["hoge_val"]=>
  uninitialized(Hoge)
}
$ 

所感

パラメータの型宣言(type hinting)がもう少し早くプリミティブ型に対応していてくれれば、あんな苦労(何)をせずに済んだのになぁ、とは思う。

いずれにしても、これで型安全なコーディングがしやすくなることは確か。

もう少し検証が必要な気がするので、それはまた後日。