函数与变量

变量

PHP的变量名以$开头,随后由字母或者下划线开头,后面跟上任意数量的字母,数字,或者下划线。变量名大小写敏感。

在PHP中无须声明、也无须初始化一个变量即可使用,这样的变量其值被视为NULL(注意随后可能会被进行类型转换)。使用这样的变量将触发 E_NOTICE 错误。使用语言结构(注意,不是函数)isset()检测一个或一批变量是否已经被初始化过且值不为NULL,也可以使用函数empty()检测一个变量(或值)。

变量的值模型、引用模型与所谓“引用”

PHP中的变量,除了值为object类型的变量,使用的总是值模型。在为变量(包括数组的单元、对象的属性等,下同)赋值时(赋值行为不一定非要使用赋值操作符,例如给函数传参时)可以使用&符号指定该变量作为另一个变量的别名——注意,这不意味着该变量使用了像JS/Python/Java那样的引用模型(尽管官方文档中使用的术语是“引用”),也不意味着该变量的值是一个表示内存地址的指针类型,而应作如下理解:由于值引用模型把变量看作用于保存值的容器,因此一个别名意味着同一个容器的另一个名字。

  • 显然,只能为一个变量,而不能为一个值创建别名。例如$bar = &(24 * 7)是非法的
  • 令一个变量充当另一个变量的别名,并没有触发context中的符号表中的名字到值的绑定(因为这一过程的确不涉及到一个的值),也就是说,如果变量c已经是一个变量a的别名,则“让它充当另一个变量b的别名”这一操作并不会导致变量a发生改变
  • 为一个未初始化过的变量创建别名会导致该变量被初始化为NULL
  • 为在给函数传参时令函数形参成为一个别名,应当在定义函数形参时使用&而不是在传参时使用,例如function foo(&$a){}。如果foo函数无法为传入的内容创建一个别名(例如传入的是一个值而不是变量或object类型的值),则会触发一个致命错误

从PHP5起,值为object类型的变量使用的是引用模型,其赋值等机制和JS/Java等语言是一样的。在PHP4中可以为object值(而不必是变量)创建别名,例如$a = & new stdClass(),但这一做法在使用引用模型的PHP5中没有任何意义,在PHP7中从语法上不允许这么做。

变量的作用域

变量,根据其被初始化的位置的不同,只有两种作用域:全局作用域(全局可见)与函数作用域(某个函数内可见)。除此之外解释器会引入一些超全局变量(用户无法创建此类变量)

  • 尽管函数是可以嵌套的,但不存在类似python中使用关键字nolocal使用自由变量的做法
  • 欲在函数中使用全局变量,使用关键字global声明,或者通过超全局变量$GLOBALS(其值是一个数组)使用。当用global $var ;声明一个变量时,实际上建立了一个到全局变量$var的别名,等效于$var =& $GLOBALS["var"];。 在函数之外使用 global 关键字不算错,可以用于在一个函数之内包含文件时。 使用超全局变量不需要此声明

静态变量

静态变量仅在局部函数域中存在,但当控制流离开它所在的作用域时,其值并不丢失。在函数内使用关键字static声明一个静态变量,声明的同时可以进行初始化:static $a = 1;,无论函数被执行几次,以此种形式进行的初始化只会发生一次。静态声明及其初始化是在编译时解析的,这意味着只能使用字面量进行初始化。

静态变量可以用于递归函数。

动态地使用变量

PHP支持动态地使用变量,即到运行时再确定使用哪个变量,而无须以硬编码的形式写在代码中(联想JS中也可以通过用方括号访问法读写window的属性从而动态地使用全局变量)。其方式如下:

$name = 'a';
$a = 'hello';
echo $$name; // 'hello'

$$a = 'world'; // 定义了变量$hello

要将可变变量用于数组,必须解决一个模棱两可的问题。这就是当写下a[1]‘时,解析器需要知道是想要$a[1]作为一个变量呢,还是想要a 作为一个变量并取出该变量中索引为 [1] 的值。解决此问题的语法是,对第一种情况用${$a[1]},对第二种情况用${$a}[1]`。

unset

语言结构unset()销毁一个或一批变量名及其值,但如果某变量只是一个别名,则并不销毁变量值,只是销毁这个变量名。

unset无法立刻触发垃圾回收,使用unset的意义更多地在于使程序的逻辑更清晰(例如明确指出从某处开始某个变量不再需要了)。

预定义变量

对于全部脚本而言,PHP 提供了大量的预定义变量。这些变量将所有的(来自宿主的、来自runtime的)外部变量表示成内建环境变量。当宿主是Web Server时,与收到的HTTP请求有关的信息都在这些预定义变量中(有时去要去流php://input里面找)。它们中的有些是超全局变量,有些只是一般的全局变量。所有预定义变量均非别名,因此对它们的操作不会互相干扰。

可以通过配置php.ini来决定应当存在哪些预定义变量。

常量

一个常量一旦被定义,就不能再改变或者取消定义。常量命名遵循与变量相同的规则,但不需要$开头。

  • 常量只能包含标量数据(boolean,integer,float 和 string)。可以定义 resource 常量,但应尽量避免,因为会造成不可预料的结果
  • 常量被创建后,其作用域规则等同于超全局变量

如果使用了一个未定义的常量,PHP 解释器假定你想要的是该常量本身的名字并以字符串的形式提供,并触发 E_NOTICE 级的错误。

常量的定义方法

可以使用函数define()定义常量,在 PHP 5.3.0 以后,可以使用 const 关键字在类定义之外定义常量。两种方法的机制不同,前者是运行时定义,且作为函数调用,有返回值(TRUE和FALSE);后者在编译时定义常量,这就意味着不能在函数内,循环内以及 if 语句之内用 const 来定义常量。在PHP7之前只能用后者来定义常量数组。

<?php
define("ANSWER", 42); // 习惯上使用大写字母来命名常量
const CONSTANT = 'Hello World'; // 5.3 后的另一种方法

预定义常量与魔术常量

PHP 解释器向它运行的任何脚本提供了大量的预定义常量。不过很多常量都是由不同的扩展库定义的,只有在加载了这些扩展库时才会出现,或者动态加载后,或者在编译时已经包括进去了。

PHP 解释器还提供了8个魔术常量,此类常量以双下划线开头和结尾,它们的值在运行时随着它们在代码中的位置改变而改变。

与常量机制有关的函数

  • constant():在运行时通过字符串动态地使用常量,而无需把常量名硬编码在代码中
  • getdefinedconstants():在运行时获得所有定义过的常量
  • defined():根据常量名字符串检查某个常量是否被定义过

函数

定义一个函数

函数定义语句

其语法形式是

function 函数名(形参列表) {
    函数体
}

任何有效的 PHP 代码,除了某些只能在编译期起作用的语句,都有可能出现在函数内部,甚至包括其它函数和类定义。

PHP允许对函数先使用再定义,但不允许“先使用再引入”——想要使用其它文件中定义的函数,必须先引入它。另外,如果某函数的定义出现在条件语句或一个函数中时,则控制流必须先流经此处,才能在之后的代码中调用它。

注意:

  • 无论一个函数在何处被定义,它的函数名都具有全局作用域(但如上文所述,对于非处在顶层的函数定义,控制流需要先流经它)
  • PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数
  • 函数名大小写不敏感

匿名函数表达式(Closure类)

PHP 5.3起支持创建作为第一等公民的lambda函数,用于需要一个值的地方。一个lambda函数是 Closure 类的实例(具有一些实用方法)。创建匿名函数的语法是function (参数列表){函数体},不需要函数名。

闭包

匿名函数的特殊之处在于,如果它在一个函数内被定义,它可以使用非全局的自由变量从而形成一个闭包——通过使用use关键字,具体语法是function (参数列表) use (自由变量列表)

注意,Closure类并不是通过实现魔术方法__invoke来实现现有功能的。

Closure类的方法

  • bindTo($this的所指, 类A的类名或类A的一个实例):类似JS中的Funtion.prototype.bind。生成一个新的lamda函数,该函数中的$this指向第一个实参(若该函数内并不使用$this,可传入NULL)。若传入了第二个参数,则新lamda函数仿佛是在类A中定义的方法一样(这决定了访问控制规则如何作用于新lamda函数,也影响函数内selfparentstatic关键字的所指)
  • bind:Closure类上的静态函数。功能同bindTo
  • 其余PHP7中引入的方法

函数参数

为函数传参也是一种赋值操作。

调用函数时,可以传入比形参列表中的数目更多的实参,也可以传入更少的实参。后者(如果没触发默认参数功能的话)将触发一个 Enotice 级别的错误,并使得相应的形参处于未初始化状态。通过在函数内调用函数funcgetargsfuncgetarg、[funcnum_args](https://secure.php.net/manual/zh/function.func-num-args.php),可以起到类似在JS中读取arguments数组的效果。

在函数传参方面,PHP支持“形参作为别名”、“默认参数”、“可变长参数”以及“类型提示”。

  • 形参作为别名:在需要作为别名的形参之前使用&,例如function func(&$a) {...}
  • 默认参数:类似JS和Python中的同名功能(其行为与JS而不是Python中的同名功能类似)。任何默认参数必须放在任何非默认参数的右侧,否则也不算错,就是无法以任何形式使得默认参数生效(考虑JS中可以通过在相应位置传入undefined来使得默认参数生效)。默认参数可以是任何标量以及array以及NULL,值必须以字面量的形式提供。自 PHP 5 起,作为别名的参数也可以有默认值
  • 类型提示:类似Python中的函数注解,但只能为形参设置类型/类(PHP7起也可以指定返回值的类型)。PHP5中可以设定为arraycallalbe、任何类名或接口名、self(对于方法),PHP7中额外支持设定boolfloatintstring。如果在调用函数时传入的参数的类型不对,会在运行时产生一个可恢复的致命错误,而在PHP 7中将会抛出一个TypeError异常。如果设定的是一个标量类型,非严格模式下PHP会试图进行类型转换
  • 可变长参数(PHP5.6+):同JS,使用...设定的可变长参数会被打包成array。在 PHP 5.5 及更早版本中,使用函数 funcnumargs(),funcgetarg(),和 funcgetargs()
  • 传参时展开参数(PHP5.6+):同JS,可以展开一个array或可遍历对象传入参数列表

函数的返回值/返回别名

在函数内使用return语句可以立刻中止函数的执行,并返回一个值。return语句是可选的,没有的话函数最终将返回NULL。

函数可以返回一变量(而不是值),这需要在定义函数时使用&

<?php
function &get_collector() {
    static $collector = [];
    return $collector;
}
array_push(get_collector(), 'foo'); // 注意,array_push是将第一个参数当作别名使用的
$a = &get_collector(); // $a 成了函数内静态变量$collector的别名
$b = get_collector(); // $b经过一个复制(因为是值模型),此时值是{0 => "foo"},但它不是$collector的别名

PHP7起可以为返回值加上类型声明。

动态地使用函数

如果一个变量名后有圆括号,PHP 将寻找与变量的值同名的函数,并且尝试执行它。

?php
function foo() {
    echo "In foo()<br />\n";
}

$func = 'foo';
$func(); // This calls foo()

此法也适合动态地决定调用哪一个类方法和静态方法。

与函数有关的函数

与函数有关的函数在函数处理页面中,大多用于在运行时动态地获取函数信息、调用函数(以及方法)甚至构造新函数。