« 感谢
概览 »
最后更新 : 2013-11-21 18:11:44
选择语言 :

控制器概览

控制器是如何运行的

当页面请求某个URL时,MyQEE首先会根据URL中路径信息去寻找控制器,然后再调用相应的action方法,并把所有的参数传给它,通过控制器的执行,完成一个完整的请求。

在MyQEE中,控制器的后缀均为 .controller.php 例如 hello.controller.php,并且控制器的文件名都是小写。控制器文件中需要定义一个同名的 Controller_ 开头的类并扩展到 Controller 上,比如

<?php
// hello.controller.php
Controller_Hello extend Controller
{
    public function action_test()
    {
        // code
    }

    public function action_test2()
    {
        // code
    }
}

这个文件保存在控制器目录中即可通过地址 /hello/test/ 访问 action_test() 方法,而 /hello/test2/ 则会调用 action_test2() 执行。

注意,所有的Action均是以 action_ 开头的,并且都是public属性。

如果控制器是在某个目录下,要注意控制器的类名也要包含目录名的,比如 test/abc.controller.php 控制器,它的类应该是这样的 Controller_Test_Abc extend Controller {}

PS:控制器类名不区分大小写,不过推荐使用首字母和下划线后的首字母都大写

所以,通过不同的URL可以直接执行对应的控制器的action方法,这样我们就可以写出优雅的控制器了。

控制器和URL访问的对应关系

默认情况下访问的URL中格式为 <directory>/<controller>/<action>/<arguments1>/<arguments2>/...

例如请求的URL为:/test/abc/def/123/456/,则有可能请求的就是 test/ 目录下的 abc.controller.phpaction_def($arg1 = '', $arg2 = '') 方法,其中,$arg1=123, $arg2=456

控制器优先级

当然,系统并没有规定 test 就一定是目录,你也可以说它是根目录下的 test.controller.php 控制器的 action_abc() 方法,后面的都是参数。是的,这样完全正确,此时就涉及到了控制器的优先级问题。

对于一个请求的URL,目录越深的控制器越优先,怎么来理解这句话呢?下面举个例子:
例如有2个控制器,分别是 test/abc/def.controller.phptest.controller.php ,请求的URL仍旧是上例中 /test/abc/def/123/456/,系统会按这样的顺序去找控制器:

  1. /test/abc/def/123/456.controller.php
  2. /test/abc/def/123.controller.php
  3. /test/abc/def.controller.php
  4. /test/abc.controller.php
  5. /test.controller.php

这样,系统就会首先发现存在 test/abc/def.controller.php 这个控制器,那么结束查找直接加载这个控制器,然后把路径后面的当相应的action 和 arguments 来处理

这样做就会产生一个非常有意思的结果:
/test/abc/def/123/456/ 执行的是 test/abc/def.controller.php 控制器的 action_123() 方法
/test/abc/defghi/123/456/ 执行的是 test.controller.phpaction_abc() 方法

虽然这2个URL看上去似乎都在 /test/abc/ 这个目录中,但是他们实际上执行了2个完全不同的控制器。

这样还不够,那么再来一个有意思的做法吧,同样是上面的这些,如果再创建 /test/abc/defghi.controller.php 这个控制器呢?显然,此时就会执行到这个控制器。所以,同样的一个URL地址你可以有更多的控制器的创建方法,完全看项目需求,更多的想象空间。

index.controller.phpdefault.controller.php 控制器

到这里,你是否觉得控制器是一个非常灵活而有趣的东西?那么我再介绍2个特殊的控制器 index.controller.phpdefault.controller.php 这2个控制器分别是首页控制器和默认控制。下面分别介绍:

index.controller.php 控制器

它是一个目录的控制器,最简单的例子,你网站的首页路径就是用的这个控制器。例如 index.controller.php 存放在控制器根目录,访问 http://your_domain/ 就可以方法到它了。

如果是某个目录下,就放在一个目录下,比如 test/index.controller.php 访问 /test/ 这样的URL就可以执行它,而访问 /test/abc/ 就执行不到它的。

注意,通常情况下,index.controller.php 里只能用到 action_index()action_default() 这其中的一个action,例如 /test/这样的URL就可以执行到这个控制器:

<?php
// test/index.controller.php
class Controller_Test_Index extend Controller 
{
    public function action_index()
    {
        echo 'it is index controller.';
    }
}

default.controller.php 控制器

它和 index.controller.php 的区别在于,index.controller.php 只接受最后一级目录的首页控制器查找,而 default.controller.php 则可以作为“不存在”的控制器的替代控制器,即此目录下的“万能”控制器。

例如,test 目录下有2个控制器,分别是 default.controller.phpabc.controller.php,那么当访问 /test/abc/123/ 这样的URL时就会使用 abc.controller.php 控制器执行,而如果是一个类似 /test/aaaa/123/ 这样的URL请求呢?此时,就会调用 default.controller.php 控制器执行 action_aaaa() 方法了。

这是一个美妙的组合。由于有了 default.controller.phpindex.controller.php 控制器的参与,在前面 “控制器优先级” 中举的优先级例子就发生了一个有趣的变化,这样 /test/abc/def/123/456/ 完整的控制器查找顺序就是这样的:

  1. /test/abc/def/123/456/index.controller.php
  2. /test/abc/def/123/456/default.controller.php
  3. /test/abc/def/123/456.controller.php
  4. /test/abc/def/123/default.controller.php
  5. /test/abc/def/123.controller.php
  6. /test/abc/def/default.controller.php
  7. /test/abc/def.controller.php
  8. /test/abc/default.controller.php
  9. /test/abc.controller.php
  10. /test/default.controller.php
  11. /test.controller.php
  12. /default.controller.php

从这个完整的顺序来看,最高优先级的控制器为 /test/abc/def/123/456/ 目录下的 index.controller.php 控制器,然后是这个目录下的 default.controller.php 控制器,然后在每一级目录下,default.controller.php 则作为当前目录的“后备”控制器来寻找。

看到这,你或许会担心MyQEE寻找控制器的性能,需要查找这么多的控制器。其实你完全不用担心,上面列出来的只是理论上需要查找的控制器,实际上MyQEE在查找控制器前,首先会找出存在的目录,如果没有相应的目录则会直接忽略到这些控制器的查找了。比如在你的控制器里,也许只有 test 这个目录,在 test 目录下没有 abc 这个目录,那么上例中1-8就会直接跳过的,直接从9开始查找控制器,实际上最多也就查找4次而已,况且找到了对应控制器就立即终止查找了,所以你完全没有必要担心MyQEE在寻找控制器上的性能问题。

控制器的Action

控制器的action是以 action_ 开头的方法,比如 action_test(), action_entry($id=0)。当系统根据URL找到一个控制器后,会去实例化对象,并把相应的参数传给控制器,然后会执行对应的action方法。

action的参数

通过前面控制器的加载或许你已经熟悉了action的使用方法,在实际使用中URL除了能体现控制器和action还会有一些参数,那么这些参数是怎么传给action呢?其实很简单,就是action方法对应的参数,例如控制器test.controller.php

<?php
// test.controller.php
class Controller_Test extend Controller
{
    public function action_abc($arg1 = '', $arg2 = '')
    {
        echo '$arg1 = ' , $arg1;
        echo "\n";
        echo '$arg2 = ' , $arg2;
    }
}

如果请求的URL是 /test/abc/123/456/789,此时控制器将输出

$arg1 = 123
$arg2 = 456

对于地址栏中 789 这个值,由于action中只有2个参数,第三个和后面的就不会读取到,需要的话则可通过 $this->arguments 来获取。

如果访问的URL是 /test/abc/def/,此时 $arg1 的值就是 def$arg2 的值就位空

是的,在url中action后面的用斜杠分开的所有参数都将会通过action的参数直接传给程序使用。

action_default() - 默认控制器

有时候我们希望能够实现更完美的URL地址,比如 /test/1/, /test/2/, /test/3/ 这样的URL,我们希望能够在 test.controller.php 得到执行,可是显然控制器后面是一个随机的内容,我们不可能为分别建立 action_1(), action_2(), action_2() 吧?那么此时 action_default() 就是最好的选择。

<?php
// test.controller.php
class Controller_Test extend Controller
{
    public function action_default($id = '')
    {
        echo $id;
    }

    public function action_abc()
    {
        echo 'test';
    }
}

这样就可以通过 action_default 得到需要 $id 了。访问 /test/1/, /test/999/ 后分别会输出1和999了,而访问 /test/abc/ 则会输出 test

before(), after() - 前、后置方法

有时候,我们希望系统能够在执行某个action前先执行一个方法,比如我们的后台或会员系统,我们希望能够在执行确定的action前判断下该用户是否登录或是否有权限,那么不需要一个个的action里写,只要把相关的代码写在before里即可,例如

<?php
// test.controller.php
class Controller_Test extend Controller
{
    public function before()
    {
        echo 'a-';
    }

    public function after()
    {
        echo '-c';
    }

    public function action_abc()
    {
        echo 'b';
    }
}

如果请求URL /test/abc/ 则最终会输出 a-b-c

从这里你可以看出before() 类似 __construct() , after() 类似 __destruct(), 那么你或许会疑问为何不用这2个系统默认的。实际上, __construct()__destruct() 并不是不能用,而是它们执行的时机不合适,因为执行 __construct() 时控制器还没有完全实例化完毕,包括各种参数还没有给控制器设置,所以我们推荐使用MyQEE提供的这2个对应的方法来执行,这样能够达到更好的效果。

__call($action_name, $arguments) 方法

这个是PHP类库中特殊的方法,用来执行一个不存在的方法,MyQEE同样支持,你可以利用它来处理一些类似404错误输出相关功能,例如一个控制器中,有固定的几个action,如果用户请求了一个不存在的action方法,此时如果设置了 __call(),系统就会直接调用,如果没有这个方法,系统则会直接调 Core::show_404() 输出一个页面不存在的404页面。

Core_Controller API

$this->arguments
页面请求的参数
$this->controller
当前访问的控制器
$this->action
当前调用的方法
$this->uri
当前请求的URI
$this->directory
当前请求的目录部分
$this->ids
当前控制器信息ID
self::$controllers
控制器实例化化对象
$this->__construct()
$this->session()
返回Session对象
$this->show_message($msg , $code = 0, $data = array ( ))
显示信息
$this->show_success($message = 'success', $data = array ( ))
输出成功信息
$this->show_error()
self::execute($uri , $print = true, $use_route = true, $is_internal = true)
执行新控制
self::redirect($url , $code = 302)
页面跳转
self::header_cache($time = 86400)
页面输出header缓存

后台控制器、系统控制器、命令行控制器、RESTFul控制器

除了正常的控制器外,MyQEE还提供了3种控制器类型对于3种运行模式。每个请求只会存在一种模式,并且在系统执行时就会判断好,这样分开可以起到相互分离、方便开发维护、提升安全的作用,这也是MyQEE框架特有的。这几种模式的控制器除了目录不一样,其它所有的规则都是一样。

普通的控制器都是在 controllers 目录下的,这3类控制器并不在这个目录下,他们分别在:

  • 后台控制器 - controllers-admin 目录
  • 系统控制器 - controllers-system 目录
  • 命令行控制器 - controllers-shell 目录
  • RESTFul控制器 - controllers-rest 目录

后台控制器:当一个URL请求时,MyQEE首先会根据URL去判断是属于哪个项目,然后会再判断是否符合项目设置中 url_admin 的URL规则,如果符合,此时系统进入后台模式,所有加载的控制器都会在 controllers-admin 目录中寻找,而不会在 controllers 里寻找,所以不用担心自己的后台控制器被前端访问到。

例如根目录 config.php 中项目配置如下:

$config['projects'] = array
(
    $config['projects'] = array
    (
        'default' => array
        (
            'name'      => '默认项目',
            'dir'       => 'default',
            'isuse'     => true,
            'url'       => '/',
            'url_admin' => '/admin/',
            'url_rest'  => '/api/',
        ),
    ),
);

当请求 /test/abc/def/ 这样的URL时,会被认为是普通控制器,只会读到 controllers 目录中的控制器,而如果是 /admin/test/abc/def/ 则会被认为是后台控制器,因为它符合 url_admin 中设置的URL前缀,那么它会在 controllers-admin 目录中寻找控制器。

RESTFul:当你提供一些基于RESTFul协议接口时,可以将控制器存放在 controllers-rest 目录中,不必担心和其它控制器混淆,另外,MyQEE还内置了验证、加密等协议提供使用,同时,系统提供了对 PHPRPC 的支持。

命令行控制:当你在命令行里使用类似 php index.php default test abc 这样的命令执行时,系统会直接进入命令行模式,其中,default 表示项目,后面的参数和URL中相似,只不过通常,URL中是以/分开,而命令行里习惯用空格分开。

系统控制器:这个主要发生在存在2个或以上的服务器时使用MyQEE系统的同步类库 HttpCall 处理服务器同步时会进入的模式。如果你设置了文件同步模式为服务器同步,则在使用 File 类进行操作文件时,也会内部调用,具体可参看“系统内部调用”。

控制器的扩展

推荐控制器都扩展到 Controller 这个基类。当然也可以扩展另外一个控制器,比如 Controller_Test_Abc extend Controller_Test_Core,然后由 Controller_Test_Core extend 到 Controller 上。

后台控制器

如果您使用了MyQEE提供的 com.myqee.administration 类库的话,建议所有的后台控制器都扩展到 Controller_Admin,这个类后台类库的一个基类,它会扩展到 Controller 上,它提供了管理员登录判断等,具体请看第三方类库的MyQEE的Adminstration类库的控制器相关文档。

命令行控制 命令行类库内置了 Controller_Shell 基类,它已扩展到了 Controller 上,建议所有的命令行控制器都继承 Controller_Shell 上,查看 Controller_Shel API

进阶

可否存在横线或特殊字符的控制器?答案是肯定的,比如: my-test.controller.php 注意到这个控制器是用-隔开的,那么控制器应该这样写:

<?php
// my-test.controller.php
class Controller_My_Test extend Controller 
{
    public function hi()
    {
        echo 'hello';
    }
}

请特别注意,是 Controller_My_Test 不是 Controller_My-Test,因为PHP的类命名只允许字母数字和下划线,所以,所有特殊字符全部转换成下划线命名即可,例如 my-+:test.controller.php 控制器的类应该命名成 Controller_My___Test

当然,这样做势必会带来一些问题,需要你特别注意,例如使用auto_load将会出现问题,如果你在一个控制器里扩展了这样特殊的类,你必须先include这个文件,再去使用。可以通过 Core::load_controller('my-+:test');,比如:

<?php
// test.controller.php

// 先加载控制器
Core::load_controller('my-+:test');

class Controller_Test extend Controller_My___Test 
{
    public function hi()
    {
        echo 'hello';
    }
}

这样做就可以先include对应控制器文件,然后 Controller_Test 即可成功扩展到了 Controller_My___Test 上了。

另外一个问题也是显而易见的,就是可能会存在控制器名称冲突,例如有2个控制器 my_test.controller.phpmy-test.controller.php 他们的控制器名按规则都应该是 Controller_My_Test, 在URL访问时,/my_test/ 加载的是前者,/my-test/ 加载的是后者,一般情况下都不会有问题,但是你不能同时载入2个文件,否则程序会报错,这个是无法解决的,你只能自行避免。

V2不支持这种特殊字符的控制器




« 感谢
概览 »