视图单元

视图单元是小型微型控制器,可以调用视图逻辑并渲染模板。单元的概念借鉴自 Ruby 中的单元,它们在其中扮演着类似的角色和目的。

何时使用单元

单元非常适合构建需要与模型、视图逻辑和渲染逻辑交互的可重用页面组件。一个简单的例子是网上商店中的购物车,或者 CMS 中的数据驱动导航菜单。

创建单元

要创建单元,请在 **src/View/Cell** 中定义一个类,并在 **templates/cell/** 中定义一个模板。在这个例子中,我们将创建一个单元来显示用户通知收件箱中的消息数量。首先,创建类文件。其内容应如下所示

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell
{
    public function display()
    {
    }
}

将此文件保存到 **src/View/Cell/InboxCell.php** 中。正如您所见,与 CakePHP 中的其他类一样,单元也遵循一些约定

  • 单元位于 App\View\Cell 命名空间中。如果您在插件中创建单元,命名空间将是 PluginName\View\Cell

  • 类名应以 Cell 结尾。

  • 类应继承自 Cake\View\Cell

我们在单元中添加了一个空的 display() 方法;这是渲染单元时传统的默认方法。我们将在后面的文档中介绍如何使用其他方法。现在,创建文件 **templates/cell/Inbox/display.php**。这将是我们新单元的模板。

您可以使用 bake 快速生成此存根代码

bin/cake bake cell Inbox

将生成我们在上面创建的代码。

实现单元

假设我们正在开发一个允许用户互相发送消息的应用程序。我们有一个 Messages 模型,我们希望显示未读消息的数量,而不必污染 AppController。这是一个非常适合单元的用例。在我们刚刚创建的类中,添加以下内容

namespace App\View\Cell;

use Cake\View\Cell;

class InboxCell extends Cell
{
    public function display()
    {
        $unread = $this->fetchTable('Messages')->find('unread');
        $this->set('unread_count', $unread->count());
    }
}

因为单元使用 LocatorAwareTraitViewVarsTrait,所以它们的行为非常像控制器。我们可以像在控制器中一样使用 fetchTable()set() 方法。在我们的模板文件中,添加以下内容

<!-- templates/cell/Inbox/display.php -->
<div class="notification-icon">
    You have <?= $unread_count ?> unread messages.
</div>

注意

单元模板具有隔离的作用域,它不与用于渲染当前控制器操作或其他单元的模板和布局的 View 实例共享。因此,它们不知道操作模板/布局中进行的任何助手调用或设置的块,反之亦然。

加载单元

可以使用 cell() 方法从视图中加载单元,并且在两种情况下都以相同的方式工作

// Load an application cell
$cell = $this->cell('Inbox');

// Load a plugin cell
$cell = $this->cell('Messaging.Inbox');

以上将加载命名的单元类并执行 display() 方法。您可以使用以下方法执行其他方法

// Run the expanded() method on the Inbox cell
$cell = $this->cell('Inbox::expanded');

如果您需要控制器逻辑来决定在请求中加载哪些单元,您可以在控制器中使用 CellTrait 来启用那里的 cell() 方法

namespace App\Controller;

use App\Controller\AppController;
use Cake\View\CellTrait;

class DashboardsController extends AppController
{
    use CellTrait;

    // More code.
}

传递参数到单元

您通常希望对单元方法进行参数化,以使单元更加灵活。通过使用 cell() 的第二个和第三个参数,您可以将操作参数和附加选项作为索引数组传递给单元类

$cell = $this->cell('Inbox::recent', ['-3 days']);

上面将匹配以下函数签名

public function recent($since)
{
}

渲染单元

单元加载并执行后,您可能希望渲染它。渲染单元最简单的方法是将其回显

<?= $cell ?>

这将渲染与我们操作名称(如 **display.php**)的小写下划线版本相匹配的模板。

因为单元使用 View 来渲染模板,所以您可以在单元模板中加载其他单元(如果需要)。

注意

回显单元使用 PHP 的 __toString() 魔术方法,该方法可以防止 PHP 显示任何致命错误引发的文件名和行号。为了获得有意义的错误消息,建议使用 Cell::render() 方法,例如 <?= $cell->render() ?>

渲染备用模板

根据约定,单元会渲染与其正在执行的操作相匹配的模板。如果您需要渲染不同的视图模板,可以在渲染单元时指定要使用的模板

// Calling render() explicitly
echo $this->cell('Inbox::recent', ['-3 days'])->render('messages');

// Set template before echoing the cell.
$cell = $this->cell('Inbox');
$cell->viewBuilder()->setTemplate('messages');

echo $cell;

缓存单元输出

渲染单元时,您可能希望缓存渲染后的输出,如果内容不经常更改,或者为了帮助提高应用程序的性能。您可以在创建单元时定义 cache 选项以启用和配置缓存

// Cache using the default config and a generated key
$cell = $this->cell('Inbox', [], ['cache' => true]);

// Cache to a specific cache config and a generated key
$cell = $this->cell('Inbox', [], ['cache' => ['config' => 'cell_cache']]);

// Specify the key and config to use.
$cell = $this->cell('Inbox', [], [
    'cache' => ['config' => 'cell_cache', 'key' => 'inbox_' . $user->id]
]);

如果生成一个键,将使用单元类和小写下划线模板名称的版本。

注意

一个新的 View 实例用于渲染每个单元,这些新对象不与主模板/布局共享上下文。每个单元都是自包含的,并且只能访问作为参数传递给 View::cell() 调用的变量。

单元内分页数据

通过利用 ORM 的分页器类,可以创建渲染分页结果集的单元。对用户收藏的消息进行分页的示例可能如下所示

namespace App\View\Cell;

use Cake\View\Cell;
use Cake\Datasource\Paging\NumericPaginator;

class FavoritesCell extends Cell
{
    public function display($user)
    {
        // Create a paginator
        $paginator = new NumericPaginator();

        // Paginate the model
        $results = $paginator->paginate(
            $this->fetchTable('Messages'),
            $this->request->getQueryParams(),
            [
                // Use a parameterized custom finder.
                'finder' => ['favorites' => [$user]],

                // Use scoped query string parameters.
                'scope' => 'favorites',
            ]
        );

        // Set the paging params as a request attribute for use the PaginatorHelper
        $paging = $paginator->getPagingParams() + (array)$this->request->getAttribute('paging');
        $this->request = $this->request->withAttribute('paging', $paging);

        $this->set('favorites', $results);
    }
}

上面的单元将使用 作用域分页参数Messages 模型进行分页。

单元选项

单元可以声明构造函数选项,这些选项在创建单元对象时会转换为属性

namespace App\View\Cell;

use Cake\View\Cell;

class FavoritesCell extends Cell
{
    protected $_validCellOptions = ['limit'];

    protected $limit = 3;

    public function display($userId)
    {
        $result = $this->fetchTable('Users')->find('friends', ['for' => $userId])
            ->limit($this->limit)
            ->all();
        $this->set('favorites', $result);
    }
}

这里我们定义了一个 $limit 属性,并将 limit 作为单元选项添加。这将允许我们在创建单元时定义该选项

$cell = $this->cell('Favorites', [$user->id], ['limit' => 10])

单元选项在您希望将数据作为属性使用时非常方便,允许您覆盖默认值。

在单元内使用助手

单元具有自己的上下文和自己的 View 实例,但在您的 AppView::initialize() 函数中加载的助手仍然照常加载。

仅为特定单元加载特定助手可以通过以下示例实现

namespace App\View\Cell;

use Cake\View\Cell;

class FavoritesCell extends Cell
{
    public function initialize(): void
    {
        $this->viewBuilder()->addHelper('MyCustomHelper');
    }
}

单元事件

单元在单元操作前后触发以下事件

  • Cell.beforeAction

  • Cell.afterAction

在 5.1.0 版本中添加。