PHPの型と自動キャストの注意点
PHPには全部で8つの型があります。
とはいえPHPは動的型付け言語なので、変数を利用する際に明示的に型を宣言する必要はありません。
このことによりプログラミングを型に縛られず柔軟に行うことができますが、一方で、処理によっては型を自動的にキャスト(変換)してしまうので、これを理解していなければ思わぬバグを引き起こすことになります。
例えば、自動キャストの仕組みによって
'0.0' == '0'
はtrueとなります。
明らかに異なる文字列同士を比べているので、falseかのように見えます。
しかしながら、自動キャストという仕組みによって、これが等しいことになるのです。
こういった想定外の結果による致命的なバグを避けるために、型と自動キャストについて理解しておく必要があります。
この仕組みについては最後に解説します。
ここではまず8つの型を簡単にまとめつつ、明示的なキャスト方法と、自動キャストについて説明します。
1 整数(int)
整数は10進数、16進数(0x~)、8進数(0~)で指定でき、正負の符号をつけることができます。
整数のサイズはプラットフォームに依存し、PHP_INT_SIZE定数で確認できます。
また、最大値もPHP_INT_MAX定数で得られます。
最大値を超えた整数は浮動小数点数型に自動キャストされます。
整数型への明示的なキャストは(int), (iteger), intval()を使います。
2 浮動小数点数型(float)
整数で表せない実数のことで、整数型の最大値を超えた整数も含まれます。
実数で指定する他に、指数表記で初期化することもできます。
明示的にキャストする場合には、(float), (double), floatval()を使います。(double)も使えるのは、実際には倍精度浮動小数点数(64bit長の浮動小数点数を格納できる)だからです。
また、環境によっては特定の計算結果として得られたfloat(10)のような浮動小数点数は内部的には9.9999999のように扱われていて、int型にキャストした場合に小数点以下が切りてられてint(9)になる場合があるようです。
なのでfloat型の値の比較がしたい場合はstring型で行うと良いようです。
3 文字列型
文字列は最も多く操作することになる型で、シングルクォート「'」またはダブルクォート「"」で初期化できます。
シングルの場合はその中の変数やエスケープ文字を展開しませんが、ダブルの場合は展開します。
また、ヒアドキュメントといって、複数行に渡る文字列を定義することもできます。
<?php
$foo = <<< EOI
<<< EOI から EOI; までの間であれば
このように複数行の
文字列の定義ができます。
EOI;
またヒアドキュメントにおいて、変数を展開したくない場合は、終端識別子を ’EOI' とシングルクォートで括ります。
文字列型を明示的に指定するには(string), strval()を利用します。
3.1 文字列の自動キャストの注意点
また、自動キャストは非常によく発生しています。
例えばechoなどで出力する場合、すべて文字列にキャストされてから出力されるので、echoによって出力された文字をそのまま比較演算する場合などは注意が必要です。
あるいは、クライアントからのリクエストや、コマンドラインから実行した場合の引数も、数字や浮動小数点数に見えるものでも文字列です。
比較演算の際に例えば「==」を用いる場合、これは型の一致を求めないので、文字列と整数を比較しても、一旦全部整数型に自動キャストされています。
なので、引数として期待している値の型が決まっているのであればint($argv)などのように型を厳密に指定して、比較演算子には極力「===」を用いるのが安全なようです。
4 論理型
真偽値を扱うもので、trueかfalseしかありません。大文字小文字の区別はありません。
明示的なキャストには(bool)か( boolean)を用います。
4.1 論理型の自動キャスト
条件式や関数の引数で論理型が指定されている場所でtrueかfalse以外を使用した場合、論理型に自動キャストされます。
またこの時、PHPでは何らかの値があるものはほとんどがtrueとされますが、下記の7通りの場合のみfalseと扱われます。
- falase (論理型)
- 0 (整理数型)
- 0.0 (浮動小数点数型)
- 空の文字列 ("")、文字列のゼロ ("0")
- 要素の数がゼロの配列
- null、値がセットされていない空の変数
- 空のタグから作成されたSimpleXMLオブジェクト
5 配列
配列は複数の値の集まりを保持するためのデータ構造です。
PHPでは添字配列と連想配列の区別はなくどちらも配列と呼ばれます。
配列の初期化は
array([要素[, ...]]); // 添字配列
array([key => 要素[, ...]]); //連想配列
で行います。
5.1.1 配列の自動キャスト
keyとして用いるのは整数型か文字列型となり、下記の型を用いた場合、この2つのどちらかにキャストされます。
- 論理型はtrueは整数の1、falseは整数の2となります。
- 浮動小数点数型は小数点以下が切り捨てられ、整数型になります。
- nullは空の文字列("")となります。
6 オブジェクト
クラスをnew演算子によってインスタンス化したものです。
クラスやオブジェクトについての解説は省略します。
7 リソース
何らかの外部ソースへの参照を保存しているものです。
PHPではたくさんの外部ソースを使う拡張機能があるため、いくつもの種類があるようで、ここでは列記しません。
リソース型はその外部リソースの専用となるので、キャストすることはできません。
変数の持つリソース型を調べるには「get_resource_type()」関数を用いることができます。
8 null
ある変数が値を持たないことを表す特別な型です。下記の場合にnullとなります。
- 明示的に定数 nullが代入されている場合
- 値が何も代入されていない場合
- unset() されている場合
よくあるエラーとなるケースは、まだ一度も使ったことのない変数を出力しようとしたり、関数の返り値を代入する場合に関数が何も返していない場合などがあります。
自動キャスト
自動キャストは次のような場合に起きていますので、注意します。
- 異なる型同士で演算を行う場合
- 演算子、制御構造、関数やメソッドが特定の型の引数を必要とする場合で、それとは異なる型の値を用いた場合
- 両辺とも数値らしい文字列の比較演算の際、該当する数値型に自動キャストする
特に比較演算子を用いる場合によく生じます
比較演算子には左右の型が違う場合にキャストする==, != , <, >と
左右の方が違う場合キャストが行われない厳密な=== ,や!==もあります。
特に文字列同士の比較で数値型に変換したくない場合、整数型と浮動小数点数型を比較する可能性がある場合などには要注意です。
冒頭の問題に戻りましょう。
'0.0' == '0'
の挙動は下記の通りとなります。
1 '0.0'は浮動小数点数らしい文字列なので、0.0にキャストされる
2 '0'は整数型らしい文字列なので、0にキャストされる
3 0.0 == 0は異なる型同士の演算なので、整数0は浮動小数点数0.0にキャストされる
4 0.0 == 0.0となり、trueが返る
ここで==の代わりに===を用いると、上記3のキャストが生じないため
4' 0.0 === 0 となり、想定どおりflaseが返ります。
こういった思いも寄らない結果を引き起こさないため、比較演算子は可能な限り厳密な===や!==を用いることが推奨されています。