PHPでの変数の型キャスト、これを行う実際的な理由は何ですか?


45

私たちのほとんどが知っているように、PHPには弱い型付けがあります。そうでない人のために、PHP.netは次のように述べています。

PHPは、変数宣言で明示的な型定義を必要としません(またはサポートしません)。変数のタイプは、変数が使用されるコンテキストによって決まります。

好きでも嫌いでも、PHPはその場で変数を再キャストします。したがって、次のコードは有効です。

$var = "10";
$value = 10 + $var;
var_dump($value); // int(20)

PHPでは、次のように変数を明示的にキャストすることもできます。

$var = "10";
$value = 10 + $var;
$value = (string)$value;
var_dump($value); // string(2) "20"

それはすべてクールです...しかし、私の人生にとって、これを行うための実用的な理由を考えることはできません。

Javaのような、それをサポートする言語での強い型付けの問題はありません。それは大丈夫で、私はそれを完全に理解しています。また、関数パラメーターの型ヒントを認識し、その有用性を完全に理解しています。

型キャストに関する問題は、上記の引用文で説明されています。PHPで型自由に交換できる場合、型を強制的にキャストした後でも可能です。また、操作で特定のタイプが必要な場合にオンザフライで実行できます。これにより、次のことが有効になります。

$var = "10";
$value = (int)$var;
$value = $value . ' TaDa!';
var_dump($value); // string(8) "10 TaDa!"

だからポイントは何ですか?


PHPでユーザー定義型キャストが理にかなっているこの理論的な例を見てみましょう。

  1. キャスト変数$fooint→ として強制します(int)$foo
  2. 変数に文字列値を保存しようとしました$foo
  3. PHPは例外をスローします!! ←それは理にかなっています。突然、ユーザー定義型キャストの理由が存在します!

PHPが必要に応じて切り替えを行うという事実により、ユーザー定義型キャストのポイントが曖昧になります。たとえば、次の2つのコードサンプルは同等です。

// example 1
$foo = 0;
$foo = (string)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

// example 2
$foo = 0;
$foo = (int)$foo;
$foo = '# of Reasons for the programmer to type cast $foo as a string: ' . $foo;

もともとこの質問をしてから1年後、誰が実際の環境でタイプキャストを使用していると思いましたか?敬具。

要件は、レストランメニューのウェブサイトに金額を表示することでした。サイトの設計では、表示が次のようになるように、末尾のゼロを削除する必要がありました。

Menu Item 1 .............. $ 4
Menu Item 2 .............. $ 7.5
Menu Item 3 .............. $ 3

私が見つけた最良の方法は、変数をフロートとしてキャストすることでした。

$price = '7.50'; // a string from the database layer.
echo 'Menu Item 2 .............. $ ' . (float)$price;

PHPは、フロートの末尾のゼロを削除し、フロートを連結用の文字列として再キャストします。


これ-> $ value = $ value 'TaDa!'; $ valueの最終値への割り当てを行う前に、$ valueを文字列にキャストします。型キャストを強制すると、型キャストが行われることはそれほど驚くことではありません。そのポイントが何であるかを尋ねることでポイントが何であるかわからない?
クリス

「#3。PHPは例外をスローします!! <---それは理にかなっています。」実際、それはまったく意味がありません。それは、Java、JavaScript、または私が知っている他のC構文言語でも問題ではありません。彼らの正しい心の誰がそれを望ましい行動と見るだろうか?どこでも(string)キャストをしたいですか?
ニコール

@Renesis:あなたは私を誤解しています。つまり、ユーザーが変数を型キャストした場合にのみ例外がスローされます。通常の動作(PHPがキャストを行う場所)では、もちろん例外はスローされません。ユーザー定義型キャストはmootであると言いたいのですが、例外がスローされた場合、それは突然意味をなします。
スティーブン

あなたが$intval.'bar'例外を投げると言っている場合、私はまだ同意しません。どの言語でも例外はスローされません。(私が知っているすべての言語は、自動キャストまたはを実行します.toString())。あなたが$intval = $stringval例外を投げると言っているなら、あなたは強く型付けされた言語について話している。私は失礼に聞こえるつもりはなかったので、すみません。それは、すべての開発者が慣れているものに反するもので、はるかに不便だと思います。
ニコール

@Stephen-調査の結果、回答を投稿しました。本当に興味深い結果-2つのケースが確かにキャストの目的を示すと思いましたが、PHPは思ったよりもさらに奇妙です。
ニコール

回答:


32

弱く型付けされた言語では、型キャストは型付き操作のあいまいさを排除するために存在します。そうでない場合、コンパイラー/インタープリターは順序または他の規則を使用して、どの操作を使用するかの仮定を行います。

通常、PHPはこのパターンに従うと思いますが、私がチェックしたケースでは、PHPはそれぞれで直感に反して動作しました。

JavaScriptを比較言語として使用している場合を次に示します。

文字列の連結

別のがあるので、明らかにこれはPHPでの問題ではありません文字列連結.)と追加(+)演算子は。

JavaScript
var a = 5;
var b = "10"
var incorrect = a + b; // "510"
var correct = a + Number(b); // 15

文字列の比較

多くの場合、コンピューターシステムでは「5」は「10」よりも大きいため、数値として解釈されません。PHPではそうではありません。PHPは、両方が文字列であっても、数字であることを認識し、キャストの必要性を取り除きます):

JavaScript
console.log("5" > "10" ? "true" : "false"); // true
PHP
echo "5" > "10" ? "true" : "false";  // false!

関数シグネチャの型付け

PHPは、関数のシグネチャに対して最低限の型チェックを実装していますが、残念ながら、非常に欠陥があるため、ほとんど使用できません。

私は何か間違っているかもしれないと思っていましたが、ドキュメントへのコメントは、配列以外の組み込み型はPHP関数シグネチャで使用できないことを確認しています-エラーメッセージは誤解を招くものです。

PHP
function testprint(string $a) {
    echo $a;
}

$test = 5;
testprint((string)5); // "Catchable fatal error: Argument 1 passed to testprint()
                      //  must be an instance of string, string given" WTF?

そして、私が知っている他の言語とは異なり、理解できる型を使用しても、その引数(must be an instance of array, null given)にnullを渡すことはできません。なんてばかな。

ブール解釈

[ 編集 ]:これは新しいものです。私は別のケースを考えましたが、やはりロジックはJavaScriptと逆になっています。

JavaScript
console.log("0" ? "true" : "false"); // True, as expected. Non-empty string.
PHP
echo "0" ? "true" : "false"; // False! This one probably causes a lot of bugs.

結論として、私が考えることができる唯一の有用なケースは...(ドラムロール)

型の切り捨て

つまり、あるタイプの値(たとえば文字列)があり、それを別のタイプ(int)として解釈し、そのタイプの値の有効なセットの1つに強制する場合:

$val = "test";
$val2 = "10";
$intval = (int)$val; // 0
$intval2 = (int)$val2; // 10
$boolval = (bool)$intval // false
$boolval2 = (bool)$intval2 // true
$props = (array)$myobject // associative array of $myobject's properties

(より多くの値を含むタイプへの)アップキャストが実際にあなたにどんな利益をもたらすかはわかりません。

したがって、私はあなたの提案されたタイピングの使用に同意しませんが(本質的に静的タイピングを提案していますが、型に強制キャストされた場合にのみエラーをスローするという曖昧さにより混乱を引き起こします)、それは良いと思います質問は、明らかにキャストするためには、持っている非常に PHPで少し目的を。


さて、E_NOTICEそれではどうですか?:)
スティーブン

@Stephen E_NOTICEは大丈夫かもしれませんが、私にはあいまいな状態が懸念されます-変数がその状態にあった場合(他の場所にキャストされている場合)、1ビットのコードを見るとどのようにわかりますか?また、別の条件を見つけて回答に追加しました。
ニコール

1
ブール値の評価に関しては、PHPのドキュメントでは、ブール値と空の文字列と文字列「0」の両方が偽と見なされる場合に偽と見なされるものが明確に記載されています。そのため、これが不自然だと感じても、それは正常で予想される動作です。
ヤチェクプルシア

混乱にビットを追加するには:echo "010" == 010 およびecho "0x10" == 0x10;-)
バルテック

1
PHP 7以降、スカラー型のヒントに関するこの回答のメモは不正確であることに注意してください。
ジョンV.

15

あなたは弱い/強いタイプと動的/静的なタイプの概念を混ぜています。

PHPは脆弱で動的ですが、問題は動的型の概念にあります。つまり、変数には型がなく、値にはあります。

「型キャスト」とは、元の型とは異なる型の新しい値を生成する式です。変数に何もしません(関係する場合)。

キャスト値を定期的に入力する1つの状況は、数値SQLパラメーターです。SQLステートメントに挿入する入力値をサニタイズ/エスケープするか、(はるかに優れた)パラメーター化されたクエリを使用する必要があります。ただし、整数でなければならない値が必要な場合は、キャストする方がはるかに簡単です。

考慮してください:

function get_by_id ($id) {
   $id = (int)$id;
   $q = "SELECT * FROM table WHERE id=$id LIMIT 1";
   ........
}

最初の行を$id省略した場合、SQLインジェクションの簡単なベクトルになります。キャストは、それが無害な整数であることを確認します。何らかのSQLを挿入しようとすると、単にクエリが実行されます。id=0


私はそれを受け入れます。さて、型キャストの有用性に関しては?
スティーブン

SQLインジェクションを起動するのは面白いです。私は、この手法を使用してユーザー入力をサニタイズする誰かとSOについて議論していました。しかし、この方法で解決mysql_real_escape_string($id);できない問題は何 ですか?
スティーブン

それはより短いです:-)もちろん、文字列にはパラメータ化されたクエリを使用するか、(古いmysql拡張機能を使用している場合)エスケープします。
ハビエル

2
mysql_real_escape_string()'0x01ABCDEF'(整数の16進表現)のような文字列に対して何もしない脆弱性があります。一部のマルチバイトエンコーディング(幸運なUnicodeではない)では、このような文字列を使用してクエリを分割できます(MySQLによって引用符を含むものに評価されるため)。そのため、整数値を処理するのに最適な選択肢はありませmysql_real_escape_string()is_int()。型キャストです。
Mchl

さらに詳細なリンク:ilia.ws/archives/…– Mchl 11
1

4

私が見つけたPHPでの型キャストの1つの使用法:

私は、データベースからデータを取得するためにサーバー上のPHPスクリプトにhttpリクエストを行うAndroidアプリを開発しています。スクリプトは、データをPHPオブジェクト(または連想配列)の形式で保存し、JSONオブジェクトとしてアプリに返します。型キャストがなければ、次のようなものを受け取ります。

{ "user" : { "id" : "1", "name" : "Bob" } }

しかし、(int)ユーザーのIDにPHPオブジェクトを格納するときに、PHPの型キャストを使用すると、代わりにアプリに返されます:

{ "user" : { "id" : 1, "name" : "Bob" } }

JSONオブジェクトがアプリで解析されると、IDを整数に解析する必要がなくなります。

参照してください、非常に便利です。


私は、外部の強力な型付けされたシステムが消費するデータのフォーマットを検討していませんでした。+1
スティーブン

これは、JSONをElasticsearchなどの外部システムと通信する場合に特に当てはまります。json_encode() - ED値は「5」値5よりも非常に異なる結果を与えるだろう
ヨハン・フレドリックVaren

3

1つの例は__toStringメソッドを持つオブジェクトです: $str = $obj->__toString();vs $str = (string) $obj;。2番目の入力ははるかに少なく、余分なものは句読点であり、入力に時間がかかります。他の人も同意しないかもしれませんが、私はそれがより読みやすいと思います。

別の方法は、単一要素の配列を作成することです: array($item);vs (array) $item;。これにより、配列内にスカラー型(整数、リソースなど)が配置されます。
または、$itemオブジェクトの場合、そのプロパティは値のキーになります。ただし、object-> array変換は少し奇妙だと思います:privateおよびprotectedプロパティは配列の一部であり、名前が変更されています。PHPのドキュメントを引用するには、プライベート変数には変数名の前にクラス名が付いています。保護された変数には、変数名の前に「*」が付加されます。

別の用途は、GET / POSTデータをデータベースに適したタイプに変換することです。MySQLはこれ自体を処理できますが、より多くのANSI準拠サーバーがデータを拒否する可能性があると思います。データベースについてのみ言及する理由は、他のほとんどの場合、データはある時点でそのタイプに応じて実行されるためです(つまり、int / floatでは通常、計算が実行されるなど)。


これらは、型キャストの仕組みの優れた例です。しかし、私は彼らがニーズを満たすとは確信していません。はい、オブジェクトを配列に変換できますが、なぜですか?新しい配列で無数のPHP配列関数を使用できるのではないでしょうか。また、PHPは通常、MySQLデータベースに送信する文字列クエリを作成するため、変数タイプは無関係です(クエリからの自動文字列変換、intまたはfloatクエリの構築時に発生します)。 (array) $itemあるきちんとした、しかし便利?
スティーブン

私は実際に同意します。私がそれらをタイプしていたので、私はいくつかの用途を考えるだろうと思ったが、私はしなかった。データベースに関しては、パラメータがクエリ文字列の一部である場合、あなたは正しいです、キャストには目的がありません。ただし、パラメーター化されたクエリを使用する場合(常に良い考えです)、パラメーターの型を指定することは可能です。
アランピアース

あぁ!パラメータ化されたクエリで有効な理由を見つけた可能性があります。
スティーブン

0

このスクリプト:

$tags = _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

は最初のケースでは配列を返しますが、2番目のケースでは配列を返さscript.php?tags[]=oneないためscript.php?tags=one、で正常に実行されますが、で失敗し_GET['tags']ます。スクリプトは配列を予期するように記述されているため(そして、スクリプトに送信されるクエリ文字列を制御することはできません)、次の結果を適切にキャストすることで問題を解決できます_GET

$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
    echo 'tag: ', $tag;
}

0

また、迅速でダーティな方法として使用して、信頼できないデータが何かを壊さないようにすることもできます。

$amount = (float) $_POST['amount'];

if( $amount > 0 ){
    $remoteService->doacalculationwithanumber( $amount );    
}

明らかにこれには欠陥があり、ifステートメントの比較演算子によって暗黙的に処理されますが、コードの実行内容を正確に把握するのに役立ちます。


1
それが壊れないことを除いて。$_POST['amount']ガベージ文字列が含まれていたとしても、phpはゼロより大きくないと評価します。正の数を表す文字列が含まれている場合、trueと評価されます。
スティーブン

1
完全に真実ではありません。金額を受け取る必要がある条件内のサードパーティサービスに$ amountが渡されることを考慮してください。誰かが$ _POST ['amount'] = "100 bobbins"を渡す場合、(float)を削除しても条件はパスできますが、$ amountは数値ではありません。
-Gruffputs

-2

私が頻繁に使用するPHP再キャスト変数の「使用」は、外部ソース(ユーザー入力またはデータベース)からデータを取得する場合です。コーダー(開発者とは言わなかったことに注意してください)が、さまざまなソースから利用可能なさまざまなデータ型を無視する(学習しないことができます。

私が継承し、まだ維持しているコードを持つ1人のコーダー(私は開発者とは言わなかったことに注意してください)は、スーパー変数に返される文字列"20"$_GET20 + 20彼女がそれを追加するときの整数演算の間に違いがあることを知らないデータベース内の値。彼女はコードが2つの文字列(MySQLからの値とからの値)を「追加」してintを取得したので、PHPが.文字列の連結に使用し+、他の言語とは異なります。varcahr$_GET

これは実用的な例ですか?コーダーがどのデータ型を使用しているかを知らなくても済むようにするという意味でのみです。個人的には嫌いです。


2
この回答がどのように議論に価値を加えるかわかりません。PHPでは、エンジニア(またはプログラマー、またはコーダー)が文字列に対して数学演算を実行できるという事実は、この質問ですでに明らかになっています。
スティーブン

ステファン、ありがとう。多分「PHPでは、データ型が何であるかを知らない人でも、理想的な条件下で期待どおりの動作をするアプリケーションを作成できる」と言うにはあまりにも多くの言葉を使いました。
-dotancohen
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.