在PHP里如何使用函数式编程
PHP不是像Lisp那样的函数式编程语言,更多的,PHP适合用C的风格来编写代码。PHP中没有“函数”这种类型,也就是说,函数不能直接用变量来传递。比如下面的代码:
1 |
function test() { |
2 |
echo "welcome to nowamagic.net"; |
3 |
} |
4 |
5 |
$test1 = test; |
6 |
echo gettype($test1); |
7 |
//输出string |
PHP是这样解析上面的代码的。
1 |
//出现一个裸字符串,不以$符开头,那么就把它当成常量 |
2 |
//PHP将test当成一个常量,但代码中并没有test这一常量,接着PHP将常量名当成其值 |
3 |
$test1 = test; |
4 |
//当PHP遇到一个未定义常量时,就将它当成一个字符串值,如下 |
5 |
var_dump(UNDEFINED_CONSTANT); |
6 |
//将输出string(18) "UNDEFINED_CONSTANT" |
这表明PHP中的函数名在非调用的情况下(后面没有括号)会被当成一个字符串。
而正巧PHP中有通过一个字符串名称动态调用一个函数的方法:
1 |
$phpInfo = 'phpinfo'; |
2 |
$phpInfo(); |
3 |
4 |
//还有另外一种方式 |
5 |
call_user_func('phpinfo'); |
在C语言中可以通过指向函数的指针来将函数作为参数传递以实现高级的FP,而在PHP中,则是通过将函数名称作为字符串传递,通过$fname()来调用 ($fname是一个字符串类型的变量,其值为函数名),或者通过call_user_func来调用。那么到这里时就会发现PHP中函数的实现所带来的不便,因为函数是通过字符串来传递和调用的,那么就要求不能有函数名称相同的两个不同函数的存在。即,PHP中的所有的函数(这里所说的“函数”不包括方法)都是全局函数, 并且不允许在运行时重定义,因为如果允许重复定义函数(比如像大家熟悉的JavaScript那样),下面的代码就会出错:
01 |
//第一次定义test1函数 |
02 |
function test1() { |
03 |
return 123; |
04 |
} |
05 |
|
06 |
//将变量$testFunction赋值为test1的函数名称 |
07 |
//事实上这种写法较低效,这样写更好一些:$testFunction='test1'; |
08 |
$testFunction=test1; |
09 |
|
10 |
//.......若干行代码之后 |
11 |
//重新定义了test1 |
12 |
function test1() { |
13 |
return 456; |
14 |
} |
15 |
|
16 |
//输出与期望值不一样了,因为变量里只是保存的函数名而已 |
17 |
//而并不是真正的对函数的引用 |
18 |
echo $testFunction(); |
既然PHP中所有函数都是全局函数,那么函数声明就自然不能嵌套着写了(指Closure),在讨论PHP中的Closure与Lambda之前先讨论一下PHP中的方法(即已经绑定到指定对象或类上的函数)。先创建一个简单的类,将其静态方法直接输出看看是什么内容。
1 |
class Demo { |
2 |
public static function test() { |
3 |
return 123; |
4 |
} |
5 |
} |
6 |
var_dump(Demo::test);//到这一步出错了 |
7 |
//因为PHP在解析Demo::test时,会将其看成是对Demo类的静态属性的读取 |
8 |
//而这个静态属性又不以$开头,那么就将其看成静态常量(const) |
9 |
//不存在这个常量,就出错了 |
那么如果要动态的调用一个类(或实例)的方法该怎么办呢? 先上一个现实中的例子,一个非常简单的Controller(当然真正的控制器不能这样简单)
01 |
class Controller { |
02 |
public function login() { |
03 |
echo "Login"; |
04 |
} |
05 |
public function home() { |
06 |
echo "Home"; |
07 |
} |
08 |
public function logout() { |
09 |
echo "Logout!!!"; |
10 |
} |
11 |
} |
12 |
$action=$_GET['action'];//要调用的方法名称在action参数里面 |
13 |
//接着要动态调用指定的方法 |
14 |
Controller::$action();//使用这种方法,这主要得益于PHP的“变量的变量”机制 |
15 |
//当然,要先检查一下类有没有这个方法,可以用PHP的反射机制检测 |
16 |
$reflectionClass=new ReflectionClass('Controller'); |
17 |
$reflectionClass->hasMethod($action);//测试一个是否有对应的方法 |
18 |
//以及测试一下方法是否是静态方法 |
19 |
$reflectionMethod=$reflectionClass->getMethod($action); |
20 |
$reflectionMethod->isStatic();//是静态方法则返回true |
除了使用PHP的可变变量机制外,还可以使用之前的call_user_func函数。
01 |
//传一个数组作为第一个参数,数组中第一个元素为类名,第二个元素为静态方法名称 |
02 |
call_user_func(array('Controller',$action)); |
03 |
//如果要向方法或函数传递参数,可以将参数附在后面 |
04 |
function test($a,$b) { |
05 |
echo $a+$b; |
06 |
} |
07 |
call_user_func('test',123,456); |
08 |
//或者在传不定参数时,使用call_user_func_array函数 |
09 |
call_user_func_array('test',array(123,456)); |
10 |
|
11 |
//在PHP 4.1之前存在call_user_method函数及 |
12 |
//call_user_method_array专门用来调用对象的方法,但现在废弃不用了 |
如果需要调用实例的方法的话,则为
01 |
class Demo { |
02 |
public function test() { |
03 |
echo "Instance Method"; |
04 |
} |
05 |
} |
06 |
$d=new Demo(); |
07 |
|
08 |
$d->$action(); |
09 |
//或者以这种方式调用 |
10 |
call_user_func(array($d,$action)); |
11 |
//如果类Demo没有将构造函数声明为私有的话,还仍然可以无需实例就调用实例方法 |
12 |
call_user_func(array('Demo',$action)); |
从上面的代码可以看得出来,PHP中的类也是以字符串形式的名称传递的。像下面的代码:
1 |
$class=Demo; |
2 |
echo gettype($class);//string |
3 |
//原因和函数一样,PHP将Demo当成一个普通的裸字符串了 |
到了PHP 5.2.3时,PHP中调用类的静态方法还有另外一种语法。
1 |
//PHP 5.2.3 |
2 |
call_user_func('Demo::'.$action); |
到了PHP 5.3开始,PHP引入了命名空间的概念,因此上面的调用将变成
1 |
call_user_func(__NAMESPACE__.'::Demo::'.$action); |
看到上面的代码,其实可以猜想,类的静态方法不就是加了一个命名空间前缀的函数嘛。
PHP中的Lambda及Closure
在PHP 5.3之前,PHP中实现Lambda的语法非常别扭,要将函数中的代码作为字符串传递给create_function方法去构造。
1 |
$lambda=create_function('$a','return addslashes(trim($a));'); |
2 |
//相当于构造这样一个函数 |
3 |
function lambda1($a) { |
4 |
return addslashes(trim($a)); |
5 |
} |
这种对Lambda的支持真是鸡肋得很,用起来反而降低了效率。
将代码写到字符串中很容易出错,这种风格的lambda创建方式只适用于非常简短的函数表达式,那再看看上面的$lambda变量中保存的什么。
1 |
var_dump($lambda); |
2 |
//仍然是字符串类型,并且命名特点为"lambda_"加一个数字 |
3 |
//后面的数字为PHP系统全局计数器,保证运行不会重复 |
事实上用create_function创建的仍然是全局函数,只是这个全局函数的名字不会被猜到而已。
可能我们会尝试创建这样命名的函数:
1 |
function lambda_1() { |
2 |
retunr 'Lambda!!!'; |
3 |
} |
4 |
$lambda=create_function('','return 123;'); |
5 |
//如果你的PHP是系统启动后第一次运行,那么$lambda里面的字符串看起来就和lambda_1一样 |
6 |
//但执行代码调仍然一点没有错误 |
7 |
echo $lambda();//返回123; |
8 |
echo lambda_1();//返回'Lambda!!!' |
其实,create_function生成的函数名有些特殊,它是NULL字符加上"lambda_"再加个一个数字标识,而NULL字符我们是没有办法在声明函数时使用这个字符的,因此上面的代码不会冲突,可以用下面的代码测试。
1 |
$lambda=create_function('','return 123;'); |
2 |
preg_match('|\d+|',$lambda,$matches);//匹配出lambda数字标识 |
3 |
//通lambda数字编号来调用些函数表达式 |
4 |
call_user_func("\x00lambda_{$matches[0]}");//在字符串前面加个NULL字符 |
5 |
//始终输出123 |
使用create_function不但写起来别扭,而且create_function方法只能创建一般的临时函数而已。
并不能实现闭包(虽然如果只是读取些标量的话可以变通一下实现,如下)
1 |
function outer() { |
2 |
$a=123; |
3 |
return create_function('$a='.$a,'return $a;'); |
4 |
} |
5 |
$inner=outer(); |
6 |
echo $inner(); |
7 |
//PHP有个很不爽的地主就是不把函数调用当成表达式,这种代码会出错 |
8 |
//outer()();或getRow()[0]; |
而到了PHP 5.3,PHP开始支持真正的闭包,而且lambda更好使了,接近于JavaScript。
01 |
#!/usr/bin/php-5.3.0/php |
02 |
|
03 |
|
04 |
$data=range(0,10); |
05 |
//更接近于JavaScript的lambda语法 |
06 |
$newData=array_map(function ($v) { |
07 |
$a=range(0,$v); |
08 |
return array_sum($a); |
09 |
},$data); |
10 |
|
11 |
function outer() { |
12 |
$a=123; |
13 |
//语法上生疏些,需要将要在lambda中访问的闭包变量列在use列表中 |
14 |
return function () use($a) {//闭包 |
15 |
echo $a; |
16 |
}; |
17 |
} |
18 |
$inner=outer();//只是不爽的地方就是,仍然不能这样写outer()(); |
19 |
$inner(); |
20 |
print_r($newData); |
21 |
|
22 |
//PHP也提供了像Lisp中的map,reduce,walk方法用于函数式编程 |
23 |
//下面的代码用于将$_REQUEST中的每一个值进行trim |
24 |
array_walk('trim',$_REQUEST); |
PHP闭包中变量引用的问题,下面的代码工作起来和想像中有些差距。
01 |
function outer() { |
02 |
$a=0; |
03 |
return function () use($a) { |
04 |
return $a++; |
05 |
}; |
06 |
} |
07 |
$inner=outer(); |
08 |
echo $inner();//outputs 0 |
09 |
echo $inner();//outputs 0 |
10 |
echo $inner();//outputs 0 |
因为PHP中的闭包,只是对原变量中值进行拷贝而已,也就是说,use中列出来的变量,在运行时都是闭包函数中的局部变量,而并不是函数外面的变量。
1 |
function outer() { |
2 |
$a=0; |
3 |
return function () use($a) {//大致相当于有这样一个步骤:lambda1::$a=outer::$a |
4 |
return $a++; |
5 |
}; |
6 |
} |
如果要达到想像中的状态的话,只要在变量前加个按引用传值标识就行了。
01 |
function outer() { |
02 |
$a=0; |
03 |
return function () use(&$a) { |
04 |
return $a++; |
05 |
}; |
06 |
} |
07 |
$inner=outer(); |
08 |
echo $inner();//0 |
09 |
echo $inner();//1 |
10 |
echo $inner();//2 |
11 |
echo $inner();//3 |

浙公网安备 33010602011771号