2016/10/18 20:43:06

PHPって型宣言(タイプヒンティング)してもint型がstring型に型変換されるの?

php-logo
目次(クリックするとジャンプします)
  • 1:型宣言(タイプヒンティング)
  • 1.1:array型ではどうだろう
  • 2:知らないうちに「暗黙の型変換」
  • 3:ちゃんと型検査したい時には
  • 4:型変換の意義
  • 5:まとめ

型宣言(タイプヒンティング)

PHPには型宣言に落とし穴があります。例外のテストの方法を勉強していて遭遇した現象です。

すごく簡素ですが、こんな感じのコードとテストを書いて再現してみます。

実行コード

<?php
class TestClass{
 public function typeError(string $data)
    {
        echo $data;
    }
}

テストコード

<?php
require "/hoge/TestClass.php";
class Test extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException     TypeError
     **/
    public function testType()
    {
        $data = 1;
        TestClass::typeError($data);
    }
}

コードを見ていただければわかるかと思いますが、typeError関数の引数はPHP7から導入されたスカラー型の型宣言をしています。stringを宣言しているので、文字列のみが引数として許されることになるはずです。

PHP7からは型宣言に抵触すると、TypeErrorという例外が投げられることになります。

テストコードでは@expectedExceptionアノテーションにてTypeErrorを指定して補足する準備をしています。ですのでこのテストはTypeErrorが補足できたら成功というテストです。

$dataint型である数字の1を格納しtypeErrorに渡しています。

typeErrorの引数はstring型で型宣言しているのですから、int型の値を与えるとTypeErrorがスローされ、このテストは成功するはずです。

しかし、成功しません。

テストでは以下の出力がされます。

Failed asserting that exception of type "TypeError" is  thrown.

これはTypeErrorの補足に失敗したことを意味します。

array型ではどうだろう

ではテストを成功させるために別な型を使ってみます。

テストコード

<?php
require "/hoge/TestClass.php";
class Test extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException     TypeError
     **/
    public function testType()
    {
        $data = [1, 2, 3];
        TestClass::typeError($data);
    }
}

値を配列(array型)にしてみました。これでテストをしてみると

PHPUnit 5.1.7 by Sebastian Bergmann and contributors.

.                                                            1 / 1 (100%)

Time: 30 ms, Memory: 4.00Mb

OK (1 tests, 1 assertions)

成功しました。ちゃんとTypeErrorを補足できたということですね。

う〜む。どういうことなんだろう?

知らないうちに「暗黙の型変換」

よくよく調べて見ると、PHPの型宣言は弱い型検査らしく、型宣言をしてもintstring可能な限り型変換が行われるようです。

intstringだけでなくintfloatstringbool互いに暗黙の型変換が行われるようですよ。

それだと型宣言の意味あるのかな〜。

ちゃんと型検査したい時には

この暗黙の型変換を回避するにはstrict(厳密)モードにする必要があります。 以下をファイルの先頭にの記述します。

declare(strict_types=1)

これでintfloat以外の暗黙の型変換は行われなくなるようです。

実際strictモードにて再テスト(引数にint型を与える)をしてみると…

実行コード

<?php
declare(strict_types=1);
class TestClass{
 public function typeError(string $data)
    {
        echo $data;
    }
}

テストコード

<?php
require "/hoge/TestClass.php";
class Test extends PHPUnit_Framework_TestCase
{
    /**
     * @expectedException     TypeError
     **/
    public function testType()
    {
        $data = 1;
        TestClass::typeError($data);
    }
}

ちゃんとテストが成功します。型変換は行われずTypeErrorがスローされたということです。

PHPUnit 5.1.7 by Sebastian Bergmann and contributors.

.                                                                   1 / 1 (100%)

Time: 35 ms, Memory: 4.00Mb

OK (1 test, 1 assertion)

型変換の意義

型変換に関してやっぱりPHPという感じです。もともと動的型付けの言語ですし、型を意識しない言語でもあります。弱い型検査はPHPの使用感を保つには理にかなっているかもしれません。

他の静的型付け言語の様に厳密をデフォルトとするのはPHPにはそぐわない気もします。

切り替えができるようにしてくれたのは吉と出るか凶とでるか…。

ともかくも型宣言を厳密に行いたいときはdeclare(strict_types=1)を忘れずに。

ということでシタ。

まとめ

  • PHPの型宣言は弱い型検査
  • デフォルトではintfloatstringboolは互いに暗黙の型変換が行われる
  • 型変換を厳密にするにはstrictモードにする必要がある
  • 厳密モードはdeclare(strict_types=1)をファイルの先頭に記述する