控制器
控制器是应用程序的核心,它们决定了如何处理 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 的控制器提供了这些属性。
Request 对象
应用程序的主 Request 实例 始终作为类属性 $this->request 可用。
Response 对象
应用程序的主 Response 实例 始终作为类属性 $this->response 可用。
Logger 对象
Logger 类的实例作为类属性 $this->logger 可用。
辅助函数
可以将辅助函数文件数组定义为类属性。每当加载控制器时,这些辅助函数文件会自动加载到内存中,以便可以在控制器内的任何地方使用它们的方法:
<?php
namespace App\Controllers;
class MyController extends BaseController
{
protected $helpers = ['url', 'form'];
}
forceHTTPS
所有控制器中都提供了一个方便的方法,用于强制通过 HTTPS 访问方法:
<?php
if (! $this->request->isSecure()) {
$this->forceHTTPS();
}
默认情况下,在支持 HTTP 严格传输安全(HSTS)标头的现代浏览器中,此调用应强制浏览器将非 HTTPS 调用转换为 HTTPS 调用,持续一年。可以通过将持续时间(以秒为单位)作为第一个参数传递来修改这一点:
<?php
if (! $this->request->isSecure()) {
$this->forceHTTPS(31536000); // one year
}
备注
有许多 基于时间的常量 始终可供使用,包括 YEAR、MONTH 等。
验证数据
$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()。
警告
应使用 validateData() 而不是 validate() 来仅验证 POST 数据。validate() 使用 $request->getVar() 返回 $_GET、$_POST 或 $_COOKIE 数据(按该顺序,取决于 php.ini request-order)。较新的值会覆盖较旧的值。如果 Cookie 具有相同的名称,POST 值可能会被覆盖。
该方法在第一个参数中接受规则数组,在可选的第二个参数中接受自定义错误消息数组(如果项目无效时显示)。
在内部,这使用控制器的 $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() 的数据,攻击者可能会更改验证的数据。
备注
$this->validator->getValidated() 方法从 v4.4.0 开始可用。
如果发现将规则保留在配置文件中更简单,可以将 $rules 数组替换为 app/Config/Validation.php 中定义的组名:
<?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();
// ...
}
}
备注
验证也可以在模型中自动处理,但有时在控制器中更容易。具体位置由你决定。
保护方法
在某些情况下,你可能希望某些方法对公共访问隐藏。要实现这一点,只需将方法声明为 private 或 protected。这样可以防止通过 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 版本加入.
自动路由(改进版)是一个新的、更安全的自动路由系统。
详见 自动路由(改进版)。
自动路由(传统版)
重要
此功能仅为向后兼容而存在。不要在新项目中使用它。即使已经在使用,我们也建议使用 自动路由(改进版) 代替。
本节描述了自动路由(传统版)的功能,这是来自 CodeIgniter 3 的路由系统。它自动路由 HTTP 请求,并在没有路由定义的情况下执行相应的控制器方法。自动路由默认是禁用的。
警告
为了防止错误配置和错误编码,我们建议不要使用自动路由(传统版)。很容易创建易受攻击的应用程序,其中控制器过滤器或 CSRF 保护被绕过。
重要
自动路由(传统版)使用 任何 HTTP 方法将 HTTP 请求路由到控制器方法。
重要
从 v4.5.0 开始,如果自动路由(传统版)找不到控制器,它将在控制器过滤器执行之前抛出 PageNotFoundException 异常。
考虑以下 URI:
example.com/index.php/helloworld/
在上面的例子中,CodeIgniter 将尝试查找名为 Helloworld.php 的控制器并加载它。
备注
当控制器的短名称与 URI 的第一分段匹配时,它将被加载。
让我们试试:Hello World!(传统版)
让我们创建一个简单的控制器,这样你就可以看到它的实际效果。使用文本编辑器,创建一个名为 Helloworld.php 的文件,并将以下代码放入其中。你会注意到 Helloworld 控制器继承了 BaseController。如果不需要 BaseController 的功能,也可以继承 CodeIgniter\Controller。
BaseController 为加载组件和执行所有控制器都需要的功能提供了一个方便的地方。可以在任何新控制器中继承此类。
出于安全原因,请确保将任何新的实用方法声明为 protected 或 private:
<?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 以查看 comment 方法:
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/Controllers 和 public/ 中使用相同名称的目录。这是因为如果存在目录,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();
}
}
扩展控制器
如果要扩展控制器,请参阅 扩展控制器。
就是这样!
简而言之,这就是关于控制器需要了解的全部内容。