一步步编写PHP的Framework(十)
PS:之前的很多内容我都没有细讲,就比如路由,真正的框架路由肯定不是通过GET方式指定的,而是正则,并且它需要兼容多个Server,多种方式。
我们知道所有的请求都需要经过控制器,所以首先还是说一下控制器。
我们之前已经 说过控制器的概念了,但是这个控制器还是功能太弱了,因为它只是从功能上是控制器,框架并没有为它做任何事情,框架实际上可以实现一些常用的功能,然后用户定义的控制器继承它,这样用户可以少写很多代码的。
之前的控制器是:
1 |
2 | class IndexController { |
3 | public function index() { |
4 | echo 'Hello world'; |
5 | } |
6 | } |
现在假设框架已经实现了控制器的一些基本功能,这个类我们称为Controller.php,那么现在代码就变成了:
1 |
2 | class IndexController extends Controller { |
3 | public function index() { |
4 | echo 'Hello world'; |
5 | } |
6 | } |
这样做有什么好处呢,由于Controller继承了Base,所以IndexController也拥有了Base的功能,这样就不需要用户再编写很多捕获的代码等。
当然,这样做也有缺点,测试IndexController的时候比较麻烦。
今天我先说一下控制器比较基础的两个功能,跳转和转发。
首先是跳转,这个用的太普遍了,比如用户登录的时候,点击登录按钮,进入后台处理的页面,处理完毕之后就需要跳转,那么怎么实现跳转呢?
有几种方式:
第一种:在JS中实现跳转;
第二种:header("Location:url");具体使用可查看PHP手册;
第三种:在HTML的meta中设置refresh;
由于header调用的时候如果之前页面已经有输出,跳转会失效,所以需要结合meta的refresh一起使用,当然,你也可以直接使用JS的这种方式来实现,只是我不太喜欢这种方式,因为我之前使用这种方式实现跳转的时候出过问题。
现在我们定义跳转这个函数的名字为_redirect,为什么前面加上_呢,这也是我的一个习惯,对于函数,只要不是public,我都使用_作为前缀。可能大家会问了,为什么不设置这个函数为public呢,因为用户编写的控制器也只会继承它而不会直接调用,所以我设置成为了protected。
1 | protected function _redirect(Array $arr) { |
2 |
3 |
4 | } |
暂时可以将功能弄得弱一点,假设跳转的参数通过数组传递过来,那么我们可以使用类型提示(Array $arr)这种方式来搞定,如果传递的参数不是数组,那么直接会报错。
我们使用的方式可以是这样:
1 | $this->_redirect(array( |
2 | 'action' => 'test', |
3 | 'controller' => 'Test', |
4 | 'param1' => '1' |
5 | )); |
它代表的意思是跳转到Test这个控制器的test这个Action,并且还传递了一个参数,这个参数名为param1,值为1。
01 | protected function _redirect(Array $arr) { |
02 | $controller= empty($_GET['c']) ? C('defaultController') : trim($_GET['c']); //设置了默认的控制器 |
03 | $action= empty($_GET['a']) ? C('defaultAction') : trim($_GET['a']); //设置了默认的Action |
04 | array_key_exists('controller',$arr) $arr['controller'] = $controller; |
05 | array_key_exists('action',$arr) $arr['action'] = $action; |
06 | $str = '/?'; |
07 | foreach($arr as $key => $val) { |
08 | if(!is_int($key)) { |
09 | $str .= ($key . '=' . $val . '&'); |
10 | } |
11 | } |
12 | $str = substr($str,0,strlen($str) - 1); |
13 | Response::redirect($str); |
14 | } |
这个就是我刚刚手写的跳转代码,实际上就是把传递的数组拼接一下然后组成一个字符串,这个字符串就可以看成是一个URL,由于现在没有对Route.php进行更多的处理,对于localhost/demo2/index.php?controller=a这种URL它跳转就会出错,暂时只支持localhost/index.php?controller=a这种URL,还有$controller和$action的获取和Route.php中的代码重复了,这些都需要在后面真正实现路由的时候再讲解,暂时就这么看看吧,虽然我自己都感觉这样的代码很恶心。
可能大家都注意到了,当这个函数拼接到URL之后,是直接调用了Response的redirect方法,这是为什么呢?
第一:有可能在真正应用中,我们直接在控制器中调用$this->_redirect满足不了我们的需求,这个时候我们就需要直接调用Response::redirect,比如跳转到百度首页就只能调用Response::redirect("http://www.baidu.com");
第二:从逻辑上,跳转是一个服务器对客户端的响应,所以需要写在Response中,具体的可参照Java。
那么我们又必须新建一个Response.php这样一个文件:
01 |
02 | class Response extends Base { |
03 | public static function redirect($url) { |
04 | if(is_string($url)) { |
05 | if(!headers_sent()) { |
06 | header("Location:" . $url); |
07 | exit(); |
08 | } else { |
09 | $str = ''; |
10 | exit($str); |
11 | } |
12 | } else { |
13 | //错误处理 |
14 | } |
15 | } |
16 | } |
这里的逻辑比较简单,实际上就是判定是否有输出,没有输出那么就直接使用header("Location")进行跳转,如果有输出,那么使用meta的refresh跳转。
注意:实际上还可以在这个跳转上开发更多的功能,但是由于我只是大概讲一下,所以更多的内容就不写了,有兴趣的人可以去Toper上面看看。
这样,一个比较简单的跳转就完成了,那么怎么实现转发呢?
可以简单这样理解,转发实际上就是再调用了一下某一个Controller的某一个Action。
所以这样我们就可以比较简单的实现转发了:
01 | protected function _forward(Array $arr) { |
02 | $controller= empty($_GET['c']) ? C('defaultController') : trim($_GET['c']); //设置了默认的控制器 |
03 | $action= empty($_GET['a']) ? C('defaultAction') : trim($_GET['a']); //设置了默认的Action |
04 | if(array_key_exists('controller',$arr)) { |
05 | $controller = $arr['controller']; |
06 | } |
07 | if(array_key_exists('action',$arr)) { |
08 | $action = $arr['action']; |
09 | } |
10 | $controller .= 'Controller'; |
11 | if($controller === get_class()) { |
12 | if(method_exists($this,$action)) { |
13 | $this->$action(); |
14 | } else { |
15 | //时间有限,不写逻辑了 |
16 | } |
17 | } else { |
18 | if(class_exists($controller)) { |
19 | $class = new $controller(); |
20 | if(method_exists($class,$action)) { |
21 | $class->$action(); |
22 | } else { |
23 | //时间有限,不写了 |
24 | } |
25 | } else { |
26 | //时间有限,不写了 |
27 | } |
28 | } |
29 | } |
实际上逻辑上就是判定一下要调用的Action是否属于本控制器,如果是本控制器,直接调用$this->$action()即可,否则,需要实例化这个控制器,即$class = new $controller(),然后再调用这个Action。
本来以为半个小时就可以写完,结果写了一个小时了,由于时间超出我的预算,所以代码都是手写的,不知道有不有什么语法错误什么的,反正看看思路就OK了。