4.2 创建函数
对于力图避免在编程中闭门造车、一切都重新发明的程序员来说,虽然PHP的众多函数库是一笔巨大的财富,但迟早都会需要标准包所不提供的内容,这意味着你需要创建定制函数,甚至创建整个函数库。为此,需要使用预定义的语法模式来定义一个函数,如下:

例如,考虑下面的函数,generate_footer(),它将输出一个页脚:

定义之后,就可以在任何地方调用这个函数。例如:

这将生成如下结果:
4.2.1 按值传递参数
你会经常发现向函数传递数据很有用。举一个例子来说,创建一个函数,它要确定销售税率,然后将税费增加到价格上,从而计算商品的总价:

这个函数接受两个参数,名为$price和$tax,它们将在计算中使用。尽管这些参数可能是浮点数,但由于PHP的松散类型特点,你完全可以传递任何数据类型的变量,不过输出可能会出乎意料。此外,可以根据需要定义任意多个参数;在这一点上,PHP没有任何强制限制。
定义函数之后,就可以调用这个函数了,如前例所示。例如,salestax()可以有如下调用:

当然,并非只能向函数传递静态值。也可以如下传递变量:

以这种方式传递参数时,称为按值传递或传值(passing by value)。这就意味着,函数范围内对这些值的任何改变在函数外部都会被忽略。如果希望在函数范围外也能反映出这些修改,则可以按引用传递参数,下面将介绍有关内容。
注解 需要说明的是,不要求非得在调用之前定义函数,因为PHP在执行前会把整个脚本读到引擎中。因此,可以在定义之前就调用salestax(),但并不推荐这种随便的做法。
4.2.2 按引用传递参数
在有些情况下,可能希望在函数内对参数的修改在函数范围外也能反映,按引用传递参数就可以满足这种需要。按引用传递参数(也称传引用)要在参数前加上&符号。下面是一个例子:

下面是输出:
注意,$tax的值依然相同,但$cost已经改变。
4.2.3 默认参数值
可以为输入参数指定默认值,在没有提供其他值的情况下,就会把这个默认值自动赋给该参数。下面来修改前面销售税的例子,假设你的销售主要集中在俄亥俄州的弗兰克林县。可以指定$tas的默认值为5.75%,如下:

记住,也可以为$tax传递其他的税率;只有如下调用salestax()时才会使用默认值5.75%:

注意,默认参数值必须是常数表达式;而不能指定函数调用或变量等非常量值。
4.2.4 可选参数
可以指定某个参数为可选(optional)参数,为此,这些参数需要放在参数列表末尾,而且要指定其默认值为空,如下:

这样如果没有销售税,就可以如下调用salestax(),而不指定第二个参数:

这个调用会返回如下内容:
如果指定了多个可选参数,可以选择性地传递某些参数。考虑下面这个例子:

调用calculate()时,可以只传递$price和$price3,如下:

它返回如下值:
4.2.5 从函数返回值
通常情况下,只依靠函数做某些事情还不够;脚本的结果可能取决于函数的结果,也可能取决于在执行函数时对数据的修改。但是,由于变量的作用域的差异,函数体无法很容易地将信息传递给调用者,那么怎样才能完成这个任务呢?可以通过关键字return向调用者传递数据。
1.
return()
return()语句可以向函数调用者返回任意确定的值,将程序控制权返回到调用者的作用域。如果return()在全局作用域内调用,将终止脚本的执行。再次修改salestax()函数,假设你不希望将计算的销售税立即显示给用户,而是将这个值返回给调用块:

还有一种方式,可以直接返回计算结果,而不需要赋给变量$total,如下:

下面是调用这个函数的例子:

2. 返回多个值
从函数中返回多个值通常很方便。例如,假设要创建一个函数,从数据库中获取用户数据,比如用户的姓名、电子邮件地址和电话号码,然后返回给调用者。完成这个任务比你想像的要简单得多,使用一个很有用的语言构造list()就可以实现。利用list()构造可以很方便地从数组中获取值,如下:

根据这个示例,你可以想到如何使用list()从函数返回这三个值:

执行此脚本将返回:
这个概念很有用,将在本书中反复使用。
4.2.6 嵌套函数
PHP支持嵌套函数(nesting function),即在函数中定义并调用其他函数。例如,完全可以在salestax()函数中定义并调用一个美元转换为英镑的函数convert_pound(),如下:

注意,PHP不限制嵌套函数的作用域。例如,salestax()之外仍可以调用convert_pound(),如下:

4.2.7 递归函数
递归函数(recursive function)即调用自身的函数,这对于程序员来说,通常有很高的实用价值,常用来将复杂的问题分解为简单的情况,反复做这种处理直到问题解决。
几乎每个入门的递归例子都会使用阶乘计算,这真是没意思。我们来做一点更实际的事情,创建一个还贷款计算器。具体地,下面的例子将使用递归来创建一个还款进度表,告诉你偿还贷款时每次支付的本金和所需的利息。递归函数amortizationTable()如代码清单4-1所列。它需要4个输入参数:$paymentNum标识还款编号,$periodicPayment表示每月总的还款额,$balance表示剩余贷款额,$monthlyInterest指定了每月的利率。这些项在另一个脚本中(名为mortgage.php)指定或确定,如代码清单4-2所列。
代码清单4-1 还贷计算器函数amortizationTable()

设置了相关变量,并完成了一些初步计算之后,代码清单4-2将调用amortizationTable()函数。因为这个函数会递归地调用自身,所以分期付款表的计算都在函数内进行,结束之后,控制权返回给调用者。
代码清单4-2 使用递归的还贷进度计算器

图4-1显示了这个例子的输出。这是一个30年期的固定贷款,贷款总额200 000.00美元,利率6.25%。为了节省篇幅,这里只列出了前10次迭代得出的还款结果。

图4-1 mortgage.php的示例输出
使用递归策略通常能大幅减少代码量,提高重用性。虽然递归函数并不总是最优的解决办法,但它对于任何语言来说都是有益的补充。
4.2.8 变量函数
PHP最吸引人的特性之一是其简洁的语法。但是,在有些情况下,使用更抽象的编程方式能够减少编写代码的许多开销。例如,考虑一种要创建很多数据访问函数的情况,比如要创建retrieveUser()、retrieveNews()和retrieveWeather()等函数,顾名思义,可以很清楚地知道这些函数各自的作用。为了触发给定的函数,可以使用URL参数和if条件语句,如下:

对于这段代码,可以传递以下的URL:

然后,index.php文件使用$trigger来确定将执行哪个函数。虽然这样也能起作用,但很麻烦,尤其是需要很多访问函数时更是如此。还可以通过一种简短得多的方式完成同样的目标:变量函数。变量函数(variable function)是指这个函数的名也要在执行前计算,这意味着函数名直到执行时才确定。就像正常的变量一样,变量函数前面有个美元符,如下:

下面使用变量函数来修改前面的例子:

虽然有时候使用变量函数很方便,但是要记住,它们存在安全风险。最显著的问题是,攻击者可能会修改用来声明函数名的变量,以此来执行PHP的任何函数。例如,如果将前面示例中的$trigger变量设置为exec,将$rowid变量设置为rm –rf,请考虑一下会有什么结果。PHP的exec()命令将“放心地”在系统级执行其参数。而命令rm –rf将从根目录开始,递归地删除所有文件。最终的结果将是灾难性的。因此,一定要确保过滤所有用户信息;否则,你永远不知道接下来会发生什么。