视图单元是小型微型控制器,可以调用视图逻辑并渲染模板。单元的概念借鉴自 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());
}
}
因为单元使用 LocatorAwareTrait
和 ViewVarsTrait
,所以它们的行为非常像控制器。我们可以像在控制器中一样使用 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 版本中添加。