控制器

控制器是您应用程序的核心,因为它们决定了如何处理 HTTP 请求。

什么是控制器?

控制器只是一个处理 HTTP 请求的类文件。 URI 路由 将 URI 与控制器关联。它返回一个视图字符串或 Response 对象。

您创建的每个控制器都应该扩展 BaseController 类。此类提供可用于所有控制器的几个功能。

构造函数

CodeIgniter 的控制器有一个特殊的构造函数 initController()。它将在 PHP 的构造函数 __construct() 执行后由框架调用。

如果您想覆盖 initController(),请不要忘记在方法中添加 parent::initController($request, $response, $logger);

<?php

namespace App\Controllers;

use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
use Psr\Log\LoggerInterface;

class Product extends BaseController
{
    public function initController(
        RequestInterface $request,
        ResponseInterface $response,
        LoggerInterface $logger
    ) {
        parent::initController($request, $response, $logger);

        // Add your code here.
    }

    // ...
}

重要

您不能在构造函数中使用 return。因此 return redirect()->to('route'); 无法正常工作。

initController() 方法设置以下三个属性。

包含的属性

CodeIgniter 的控制器提供以下属性。

请求对象

应用程序的主 请求实例 始终作为类属性 $this->request 可用。

响应对象

应用程序的主 响应实例 始终作为类属性 $this->response 可用。

日志记录器对象

日志记录器 类的实例作为类属性 $this->logger 可用。

辅助函数

您可以将辅助函数文件数组定义为类属性。每当加载控制器时,这些辅助函数文件将自动加载到内存中,以便您可以在控制器中的任何地方使用它们的方法。

<?php

namespace App\Controllers;

class MyController extends BaseController
{
    protected $helpers = ['url', 'form'];
}

forceHTTPS

所有控制器中都提供了一种方便的方法,用于强制通过 HTTPS 访问方法。

<?php

if (! $this->request->isSecure()) {
    $this->forceHTTPS();
}

默认情况下,以及在支持 HTTP 严格传输安全标头的现代浏览器中,此调用应强制浏览器将非 HTTPS 调用转换为 HTTPS 调用,持续一年。您可以通过将持续时间(以秒为单位)作为第一个参数传递来修改此设置。

<?php

if (! $this->request->isSecure()) {
    $this->forceHTTPS(31536000); // one year
}

注意

始终可以使用许多 基于时间的常量,包括 YEARMONTH 等。

验证数据

$this->validateData()

4.2.0 版本新增。

为了简化数据检查,控制器还提供了便捷方法 validateData()

该方法接受 (1) 要验证的数据数组,(2) 规则数组,(3) 可选的自定义错误消息数组,用于在项目无效时显示,(4) 可选的数据库组。

验证库文档 中详细介绍了规则和消息数组格式,以及可用的规则。

<?php

namespace App\Controllers;

class StoreController extends BaseController
{
    public function product(int $id)
    {
        $data = [
            'id'   => $id,
            'name' => $this->request->getPost('name'),
        ];

        $rule = [
            'id'   => 'integer',
            'name' => 'required|max_length[255]',
        ];

        if (! $this->validateData($data, $rule)) {
            return view('store/product', [
                'errors' => $this->validator->getErrors(),
            ]);
        }

        // ...
    }
}

$this->validate()

重要

此方法仅出于向后兼容性而存在。不要在新项目中使用它。即使您已经在使用它,我们也建议您改用 validateData() 方法。

控制器还提供了便捷方法 validate()

警告

不要使用 validate(),而应使用 validateData() 来验证 POST 数据。 validate() 使用 $request->getVar(),它按顺序返回 $_GET$_POST$_COOKIE 数据(取决于 php.ini request-order)。较新的值会覆盖较旧的值。如果 POST 值与 cookie 的名称相同,则 POST 值可能会被 cookie 覆盖。

该方法在第一个参数中接受规则数组,在可选的第二个参数中接受自定义错误消息数组,用于在项目无效时显示。

在内部,它使用控制器的 $this->request 实例来获取要验证的数据。

验证库文档 中详细介绍了规则和消息数组格式,以及可用的规则。

<?php

namespace App\Controllers;

class UserController extends BaseController
{
    public function updateUser(int $userID)
    {
        if (! $this->validate([
            'email' => "required|is_unique[users.email,id,{$userID}]",
            'name'  => 'required|alpha_numeric_spaces',
        ])) {
            // The validation failed.
            return view('users/update', [
                'errors' => $this->validator->getErrors(),
            ]);
        }

        // The validation was successful.

        // Get the validated data.
        $validData = $this->validator->getValidated();

        // ...
    }
}

警告

当您使用 validate() 方法时,您应该使用 getValidated() 方法来获取验证后的数据。因为 validate() 方法在内部使用 Validation::withRequest() 方法,并且它验证来自 $request->getJSON()$request->getRawInput()$request->getVar() 的数据,攻击者可以更改要验证的数据。

注意

从 v4.4.0 版本开始,可以使用 $this->validator->getValidated() 方法。

如果您觉得在配置文件中保留规则更简单,您可以用 app/Config/Validation.php 中定义的组名替换 $rules 数组。

<?php

namespace App\Controllers;

class UserController extends BaseController
{
    public function updateUser(int $userID)
    {
        if (! $this->validate('userRules')) {
            // The validation failed.
            return view('users/update', [
                'errors' => $this->validator->getErrors(),
            ]);
        }

        // The validation was successful.

        // Get the validated data.
        $validData = $this->validator->getValidated();

        // ...
    }
}

注意

验证也可以在模型中自动处理,但有时在控制器中处理更容易。在哪里取决于您。

保护方法

在某些情况下,您可能希望将某些方法隐藏在公共访问之外。要实现这一点,只需将方法声明为 privateprotected。这将阻止它被 URL 请求服务。

例如,如果您要为 Helloworld 控制器定义一个这样的方法

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    protected function utility()
    {
        // some code
    }
}

并为该方法定义一个路由 (helloworld/utitilty)。然后尝试使用以下 URL 访问它将不起作用

example.com/index.php/helloworld/utility

自动路由也不起作用。

自动路由(改进)

4.2.0 版本新增。

从 v4.2.0 版本开始,引入了新的更安全的自动路由。

注意

如果您熟悉自动路由,该路由从 CodeIgniter 3 到 4.1.x 版本默认启用,您可以在 ChangeLog v4.2.0 中查看差异。

本节描述了新自动路由的功能。它会自动路由 HTTP 请求,并在没有路由定义的情况下执行相应的控制器方法。

从 v4.2.0 版本开始,自动路由默认禁用。要使用它,请参见 启用自动路由

考虑以下 URI

example.com/index.php/helloworld/

在上面的示例中,当启用自动路由时,CodeIgniter 将尝试查找名为 App\Controllers\Helloworld 的控制器并加载它。

注意

当控制器的简称与 URI 的第一个段匹配时,它将被加载。

让我们试试:Hello World!

让我们创建一个简单的控制器,以便您可以在实际操作中看到它。使用您的文本编辑器,创建一个名为 Helloworld.php 的文件,并将以下代码放入其中。您会注意到 Helloworld 控制器正在扩展 BaseController。如果您不需要 BaseController 的功能,也可以扩展 CodeIgniter\Controller

BaseController 为加载组件和执行所有控制器都需要执行的功能提供了一个方便的地方。您可以在任何新的控制器中扩展此类。

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    public function getIndex()
    {
        return 'Hello World!';
    }
}

然后将文件保存到您的 app/Controllers 目录中。

重要

该文件必须命名为 Helloworld.php,以大写字母 H 开头。当您使用自动路由时,控制器类名必须以大写字母开头,并且只有第一个字符可以是大写字母。

重要

将由自动路由(改进)执行的控制器方法需要 HTTP 动词(getpostput 等)前缀,例如 getIndex()postCreate()

现在使用类似于此的 URL 访问您的网站

example.com/index.php/helloworld

如果您操作正确,您应该看到

Hello World!

这是有效的

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    // ...
}

这是无效的

<?php

namespace App\Controllers;

class helloworld extends BaseController
{
    // ...
}

这是无效的

<?php

namespace App\Controllers;

class HelloWorld extends BaseController
{
    // ...
}

此外,始终确保您的控制器扩展父控制器类,以便它可以继承其所有方法。

注意

当在定义的路由中找不到匹配项时,系统将尝试通过将每个段与 app/Controllers 中的目录/文件匹配来将 URI 与控制器匹配。这就是为什么您的目录/文件必须以大写字母开头,其余部分必须是小写字母的原因。

如果您想要其他命名约定,您需要使用 定义的路由路由 手动定义它。以下是一个基于 PSR-4 自动加载器的示例

<?php

/*
 * Folder and file structure:
 * \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
 */

$routes->get('helloworld', '\App\Controllers\HelloWorld::index');

方法

方法可见性

当您定义一个可通过 HTTP 请求执行的方法时,该方法必须声明为 public

警告

出于安全原因,请确保将任何新的实用程序方法声明为 protectedprivate

默认方法

在上面的示例中,方法名称为 getIndex()。该方法(HTTP 动词 + Index())称为 **默认方法**,如果 URI 的 **第二个段** 为空,则会加载该方法。

普通方法

URI 的第二个段决定了控制器中哪个方法被调用。

让我们试一试。在您的控制器中添加一个新方法

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    public function getIndex()
    {
        return 'Hello World!';
    }

    public function getComment()
    {
        return 'I am not flat!';
    }
}

现在加载以下 URL 以查看 getComment() 方法

example.com/index.php/helloworld/comment/

您应该会看到您的新消息。

将 URI 段传递给您的方法

如果您的 URI 包含两个以上的段,它们将作为参数传递给您的方法。

例如,假设您有一个像这样的 URI

example.com/index.php/products/shoes/sandals/123

您的方法将被传递 URI 段 3 和 4 ('sandals''123')

<?php

namespace App\Controllers;

class Products extends BaseController
{
    public function getShoes($sandals, $id)
    {
        return $sandals . $id;
    }
}

默认控制器

默认控制器是一个特殊的控制器,当 URI 以目录名结尾或 URI 不存在时使用,例如,当仅请求您的网站根 URL 时。

定义默认控制器

让我们用 Helloworld 控制器试一试。

要指定默认控制器,请打开您的 **app/Config/Routing.php** 文件并设置此属性

public string $defaultController = 'Helloworld';

其中 Helloworld 是您要使用的控制器类的名称。

并注释掉 **app/Config/Routes.php** 中的这一行。

$routes->get('/', 'Home::index');

如果您现在浏览您的网站,不指定任何 URI 段,您将看到“Hello World”消息。

重要

当您使用自动路由(改进版)时,您必须删除 $routes->get('/', 'Home::index'); 这行。因为定义的路由优先于自动路由,并且出于安全原因,自动路由(改进版)拒绝访问定义的路由中定义的控制器。

有关更多信息,请参阅 配置选项 文档。

默认方法回退

版本 4.4.0 中新增。

如果与方法名称的 URI 段相对应的控制器方法不存在,并且定义了默认方法,则将剩余的 URI 段传递给默认方法以执行。

<?php

namespace App\Controllers;

class Product extends BaseController
{
    public function getIndex($id = null, $action = '')
    {
        // ...
    }
}

加载以下 URL

example.com/index.php/product/15/edit

该方法将传递 URI 段 2 和 3 ('15''edit')

重要

如果 URI 中的参数多于方法参数,自动路由(改进版)不会执行该方法,并导致 404 未找到。

回退到默认控制器

如果与控制器名称的 URI 段相对应的控制器不存在,并且默认控制器(默认情况下为 Home)存在于目录中,则将剩余的 URI 段传递给默认控制器的默认方法。

例如,当您在 **app/Controllers/News** 目录中具有以下默认控制器 Home

<?php

namespace App\Controllers\News;

use App\Controllers\BaseController;

class Home extends BaseController
{
    public function getIndex($id = null)
    {
        // ...
    }
}

加载以下 URL

example.com/index.php/news/101

将找到 News\Home 控制器和默认的 getIndex() 方法。因此,将向默认方法传递 URI 段 2 ('101')

注意

如果存在 App\Controllers\News 控制器,它将优先。URI 段将按顺序搜索,找到的第一个控制器将被使用。

注意

如果 URI 中的参数多于方法参数,自动路由(改进版)不会执行该方法,并导致 404 未找到。

将您的控制器组织到子目录中

如果您正在构建一个大型应用程序,您可能希望将您的控制器按层次结构组织或结构化到子目录中。CodeIgniter 允许您这样做。

只需在主 **app/Controllers** 下创建子目录,并将您的控制器类放在其中。

重要

目录名称**必须**以大写字母开头,并且**只有**第一个字符可以是大写。

使用此功能时,URI 的第一个部分必须指定目录。例如,假设您有一个位于此处的控制器

app/Controllers/Products/Shoes.php

要调用上述控制器,您的 URI 将类似于以下内容

example.com/index.php/products/shoes/show/123

注意

您不能在**app/Controllers**和**public**中拥有相同名称的目录。这是因为如果存在目录,Web 服务器将搜索它,并且它不会被路由到 CodeIgniter。

您的每个子目录都可以包含一个默认控制器,如果 URL **仅**包含子目录,则将调用该控制器。只需在其中放置一个与您在**app/Config/Routing.php**文件中指定的默认控制器名称匹配的控制器即可。

CodeIgniter 还允许您使用其定义路由路由映射您的 URI。

自动路由(旧版)

重要

此功能仅出于向后兼容性而存在。不要在新项目中使用它。即使您已经在使用它,我们建议您使用自动路由(改进版)

本节介绍来自 CodeIgniter 3 的自动路由(旧版)的功能,它是一个路由系统。它会自动路由 HTTP 请求,并在没有路由定义的情况下执行相应的控制器方法。自动路由默认情况下是禁用的。

警告

为了防止配置错误和编码错误,我们建议您不要使用自动路由(旧版)。很容易创建容易受到攻击的应用程序,其中控制器过滤器或 CSRF 保护会被绕过。

重要

自动路由(旧版)将使用**任何**HTTP 方法的 HTTP 请求路由到控制器方法。

考虑以下 URI

example.com/index.php/helloworld/

在上面的示例中,CodeIgniter 将尝试找到名为**Helloworld.php**的控制器并加载它。

注意

当控制器的简称与 URI 的第一个段匹配时,它将被加载。

让我们试试:Hello World!(旧版)

让我们创建一个简单的控制器,以便您可以在实际操作中看到它。使用您的文本编辑器,创建一个名为 Helloworld.php 的文件,并将以下代码放入其中。您会注意到 Helloworld 控制器正在扩展 BaseController。如果您不需要 BaseController 的功能,也可以扩展 CodeIgniter\Controller

BaseController 为加载组件和执行所有控制器都需要执行的功能提供了一个方便的地方。您可以在任何新的控制器中扩展此类。

出于安全原因,请确保将任何新的实用程序方法声明为protectedprivate

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    public function index()
    {
        return 'Hello World!';
    }
}

然后将文件保存到您的 app/Controllers 目录中。

重要

该文件必须命名为 Helloworld.php,以大写字母 H 开头。当您使用自动路由时,控制器类名必须以大写字母开头,并且只有第一个字符可以是大写字母。

现在使用类似于此的 URL 访问您的网站

example.com/index.php/helloworld

如果您操作正确,您应该看到

Hello World!

这是有效的

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    // ...
}

这是无效的

<?php

namespace App\Controllers;

class helloworld extends BaseController
{
    // ...
}

这是无效的

<?php

namespace App\Controllers;

class HelloWorld extends BaseController
{
    // ...
}

此外,始终确保您的控制器扩展父控制器类,以便它可以继承其所有方法。

注意

当在定义的路由中找不到匹配项时,系统将尝试通过将每个段与 app/Controllers 中的目录/文件匹配来将 URI 与控制器匹配。这就是为什么您的目录/文件必须以大写字母开头,其余部分必须是小写字母的原因。

如果您想要其他命名约定,您需要使用 定义的路由路由 手动定义它。以下是一个基于 PSR-4 自动加载器的示例

<?php

/*
 * Folder and file structure:
 * \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
 */

$routes->get('helloworld', '\App\Controllers\HelloWorld::index');

方法(旧版)

在上面的示例中,方法名称为index()。如果 URI 的**第二个部分**为空,则默认情况下始终加载index()方法。另一种显示“Hello World”消息的方法是

example.com/index.php/helloworld/index/

URI 的第二个段决定了控制器中哪个方法被调用。

让我们试一试。在您的控制器中添加一个新方法

<?php

namespace App\Controllers;

class Helloworld extends BaseController
{
    public function index()
    {
        return 'Hello World!';
    }

    public function comment()
    {
        return 'I am not flat!';
    }
}

现在加载以下 URL 以查看注释方法

example.com/index.php/helloworld/comment/

您应该会看到您的新消息。

将 URI 段传递给您的方法(旧版)

如果您的 URI 包含两个以上的段,它们将作为参数传递给您的方法。

例如,假设您有一个像这样的 URI

example.com/index.php/products/shoes/sandals/123

您的方法将被传递 URI 段 3 和 4 ('sandals''123')

<?php

namespace App\Controllers;

class Products extends BaseController
{
    public function shoes($sandals, $id)
    {
        return $sandals . $id;
    }
}

默认控制器(旧版)

默认控制器是一个特殊控制器,当 URI 以目录名称结尾或 URI 不存在时使用,例如,当仅请求您的网站根 URL 时。

定义默认控制器(旧版)

让我们用 Helloworld 控制器试一试。

要指定默认控制器,请打开您的 **app/Config/Routing.php** 文件并设置此属性

public string $defaultController = 'Helloworld';

其中 Helloworld 是您要使用的控制器类的名称。

并注释掉 **app/Config/Routes.php** 中的这一行。

$routes->get('/', 'Home::index');

如果您现在浏览您的网站,不指定任何 URI 段,您将看到“Hello World”消息。

注意

代码 $routes->get('/', 'Home::index'); 是一个优化,您将在“真实世界”应用程序中使用它。但为了演示目的,我们不想使用该功能。 $routes->get()URI 路由 中解释。

有关更多信息,请参阅 配置选项(旧版) 文档。

将控制器组织到子目录中(旧版)

如果您正在构建一个大型应用程序,您可能希望将您的控制器按层次结构组织或结构化到子目录中。CodeIgniter 允许您这样做。

只需在主 **app/Controllers** 下创建子目录,并将您的控制器类放在其中。

重要

目录名称**必须**以大写字母开头,并且**只有**第一个字符可以是大写。

使用此功能时,URI 的第一个部分必须指定目录。例如,假设您有一个位于此处的控制器

app/Controllers/Products/Shoes.php

要调用上述控制器,您的 URI 将类似于以下内容

example.com/index.php/products/shoes/show/123

注意

您不能在 app/Controllerspublic/ 中拥有相同名称的目录。这是因为如果存在目录,Web 服务器将搜索它,并且它不会被路由到 CodeIgniter。

您的每个子目录都可以包含一个默认控制器,如果 URL **仅**包含子目录,则将调用该控制器。只需在其中放置一个与您在**app/Config/Routing.php**文件中指定的默认控制器名称匹配的控制器即可。

CodeIgniter 还允许您使用其定义路由路由映射您的 URI。

重新映射方法调用

注意

自动路由(改进) 故意不支持此功能。

如上所述,URI 的第二个段通常决定控制器中哪个方法被调用。CodeIgniter 允许您通过使用 _remap() 方法来覆盖此行为。

<?php

namespace App\Controllers;

class Products extends BaseController
{
    public function _remap()
    {
        // Some code here...
    }
}

重要

如果您的控制器包含名为 _remap() 的方法,它将始终被调用,无论您的 URI 包含什么。它覆盖了 URI 决定调用哪个方法的正常行为,允许您定义自己的方法路由规则。

被覆盖的方法调用(通常是 URI 的第二个段)将作为参数传递给 _remap() 方法。

<?php

namespace App\Controllers;

class Products extends BaseController
{
    public function _remap($method)
    {
        if ($method === 'some_method') {
            return $this->{$method}();
        }

        return $this->default_method();
    }
}

方法名后的任何额外段落都会传递到 _remap() 中。这些参数可以传递给方法以模拟 CodeIgniter 的默认行为。

示例

<?php

namespace App\Controllers;

class Products extends BaseController
{
    public function _remap($method, ...$params)
    {
        $method = 'process_' . $method;

        if (method_exists($this, $method)) {
            return $this->{$method}(...$params);
        }

        throw \CodeIgniter\Exceptions\PageNotFoundException::forPageNotFound();
    }
}

扩展控制器

如果您想扩展控制器,请参阅 扩展控制器.

就是这样!

简而言之,这就是关于控制器的所有内容。