控制器
控制器是你应用程序的核心,因为它们决定了如何处理 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 严格传输安全标头的现代浏览器中,此调用应该强制浏览器将非 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()
。
警告
不要使用 validate()
,而要使用 validateData()
来仅验证 POST 数据。validate()
使用 $request->getVar()
,它会按顺序返回 $_GET
、$_POST
或 $_COOKIE
数据(取决于 php.ini request-order)。较新的值会覆盖较旧的值。如果 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() 的数据,攻击者可能会更改验证的数据。
备注
$this->validator->getValidated() 方法从 v4.4.0 开始可用。
如果你发现将规则保存在配置文件中更简单,你可以用在 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();
// ...
}
}
备注
验证也可以在模型中自动处理,但有时在控制器中处理更容易。这由你决定。
保护方法
在某些情况下,你可能希望某些方法不被公开访问。要实现这一点,只需将方法声明为 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 来查看评论方法:
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();
}
}
扩展控制器
如果你想扩展控制器,请参见 扩展控制器。
就是这样!
简而言之,这就是关于控制器你需要知道的全部内容。