您需要(和不需要)了解的关于PHP类型系统的所有内容

2020-07-26 11:50:01

PHP是一种动态类型的脚本语言,在2015年之前,PHP根本不支持静态声明的类型。可以在代码中显式地强制转换为标量类型,但是直到PHP7.0使用标量类型声明和返回类型声明RFC时,才在方法和函数签名中声明标量类型。

不过,这并不意味着从7.0版开始,PHP就变成了一种静态类型语言。它有类型提示,可以统计分析,但仍然支持动态类型,甚至可以混合使用,如下例所示:

毫无疑问,那里有一个类型不匹配。返回类型应该是int,而返回类型实际上是一个字符串。现在,PHP要做的是自动将标记转换为整数,以便返回所需的类型。尽管它似乎有额外的成本,但它没有。PHP的类型杂耍在许多情况下几乎是免费的。

为了更好地阐明该语言如何处理类型,我将本文分为以下几个部分:

如果你对在这里添加什么有什么建议,请随时在Twitter上联系我,或者在Github上打开一个问题。

当涉及到语言特性时,PHP的类型系统非常简单。例如,没有字符类型,或无符号类型,甚至int8、int16、int32、int64。

字符类型简化为字符串类型,所有整数变量简化为整数类型。这是好事还是坏事,由你决定。

可以随时使用gettype()函数或var_dump()函数检查变量的类型并检查其输出。

根据定义,标量类型本身不携带行为或状态。像100->;toString()或';thephp.web';::length()这样的表达式是无效的。

复合类型要有趣得多,因为尽管它们与标量类型非常相似,但四种复合类型中的每一种都具有不同的语法功能。

数组实际上是语言内置的哈希图。这意味着它以key=>;value的方式存储值。即使你纯粹把它当做一个矢量来使用。

当涉及到大小、内部类型和键值映射时,数组是非常灵活的结构。下面的示例都是有效的数组:

<;?PHP$vec=[0,1,2];//$vec[1]为int(1)$map=[';a';=>;1,';b';=>;2];//$map[';a';]为int(1)$map_ish=[';a';=>;1,0=>;2];/。]is int(1)//$map_ish[0]=>;is int(2)。

与C不同,php不要求您在创建数组之前定义数组的大小。这当然伴随着内存消耗成本:数组大小越大,占用的内存就越多(实际上,数组是按2的幂分配的)。这种消耗的工作原理超出了本文的讨论范围,如果您想了解更多信息,请随时联系我。

如果您对这句话很好奇,下面的视频提供了一些关于数组与对象内存配置文件的图表和进一步的见解。

正如您将在下面看到的,数组也被认为是可迭代的类型,这意味着您可以使用foreach循环迭代它们。但是它们也提供了可以操作其内部指针的特定函数。

主要结论:Array是一种非常灵活的复合类型,可以被认为是一个HashMap,也可以被认为是一种可迭代的类型。

由于php';的架构,与数组相比,对象复合类型通常具有更低的内存消耗配置文件。这是因为通常人们会通过创建类的实例来使用Object类型。

对象可以携带状态和行为。这意味着php将提供解引用语言结构来访问对象的内部结构。下面的代码片段说明了一个php对象的代码:

<;?phpclass MyClass{private const A=1;public int$property=0;public function method():void{}}$obj=new MyClass();//$obj is object(MyClass)//$obj::A is int(1)//$obj->;属性is int(0)//$obj->;method()为空。

通常也可以通过从数组强制转换类型来创建对象。将数组的键转换为对象属性名。这种强制转换总是会导致对象(StdClass)类型。

重要的是要注意,将带有数字键的数组转换为对象是有效的,但是不能取消引用它的值,因为属性名称不能以数字开头。

要点:对象通常比数组具有更低的内存配置文件,它们携带状态和行为,它们都继承自stdClass,并且可以通过强制转换数组来创建。

在php中,可调用的是任何可以调用的东西(哦,别这么说!!)。带有括号和call_user_func()函数。换句话说,可调用对象可以履行我们所知的功能的责任。函数和方法始终是可调用的。对象和类也可能是可调用的。

根据定义,可调用对象可以将其引用存储在变量中。如下所示:

<;?phpclass MyClass{public function myMethod():int{return 1;}}$obj=new MyClass();var_dump([$obj,';myMethod';]());//int(1)。

看起来很奇怪吗?我知道它看起来像一个阵列。事实上是这样的。除非您将其命名为可调用的👀。

上面的这种可调用(对象-方法引用)非常有趣,因为如果您在类的作用域内,您可以用它调用私有或受保护的方法。否则,您只能用它调用公共方法。

此外,实现__Invoke()魔术方法的类本身会自动将其实例转换为可调用对象。如下所示:

主要内容:可调用函数包含对函数或方法的引用,并且可以以不同的方式构造。

Iterables更容易解释:根据定义,它们是一个数组或Traversable接口的实例。迭代量的主要特点是,它可以在foreach()循环中使用,带有语句或扩展运算符的Year from Statementor。

<;?phpfunction Generator_Function():Generator{//...};//这里的所有变量都是迭代变量$a=[0,1,2];$b=Generator_Function();$c=new ArrayObject();

有两种特殊的类型。它们之所以被称为特殊类型,最大的原因是你不能使用这些类型。特殊类型既有资源类型,也有NULL类型。

资源表示外部资源的句柄。它可以是文件句柄、I/O流或数据库连接句柄。现在您可能会猜到为什么不能将资源转换为任何其他类型。

NULL类型表示空值。这意味着保存NULL的变量在运行时未初始化、赋值为NULL或未设置。

类实例具有类型对象,并且将始终以这样的方式呈现。对对象执行gettype()将始终返回字符串(";object";),对对象调用var_dump()将始终使用对象(ClassName)表示法打印其值。如果需要将对象的类作为字符串获取,请使用get_class()函数。

当涉及到类型时,人们可以使用PHP进行不同的操作。我相信在这里清楚地说明它们是很重要的,这样我们以后就不会把事情搞混了。

类型转换是指将类型从A转换为B。例如:从整型转换为浮点型。

类型强制转换意味着手动或显式地将类型从A转换为B,如$100=(INT)100.0。(FLOAT(100.0)变成INT(100))。

类型强制意味着隐式地将类型从A转换为B,如$20=10+#39;10香蕉。(String(";10香蕉";)变为int(10))

话虽如此,下面几节将解释它在php中是如何发生的。稍后,您将找到更多有关键入杂耍的信息。

与Java类似,php允许类型转换。这意味着当变量指向可以强制转换为不同类型的值时,它允许手动(显式)类型转换。

给定保存字符串的变量$100.00,其值可以手动转换(强制转换)为整型或浮点型(100)-或任何其他标量类型或复合类型数组或对象中的一种。在给定的变量中,它的值可以手动转换(强制转换)为整数(100)或浮点型(100)-或任何其他标量类型或复合类型数组或对象之一。

现在,Java在php代码中做了一件完全非法的事情,那就是将变量指针转换(强制转换)为不同的类。这意味着我们在php中只能强制转换为标量和一些复合类型:

值得注意的是!Php中的类型转换只允许转换为标量类型*。这意味着将对象强制转换为不同的类实例是非法的,但将对象强制转换为标量类型是完全有效的。

也可以将值转换为数组或对象类型,这些类型不是标量类型,而是复合类型(命名非常困难,是吧?)。

上面的代码会生成一些通知,但仍然有效。稍后我将解释这个int(1)值从何而来。

主要内容:PHP允许强制转换为标量类型、数组或对象。不允许进行类强制转换。

类型强制是处理不匹配或未声明的类型时产生的副作用,本文稍后将对此进行深入解释。现在,只要知道php会在运行时必要时自动转换代码中的类型即可。

类型强制的一个示例可以是将整数乘以浮点数。表达式int(100)乘以浮点数(2.0)得到浮点数(200)值。

主要结论:PHP有一种在运行时隐式规范化类型的机制,您应该始终保持警惕。

类型提示既是一种强制实施,也是一种严格的类型机制。它是在7.0版中引入php语言并影响函数和方法签名的。从php7.4开始,输入提示类属性也是可能的。

这里的提示是变量$a自然或强制类型为int,变量$bis自然或强制类型为int,此函数的结果自然为int类型。

你注意到我是如何自然地或强迫地使用上面的吗?这是因为如果您使用非整数值调用此函数,PHP不会有任何抱怨。事实上,如果参数还不是整数,它会尝试隐式地将参数转换(强制)为整数。

在此函数的主体中,您可以始终相信$a和$b是整数。但是它们是否会有预期的整数,将取决于函数的调用者。

<;?phpfunction sum(int$a,int$b):int{//$a is int(10)//$b is int(10)return$a+$b;}sum(';10个苹果&39;,';10个香蕉';);

当类型不匹配时,也可以使用名为Strict_Types的php指令来避免强制和简单地引发错误。如下所示:

<;?phpECLARE(STRICT_TYES=1);函数SUM(int$a,int$b):int{return$a+$b;}sum(';10香蕉';,';10苹果';);//PHP致命错误:未捕获//TypeError:传递给sum()的参数1必须是//int类型,给定的字符串。

这并不意味着打开严格类型时php是静态类型的!事实上,类型提示只会增加php引擎的处理开销。在内部,它将始终执行类型调整,并且永远不会信任您的变量类型提示。

类型提示只有两个目的:定义在打开严格类型时,应该将值强制转换为哪些类型,或者引发致命错误。

主要结论:类型提示只是向引擎提供有关类型的提示,而不是命令。严格的类型是您的选择,这会带来很小的开销。

在我们开始打字之前,我想快速讨论一下UNION类型,因为这里似乎更有意义。

除了php的三种类型(标量、复合和特殊),php手册中还提供了一个伪类型,它的存在只是为了可读性。这个类型并不是真的存在,只是一个约定。

我希望您注意一个非常具体的伪类型:文档中经常使用array|object伪类型来指定参数或返回类型。

可迭代类型也是一种联合类型。可以定义为array|traversable。

从php7.1开始,该语言通过引入可为空的类型为联合类型添加了一种支持。如果您真的认为可空类型只是T|NULL的并集。例如?int表示int|null。

因此,在经历了这么多未知的联合类型之后,php8.0提供了一个正确的联合类型特性,在这里您可以定义任何需要的联合,而不必依赖伪类型或约定。它的工作原理是这样的:

这可能不是你第一次听到打字杂耍这个术语,是吗?这是php提供的最重要的核心功能之一,但也是最不为人所知的功能之一。

我不能责怪人们不知道这件事。它之所以被称为变戏法是有原因的。在每个上下文中,变量类型的变体如此之多,以至于要理解您正在处理的是哪种类型可能会变得相当麻烦。

让我们从以下语句开始:PHP不支持变量声明中的显式类型定义。这是非常强大的!

无论何时声明变量,php都会根据其赋值推断它应该包含哪种类型。而$var;创建一个空值的变量,$one=1;创建一个整数,$obj=new stdClass()创建一个对象(StdClass)。

根本没有类型定义!PHP尽量猜测变量应该属于哪种类型。

PHP变量是非常动态的,以至于它们可以在运行时毫不费力地更改类型!以下代码有效:

<;?php$var;//$var is null$var=1;//$var is int(1)$var=';thephp.web';//$var is string(";thephp.web";)$var=new stdClass();//$var is object(StdClass)。

由于变量是如此动态,php中的许多操作都需要引擎根据操作的上下文检查它们的值。像SUM(a+b)这样的表达式将在内部检查第一个操作数的类型,然后猜测第二个操作数的类型。

看一下php';源代码中的这段代码,如果op1是长的(a是整数),那么检查op2是不是长的(b是整数)。如果是,则执行长和,否则检查op2是否为双精度,如果是,则执行双和,并且此表达式可能返回整数或浮点数。

这也意味着类型强制(隐式强制转换)也将自动发生。但是他们不应该是一个惊喜!在某些非常特定的时刻,可能会发生一种类型的强制。

您可能会想:如果强制无处不在,那么php如何处理不兼容的类型呢?将整数转换为布尔值似乎还可以,但是将数组转换为整数听起来就有点笨拙了。

嗯,php有非常好定义的类型转换规则。首先必须了解结果类型应该是什么,然后评估强制转换。

例如,如果一个表达式出现在if()语句中,我们可以很快意识到该表达式应该产生一个布尔值。

<;?PHP$var=100;//$var为int(100)//$var被视为布尔值//并计算为TRUEif($var){//$var仍为int(100)}//$var仍为int(100)。

注意$var在整个生命周期中是int(100),但在if()语句中被视为bool(True)。这是因为if()语句期望一个计算结果为布尔值的表达式。类型杂耍正是php在幕后为您做的事情。

为了说明这一点,下面是将类型转换为布尔值的决策树。当原始值为以下值时,布尔转换将返回FALSE:

有关类型比较和转换表的完整文档也可以在语言手册中找到。我自己没有勇气阅读它,但在🤷🏻‍♀️上提供它是我工作的一部分。

这里需要注意的是:在php8.0中,语言引入了联合类型,带来了额外的复杂性。在处理联合类型时,类型调整必须遵循优先顺序。而且这个优先级是明确定义的,而不是基于类型顺序。

因此,如果您没有使用STRICT_TYPE,您的联合类型将遵循此规则。如果联合类型不包含主题的类型,它可能会按以下优先顺序强制其值:INT、FLOAT、STRING和BOOL。

<;?phpfunction f(int|string$v):void{var_dump($v);}f(";";);//string为联合类型//string(";";)f(0);//int为联合类型f(0.0);//浮动为联合类型//int(0)f([]);//array为';联合类型中的t//unauttTypeError://f():参数#1($v)//必须是String|int类型。

在上面的例子中,发生了一些非常有趣的事情!数组类型不会转换为布尔值(FALSE)。它反而会引发TypeError!

您已经意识到php可以通过两种方式处理类型。一种方式被称为强制类型模式,所有这些杂耍和猜谜游戏都在进行中。另一种方式是严格类型模式,在这种模式下仍然会发生杂耍和猜测,但是当设置了显式定义的类型时,如果发生类型错误匹配,就会抛出一些TypeError。

现在,我认为PHP开发人员期望引擎尊重等价交换(等価交換法)的法律,并付出我们严格键入所有内容的努力来提高性能,因为这样它最终将能够绕过所有类型检查并立即执行操作。

虽然我明白为什么有人会这样想,但我必须告诉你:这是完全错误的!让我们检查php';源代码中的以下strlen()逻辑。

每次我们需要检查我们是否在使用严格模式时,我们都会从ex_use_strict_type()中获取布尔值。如果是真的,我们正处于严格模式。如果为假,则我们处于强制模式。

//...zval*value;value=GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);//value是//strlen()的参数,如果(Expect(Z_TYPE_P(值)==IS_STRING)){ZVAL_LONG(EX_VAR(opine->;result t.var),Z_STRLEN_P(Value));free_OP1();ZEND_VM_NEXT_OPCODOD。

您看到那里的第一个if()语句了吗?猜猜它在做什么。一点儿没错!。它会检查您的参数类型!!

Else子句包含可能使用严格类型或不使用严格类型的有趣内容。

//...}ELSE{//看起来很有前途的ZEND_BOOL STRIST;//😭IF((OP1_TYPE&;(IS_VAR|IS_CV))&ANP;&ANP;Z_TYPE_P(值)==IS_REFERENCE){//...}//...//👀STRIGHT=EX_USES_STRIGN_TYPE();执行{IF(Expect(!Strict)){//...}ZEND_INTERNAL_TYPE_ERROR(Strict,/*...*/);ZVAL_NULL(EX_VAR(opine->;result t.var));}While(0);}

从上面的代码片段中,我们可以看到严格模式不会切断任何处理的示例。事实上,它创建了几个额外的检查,目的只有一个:提高产前错误。

我并不是说这是一个糟糕的实现。我个人对此非常满意。但我认为重要的是要弄清楚,它不会对绩效产生积极影响。

这篇文章是一个很大的探索。我正在认真考虑写一本书。仅这一页就占到了一本已经很大的书的15%,这本书已经😂了。

我希望我在这里收集的信息对你有用。如果不是,至少很有趣。

我相信php的类型系统非常丰富,包含了许多创新的和遗留的特性,当你回顾语言发展的历史时,它们都是很有意义的。

像往常一样,如果你有什么要说的,请随时在推特上联系我。打开一期杂志或拉出请求,就会很开心。

主要内容:写这篇文章花了我难以置信的时间。如果你想表示任何形式的支持,请在你的社交媒体和社交圈🙏上分享