Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
36
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

Organization

PHPで型を意識し安全なプログラムを書く

はじめに

PHPは動的型付けであり、弱い型付けの言語です。
各言語の型の特徴をまとめてみた

昔はPHPはちょっとしたホームページを簡単にちゃちゃっと作るものだったので
緩くてよかったんだと思いますが、
昨今はPHPでの大型システム構築も多いので、
緩い型の仕様だとバグのリスクが大きいです。

そんな感じで、
PHPでも型をちゃんと意識していこうぜっていう風潮が強いですね。

実際、PHPのバージョンアップでも
型の緩い仕様を改善するような機能追加が多いです。

PHPでも、
最近追加されているいろいろな機能をちゃんと意識して利用すれば
ある程度型安全なプログラムを書くことが可能です。

この記事では、

  • PHPの型の特徴
  • PHPで型を意識した安全なプログラムを書くために利用すべき機能

を紹介します。

PHPの型の特徴

そもそもPHPにはどのような型があるのかについてはこちらを参照ください。
PHPの型一覧 変数の型とタイプヒンティングできる型は別

変数の型宣言をしない

PHPでは変数の型が自動で決まります。
変数に文字列を入れればその変数はstring型になるし、
整数を入れればint型になります。

変数を作るときに型を定義する必要がありません。

$string = 'aaa';     //文字列を入れる
var_dump($string);   //string(3) "aaa"  //stging型になる

$int = 123;          //整数を入れる
var_dump($int);      //int(123)  //int型になる

$array = ['aaa'];    //配列を入れる
var_dump($array);    //array(1) {[0]=>  string(3) "aaa"}  //array型になる

PHPしか使ったことがない人からすると当たり前に感じるかもしれませんが、
例えばJavaなどほかの言語では、
まず初めに変数宣言をして、その時にその変数の型を決めてしまいます。

int integer;     // int型の変数integerを宣言
String str;     // String型の変数strを宣言

その後はその決めた型のデータしか変数に入れることはできません。
※明示的にキャストすれば型変更可能
 
 

暗黙的な型変換

PHPではプログラムの文脈に応じて自動で型を変換して処理をしてくれる
暗黙的な型変換があります。

例(PHP: 型の相互変換 - Manual

\$foo = "1"; // \$foo は文字列 (ASCII 49) です
\$foo *= 2; // ここでは、\$foo は整数 (2) です
\$foo = \$foo * 1.3; // ここでは、\$foo はfloat (2.6) です
\$foo = 5 * "10 Little Piggies"; // \$foo は整数 (50) です
\$foo = 5 * "10 Small Pigs"; // \$foo は整数 (50) です

つまり、最初にstring型のデータを変数に入れていたのに、
いろいろな処理を通った結果
知らぬ間にintになってたりfloatになっていたりするってことですね。
 

PHPの型のメリット・デメリット

これらのPHPの特徴は、
よく言えば
型を意識しなくてもPHPが勝手にいい感じにいろいろやってくれるので
特に初心者には優しく、簡単にプログラミングすることができます。
 

ただ、デメリットとしては
どの変数がどの型になっているのか明確ではないため
想定外のバグを生んでしまうリスクなります。
(ほかの言語専門のプログラマがよく文句言ってる。)
 
 
メリット、デメリットありますが、
PHPでもできるだけ型を意識した安全なプログラムを書いたほうがいいという意見が多いですね。
 
 

型を意識したPHPプログラミングの機能

PHPで型を意識したプログラミングをすることができる機能として
この5つを簡単に紹介します。

  • 厳密な比較演算子(===)を使う
  • 比較系関数の厳密比較オプションをtrueにする
  • 引数の型宣言をする
  • 返り値の型宣言をする
  • strict_types=1を指定する

厳密な比較演算子(===)を使う

PHPには普通の比較演算子(==)と厳密な比較演算子(===)があります。

普通の比較演算子では、
比較対象が別々の型だとしても、
自動的に型を変換して比較を行います。

この仕様は便利な場合もありますが、
想定外のバグを生むことも多いです。

// 普通の比較
var_dump('aaa' == 'aaa');  // bool(true)
var_dump('aaa' == 'bbb');  // bool(false)
var_dump(123 == 123);      // bool(true)
var_dump(123 == 999);      // bool(false)


// 文字列と数値の比較
// これは便利な場合も多い
var_dump('123' == 123);    // bool(true)


// これはバグを生みそう
var_dump('123abc' == 123); // bool(true)
var_dump('abc' == 0);      // bool(true)

一番下の2つの様な例は、
多くの場合trueになることを期待していないと思います。

このような想定外の比較結果が出ることを防ぐために
厳密な比較演算子を利用します。

厳密な比較演算子は、
値だけでなく型も含めて一致するかどうかを判定します。

先ほどの例でtrueになってしまった
文字列と数値の比較もちゃんとfalseとなります。

var_dump('123' === 123);    // bool(false)
var_dump('123abc' === 123); // bool(false)
var_dump('abc' === 0);      // bool(false)

比較系関数の厳密比較オプションをtrueにする

PHPの組み込み関数には、
in_arrayなど比較を行う関数がいくつかあります。

それらの関数の内部では
デフォルトでは普通の比較演算子(==)で比較が行われているため
先ほど書いた文字列と数値の比較などをする際に
想定外の比較結果になってしまうことがあります。

in_arrayの例を見てみます。

var_dump(
    in_array('xxx', ['aaa', 'bbb', 'ccc'])
);
// bool(false)

var_dump(
    in_array(123, ['aaa', 'bbb', '123ccc'])
);
// bool(true)

厳密な比較を行っていないため、
2つ目の例がtrueになってしまいます。

このような事象を防ぐために、
オプション引数を渡すことで厳密な比較を行うことができます。

var_dump(
    in_array(123, ['aaa', 'bbb', '123ccc'], true)
);
// bool(false)

先ほどこの例はtrueになってしまっていましたが、
in_arrayの第3引数にtrueを渡すことによって
内部で厳密比較を行ってくれました。

その結果、比較対象の型が違うためfalseとなっています。

ほかにも
array_searcharray_keysなど、
デフォルトでは普通の比較を行うが
オプション引数を渡すことで厳密比較をしてくれる関数がいくつかあります。

何か比較処理を行うようなPHP組み込み関数を利用する際には、
厳密比較を行うオプションを指定できないかどうか確認し、
可能であればオプション指定するようにするといいです。

引数の型宣言をする

PHPでは、
関数の引数として受け入れるデータ型を指定することができます。

例えば、このような整数を2倍にして返す関数。

function doubleInt($int) {
    return $int * 2;
}

echo doubleInt(5);  // 10

 
 
この関数では整数のみを引数として受け取りたいので、
このように型宣言をします。

function doubleInt(int $int) {  // ←この引数部分にintと記述すると型宣言できる
    return $int * 2;
}

echo doubleInt(5);      // 10
echo doubleInt('aaa');  // error

これで、
int型の5を引数で渡した場合は正常に動作しますが、
string型の'aaa'を渡した場合はエラーになります。

このように引数の型を宣言することで、
想定外の型の引数を渡されないのでバグのリスクを減らせます。

また、関数内で受け取った引数の型チェックのロジックなどを書く必要もなくなるので
コードもシンプルにすることができます。

型宣言で指定可能な型一覧をこちらで把握しておくことをお勧めします。
PHP: 関数の引数 - Manual

返り値の型宣言をする

PHPでは、
関数の返り値の型を指定することができます。

例えば、先ほどのdoubleInt()関数では
返り値はint型である想定なので、そのように型宣言をします。

function doubleInt(int $int): int {  // ←この部分で「: データ型」の形で返り値の型宣言をする
    return $int * 2;
}

echo doubleInt(5);  // 10

 
もし宣言した型と違う型を返した場合はエラーが発生します。

function doubleInt(int $int): int {
    return 'aaa';                    // 文字列'aaa'を返す
}

echo doubleInt(5);  // error

この戻り値の型宣言をすることによって、
関数を複雑に作りこんだ時などに
自分の想定していないデータ型の値を返してしまう
ようなことを防ぐことができます。

また、
関数を使う側からしても、
返却されるデータの型が保証されている状態なので
安心して関数を使うことができます。

PHP: 返り値 - Manual

strict_types=1を指定する

先ほど紹介した
引数の型宣言と返り値の型宣言で、
「宣言した型と違う型を渡す(返す)とエラーになる」
と言いましたが、
実は正確には間違っています。
 
 
PHPのデフォルトでは 弱い型付け の状態になっていて、
この状態だと
宣言した型と違う型を渡した(返した)としても
可能な限りは自動で型変換を行って処理を進めてしまいます。

function doubleInt(int $int) {  // 引数はint型で型宣言されている
    return $int * 2;
}

echo doubleInt('5');   // 10  // string型の'5'を渡してもint型に自動変換されて処理される 
echo doubleInt(true);  // 2   // bool型のtrueを渡してもint型の1に自動変換されて処理される

このように、引数をint型として型宣言していたとしても、
string'5'やbooltrueはエラーとならずintに自動変換されて処理が行われます。
※string'aaa'やarray、objectなどintに自動変換するのが無理な値の場合はエラーとなります
 
 

このような状態を防ぐために
declaer命令でstrict_types=1を指定することで
強い型付けの状態にすることができます。

方法は、PHPファイルの一番最初に
declare(strict_types=1);を1行書くだけです。
 
 

これでPHPが強い型付けの状態となり、
先ほどのような宣言と違う型のデータを渡した(返した)場合には
必ずエラーが発生するようになります。

declare(strict_types=1);  // 強い型付けの設定

function doubleInt(int $int) {
    return $int * 2;
}

echo doubleInt('5');      // error
echo doubleInt(true);     // error

弱い型付けの状態では厳密な型チェックが行われないため
想定外のバグを生むリスクがあります。

基本的にはstryct_types=1を指定する習慣としたほうがいいです。

まとめ

PHPで型を意識し安全なプログラムを書くための機能として
5つ紹介しました。

  • 厳密な比較演算子(===)を使う
  • 比較系関数の厳密比較オプションをtrueにする
  • 引数の型宣言をする
  • 返り値の型宣言をする
  • strict_types=1を指定する

もしほかにも何かあれば
コメントで教えていただけるとありがたいです。

おまけ

PHP7.4ではプロパティの型宣言ができるようになる見込みらしいです。
【PHP7.4】ついにPHPにプロパティ型指定がやってくる

参考

独習PHP
in_arrayを使うときは黙って第三引数を付けること
PHPの型と型安全について(PHP7からのPHPプログラミング)
[PHP] 型宣言(PHP7)
PHP7でタイプセーフなPHPを書きたい時

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
36
Help us understand the problem. What are the problem?