PHP 中的类型转换变量,这样做的实际原因是什么?
https://softwareengineering.stackexchange.com/questions/24378
-
22-10-2019 - |
题
正如我们大多数人所知,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 中有意义:
- 您强制转换变量
$foo
作为int
→(int)$foo
. - 您尝试在变量中存储字符串值
$foo
. - PHP 抛出异常!!← 这是有道理的。突然之间,用户定义类型转换的原因就存在了!
事实上 PHP 会根据需要进行切换,这使得用户定义类型转换的意义变得模糊。例如,以下两个代码示例是等效的:
// 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;
在最初提出这个问题一年后,猜猜谁发现自己在实际环境中使用类型转换?敬上。
要求是在网站上显示餐厅菜单的货币价值。该站点的设计要求修剪尾随零,以便显示如下所示:
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 修剪浮点数的尾随零,然后将浮点数重新转换为字符串以进行串联。
解决方案
在一种弱化的语言中,存在类型铸造以消除键入操作中的歧义,否则编译器/解释器将使用订单或其他规则来假设要使用哪种操作。
通常,我会说PHP遵循这种模式,但是在我检查过的情况下,PHP在每种情况下都表现出违反直觉。
以下是这些情况,使用JavaScript作为比较语言。
字符串串联
显然,这不是PHP的问题,因为有独立的 字符串串联 (.
)和加法(+
)操作员。
var a = 5;
var b = "10"
var incorrect = a + b; // "510"
var correct = a + Number(b); // 15
字符串比较
通常,在计算机系统中,“ 5”大于“ 10”,因为它不会将其解释为数字。在PHP中并非如此,即使 两个都 是字符串,意识到它们是数字,并且消除了对演员的需求):
JavaScriptconsole.log("5" > "10" ? "true" : "false"); // true
php
echo "5" > "10" ? "true" : "false"; // false!
功能签名键入
PHP在功能签名上进行了裸露的类型检查,但不幸的是,它是如此有缺陷,可能很少可用。
我以为我可能做错了什么,但是 评论文档 确认在PHP功能签名中不能使用以外的内置类型 - 尽管错误消息具有误导性。
phpfunction 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?
而且,与我知道的其他任何语言不同,即使您使用了它理解的类型,NULL也无法再传递给该参数(must be an instance of array, null given
)。多么愚蠢。
布尔解释
[编辑]:这是新的。我想到了另一种情况,逻辑再次从JavaScript逆转。
JavaScriptconsole.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),并且要强迫它成为该类型中的有效值集之一:
$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几乎没有目的。
其他提示
您正在混合弱/强,动态/静态类型的概念。
PHP是弱且动态的,但是您的问题与动态类型的概念有关。这意味着,变量没有类型,值可以。
“类型铸造”是一种产生不同类型原始类型的新值的表达。它对变量没有任何作用(如果涉及的话)。
我经常键入铸造值的一种情况是在数字SQL参数上。您应该对您插入SQL语句的任何输入值进行消毒/逃脱任何输入值,或者(更好)使用参数化查询。但是,如果您想要一些必须是整数的价值,那么施放它就容易得多。
考虑:
function get_by_id ($id) {
$id = (int)$id;
$q = "SELECT * FROM table WHERE id=$id LIMIT 1";
........
}
如果我忽略了第一行, $id
将是SQL注入的简单矢量。演员们确保这是一个无害的整数。任何插入某些SQL的尝试都会导致查询 id=0
我发现的PHP中类型铸造的一种用途:
我正在开发一个Android应用程序,该应用程序向服务器上的PHP脚本提出HTTP请求,以从数据库中检索数据。该脚本以PHP对象(或关联数组)的形式存储数据,并作为JSON对象返回到该应用程序。没有类型的铸造,我会收到这样的东西:
{ "user" : { "id" : "1", "name" : "Bob" } }
但是,使用PHP类型铸造 (int)
在用户的ID上存储PHP对象时,我将其返回到应用程序:
{ "user" : { "id" : 1, "name" : "Bob" } }
然后,当JSON对象在应用程序中解析时,它使我不得不将ID解析为整数!
看,非常有用。
一个示例是具有 __toString 方法的对象:$str = $obj->__toString();
与$str = (string) $obj;
. 。第二次打字要少得多,额外的东西是标点符号,打字需要更长的时间。我还认为它更具可读性,尽管其他人可能不同意。
另一个是制作一个单元素数组:array($item);
与 (array) $item;
. 。这会将任何标量类型(整数、资源等)放入数组中。
或者,如果 $item
是一个对象,它的属性将成为其值的关键。但是,我确实认为对象->数组转换有点奇怪:private 和 protected 属性是数组的一部分,并被重命名。引用 PHP 文档: :私有变量的变量名前面带有类名;受保护的变量在变量名前有一个“*”。
另一个用途是将 GET/POST 数据转换为适合数据库的类型。MySQL 可以自己处理这个问题,但我认为更符合 ANSI 的服务器可能会拒绝数据。我只提到数据库的原因是,在大多数其他情况下,数据将在某个时刻根据其类型对其执行操作(即int/floats 通常会对其进行计算,等等)。
这个脚本:
$tags = _GET['tags'];
foreach ($tags as $tag) {
echo 'tag: ', $tag;
}
会很好 script.php?tags[]=one
但会失败 script.php?tags=one
, , 因为 _GET['tags']
在第一种情况下返回数组,但在第二种情况下不返回。由于脚本被编写为期望数组(并且您对发送给脚本的查询字符串的控制权更少),因此可以通过适当地从中施放结果来解决问题 _GET
:
$tags = (array) _GET['tags'];
foreach ($tags as $tag) {
echo 'tag: ', $tag;
}
它也可以用作快速而肮脏的方法,以确保不受信任的数据不会破坏某些内容,例如使用具有废话验证并且只能接受数字的远程服务。
$amount = (float) $_POST['amount'];
if( $amount > 0 ){
$remoteService->doacalculationwithanumber( $amount );
}
显然,这是有缺陷的,也由IF语句中的比较操作员隐含地处理,但有助于确保您确切知道代码在做什么。
我经常看到的一种“使用”重新计算变量的一种“使用”是在从外部来源检索数据(用户输入或数据库)时。它可以让编码器 (请注意,我没有说开发人员) 忽略(或甚至不学习)不同来源可用的不同数据类型。
一个编码器 (请注意,我没有说开发人员) 我继承和仍然维护的代码似乎并不知道 存在字符串之间的区别 "20"
在 $_GET
超级变量,到整数操作之间 20 + 20
当她将其添加到数据库中的值中时。她只幸运的是PHP使用 .
用于字符串串联而不是 +
像其他所有语言一样,因为我看到了她的代码“添加”两个字符串(a varcahr
从mysql和一个值 $_GET
)并获得一个int。
这是一个实用的例子吗?只有从某种意义上说,它才能使编码员不知道他们正在使用哪些数据类型。 我个人讨厌它。