PHP Pro : 动态类型

为什么 php 这么流行

php 的流行已经不用再提及了, 那么, php 具有哪些优点使得它如此流行呢?

  • 接近 c(<5.2) 和 java (~>5.3.8) 的语法
  • 动态类型, 编码成本极低
  • 特定任务所需的基础库完备

优点 1 就不多说了, 用的人都知道。基础的 functionclass 语法都极其相似。
优点 3 涉及的话题很多,暂时不进行展开, 这里重点聊下 php 中的动态类型.

php 和 类型

先看一组普通的 PHP 语句:

1
2
3
4
5
6
class A{}; //定义一个简单的 class A
$a = 1;
$a = 1.1;
$a = "A";
$a = new $a(); //类的名字都是直接计算出来的
$a = function() { return 1; }; //你没看错, 这是匿名函数, php 中叫闭包。

从上可以看到的 php 语句的两个重要特点

  • 可以给一个变量名赋上随便哪种值,随便什么类型,什么时候,重复赋值也行。
  • 变量名以 $ 开头, 后面会聊为什么这里有一个 $

这一点让 php 开发者写代码的时候非常轻松。很多东西都不需要考虑。这也就是为什么 php 容易上手。

接下来是一个基础的函数定义代码:

1
2
3
4
5
6
7
8
9
function someFunc( $a = 1) { //一个带默认值的参数 $a
if ($a === 2 || $a === 1) { // int 类型的严格判断
return $a+1;
} else if (is_array($a)&& isset($a[0]) ) { // 数组类型
return $a[0];
} else {
return "$a is unknown";
}
};

这里我们看到, php 的动态性不仅体现在语句上, 函数的定义的参数和返回值也是没有类型的。 对于 php 而言, 返回值是 int 或者 void (在 php 里应该认为是 null) 类型, 在函数定义中没有区别,参数也是。

所以用 php 写出无法维护的代码是轻而易举的事情。

在实际使用过程中, php 开发者经常干的一件事情是把函数的返回值打印出来,看下数组结构, 找到自己需要的内容。再接着写代码.

默认情况下,php 的函数和类型都是全局的, 5.3 中引入的命名空间改善了这一点。

当然, 例子中的做法会让使用这个函数的人很头疼, 除非是非常常用以至于用户不可能误解的接口,否则别这么做.

jQuery 的 $() 是一个很好的例子,很多人认为是很反人类的设计.
但是显然它的用户觉得用起来很爽

代码中的 !empty($a) , 这个也是很常用的技巧, 用于判断一个变量是不是 null, 空字符串, False ,空数组, 等等等等(当然包括了变量不存在,数组下标和字段 key 不存在等等), 也就是直觉中所有的空值都会命中,很方便不是吗。

php 进行真假值判断时, 会尽可能地进行类型传唤,除了数字类型和 null, 字符串也会尽可能地转换成数字再转换到 true、false。甚至自定义类型,也可以通过实现特殊接口, 支持 empty 操作。

empty() 的陷阱

针对字符串类型进行 empty() 操作,比如 "0 ", 和 "0"empty() 中是不一样的, 虽然在 intval 函数中, 它的结果都是 0。 也就是, 只有 """0" 这两个特殊的字符串, 其他长度大于0 的字符串都会认为是非 empty 的。

empy 看起来像是一个普通的库函数, 但是它是一个特殊的指令, 并不是函数调用。类似于 echoisset。 这也留下一个 bug, empty 只能针对左值操作,也就是变量名.
而对于表达式,比如 empty("a") , 在目前的 php 语法中是不合法的, 必须提前计算结果并赋值给一个临时变量。 这个小小的麻烦,在未来的 php 版本中说不定会优化掉。

isset 和 empty 的区别

isset 只用来判断数组下标或者变量是否为 null 。它不能区分 null 和 变量未定义.
对于 $a=array( "a"=> null), isset($a["a"])isset($a["b"]) 都返回 false.

在 php 中, 不存在是一个模糊的定义,和 null 几乎是等同的。千万不要在代码中依赖一个变量是否定义这种模糊的条件, 这种奇葩的行为只有 javascript 才会关心, 毕竟我么谈的是动态类型语言, 存在感这种东西太弱了。

然而, php 从 c 中抄来的常量定义 define(“A”, a), 是可以用 defined(“A”) 进行判断的.
比如 defined("DEV_MODE") 判断应用是否运行在开发模式下.

关于 isset 和 empty 后续的面向对象章节中会谈到。

在静态类型语言的使用者眼里, php 大概就是一种到处都是 object 和 强制转型的及其不安全的语言了。

又爱又恨的数组

Array 应该是 php 使用频繁的基础类型了。 也就是它导致了 ide 几乎没办法正确地分析代码中函数返回值和参数的正确性, 因为一切行为都因为 array 的存在而过于灵活。

在 php 中, 数组和字典(或者说hashmap), 是混合存在的, 都叫 array。 这个混杂的类型,在处理 key 为数组的hash 数据时,很容易埋下 bug。

相比之下,javascript 还是严格地划分了 array 和 object 之间的区别。

使用 array 模拟 struct

在实际使用中, 针对需要返回复杂结果,但又不需要进行面向对象的包装时,习惯使用 array 做为返回值, 作为 struct 的替代用法.

1
2
3
4
5
6
7

function someFunc2() {
return array( "a" => 1, "b" =>2 );
}

$a = someFunc2();
$b = $a["a"];

或者使用 array 做为参数, 避免函数的参数过多带来的麻烦。相对于使用 call_user_func 模拟函数调用要更加自然方便。

$c = someFunc3( array( "a" => 1, "b" => 2));

这里应该说, php 的动态类型特别发挥地淋漓尽致, 虽然可能很多开发者并没有意识到这点, 但是不要紧,这并不妨碍他们用最舒适的方式使用 php。

php 开发者在写代码过程中, 心里只要了解一个函数、方法的作用,剩下的就是填充合适的参数和获取返回值,用 empty, isset 等函数进行判断是否空返回值。

PHP 和 $

和同样是动态类型的语言 javascript 相比, php 有什么特点呢?我能想起来的最显眼的区别, 就是 php 的变量名必须有一个 $ 符号。

当然,这不过是当年从 perl 遗传下来的。

那么这个 $ 符号, 除了必须写上,还有什么实际用途吗?

1
echo "$a is b";

哦, 原来如此。$ 在输出字符串的过程中用于定位字符串中的变量,在输出的过程中会替换为实际的变量内容。

对于 php 而言, “” 风格的字符串意味着需要进行变量的替换, 而 ‘’ 则说明直接输出内容就行了。
这个原则甚至适用与数组的 key .

如果如果变量名和后续内容没有空格等区分,那么 "${a}isb" , 通过加大括号就可以解决了。

这样连数组类型也支持了

echo "${a[0]}isb"

也就是

"$a is b" 等价于 $a . "is b"

注: 在 php 中, `.` 英文句号用于连接字符串.
其他语言中常用的 `+` 加号

总之, php 的约束很弱,可以很灵活地使用,也很容易失控,这也就是为什么 php 构建大型系统很困难。除了性能问题, 还存在对使用者的约束很弱。

但同时也存在好处, 动态类型和宽泛的约束,使得框架的实现上灵活, 可以轻松实现非常强大的特性,(后面在面向对象和类加载中会谈到)毕竟 php 开发者也习惯了基于约定编程的,只要团队不大,通过开发约定也可能解决这个问题。