PHP 类型声明如何工作
类型声明是许多开发人员日常使用的简单编程概念。在下面的代码示例中,我们看到一个使用类型声明的基本方法。该方法在$foo
参数上添加了一个字符串类型提示,并在方法本身上添加了一个字符串返回类型。
function bar(string $foo): string
{
return $foo;
}
$result = bar('Hello');
var_dump($result);
// Output: string(5) "Hello"
这些类型声明告诉我们有关该方法的两件事。它接受一个字符串参数,并返回一个字符串。在像 Go 这样的严格类型语言中,如果bar()
调用该方法并传入一个整数参数(例如 123),则会发生错误。如果该方法由于某种原因返回的不是字符串,代码也会出错。
类型声明在代码中有两个基本用途:
- 低级数据/代码完整性:方法滥用变得更加困难,类型检查也减少了。
- 代码可读性:方法接受什么以及返回什么更加清晰。
PHP 一直以来都是一门弱类型语言。类型声明直到 PHP 5 才引入,并且形式有限。直到 2015 年 PHP 7 发布,完整的类型声明和严格类型才出现。
对于我们这些喜欢类型声明和严格类型代码的人来说,PHP 7 是一个令人兴奋的进步。不过,PHP 开发人员应该注意两个重要的注意事项。
PHP 7 是一种弱类型语言
默认情况下,PHP 7 仍然是弱类型语言。考虑到引入了完整类型声明和严格类型,这似乎有些违反直觉。但为了提高向后兼容性,PHP 代码中不会强制使用类型声明。开发者可以通过将declare(strict_types=1);
方法放在 PHP 文件的顶部来启用严格类型。
此实现意味着 PHP 将“忽略”类型提示并返回类型,除非该declare(strict_types=1);
语句出现在文件顶部。这很有意义,因为目前仍有大量遗留的 PHP 代码在使用。开发人员应该能够升级到最新版本的 PHP,而无需重写整个代码库。
不过,这种方法确实存在一些后果。如果开发人员定义了类型声明但没有添加,declare(strict_types=1);
PHP 会使用类型强制转换来实现。类型强制转换的本质是,当需要时,一种类型的值会被强制转换为另一种类型的值。在使用类型声明时,这种方法并不理想,因为方法可能无法按预期运行。
举个例子,如果我们编辑上面的“foo bar”代码,就会看到类型强制转换的作用。bar 方法现在会在$foo
参数上显示一个整数类型提示,并在方法上显示一个字符串返回类型。当我们使用整数参数 123 调用 bar 方法时,该方法将返回字符串形式的 123。这是因为类型强制转换已将整数值转换为字符串值,以符合定义的返回类型。
function bar(int $foo): string
{
return $foo;
}
$result = bar(123);
var_dump($result);
// Output: string(3) "123"
只有当我们将其添加declare(strict_types=1);
到PHP 文件的顶部时,才会施加类型声明。
declare(strict_types=1);
function bar(int $foo): string
{
return $foo;
}
bar(123);
现在,当我们使用整数参数 123 调用 bar 方法时,会报错。这是因为该方法期望返回一个字符串,但它却试图返回一个整数。该方法被强制使用了严格类型,因此输出了以下错误。
FATAL ERROR Uncaught TypeError: Return value of bar() must be of the type string, integer returned
严格类型是针对每个文件强制执行的
另一个需要注意的问题是,严格类型必须针对每个文件单独设置,不能全局应用。这意味着您必须declare(strict_types=1);
在每个需要设置严格类型的文件中都添加一个。这样做是为了保持向后兼容性,但这意味着您无法轻松地在整个应用程序中强制使用严格类型。
为了实际演示这一点,我们来看另一个例子。假设我们有一个小应用程序,它从两个独立的文件中提取两个函数,foo.php
和bar.php
。我们将在应用程序文件的顶部添加一个,declare(strict_types=1);
只是为了证明它对提取的文件没有影响。此外,我们将调用的方法放在try catch中,以处理任何抛出的错误。
/**
* This declares strict types but has no effect on the code.
*/
declare(strict_types=1);
/**
* Call in the foo.php and bar.php files.
*/
require __DIR__ . '/../src/foo.php';
require __DIR__ . '/../src/bar.php';
try {
/**
* This method will output a string 123.
*/
var_dump(foo(123));
/**
* This method will throw an error.
*/
var_dump(bar(123));
} catch (Error $e) {
/**
* Print out the error message thrown by the bar(123) method
* "Return value of bar() must be of the type string, integer returned"
*/
echo $e->getMessage() . PHP_EOL;
}
在 foo 文件中,我们将有一个 foo 方法,它接受一个整数并返回一个字符串。此文件中没有任何declare(strict_types=1);
语句。
function foo(int $bar): string
{
return $bar;
}
在 bar 文件中,我们将添加一个 bar 方法,它也接受一个整数并返回一个字符串。不过这次我们将declare(strict_types=1);
在文件顶部添加一个。
declare(strict_types=1);
function bar(int $foo): string
{
return $foo;
}
当我们分别调用foo(123)
和bar(123)
方法时,我们会得到一些截然不同的输出。foo 方法将返回一个字符串,因此我们将看到输出string(3) "123"
。相比之下,bar 方法会抛出一个错误,因为强制使用了严格类型。我们将看到一条错误消息输出 。Return value of bar() must be of the type string, integer returned
此示例表明,在同一个应用程序中,严格类型可以同时处于开启和关闭状态。
后果
我不想纠结 PHP 类型声明方法的对错,毕竟比我聪明的人也设计和实现了这些方法。但这种方法的后果显而易见。最重要的是 PHP 的类型系统不可信。这意味着除非你对declare(strict_types=1);
语句一丝不苟,否则低级的完整性错误可能会潜入代码中。
这也意味着你在测试代码时应该更加小心。例如,在使用PHPUnit进行单元测试时,你应该使用assertSame()
而不是assertEquals()
。这是因为assertSame()
会检查值和类型,而assertEquals()
只检查值。如果你想确保代码的完整性,你需要同时检查值和类型。
类型声明是 PHP 的一大进步,我相信所有 PHP 开发者都应该使用它们,无论declare(strict_types=1);
是否启用。然而,PHP 开发者了解它们的工作原理至关重要,因为误解 PHP 的类型系统会导致错误,并对应用程序产生负面影响。
有关 PHP 类型系统的更多信息,我建议您阅读有关 PHP 标量类型声明的 RFC。
鏂囩珷鏉ユ簮锛�https://dev.to/robdwaller/how-php-type-declarations-actually-work-1mm5