阅读时间:1 分钟
0 字
视图模板(Latte 语法层)
本章只讲 Latte 语法和模板组织方式。
模板引擎作为“独立运行环境”的机制与执行流程,已在 preprocessor.md 中说明。
定位与职责
- 模板引擎文档:讲运行环境、超语法、数据调用、内建运行时能力
- 本章节:只讲 Latte 语法、模板结构和组织规范
基础语法
变量输出
html
<!-- 基本变量输出 -->
<h1>{$title}</h1>
<p>{$user->name}</p>
<!-- 带默认值的输出 -->
<img src="{$user->avatar|default:'/images/default-avatar.png'}" alt="头像">
<p>{$post->excerpt|default:'暂无简介'}</p>
<!-- 原始HTML输出(不转义) -->
<div class="content">{$post->content|noescape}</div>
<!-- 条件输出 -->
<span class="status">{$user->isVip ? 'VIP用户' : '普通用户'}</span>注释
html
{* 这是单行注释 *}
{*
这是多行注释
可以跨越多行
*}条件语句
html
<!-- 简单条件 -->
{if $user}
<p>欢迎,{$user->name}!</p>
{else}
<p>请先登录</p>
{/if}
<!-- 复杂条件 -->
{if $user && $user->isActive}
<div class="user-panel">
<h3>{$user->name}</h3>
{if $user->isVip}
<span class="vip-badge">VIP</span>
{/if}
</div>
{elseif $user && !$user->isActive}
<div class="alert alert-warning">
您的账户已被暂停,请联系管理员
</div>
{else}
<div class="login-prompt">
<a href="/login">立即登录</a>
</div>
{/if}
<!-- 状态判断 -->
{if $status === 'published'}
<span class="badge badge-success">已发布</span>
{elseif $status === 'draft'}
<span class="badge badge-secondary">草稿</span>
{elseif $status === 'pending'}
<span class="badge badge-warning">待审核</span>
{else}
<span class="badge badge-danger">已删除</span>
{/if}循环语句
html
<!-- 简单循环 -->
<ul>
{foreach $posts as $post}
<li>{$post->title}</li>
{/foreach}
</ul>
<!-- 带索引的循环 -->
<ol>
{foreach $posts as $index => $post}
<li data-index="{$index}">{$post->title}</li>
{/foreach}
</ol>
<!-- 复杂循环,使用 $iterator 变量 -->
<div class="post-list">
{foreach $posts as $post}
<article class="post-item{if $iterator->first} first{/if}{if $iterator->last} last{/if}">
<h3>{$post->title}</h3>
<p>{$post->excerpt}</p>
<div class="meta">
<span>第 {$iterator->counter} / {$iterator->count} 篇</span>
<span>{$post->created_at|date:'Y-m-d'}</span>
</div>
</article>
{else}
<p class="no-posts">暂无文章</p>
{/foreach}
</div>
<!-- 嵌套循环 -->
{foreach $categories as $category}
<div class="category">
<h2>{$category->name}</h2>
<div class="posts">
{foreach $category->posts as $post}
<div class="post">
<h3>{$post->title}</h3>
<p>{$post->excerpt}</p>
</div>
{else}
<p>该分类下暂无文章</p>
{/foreach}
</div>
</div>
{/foreach}模板继承和布局(Latte)
基础布局模板
基础布局模板(layouts/base.latte):
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{block title}{$title|default:'DuxLite'}{/block}</title>
{block meta}
<meta name="description" content="{block description}DuxLite 框架{/block}">
<meta name="keywords" content="{block keywords}DuxLite,PHP,Framework{/block}">
{/block}
{block styles}
<link rel="stylesheet" href="/assets/css/app.css">
{/block}
</head>
<body class="{block bodyClass}{/block}">
<header class="header">
{block header}
<nav class="navbar">
<div class="container">
<a href="/" class="navbar-brand">
{$appName|default:'DuxLite'}
</a>
<ul class="navbar-nav">
{block navigation}
<li><a href="/">首页</a></li>
<li><a href="/about">关于</a></li>
<li><a href="/contact">联系</a></li>
{/block}
</ul>
<div class="navbar-user">
{if $user}
<span>欢迎,{$user->name}</span>
<a href="/logout">退出</a>
{else}
<a href="/login">登录</a>
{/if}
</div>
</div>
</nav>
{/block}
</header>
<main class="main">
{block breadcrumb}{/block}
{block flashMessages}
{if $flash->success}
<div class="alert alert-success">{$flash->success}</div>
{/if}
{if $flash->error}
<div class="alert alert-error">{$flash->error}</div>
{/if}
{/block}
<div class="container">
{block content}{/block}
</div>
</main>
<footer class="footer">
{block footer}
<div class="container">
<p>© {date('Y')} {$appName|default:'DuxLite'}. All rights reserved.</p>
</div>
{/block}
</footer>
{block scripts}
<script src="/assets/js/app.js"></script>
{/block}
</body>
</html>子模板(index.latte):
html
{layout 'layouts/base.latte'}
{block title}首页 - {include parent}{/block}
{block description}DuxLite 框架首页,展示最新功能和内容{/block}
{block bodyClass}home-page{/block}
{block content}
<div class="hero-section">
<h1>欢迎来到 {$appName|default:'DuxLite'}</h1>
<p>一个现代化的 PHP 开发框架</p>
<a href="/docs" class="btn btn-primary">查看文档</a>
</div>
<div class="features-section">
<h2>主要特性</h2>
<div class="features-grid">
{foreach $features as $feature}
<div class="feature-card">
<h3>{$feature->title}</h3>
<p>{$feature->description}</p>
</div>
{/foreach}
</div>
</div>
{if $posts}
<div class="posts-section">
<h2>最新文章</h2>
{include 'partials/post-list.latte', posts: $posts}
</div>
{/if}
{/block}
{block scripts}
{include parent}
{/block}组件化开发
可复用的组件(partials/post-card.latte):
html
<!-- 文章卡片组件 -->
<article class="post-card">
<div class="post-header">
{if $post->featured_image}
<img src="{$post->featured_image}" alt="{$post->title}" class="post-image">
{/if}
<div class="post-meta">
<time datetime="{$post->created_at|date:'c'}">{$post->created_at|date:'Y-m-d'}</time>
<span class="post-category">{$post->category->name}</span>
</div>
</div>
<div class="post-content">
<h3 class="post-title">
<a href="/blog/{$post->id}">{$post->title}</a>
</h3>
<p class="post-excerpt">{$post->excerpt|truncate:120}</p>
<div class="post-tags">
{foreach $post->tags as $tag}
<span class="tag">{$tag->name}</span>
{/foreach}
</div>
</div>
<div class="post-footer">
<div class="post-author">
<img src="{$post->author->avatar|default:'/images/default-avatar.png'}" alt="{$post->author->name}">
<span>{$post->author->name}</span>
</div>
<div class="post-stats">
<span class="views">{$post->views} 次浏览</span>
<span class="comments">{$post->comments_count} 条评论</span>
</div>
</div>
</article>在模板中使用组件:
html
<!-- 文章列表页 -->
{layout 'layouts/base.latte'}
{block content}
<div class="posts-container">
{foreach $posts as $post}
{include 'partials/post-card.latte', post: $post}
{/foreach}
{if empty($posts)}
<div class="empty-state">
<h3>暂无文章</h3>
<p>还没有发布任何文章</p>
</div>
{/if}
</div>
{/block}内置过滤器
常用过滤器
html
<!-- 日期格式化 -->
<time>{$post->created_at|date:'Y-m-d H:i:s'}</time>
<span>{$user->birthday|date:'Y年m月d日'}</span>
<!-- 字符串处理 -->
<h1>{$post->title|upper}</h1> <!-- 转大写 -->
<p>{$post->content|truncate:100}</p> <!-- 截断字符串 -->
<span>{$post->slug|capitalize}</span> <!-- 首字母大写 -->
<!-- 默认值 -->
<img src="{$user->avatar|default:'/images/default-avatar.png'}" alt="头像">
<p>{$post->excerpt|default:'暂无简介'}</p>
<!-- HTML 转义控制 -->
<div class="content">{$post->content|noescape}</div> <!-- 不转义,输出原始HTML -->
<p>{$user->bio}</p> <!-- 默认自动转义 -->
<!-- 数组处理 -->
<span>共 {$tags|length} 个标签</span> <!-- 数组长度 -->
<span>{$categories|first}</span> <!-- 第一个元素 -->
<span>{$posts|last}</span> <!-- 最后一个元素 -->自定义过滤器
在控制器应用初始化中添加自定义过滤器:
php
// 价格格式化过滤器
$latte->addFilter('price', function ($price, $currency = '¥') {
return $currency . number_format((float)$price, 2);
});
// 人性化时间过滤器
$latte->addFilter('timeAgo', function ($datetime) {
$time = is_string($datetime) ? strtotime($datetime) : $datetime->getTimestamp();
$diff = time() - $time;
if ($diff < 60) return '刚刚';
if ($diff < 3600) return floor($diff / 60) . '分钟前';
if ($diff < 86400) return floor($diff / 3600) . '小时前';
if ($diff < 2592000) return floor($diff / 86400) . '天前';
return date('Y-m-d', $time);
});在模板中使用:
html
<!-- 价格格式化 -->
<span class="price">{$product->price|price:'$'}</span>
<!-- 人性化时间 -->
<time>{$post->created_at|timeAgo}</time>错误页面处理
自定义错误模板
404 错误页面(errors/404.latte):
html
{layout 'layouts/base.latte'}
{block title}页面不存在 - {include parent}{/block}
{block content}
<div class="error-page error-404">
<div class="error-content">
<div class="error-code">404</div>
<h1 class="error-title">{$title|default:'页面不存在'}</h1>
<p class="error-message">{$message|default:'您访问的页面不存在或已被删除'}</p>
<div class="error-actions">
<a href="/" class="btn btn-primary">返回首页</a>
<a href="javascript:history.back()" class="btn btn-secondary">返回上页</a>
</div>
</div>
</div>
{/block}500 错误页面(errors/500.latte):
html
{layout 'layouts/base.latte'}
{block title}服务器错误 - {include parent}{/block}
{block content}
<div class="error-page error-500">
<div class="error-content">
<div class="error-code">500</div>
<h1 class="error-title">服务器错误</h1>
<p class="error-message">抱歉,服务器遇到了一些问题,请稍后再试。</p>
<div class="error-actions">
<a href="/" class="btn btn-primary">返回首页</a>
<a href="javascript:location.reload()" class="btn btn-secondary">重新加载</a>
</div>
</div>
</div>
{/block}最佳实践
1. 模板组织结构
templates/
├── layouts/ # 布局模板
│ ├── base.latte # 基础布局
│ └── admin.latte # 管理后台布局
├── partials/ # 可复用组件
│ ├── header.latte
│ └── footer.latte
├── errors/ # 错误页面
│ ├── 404.latte
│ └── 500.latte
└── pages/ # 具体页面
├── index.latte
└── blog/
├── index.latte
└── show.latte2. 路径写法
- 相对路径:
{include 'partials/user-card.latte'} - 绝对路径:
{include '/views/partials/user-card.latte'} - 根目录前缀:
{include '@partials/user-card.latte'} - 同样适用于
{layout ...}与{embed ...}
3. 安全考虑
html
<!-- ✅ 推荐:默认自动转义 -->
<p>{$user->bio}</p>
<!-- ✅ 推荐:明确不转义可信内容 -->
<div class="content">{$post->htmlContent|noescape}</div>
<!-- ❌ 避免:盲目使用 noescape -->
<p>{$userInput|noescape}</p>4. 组件化开发
html
<!-- ✅ 推荐:创建可复用组件 -->
{include 'partials/user-card.latte', user: $user, showActions: true}
<!-- ✅ 推荐:使用块定义组件接口 -->
{define userCard}
<div class="user-card">
<img src="{$user->avatar}" alt="{$user->name}">
<h3>{$user->name}</h3>
<p>{$user->bio}</p>
</div>
{/define}通过遵循这些最佳实践,可以构建结构清晰、安全可靠的模板系统。