PHP语言特性小结 - 2017.07.17
背景
作为PHP工程师,也写了3年的PHP了,除了使用框架以及平时业务流程上的实现外,也不断加深了对语言的理解,这里记录一些关注过的特性,尤其是此种特性与OO之间的联系。因为对计算机语言设计没有一个特别清晰的概念,真正业务上使用过、了解的语言也只有C、PHP、Golang三种,希望能通过学习,掌握计算机语言设计上基本的范式,避免对一些语法糖沾沾自喜而错过真正的理论。话说《7天7语言》也买了1年了,并没有去阅读,啪啪打脸呀。
参考资料
正文
a. 关于php interface 和abstract class的使用场景?
- 首先写接口/抽象类的程序员定义其他用户如何使用该抽象。如果只限定子类实现的方法,则使用接口;如果除了限定子类实现哪些方法,还提供了通用工具接口(已经实现的方法),则使用抽象类。一种普遍存在的最佳实践就是:当你拿不准一种抽象该使用抽象类还是接口的时候,设计一个接口,然后用一个抽象类implement它。
- 具体到语言细节:
- interface:可以用implements实现,也可以用extends继承,所有方法必须是public,类实现interface必须实现所有的public function,并且参数表保证包含关系。关于常量,接口常量与类常量的设计完全相同,唯一不同的就是无法被子类/接口覆盖。
- abstract:当类用此关键字修饰时候,则不可以实例化;如果类中有1个或以上抽象方法则该类必须定义为抽象类;实现抽象方法时与接口的要求相同,(可见性要大于抽象类),抽象方法的实现必须包含声明的参数列表; 继承抽象类与普通的父子继承相同(可以overlload方法)
- 区别:一个子类可以实现多个接口,但只能继承一个抽象类;并且接口里面的常量不可以被实现TA的类覆盖,抽象类可以overload,前提是开放程度要高于抽象类;
b. php foreach是否能保证array的顺序?
- 可知结论就是foreach出的kv对和key的顺序无关,只与当前array中元素插入的顺序相关,先插入的先遍历。称之为线性遍历。而for循环以(i的赋值为基础,和可以与)i的算法相关了。
c. php中 $this self static 都代表什么,在什么场景下使用?
- (this和self经常放到一起对比,实际上比较好区分:静态的属性/方法在类内部被调用时候,因为不需要当前类被实例化,因此直接用self引用,并且在PHP中使用引用符号"::";反之,引用一个非静态的属性/方法时候,使用)this比较合适。为什么把static单独提出来,因为这个关键字应用范围广(从我的角度看,过于广了,根据功能不同,应该拆开为3个关键字):
- 在类中 static指定当前属性/方法为静态的,可以不实例化当前类,直接用::引用;
- 在局部函数/方法中 static表示变量是局部静态变量,当多次调用这一函数的时候,本变量会保存上次调用时该变量的值。这一特性在递归调用时非常有作用,避免了递归调用缺少终止条件的情况,可以指定一个静态计数器到一定层数直接退出,以免无线循环用完系统资源;
- 在php5.3之后,static用于指定后期静态绑定,解决self只能引用定义类中的成员,无法实现多态的问题;即使用static::value的方式引用value成员,完成一部分多态的特性,可以面向变量编程了。
d. trait关键字的特性,应用的场景、最佳实践?
- 当有一段代码在类之间被copy paste的时候,可以多想想trait关键字;
- 打破php单继承的限制(后续在设计模式中确定应用场景)5.4之后支持;但是使用的是多个class组合的方式,因此trait本身使用不需要继承,使用use关键字即可;
- 类中use trait的时候,同名方法的优先级是:类中function > trait中function > 父类中function
- use用在trait中时在class内部,use用在namespace时,在class定义外部文件头部,替代include、require
- 当类中用多个trait的时候,trait内有同名方法会报fatal error。解决方法是显示的使用insteadof确定一个具体的那个方法
- trait支持属性、方法、静态方法
- trait中的属性,类无法定义同名
e. php override(重写/覆盖)与overload(重载)的含义?
-
《PHP核心技术与最佳实践》中描述的:“php重载与c++等的重载概念不同,指的是动态创建类的属性与方法,而在JAVA中重载指的是在同一个类中可以包含同名、不同参数列表的方法。”
-
php实现重载的方法是使用magic methods实现,所有方法都必须是public,方法的参数都不可以通过引用传递。这些magic method 包括:__set __get __iset __unset __call __callStatic,提供了一种对所有未预先定义的值/方法的统一处理方式。理解一下__set、__get
-
override很明显是父类与子类之间的一种多态关系,子类对父类允许访问的方法进行重写(参数列表、返回值可以完全不同)。在PHP中子类可以对父类的同名方法进行重写,实现类方法的多态性,而像C++/JAVA这种利用不同参数列表的方式来实现重载,PHP是做不到的,因为其本来就是动态类型,参数本身就可以接收不同类型的参数,同时可以接收不定个数的入参,因此在同一个类中无法定义同名方法。命名空间解决了多个文件之间方法重名的问题。
-
总结一下计算机语言上实现两者的差异(以对两者实现完整的JAVA为例):
区别点 重载方法 重写方法 参数列表 必须修改 一定不能修改 返回类型 可以修改 一定不能修改 异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常 访问 可以修改 一定不能做更严格的限制(可以降低限制) -
php的重写参数列表可以修改、返回类型也可以修改,异常可以修改,访问不能做更严格的限制。所以可以看到PHP在实现多态上与JAVA有很大区别。
f. final关键字需要注意的点,以及用法?
- 只可以修饰类和方法,继承一个final class会报fatal error
g. 全局变量作用域以及使用方式?
- PHP中在局部需要引用全局变量,必须显示的指定global。与C语言正好相反,C语言在局部(如函数中)使用全局变量同名变量会直接引用全局,除非在局部重新声明了变量。这种处理符合动态静态类型和动态类型之间的划分,因为C的变量不声明就无法使用,自然的想法就是局部不声明使用全局,而php作为动态类型可以直接使用不声明的变量,局部直接使用局部变量。了解区别,然后开发过程中注意即可。
h. PHP哪些组成部分大小写敏感,此为一小坑。
-
除了这个坑之外,php还会把同名的函数加载为construct,但在当前版本中会逐渐去掉,不要使用此种特性。言归正传,大小写敏感汇总:
- 变量名区分大小写
- 常量名区分大小写(通常为大写)
- php.ini大小写敏感
- 函数名、方法名、类名 不区分大小写(保证同样的编码风格,不要滥用)
- 魔术常量不区分大小写(推荐大写)
- NULL、TRUE、FALSE不区分大小写
- 类型强制转换,不区分大小写
i. PHP提供了spl,为现代框架的编写提供了有力的武器,优于5.3之前简单的__autoload函数,可以保证多个不同的加载函数(维护了一个autoload函数的队列)。需要注意的有:
- __autoload魔术方法全局只能存在一个,含有多个自动加载方法,可以多次调用spl_autoload_register(<func_name_string>),尝试加载顺序会按照本函数调用顺序为准,但spl_autoload_register还有2个可选参数,第一个true/false决定该自动加载逻辑失败时是否throw exception,第二个决定是把此次注册push到list的首还是尾,默认尾部。
- 当__autoload和spl_autoload_register共存时,必须使用spl_autoload_register('__autoload')显示注册一下自动加载魔术方法,因为一旦调用spl,会将Zend Engine中的__autoload()函数取代为spl_autoload()或spl_autoload_call()。此处需要注意。
- 简而言之,自动加载应当使用spl_autoload_register并且关注一下注册顺序即可。与composer配合真的是很好用,可以兼容各种不同的命名、寻址规则。当然出于规范的想法,PSR4才是王道,空闲时候可以思考一下,规范与自动化检测工具之间的边界。
- ps:一个题外话,今天有个例子,项目域下的uri在框架层是做了大小写处理的,统一转成小写字母。但是一线同学不了解这部分工作,甚至以为是浏览器做的工作,这就与我们编写功能强大的底层框架的思路有所违背。当自动化检测工具做的很强大时,人就会变懒,变得不了解规范;但完全依赖规范明显无法提升效率。这个中间的度需要如何掌握?作为一线leader需要反复询问自身。
j. PHP中关于 $this、self、parent变量&关键字的解析。对于new self($val)与new static($val)之间的区别。
- $this 指向当前实例 self指向类,通常用来调用静态属性、方法 parent顾名思义调用父类,不会混淆
- new static ((val) 返回当前类 new self()val) 就是当前代码段中定义的这个类
eg:
<?php
class A {
public static function getSelf() {
return new self();
}
public static function getStatic() {
return new static();
}
}
class B extends A {
}
var_dump(get_class(A::getSelf())); //A
var_dump(get_class(B::getSelf())); //A
var_dump(get_class(A::getStatic())); //A
var_dump(get_class(B::getStatic())); //B