4.2. Controller

4.2.1. Controller基础
4.2.2. 获取传值
4.2.3. 对象容器
4.2.4. init和destroy
4.2.5. 使用视图方案
4.2.6. 路由

4.2.1. Controller基础

Controller(控制器)负责将数据(模型)从不同的渠道中取出(可能是一个关系数据库,也可能是一个文本文件),然后将其交给View层处理。 上面的HelloWorldController就是一个简单的控制器,而appIndex和appTest1两个分别定义了控制的两个动作(action):index和test1。你可以通过给HelloWorld添加更多的appXxx方法来定义更多的动作。

控制器类放在ipr/app/controllers目录下,而且类名和文件名一定要一致,比如HelloWorldController,就需要放在HelloWorldController.php中,目录结构如下

/web
    /test
        /ipa
            /app
                /controllers
                    IndexController.php
                    HelloWorldController.php
                    ...
                /models
            /config
        /index.php

控制器中的动作可以通过传入参数action来调用,比如

http://localhost/test/index.php/helloWorld/index // 调用index动作
http://localhost/test/index.php/helloWorld/test1 // 调用test1动作

如果请求的动作在类中没有定义会怎么样?比如调用

http://localhost/test/index.php/helloWorld/myaction

显然HelloWorldController类里没有定义appMyaction方法,那么I-F就会自动发现这个错误,并抛出一个很有意义的异常信息:

I-Framework IException Message: fatal error: can't find action 'myaction' in 'HelloWorldController'
Script: code 0 on line 106 in file F:\publish\blog\test\I-F\util\core\ILogger.php
URL: /test/index.php/helloWorld/myaction
#0 F:\publish\blog\test\I-F\IApplication.php(293): ILogger::fatal('can't find acti...')
#1 F:\publish\blog\test\I-F\util\core\IRoute.php(220): IApplication->run()
#2 F:\publish\blog\test\I-F\IApplication.php(900): IRoute->run()
#3 F:\publish\blog\test\index.php(11): IApplication::startup()
#4 {main}

通过这条异常信息,我们很容易知道HelloWorldController里没有定义myaction这个动作.

另外需要注意的是,所有动作对应的方法appXxx不能有必需的参数,比如在控制器类中加入方法

public function appTest($arg) {
    echo 
"i am test";
}

就是一个错误的方法,当调用test动作时,I-F将指出该方法不能带有参数:

I-Framework IException Message: fatal error: method 'HelloWorldController::apptest' can't has arguments
Script: code 0 on line 106 in file F:\publish\blog\test\I-F\util\core\ILogger.php
URL: /test/index.php/helloWorld/test
#0 F:\publish\blog\test\I-F\IApplication.php(247): ILogger::fatal('method 'HelloWo...')
#1 F:\publish\blog\test\I-F\util\core\IRoute.php(220): IApplication->run()
#2 F:\publish\blog\test\I-F\IApplication.php(900): IRoute->run()
#3 F:\publish\blog\test\index.php(11): IApplication::startup()
#4 {main}

4.2.2. 获取传值

在控制器里使用in对象访问请求的数据。对于 /helloWorld/index?id=1&title=mysql 来说,可以使用

class HelloWorldController extends IApplication {
    public function 
appIndex() {
       
$id $this->in->id;
       
$title $this->in->title;
    }
}

来获取id和title的值。

从0.2.1起,可以使用in的getInt/getStr方法自动对输入进行过滤:

$id $this->in->getInt("id");//将id的值转成整型
$title $this->in->getStr("title");//去除title两边空格并返回

使用in对象可以获取以下三种方法传递到控制器里的值:

  1. GET方法,比如/index/query?id=1&title=mysql中的id和title两个值

  2. POST方法,比如

    <form action="/helloWorld/index" method="post">
        <input type="text" name="id" value=""/>
        <input type="text" name="title" value=""/>
        <input type="submit" value="submit!"/>
    </form>

    表单中的id和title两个值

  3. 通过命令行方法传递的值。比如

    php -f index.php -- -id=1 -title=mysql
    php index.php -id=1 -title=mysql

    同样能使用$this->in->id和$this->in->title获取id和title的值。

4.2.3. 对象容器

在控制器里可以随意创建如下形式的任何空的对象:

class HelloWorldController extends IApplication {
        public function 
appEmpty() {
    
    $this->x->y->z 
= array("a""b""c""d""e");
        
print_r($this->x->y->z);
    }
}

输出的结果将是:

Array
(
    [
0] => a
    
[1] => b
    
[2] => c
    
[3] => d
    
[4] => e
)

你甚至可以使用数组的形式创建新的对象:

public function appEmpty() {
    
$this->x["y"]["z"
= array("a""b""c""d""e");
    
print_r($this->x["y"]["z"]);
}

这是一个非常好的特性,免去了你得手工创建各级对象并进行初始化的烦恼。

4.2.4. init和destroy

如果Controller中定义了init和destroy方法,则会在调用任何动作之前调用init方法,在调用动作之后再调用destroy方法。同动作方法定义一样,init和destroy不能有必需的参数。

例 4.1. init/destroy

class HelloWorldController extends IApplication {
    public function 
init() {
        echo 
"调用前init<br/>";
    }
    
    public function 
appIndex() {
        echo 
"调用 Hello,World<br/>";
    }
    
    public function 
destroy() {
        echo 
"调用后destroy<br/>";
    }
}

调用 /helloWorld/indx 时将输出

调用前init
调用 Hello,World
调用后destroy

init这个特性使得我们可以所有动作之前

  1. 检查用户输入

  2. 检查用户是否登录/是否有权限执行当前动作

  3. 其他需要全局控制的事情


destroy方法里可以使用 $this->getActionReturn() 方法得到动作的返回,从而可以根据此返回值进行相应的操作,比如

  1. 根据返回对应的模板

  2. 决定是显示还是跳转

  3. 记录执行日志

  4. 销毁使用的资源

总之init和destroy可以实现我们对控制器和动作的更为强大的控制。

4.2.4.1. 示例:实现控制器的登录控制

现在举一个需要登录才能访问控制器的功能。假设我们有UserController和ProfileController需要登录才能访问,又假设我们已经写好checkLogin()函数来检查登录了,最直接的方法是:

class UserController extends IApplication {
    
public function init() {
        
checkLogin();
    }

    
    
public function appIndex() {
        
    }
}

class 
ProfileController extends IApplication {
    public function init() {
        
checkLogin();
    }

    
    public function appIndex() {
        
    }
}

当访问/user/index/profile/index时,未登录的用户都会被init方法所拦截,现在既然两个控制器都有一模一样的init方法,我们可以将其挪到他们的父类当中去,这个父类不是IApplication,而是我们新写的一个LoginController:

class LoginController extends IApplication {
    public function 
init() {
        
checkLogin();
    }
}

然后使UserController和ProfileController继承该类,即可实现控制登录的功能。

class UserController extends LoginController {
    public function 
appIndex() {
        
    }
}

class 
ProfileController extends LoginController {
    public function 
appIndex() {
        
    }
}

这样一来,代码就会更加容易维护,而且添加更多的需要登录的控制器时,只需将该类继承LoginController即可。

4.2.5. 使用视图方案

在控制器里可以使用多种视图方案,让模型数据通过不同的方式显示,目前可选的有

  • IModelAndView - 模型和视图类结合

  • IModelAndTemplate - 模型和mint模板引擎结合

  • IModelAndSmarty - 模型和smarty模板引擎结合

  • IModelAndPHP - 模型使用PHP代码输出

  • IModelAndXml - 模型以XML格式输出

  • IJSON - 模型以JSON格式输出

  • IJSONResponse - 模型以JSON格式输出,并保证对象中含有code,message,data,exception等响应标准信息

具体的使用方法请见“View”章。

4.2.6. 路由

可以通过系统配置路由使控制器支持更多的功能,也可以美化我们的URL,具体请参见[MVC配置]

4.2.6.1. 带有包的控制器访问方法

在I-F中,带有包的控制器可以使用点(.)符号连接,如果一个控制器所在如下

/controlers
    /a/b/c/HelloWorldController

那么可以通过

http://localhost/test/index.php/a.b.c.helloWorld/index

来访问。

还可以直接通过目录的形式的来访问:

http://localhost/test/index.php/a/b/c/helloWorld/index