从 HTML 中分离
PHP 解析器会忽略一对开始和结束标签之外的内容,这使得 PHP 文件可以包含混合内容——HTML 标记与 PHP 代码共存于同一文件中。这是 PHP 作为服务端模板引擎的核心能力,也是 PHP 最早的设计初衷之一。
前置知识
- 已掌握 PHP 标签 的使用
- 基本的 HTML 文档结构知识
- 了解 PHP 代码的执行流程
基础概念
PHP 解析器处理文件时,只会解析 <?php ... ?> 标签对之间的代码。标签之外的所有内容(包括 HTML、CSS、JavaScript、纯文本等)都会被原样输出到浏览器。这种机制被称为"从 HTML 中分离",它允许开发者在 HTML 模板中灵活地嵌入动态逻辑。
混合内容的基本模式
PHP 与 HTML 的混编遵循一个简单的原则:在标签外写静态内容,在标签内写动态逻辑。
<?php
declare(strict_types=1);
// 这个文件混合了 PHP 代码和 HTML 内容
$currentDate = date("Y-m-d H:i:s");
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>PHP 与 HTML 混合示例</title>
</head>
<body>
<p>当前时间:<?= htmlspecialchars($currentDate, ENT_QUOTES, 'UTF-8') ?></p>
<p>这段 HTML 由 PHP 引擎原样输出。</p>
</body>
</html>基本嵌入方式
最简单的嵌入
在 HTML 中嵌入 PHP 最简单的方式是用标准标签包裹需要执行的 PHP 代码:
<?php
declare(strict_types=1);
$username = "访客";
?>
<p>欢迎你,<?= htmlspecialchars($username) ?>!</p>
<?php
// PHP 标签外的 HTML 被原样输出
// 标签内的 PHP 代码被解析执行
echo "<p>这是通过 echo 输出的内容。</p>\n";
?>
<p>这又是标签外的 HTML。</p>性能提示
当需要输出大段静态文本时,跳出 PHP 解析模式(使用标签外的 HTML)比在 PHP 内部使用 echo 或 print 输出更高效。因为 PHP 解析器跳过标签外的内容,不需要逐字符解析。
变量在标签间的传递
PHP 变量在标签之间是持续可用的,不需要额外的传递机制:
<?php
declare(strict_types=1);
$siteName = "我的网站";
$version = "2.0";
?>
<h1><?= htmlspecialchars($siteName) ?></h1>
<p>版本:<?= $version ?></p>
<?php
// 在新的 PHP 标签中,变量仍然可用
$copyright = "{$siteName} v{$version}";
?>
<footer><?= htmlspecialchars($copyright) ?></footer>条件输出与高级分离
PHP 最强大的特性之一是条件分离——使用控制结构来决定哪些 HTML 内容应该被输出,哪些应该被跳过。
使用 if 语句进行条件输出
在标签外的 HTML 区域中,PHP 会根据条件语句来判断是否输出内容:
<?php
declare(strict_types=1);
$isLoggedIn = true;
$userRole = "admin";
?>
<?php if ($isLoggedIn): ?>
<div class="user-panel">
<p>欢迎回来!</p>
<?php if ($userRole === "admin"): ?>
<a href="/admin">管理后台</a>
<?php endif; ?>
</div>
<?php else: ?>
<div class="login-panel">
<a href="/login">请登录</a>
</div>
<?php endif; ?>关键原理
条件分离的关键在于:当 PHP 解释器在条件语句中间遇到结束标签 ?> 时,它不会立即输出后面的 HTML,而是根据条件判断来决定是否输出。条件为 false 时的 HTML 段落会被完全跳过,包括其中的 PHP 标签也不会执行。
替代语法控制结构
PHP 提供了控制结构的替代语法(使用 endif、endwhile、endfor、endforeach、endswitch),这在模板混编中非常常用:
<?php
declare(strict_types=1);
$items = [
["name" => "PHP 入门", "price" => 59.00],
["name" => "Laravel 实战", "price" => 89.00],
["name" => "MySQL 精通", "price" => 69.00],
];
$showDiscount = true;
$discount = 0.8;
?>
<?php if (count($items) > 0): ?>
<ul class="product-list">
<?php foreach ($items as $item): ?>
<li>
<?= htmlspecialchars($item["name"]) ?>
- ¥<?= number_format($item["price"], 2) ?>
<?php if ($showDiscount): ?>
<span class="discount">
优惠价:¥<?= number_format($item["price"] * $discount, 2) ?>
</span>
<?php endif; ?>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>暂无商品信息。</p>
<?php endif; ?>在 HTML 标签属性中使用 PHP
PHP 可以嵌入在 HTML 标签的属性中,实现动态属性控制:
<?php
declare(strict_types=1);
$isActive = true;
$itemClass = "nav-item";
$targetUrl = "/dashboard";
$tooltip = "点击进入控制面板";
?>
<a
href="<?= htmlspecialchars($targetUrl, ENT_QUOTES) ?>"
class="<?= $itemClass ?><?php if ($isActive): ?> active<?php endif; ?>"
title="<?= htmlspecialchars($tooltip, ENT_QUOTES) ?>"
>
控制面板
</a>这种技巧特别适用于可选的 CSS 类名:
<?php
declare(strict_types=1);
$priority = "high";
$status = "pending";
$classes = ["task-item", "task-{$status}"];
if ($priority === "high") {
$classes[] = "urgent";
}
?>
<div class="<?= implode(" ", $classes) ?>">
任务内容
</div>详细说明
解析器的处理流程
理解 PHP 解析器处理混合内容文件的流程有助于写出更高效的代码:
- 解析器从文件开头开始扫描
- 遇到
<?php或<?=时进入 PHP 解析模式 - 遇到
?>时退出 PHP 解析模式,后续内容原样输出 - 重复步骤 2~3 直到文件结束
<?php
declare(strict_types=1);
// 步骤 1:进入 PHP 解析模式
$data = ["A", "B", "C"];
?>
<!-- 步骤 2:退出 PHP 模式,原样输出这段 HTML -->
<ul>
<?php foreach ($data as $item): ?>
<!-- PHP 条件内的 HTML,根据条件决定是否输出 -->
<li><?= htmlspecialchars($item) ?></li>
<?php endforeach; ?>
</ul>include 文件中的作用域继承
当使用 include 或 require 包含文件时,被包含文件会继承包含语句所在位置的所有变量:
<?php
declare(strict_types=1);
// main.php
$pageTitle = "首页";
$contentTemplate = "home.php";
// $pageTitle 变量会传递给被包含的文件
include $contentTemplate;<?php
declare(strict_types=1);
// home.php(被包含文件)
// $pageTitle 从 main.php 继承而来
?>
<h1><?= htmlspecialchars($pageTitle) ?></h1>
<p>这是首页内容。</p>如果 include 语句在函数内部执行,被包含文件只继承函数作用域内的变量:
<?php
declare(strict_types=1);
function renderPage(string $template, string $title): void
{
// $template 和 $title 在函数作用域内
include __DIR__ . "/templates/{$template}";
}
// $title 可以在模板中使用
renderPage("home.php", "首页");在 XML/XHTML 中使用 PHP
如果 PHP 代码嵌入到 XML 或 XHTML 文档中,必须使用标准标签 <?php ... ?>:
<?php
declare(strict_types=1);
// 在 XML 文档中,短标签 <? 可能与 XML 声明冲突
// 因此必须使用标准标签输出 XML 声明
echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
?>
<rss version="2.0">
<channel>
<title><?= htmlspecialchars("我的 RSS 订阅") ?></title>
<link>https://example.com</link>
</channel>
</rss>单行注释中的结束标签陷阱
重要陷阱
单行注释 // 和 # 中的 ?> 不会被当作注释内容,而会被解析为 PHP 结束标签。这会导致意外的行为。多行注释 /* */ 不受此影响。
<?php
declare(strict_types=1);
// 危险!这行中的 ?> 会被当作结束标签
// $file_contents = '<?php die(); ?>' . "\n";
// 下面这行在 ?> 之后会被当作 HTML 原样输出
// 导致 ' . "\n"; 出现在页面上
// 安全的写法 - 使用多行注释
/*
$file_contents = '<?php die(); ?>' . "\n";
*/
// 或者拼接方式避免
$fileContents = '<' . '?php die(); ?' . '>' . "\n";
echo $fileContents;实战示例
一个完整的模板页面
<?php
declare(strict_types=1);
// 模板数据准备
$pageTitle = "文章列表";
$articles = [
[
"title" => "PHP 8 新特性详解",
"author" => "张三",
"date" => "2026-07-10",
"summary" => "介绍 PHP 8 中的命名参数、联合类型等新特性。",
],
[
"title" => "Laravel 性能优化实践",
"author" => "李四",
"date" => "2026-07-08",
"summary" => "分享 Laravel 应用性能优化的实用技巧。",
],
[
"title" => "MySQL 索引最佳实践",
"author" => "王五",
"date" => "2026-07-05",
"summary" => "深入理解 MySQL 索引原理与优化策略。",
],
];
$currentPage = 1;
$totalPages = 5;
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></title>
</head>
<body>
<header>
<h1><?= htmlspecialchars($pageTitle, ENT_QUOTES, 'UTF-8') ?></h1>
</header>
<main>
<?php if (!empty($articles)): ?>
<section class="article-list">
<?php foreach ($articles as $article): ?>
<article class="article-item">
<h2>
<a href="/article/<?= urlencode($article["title"]) ?>">
<?= htmlspecialchars($article["title"], ENT_QUOTES, 'UTF-8') ?>
</a>
</h2>
<div class="meta">
<span>作者:<?= htmlspecialchars($article["author"]) ?></span>
<span>日期:<?= $article["date"] ?></span>
</div>
<p class="summary">
<?= htmlspecialchars($article["summary"], ENT_QUOTES, 'UTF-8') ?>
</p>
</article>
<?php endforeach; ?>
</section>
<nav class="pagination">
<?php for ($i = 1; $i <= $totalPages; $i++): ?>
<?php if ($i === $currentPage): ?>
<span class="current"><?= $i ?></span>
<?php else: ?>
<a href="?page=<?= $i ?>"><?= $i ?></a>
<?php endif; ?>
<?php endfor; ?>
</nav>
<?php else: ?>
<p class="empty">暂无文章。</p>
<?php endif; ?>
</main>
<footer>
<p>© <?= date("Y") ?> 我的博客</p>
</footer>
</body>
</html>可选 HTML 属性的动态生成
<?php
declare(strict_types=1);
$formData = [
"username" => "zhangsan",
"email" => "",
"bio" => "PHP 开发者",
"website" => "https://example.com",
];
$errors = [
"email" => "请输入邮箱地址",
];
function renderInput(
string $name,
string $label,
string $value,
array $errors = [],
string $type = "text"
): void {
$hasError = isset($errors[$name]);
$errorClass = $hasError ? " has-error" : "";
$error = $hasError ? htmlspecialchars($errors[$name]) : "";
$escapedValue = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
?>
<div class="form-group<?= $errorClass ?>">
<label for="<?= $name ?>"><?= htmlspecialchars($label) ?></label>
<input
type="<?= $type ?>"
id="<?= $name ?>"
name="<?= $name ?>"
value="<?= $escapedValue ?>"
<?php if ($hasError): ?>
aria-describedby="<?= $name ?>-error"
<?php endif; ?>
>
<?php if ($hasError): ?>
<span id="<?= $name ?>-error" class="error-message"><?= $error ?></span>
<?php endif; ?>
</div>
<?php
}
// 使用该函数渲染表单
?>
<form method="post" action="/profile">
<?php renderInput("username", "用户名", $formData["username"] ?? "", $errors) ?>
<?php renderInput("email", "邮箱", $formData["email"] ?? "", $errors, "email") ?>
<?php renderInput("bio", "简介", $formData["bio"] ?? "", $errors) ?>
<?php renderInput("website", "网站", $formData["website"] ?? "", $errors, "url") ?>
<button type="submit">保存</button>
</form>注意事项
1. HTML 注释不会阻止 PHP 执行
HTML 注释 <!-- --> 对 PHP 解析器无效,其中的 PHP 代码仍会执行:
<?php
declare(strict_types=1);
// 以下代码中的 PHP 仍会执行!
// <!-- <?php echo "hidden"; ?> -->
// 如果确实需要注释掉 PHP 代码,使用 PHP 注释
// <?php // echo "truly commented"; ?>2. 空白字符的隐式输出
结束标签 ?> 后紧跟的换行符会被隐式吃掉(PHP 会忽略紧跟 ?> 的第一个换行符),但其他空白字符(如空格、制表符)不会:
<?php
declare(strict_types=1);
echo "A";?>
B
// 输出:AB(换行符被吃掉)
echo "A";?>
B
// 输出:A B(空格保留)3. 嵌套包含文件中的分离
在多层 include 的场景中,每个被包含文件都独立处理自己的 PHP 标签:
<?php
declare(strict_types=1);
// layout.php
$headerText = "我的网站";
?>
<!DOCTYPE html>
<html>
<head><title><?= htmlspecialchars($headerText) ?></title></head>
<body>
<?php include __DIR__ . "/content.php"; ?>
</body>
</html><?php
declare(strict_types=1);
// content.php - 可以继续使用 PHP 与 HTML 混合
// $headerText 从 layout.php 继承
?>
<h1><?= htmlspecialchars($headerText) ?></h1>
<p>这是内容区域。</p>4. 输出缓冲与混编
在混编模式中,输出缓冲函数(ob_start()、ob_get_clean() 等)非常有用,可以捕获模板输出来做后续处理:
<?php
declare(strict_types=1);
function renderTemplate(string $templatePath, array $data = []): string
{
extract($data);
ob_start();
include $templatePath;
return ob_get_clean();
}
// 使用
$html = renderTemplate(__DIR__ . "/views/user.php", [
"name" => "张三",
"age" => 25,
]);
echo $html;最佳实践
- 模板逻辑与业务逻辑分离:在控制器中准备数据,在模板中只做展示逻辑
- 始终对输出进行转义:使用
htmlspecialchars()防止 XSS 攻击 - 使用替代语法:在模板中优先使用
if:/endif、foreach:/endforeach替代语法 - 大段静态文本用标签外方式:比
echo更高效且可读性更好 - 避免复杂的业务逻辑:模板中只保留简单的条件判断和循环
- 考虑使用模板引擎:对于复杂项目,推荐使用 Twig、Blade 等模板引擎
- 注意编码一致性:确保 PHP 文件、HTML 输出和数据库连接使用相同的字符编码
下一节
了解了 PHP 如何与 HTML 混合编写之后,接下来将学习 PHP 的指令分隔符——分号的使用规则,以及语句块的正确写法。请阅读 指令分隔符。