在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可以随意交换类型即使您强制转换了类型,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将根据需要切换事物的事实使得用户定义类型转换的意义变得模糊。例如,以下两个代码示例是等效的:

// 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修剪浮点数的尾随零,然后将浮点数重铸为字符串以进行串联。


这-> $ value = $ value。“塔达!” 在将赋值给$ value的最终值之前,会将$ value强制转换回字符串。如果您强制执行强制类型转换,您将得到强制类型转换也就不足为奇了。不确定要问的重点是什么?
克里斯,2010年

“#3。PHP引发异常!! <---这很有道理。” 实际上,这根本没有任何意义。我所知道的Java,JavaScript或任何其他C语法语言甚至都不是问题。谁在他们的头脑中认为这是可取的行为?你想有(string)石膏到处
妮可

@Renesis:你误会了我。我的意思是,仅当用户键入类型变量时,才会引发异常。正常的行为(PHP为您进行强制转换)当然不会引发异常。我想说的是,用户定义的类型转换是没有意义的,但是如果抛出异常,则突然变得有意义。
斯蒂芬,2010年

如果您说$intval.'bar'抛出异常,我仍然不同意。这不会在任何语言中引发异常。(我知道的所有语言都执行自动强制转换或.toString())。如果您说$intval = $stringval抛出异常,那么您是在谈论强类型语言。我本来不是很粗鲁的,所以,如果我这样做,我很抱歉。我只是认为这违背了每个开发人员的习惯,而且方便程度大大降低。
妮可

@Stephen-经过一些调查,我发布了答案。真正有趣的结果-我认为其中两种情况肯定会显示出转换的目的,但是PHP比我想象的还要奇怪。
妮可

Answers:


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中不是这样,即使它们都是字符串,也可以意识到它们是数字,并且不需要强制转换):

的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?

而且与我所知道的任何其他语言不同,即使您使用它可以理解的类型,也无法再将null传递给该参数(must be an instance of array, null given)。真蠢

布尔解释

[ 编辑 ]:这是新的。我想到了另一种情况,逻辑又一次与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),并且想要强制其成为该类型的有效值之一时:

$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?:)
斯蒂芬·2010年

@Stephen E_NOTICE可能还可以,但是对我来说,模棱两可的状态令人担忧-您如何通过查看一点代码来知道变量是否处于该状态(已在其他地方强制转换)?另外,我发现了另一种情况并将其添加到我的答案中。
妮可

1
至于布尔值评估,PHP文档清楚地说明了对布尔值进行评估时认为什么是错误的,并且空字符串和字符串“ 0”都被认为是错误的。因此,即使感觉到这种琐事,这也是正常的和预期的行为。
Jacek Prucia 2011年

有点混乱:echo "010" == 010echo "0x10" == 0x10;-)
vartec 2013年

1
请注意,从PHP 7开始,此答案有关标量类型提示的注释不准确。
John V.

15

您正在混合使用弱/强和动态/静态类型的概念。

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


我会接受的。现在,至于类型转换的用处?
斯蒂芬

调出SQL注入很有趣。我正在和使用此技术的用户进行SO争论,以清理用户输入。但是该方法mysql_real_escape_string($id);尚未解决什么问题 ?
斯蒂芬

它更短:-)当然,对于字符串,我使用参数化查询,或者(如果使用旧版mysql扩展名)对其进行转义。
哈维尔2010年

2
mysql_real_escape_string()有一个漏洞,无法对字符串“ 0x01ABCDEF”(即整数的十六进制表示)进行任何操作。在某些多字节编码中(不幸的是,不是Unicode),可以使用这样的字符串来中断查询(因为它被MySQL评估为包含引号的内容)。这就是为什么mysql_real_escape_string()也不is_int()是处理整数值的最佳选择的原因。类型转换是。
Mchl 2011年

:有一些细节的链接ilia.ws/archives/...
MCHL

4

我发现了在PHP中进行类型转换的一种用法:

我正在开发一个Android应用,该应用向服务器上的PHP脚本发出HTTP请求以从数据库中检索数据。该脚本以PHP对象(或关联数组)的形式存储数据,并以JSON对象的形式返回给应用程序。如果没有类型转换,我将收到以下内容:

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

但是,(int)在将用户对象存储为PHP对象时,使用对用户ID的PHP类型转换将其返回给应用程序:

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

然后,当在应用程序中解析JSON对象时,它使我不必将id解析为Integer!

看,非常有用。


我没有考虑过格式化要使用的外部强类型系统的数据。+1
斯蒂芬

在将JSON与Elasticsearch之类的外部系统进行通讯时尤其如此。甲json_encode() - ED值“5”将给出比值5非常不同的结果
约翰的Fredrik Varen

3

一个示例是带有__toString方法的对象: $str = $obj->__toString();vs $str = (string) $obj;。第二种类型的打字要少得多,而额外的东西是标点符号,这需要更长的时间才能键入。我也认为它更具可读性,尽管其他人可能不同意。

另一个方法是制作一个单元素数组: array($item);vs (array) $item;。这会将任何标量类型(整数,资源等)放入数组中。
替代地,如果$item是对象,则其属性将成为其值的键。但是,我确实认为object-> array转换有点奇怪:private和protected属性是数组的一部分,并已重命名。引用PHP文档:私有变量的类名在变量名之前;受保护的变量在变量名前带有“ *”。

另一个用途是将GET / POST数据转换为数据库的适当类型。MySQL可以自行处理,但我认为符合ANSI标准的服务器可能会拒绝数据。我之所以仅提及数据库,是因为在大多数其他情况下,数据将在某个时候根据其类型在数据库上执行操作(即,通常对int / float进行计算等)。


这些是类型转换如何工作的好例子。但是,我不相信他们满足了需求。是的,您可以将对象转换为数组,但是为什么呢?我猜是因为您然后可以在新数组上使用无数的PHP数组函数,但是我无法理解这样做的用处。另外,PHP通常会创建字符串查询以发送到MySQL数据库,因此变量类型无关紧要(构建查询时会自动发生intfloat将发生字符串自动转换)。 (array) $item整齐的,但有用吗?
斯蒂芬2010年

我真的同意。当我键入它们时,我以为我会想到一些用途,但我没有。对于数据库而言,如果参数是查询字符串的一部分,那么您是对的,强制转换没有任何目的。但是,在使用参数化查询时(总是一个好主意),可以指定参数的类型。
艾伦·皮尔斯

啊哈!您可能已经通过参数化查询找到了有效的理由。
斯蒂芬,2010年

0

该脚本:

$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;
}

0

它也可以用作一种快速而肮脏的方法,以确保不受信任的数据不会破坏某些内容,例如,如果使用具有废话验证并且只能接受数字的远程服务。

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

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

显然,这是有缺陷的,还可以由if语句中的比较运算符进行隐式处理,但有助于确保您确切地知道代码在做什么。


1
除非它不会损坏。即使$_POST['amount']包含垃圾字符串,php也会评估它不大于零。如果它包含表示正数的字符串,则它将评估为true。
斯蒂芬

1
并非完全正确。考虑将$ amount传递给必须接收数字的条件内的第三方服务。如果有人要传递$ _POST ['amount'] =“ 100个线轴”,则删除(float)仍将允许有条件的传递,但$ amount不能为数字。
Gruffputs

-2

我经常看到的一种动态使用PHP重新广播变量的“用法”是从外部源(用户输入或数据库)检索数据时。它使编码人员(请注意,我没有说开发人员)忽略(甚至不学习)可从不同来源获得的不同数据类型。

我继承并仍然维护其代码的一个编码器(请注意,我没有说开发人员)似乎并不知道在将超级变量返回的字符串"20"$_GET20 + 20将其添加到整数操作之间的差异之间存在差异。数据库中的值。她只是很幸运,PHP .用于字符串连接,而+不像其他任何语言一样,因为我看到她的代码“添加”了两个字符串(一个varcahr来自MySQL,一个来自$_GET),并获得一个整数。

这是一个实际的例子吗?仅从某种意义上说,它可以使编码人员摆脱不知道他们正在使用哪种数据类型的想法。我个人讨厌它。


2
我看不出此答案如何为讨论增加价值。PHP允许工程师(或程序员,或编码员,您拥有什么)对字符串执行数学运算的事实在这个问题上已经很清楚了。
斯蒂芬

谢谢斯蒂芬。我也许用太多的话说:“ PHP让不知道数据类型是什么的人创建了在理想条件下能够完成他们期望的应用程序”。
dotancohen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.