视图是 MVC 中的 V。视图负责生成请求所需的特定输出。通常,这以 HTML、XML 或 JSON 的形式出现,但流式文件和创建用户可以下载的 PDF 也是视图层面的职责。
CakePHP 附带了一些内置的视图类,用于处理最常见的渲染场景。
要创建 XML 或 JSON web 服务,您可以使用 JSON 和 XML 视图。
要提供受保护的文件或动态生成的文件,您可以使用 发送文件。
要创建多个主题视图,您可以使用 主题。
AppView
是您的应用程序的默认视图类。 AppView
本身扩展了 CakePHP 中包含的 Cake\View\View
类,并在 src/View/AppView.php 中定义如下
<?php
namespace App\View;
use Cake\View\View;
class AppView extends View
{
}
您可以使用 AppView
加载将在应用程序中呈现的每个视图中使用的帮助程序。CakePHP 提供了一个 initialize()
方法,在视图构造函数的末尾调用此方法用于此类用途
<?php
namespace App\View;
use Cake\View\View;
class AppView extends View
{
public function initialize(): void
{
// Always enable the MyUtils Helper
$this->addHelper('MyUtils');
}
}
CakePHP 的视图层是您与用户交流的方式。大多数时候,您的视图将渲染 HTML/XHTML 文档到浏览器,但您可能还需要通过 JSON 回复远程应用程序,或为用户输出 CSV 文件。
CakePHP 模板文件是常规的 PHP 文件,并利用 替代 PHP 语法 用于控制结构和输出。这些文件包含将从控制器接收的数据准备成面向您的受众的演示格式所需的逻辑。
在模板中回显或打印变量
<?php echo $variable; ?>
使用简短标签支持
<?= $variable ?>
控制结构,如 if
、for
、foreach
、switch
和 while
可以使用简化的格式编写。请注意,没有大括号。相反,foreach
的结束大括号被替换为 endforeach
。上面列出的每个控制结构都有类似的结束语法:endif
、endfor
、endforeach
和 endwhile
。还要注意,不是在每个结构(最后一个除外)之后使用 分号
,而是使用 冒号
。
以下是使用 foreach
的示例
<ul>
<?php foreach ($todo as $item): ?>
<li><?= $item ?></li>
<?php endforeach; ?>
</ul>
另一个使用 if/elseif/else 的示例。请注意冒号
<?php if ($username === 'sally'): ?>
<h3>Hi Sally</h3>
<?php elseif ($username === 'joe'): ?>
<h3>Hi Joe</h3>
<?php else: ?>
<h3>Hi unknown user</h3>
<?php endif; ?>
如果您更愿意使用像 Twig 这样的模板语言,请查看 CakePHP Twig 插件
模板文件存储在 templates/ 中,位于使用这些文件的控制器的文件夹中,并以其对应的操作命名。例如,Products
控制器的 view()
操作的视图文件通常位于 templates/Products/view.php 中。
CakePHP 中的视图层可以由许多不同的部分组成。每个部分都有不同的用途,本章将介绍这些部分
templates:模板是页面中对正在执行的操作唯一的部分。它们构成了应用程序响应的核心内容。
elements:小型的可重用视图代码段。元素通常在视图内部呈现。
layouts:模板文件,包含包装应用程序中许多界面的演示代码。大多数视图都在布局内呈现。
helpers:这些类封装了视图层中许多地方都需要的视图逻辑。除其他事项外,CakePHP 中的帮助程序可以帮助您构建表单、构建 AJAX 功能、分页模型数据或提供 RSS 提要。
cells:这些类为创建自包含 UI 组件提供类似迷你控制器的功能。有关详细信息,请参阅 视图单元格 文档。
您在控制器中使用 set()
设置的任何变量都将在视图和操作呈现的布局中可用。此外,任何设置的变量也将在任何元素中可用。如果您需要从视图传递额外的变量到布局,您可以选择在视图模板中调用 set()
,或使用 视图块。
您应该记住 始终 在输出用户数据之前对其进行转义,因为 CakePHP 不会自动转义输出。您可以使用 h()
函数转义用户内容
<?= h($user->bio); ?>
视图有一个 set()
方法,类似于控制器对象中的 set()
。从您的视图文件使用 set() 将向将在以后呈现的布局和元素添加变量。有关使用 set()
的更多信息,请参阅 设置视图变量。
在您的视图文件中,您可以执行以下操作
$this->set('activeMenuButton', 'posts');
然后,在你的布局中,$activeMenuButton
变量将可用,并包含值 ‘posts’。
视图扩展允许你将一个视图包装到另一个视图中。将此与视图块 结合使用,为你提供了一种强大的方法来保持你的视图DRY。例如,你的应用程序有一个侧边栏,需要根据正在渲染的特定视图进行更改。通过扩展一个公共视图文件,你可以避免重复侧边栏的公共标记,并且只定义更改的部分。
<!-- templates/Common/view.php -->
<h1><?= h($this->fetch('title')) ?></h1>
<?= $this->fetch('content') ?>
<div class="actions">
<h3>Related actions</h3>
<ul>
<?= $this->fetch('sidebar') ?>
</ul>
</div>
上面的视图文件可以用作父视图。它期望扩展它的视图将定义sidebar
和 title
块。 content
块是 CakePHP 创建的一个特殊块。它将包含来自扩展视图的所有未捕获的内容。假设我们的视图文件有一个 $post
变量,包含关于我们帖子的数据,该视图可能如下所示
<!-- templates/Posts/view.php -->
<?php
$this->extend('/Common/view');
$this->assign('title', $post->title);
$this->start('sidebar');
?>
<li>
<?php
echo $this->Html->link('edit', [
'action' => 'edit',
$post->id,
]);
?>
</li>
<?php $this->end(); ?>
// The remaining content will be available as the 'content' block
// In the parent view.
<?= h($post->body) ?>
上面的帖子视图展示了如何扩展视图并填充一组块。任何不在已定义块中的内容都将被捕获并放入名为 content
的特殊块中。当一个视图包含对 extend()
的调用时,执行将继续到当前视图文件的底部。完成后,将渲染扩展的视图。在视图文件中多次调用 extend()
将覆盖将被处理的下一个父视图
$this->extend('/Common/view');
$this->extend('/Common/index');
以上将导致 /Common/index.php 被渲染为当前视图的父视图。
你可以根据需要嵌套扩展视图。每个视图都可以根据需要扩展另一个视图。每个父视图都将获得前一个视图的内容作为 content
块。
注意
你应该避免在应用程序中使用 content
作为块名。CakePHP 将此用于扩展视图中的未捕获内容。
与视图一样,布局也可以扩展。与视图一样,你使用 extend()
来扩展布局。布局扩展可以更新或替换块,以及更新或替换子布局渲染的内容。例如,如果我们想用额外的标记包装一个块,你可以这样做
// Our layout extends the application layout.
$this->extend('application');
$this->prepend('content', '<main class="nosidebar">');
$this->append('content', '</main>');
// Output more markup.
// Remember to echo the contents of the previous layout.
echo $this->fetch('content');
视图块提供了一个灵活的 API,它允许你在你的视图/布局中定义插槽或块,这些插槽或块将在其他地方定义。例如,块非常适合实现侧边栏或区域,以便在布局的底部/顶部加载资产。块可以以两种方式定义:作为捕获块或通过直接分配。 start()
、append()
、prepend()
、assign()
、fetch()
和 end()
方法允许你使用捕获块
// Create the sidebar block.
$this->start('sidebar');
echo $this->element('sidebar/recent_topics');
echo $this->element('sidebar/recent_comments');
$this->end();
// Append into the sidebar later on.
$this->start('sidebar');
echo $this->fetch('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();
你也可以使用 append()
将内容追加到块中
$this->append('sidebar');
echo $this->element('sidebar/popular_topics');
$this->end();
// The same as the above.
$this->append('sidebar', $this->element('sidebar/popular_topics'));
如果你需要清除或覆盖一个块,有几个选择。 reset()
方法将随时清除或覆盖一个块。带有空内容字符串的 assign()
方法也可以用于清除指定的块。
// Clear the previous content from the sidebar block.
$this->reset('sidebar');
// Assigning an empty string will also clear the sidebar block.
$this->assign('sidebar', '');
当你想将视图变量转换为块时,分配块的内容通常很有用。例如,你可能想使用一个块作为页面标题,有时在控制器中将标题分配为视图变量
// In view file or layout above $this->fetch('title')
$this->assign('title', $title);
prepend()
方法允许你将内容预先添加到现有块中
// Prepend to sidebar
$this->prepend('sidebar', 'this content goes on top of sidebar');
你可以使用 fetch()
方法显示块。 fetch()
将输出一个块,如果块不存在,则返回 ‘’
<?= $this->fetch('sidebar') ?>
你也可以使用 fetch 有条件地显示应该围绕块的内容(如果存在)。这在布局或扩展视图中很有用,在这些视图中你想要有条件地显示标题或其他标记
// In templates/layout/default.php
<?php if ($this->fetch('menu')): ?>
<div class="menu">
<h3>Menu options</h3>
<?= $this->fetch('menu') ?>
</div>
<?php endif; ?>
你也可以为一个块提供一个默认值(如果它不存在)。这允许你在一个块不存在时添加占位符内容。你可以使用第二个参数提供一个默认值
<div class="shopping-cart">
<h3>Your Cart</h3>
<?= $this->fetch('cart', 'Your cart is empty') ?>
</div>
HtmlHelper
与视图块相关联,它的 script()
、css()
和 meta()
方法在使用 block = true
选项时,都会用相同名称的块更新该块
<?php
// In your view file
$this->Html->script('carousel', ['block' => true]);
$this->Html->css('carousel', ['block' => true]);
?>
// In your layout file.
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= h($this->fetch('title')) ?></title>
<?= $this->fetch('script') ?>
<?= $this->fetch('css') ?>
</head>
// Rest of the layout follows
Cake\View\Helper\HtmlHelper
也允许你控制脚本和 CSS 放在哪个块中
// In your view
$this->Html->script('carousel', ['block' => 'scriptBottom']);
// In your layout
<?= $this->fetch('scriptBottom') ?>
布局包含围绕视图的表示代码。你想在所有视图中看到的内容都应该放在布局中。
CakePHP 的默认布局位于 templates/layout/default.php。如果你想改变应用程序的整体外观,那么这就是开始的正确地方,因为控制器渲染的视图代码在页面渲染时被放置在默认布局中。
其他布局文件应该放置在 templates/layout 中。当你创建一个布局时,你需要告诉 CakePHP 将你的视图的输出放置在哪里。为此,请确保你的布局包含一个用于 $this->fetch('content')
的位置。以下是一个默认布局的示例
<!DOCTYPE html>
<html lang="en">
<head>
<title><?= h($this->fetch('title')) ?></title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<!-- Include external files and scripts here (See HTML helper for more info.) -->
<?php
echo $this->fetch('meta');
echo $this->fetch('css');
echo $this->fetch('script');
?>
</head>
<body>
<!-- If you'd like some sort of menu to
show up on all of your views, include it here -->
<div id="header">
<div id="menu">...</div>
</div>
<!-- Here's where I want my views to be displayed -->
<?= $this->fetch('content') ?>
<!-- Add a footer to each displayed page -->
<div id="footer">...</div>
</body>
</html>
script
、css
和 meta
块包含使用内置 HTML 帮助程序在视图中定义的任何内容。适用于从视图中包含 JavaScript 和 CSS 文件。
注意
当在模板文件中使用 HtmlHelper::css()
或 HtmlHelper::script()
时,指定 'block' => true
将 HTML 源代码放置在具有相同名称的块中。(有关使用详情,请参阅 API)。
content
块包含渲染视图的内容。
你可以在视图文件中设置 title
块内容
$this->assign('title', 'View Active Users');
title
块的空值将自动替换为当前模板路径的表示,例如 'Admin/Articles'
。
你可以根据需要创建任意数量的布局:只需将它们放在 templates/layout 目录中,然后在你的控制器操作中使用控制器或视图的 $layout
属性在它们之间切换
// From a controller
public function view()
{
// Set the layout.
$this->viewBuilder()->setLayout('admin');
}
// From a view file
$this->layout = 'loggedin';
例如,如果我网站的一部分包含一个较小的广告横幅空间,我可能会创建一个带有较小广告空间的新布局,并将其指定为所有控制器操作的布局,使用类似下面的内容
namespace App\Controller;
class UsersController extends AppController
{
public function viewActive()
{
$this->set('title', 'View Active Users');
$this->viewBuilder()->setLayout('default_small_ad');
}
public function viewImage()
{
$this->viewBuilder()->setLayout('image');
// Output user image
}
}
除了默认布局之外,CakePHP 的官方骨架应用程序还具有 ‘ajax’ 布局。Ajax 布局对于制作 AJAX 响应非常方便 - 它是一个空的布局。(大多数 AJAX 调用只需要少量标记作为返回,而不是完全渲染的界面。)
骨架应用程序还有一个默认布局,用于帮助生成 RSS。
如果你想使用插件中存在的布局,可以使用插件语法。例如,要使用 Contacts 插件中的 contact 布局
namespace App\Controller;
class UsersController extends AppController
{
public function viewActive()
{
$this->viewBuilder()->setLayout('Contacts.contact');
}
}
许多应用程序都有需要从一个页面重复到另一个页面的少量表示代码块,有时还会在布局中的不同位置重复这些代码块。CakePHP 可以帮助你重复需要重复使用网站的一部分。这些可重复使用的部分称为元素。广告、帮助框、导航控制、额外的菜单、登录表单和调用通常在 CakePHP 中实现为元素。元素基本上是一个迷你视图,可以包含在其他视图、布局甚至其他元素中。元素可以用来使视图更易读,将重复元素的渲染放在它自己的文件中。它们还可以帮助你在应用程序中重复使用内容片段。
元素位于 templates/element/ 文件夹中,并具有 .php 文件名扩展名。它们使用视图的 element 方法输出
echo $this->element('helpbox');
你可以通过元素的第二个参数将数据传递给元素
echo $this->element('helpbox', [
'helptext' => 'Oh, this text is very helpful.',
]);
在元素文件中,所有传递的变量都可以作为参数数组的成员使用(与控制器中的 Controller::set()
对模板文件的作用方式相同)。在上面的示例中,templates/element/helpbox.php 文件可以使用 $helptext
变量
// Inside templates/element/helpbox.php
echo $helptext; // Outputs `Oh, this text is very helpful.`
请记住,在这些视图变量中,将合并视图本身的视图变量。因此,所有使用控制器中的 Controller::set()
和视图本身中的 View::set()
设置的视图变量,在元素中也可以使用。
View::element()
方法还支持元素的选项。支持的选项是 ‘cache’ 和 ‘callbacks’。例如
echo $this->element('helpbox', [
'helptext' => "This is passed to the element as $helptext",
'foobar' => "This is passed to the element as $foobar",
],
[
// uses the `long_view` cache configuration
'cache' => 'long_view',
// set to true to have before/afterRender called for the element
'callbacks' => true,
]
);
元素缓存通过 Cache
类实现。您可以将元素配置为存储在您设置的任何缓存配置中。这为您提供了很大的灵活性来决定在哪里以及存储元素多长时间。要在应用程序中缓存同一元素的不同版本,请使用以下格式提供唯一的缓存键值
$this->element('helpbox', [], [
'cache' => ['config' => 'short', 'key' => 'unique value'],
]
);
如果您需要在元素中添加更多逻辑,例如来自数据源的动态数据,请考虑使用 View Cell 而不是元素。了解更多信息 关于 View Cells.
如果您提供缓存参数,则可以利用 CakePHP 视图缓存。如果设置为 true
,它将缓存元素在“default”缓存配置中。否则,您可以设置要使用的缓存配置。有关配置 Cache
的更多信息,请参见 缓存。缓存元素的一个简单示例是
echo $this->element('helpbox', [], ['cache' => true]);
如果您在视图中多次渲染同一个元素并启用了缓存,请确保每次将“key”参数设置为不同的名称。这将防止每个后续调用覆盖之前的 element()
调用缓存的结果。例如
echo $this->element(
'helpbox',
['var' => $var],
['cache' => ['key' => 'first_use', 'config' => 'view_long']]
);
echo $this->element(
'helpbox',
['var' => $differenVar],
['cache' => ['key' => 'second_use', 'config' => 'view_long']]
);
以上将确保两个元素结果分别缓存。如果您希望所有元素缓存都使用相同的缓存配置,您可以通过将 View::$elementCache
设置为您要使用的缓存配置来避免一些重复。CakePHP 将在没有提供配置时使用此配置。
如果您使用插件并希望使用插件中的元素,只需使用熟悉的 插件语法。如果视图正在为插件控制器/操作渲染,则插件名称将自动作为所有使用元素的前缀,除非存在另一个插件名称。如果元素在插件中不存在,它将在主 APP 文件夹中查找
echo $this->element('Contacts.helpbox');
如果您的视图是插件的一部分,您可以省略插件名称。例如,如果您在 Contacts 插件的 ContactsController
中,以下
echo $this->element('helpbox');
// and
echo $this->element('Contacts.helpbox');
是等效的,并将导致渲染相同的元素。
对于插件子文件夹中的元素(例如,plugins/Contacts/Template/element/sidebar/helpbox.php),请使用以下
echo $this->element('Contacts.sidebar/helpbox');
如果您配置了路由前缀,则元素路径解析可以切换到前缀位置,就像布局和操作视图一样。假设您配置了一个前缀“Admin”,并且您调用
echo $this->element('my_element');
元素首先在 templates/Admin/element/ 中查找。如果这样的文件不存在,它将在默认位置查找。
有时,由于渲染了 View Cells 或昂贵的辅助操作,生成视图输出的部分可能很昂贵。为了帮助提高应用程序的运行速度,CakePHP 提供了一种缓存视图部分的方法
// Assuming some local variables
echo $this->cache(function () use ($user, $article) {
echo $this->cell('UserProfile', [$user]);
echo $this->cell('ArticleFull', [$article]);
}, ['key' => 'my_view_key']);
默认情况下,缓存的视图内容将进入 View::$elementCache
缓存配置,但您可以使用 config
选项更改此配置。
与控制器一样,视图会触发几个事件/回调,您可以使用这些事件/回调在渲染生命周期中插入逻辑
View.beforeRender
View.beforeRenderFile
View.afterRenderFile
View.afterRender
View.beforeLayout
View.afterLayout
您可能需要创建自定义视图类来启用新类型的数据视图,或在您的应用程序中添加其他自定义视图渲染逻辑。与 CakePHP 的大多数组件一样,视图类有一些约定
视图类文件应放在 src/View 中。例如:src/View/PdfView.php
视图类应以 View
结尾。例如:PdfView
。
引用视图类名时,您应该省略 View
后缀。例如:$this->viewBuilder()->setClassName('Pdf');
。
您还需要扩展 View
以确保一切正常工作
// In src/View/PdfView.php
namespace App\View;
use Cake\View\View;
class PdfView extends View
{
public function render($view = null, $layout = null)
{
// Custom logic here.
}
}
替换 render 方法可以完全控制内容的渲染方式。