组件

组件是控制器之间共享的逻辑包。CakePHP 附带了一套很棒的核心组件,您可以使用它们来帮助完成各种常见任务。您也可以创建自己的组件。如果您发现自己想在控制器之间复制和粘贴内容,您应该考虑创建自己的组件来包含该功能。创建组件可以使控制器代码保持整洁,并允许您在不同的控制器之间重用代码。

有关 CakePHP 中包含的组件的更多信息,请查看每个组件的章节

配置组件

许多核心组件都需要配置。一个例子是 FormProtection。这些组件以及一般组件的配置通常通过在控制器的 initialize() 方法中或通过 $components 数组中的 loadComponent() 来完成。

class PostsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('FormProtection', [
            'unlockedActions' => ['index'],
        ]);
        $this->loadComponent('Flash');
    }
}

您可以在运行时使用 setConfig() 方法配置组件。通常,这在控制器的 beforeFilter() 方法中完成。以上也可以表示为

public function beforeFilter(EventInterface $event)
{
    $this->FormProtection->setConfig('unlockedActions', ['index']);
}

与助手类似,组件实现 getConfig()setConfig() 方法来读取和写入配置数据。

// Read config data.
$this->FormProtection->getConfig('unlockedActions');

// Set config
$this->Flash->setConfig('key', 'myFlash');

与助手一样,组件会自动将它们的 $_defaultConfig 属性与构造函数配置合并,以创建 $_config 属性,该属性可以使用 getConfig()setConfig() 访问。

组件别名

一个常用的设置是 className 选项,它允许您为组件创建别名。当您想要用自定义实现替换 $this->Flash 或其他常见的组件引用时,此功能很有用。

// src/Controller/PostsController.php
class PostsController extends AppController
{
    public function initialize(): void
    {
        $this->loadComponent('Flash', [
            'className' => 'MyFlash',
        ]);
    }
}

// src/Controller/Component/MyFlashComponent.php
use Cake\Controller\Component\FlashComponent;

class MyFlashComponent extends FlashComponent
{
    // Add your code to override the core FlashComponent
}

以上将控制器中的 $this->Flash 创建 MyFlashComponent别名

注意

为组件创建别名会替换该实例在组件使用的任何地方,包括其他组件中。

动态加载组件

您可能不需要在每个控制器操作中都提供所有组件。在这种情况下,您可以使用控制器中的 loadComponent() 方法在运行时加载组件。

// In a controller action
$this->loadComponent('OneTimer');
$time = $this->OneTimer->getTime();

注意

请记住,动态加载的组件不会调用错过的回调。如果您依赖于 beforeFilterstartup 回调被调用,您可能需要根据加载组件的时间手动调用它们。

使用组件

在您将一些组件包含在控制器中之后,使用它们非常简单。您使用的每个组件都作为控制器上的属性公开。如果您已在控制器中加载了 Cake\Controller\Component\FlashComponent,您可以像这样访问它。

class PostsController extends AppController
{
    public function initialize(): void
    {
        parent::initialize();
        $this->loadComponent('Flash');
    }

    public function delete()
    {
        if ($this->Post->delete($this->request->getData('Post.id')) {
            $this->Flash->success('Post deleted.');

            return $this->redirect(['action' => 'index']);
        }
    }

注意

由于模型和组件都作为属性添加到控制器,因此它们共享相同的“命名空间”。请确保不要为组件和模型指定相同的名称。

Changed in version 5.1.0: 组件能够使用 依赖注入 来接收服务。

创建组件

假设我们的应用程序需要在应用程序的许多不同部分执行一个复杂的数学运算。我们可以创建一个组件来容纳这个共享逻辑,以便在许多不同的控制器中使用。

第一步是创建一个新的组件文件和类。在 src/Controller/Component/MathComponent.php 中创建文件。组件的基本结构看起来像这样

namespace App\Controller\Component;

use Cake\Controller\Component;

class MathComponent extends Component
{
    public function doComplexOperation($amount1, $amount2)
    {
        return $amount1 + $amount2;
    }
}

注意

所有组件都必须扩展 Cake\Controller\Component。如果不这样做,将触发异常。

组件可以使用 依赖注入 来接收作为构造函数参数的服务。

namespace App\Controller\Component;

use Cake\Controller\Component;
use App\Service\UserService;

class SsoComponent extends Component
{
    public function __construct(
        ComponentRegistry $registry,
        array $config = [],
        UserService $users
    ) {
        parent::__construct($registry, $config);
        $this->users = $users;
    }
}

在控制器中包含组件

组件完成后,我们可以在应用程序的控制器中使用它,方法是在控制器的 initialize() 方法中加载它。加载后,控制器将获得一个以组件命名的新属性,通过该属性我们可以访问它的实例。

// In a controller
// Make the new component available at $this->Math,
// as well as the standard $this->Flash
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Math');
    $this->loadComponent('Flash');
}

在控制器中包含组件时,您还可以声明一组将传递给组件构造函数的参数。这些参数随后可以由组件处理。

// In your controller.
public function initialize(): void
{
    parent::initialize();
    $this->loadComponent('Math', [
        'precision' => 2,
        'randomGenerator' => 'srand',
    ]);
    $this->loadComponent('Flash');
}

以上将包含精度和随机生成器的数组传递给 MathComponent::initialize()$config 参数中。

在组件中使用其他组件

有时您的一个组件可能需要使用另一个组件。您可以通过将它们添加到 $components 属性中来加载其他组件。

// src/Controller/Component/CustomComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class CustomComponent extends Component
{
    // The other component your component uses
    protected array $components = ['Existing'];

    // Execute any other additional setup for your component.
    public function initialize(array $config): void
    {
        $this->Existing->foo();
    }

    public function bar()
    {
        // ...
    }
}

// src/Controller/Component/ExistingComponent.php
namespace App\Controller\Component;

use Cake\Controller\Component;

class ExistingComponent extends Component
{
    public function foo()
    {
        // ...
    }
}

注意

与包含在控制器中的组件相反,组件的组件不会触发任何回调。

访问组件的控制器

在组件内部,您可以通过注册表访问当前控制器。

$controller = $this->getController();

组件回调

组件还提供了一些请求生命周期回调,允许它们扩展请求周期。

beforeFilter(EventInterface $event)

在控制器的 beforeFilter() 方法之前调用,但在控制器的 initialize() 方法之后调用。

startup(EventInterface $event)

在控制器的 beforeFilter() 方法之后调用,但在控制器执行当前操作处理程序之前调用。

beforeRender(EventInterface $event)

在控制器执行请求的操作的逻辑之后调用,但在控制器渲染视图和布局之前调用。

afterFilter(EventInterface $event)

在将输出发送到浏览器之前,在 Controller.shutdown 事件期间被调用。

beforeRedirect(EventInterface $event, $url, Response $response)

当控制器调用重定向方法但尚未执行任何其他操作时被调用。如果此方法返回 false,则控制器将不会继续重定向请求。$url 和 $response 参数允许您检查和修改位置或响应中的任何其他标头。

在组件事件中使用重定向

要在组件回调方法中进行重定向,您可以使用以下方法

public function beforeFilter(EventInterface $event)
{
    $event->stopPropagation();

    return $this->getController()->redirect('/');
}

通过停止事件,您让 CakePHP 知道您不希望任何其他组件回调运行,并且控制器不应进一步处理操作。从 4.1.0 开始,您可以引发一个 RedirectException 来指示重定向

use Cake\Http\Exception\RedirectException;
use Cake\Routing\Router;

public function beforeFilter(EventInterface $event)
{
    throw new RedirectException(Router::url('/'))
}

引发异常将停止所有其他事件侦听器并创建一个新的响应,该响应不保留或继承任何当前响应的标头。在引发 RedirectException 时,您可以包含其他标头

throw new RedirectException(Router::url('/'), 302, [
    'Header-Key' => 'value',
]);