Skip to content

从 HTML 中分离

PHP 解析器会忽略一对开始和结束标签之外的内容,这使得 PHP 文件可以包含混合内容——HTML 标记与 PHP 代码共存于同一文件中。这是 PHP 作为服务端模板引擎的核心能力,也是 PHP 最早的设计初衷之一。

前置知识

  • 已掌握 PHP 标签 的使用
  • 基本的 HTML 文档结构知识
  • 了解 PHP 代码的执行流程

基础概念

PHP 解析器处理文件时,只会解析 <?php ... ?> 标签对之间的代码。标签之外的所有内容(包括 HTML、CSS、JavaScript、纯文本等)都会被原样输出到浏览器。这种机制被称为"从 HTML 中分离",它允许开发者在 HTML 模板中灵活地嵌入动态逻辑。

混合内容的基本模式

PHP 与 HTML 的混编遵循一个简单的原则:在标签外写静态内容,在标签内写动态逻辑。

php
<?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
<?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 内部使用 echoprint 输出更高效。因为 PHP 解析器跳过标签外的内容,不需要逐字符解析。

变量在标签间的传递

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
<?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 提供了控制结构的替代语法(使用 endifendwhileendforendforeachendswitch),这在模板混编中非常常用:

php
<?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
<?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
<?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 解析器处理混合内容文件的流程有助于写出更高效的代码:

  1. 解析器从文件开头开始扫描
  2. 遇到 <?php<?= 时进入 PHP 解析模式
  3. 遇到 ?> 时退出 PHP 解析模式,后续内容原样输出
  4. 重复步骤 2~3 直到文件结束
php
<?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 文件中的作用域继承

当使用 includerequire 包含文件时,被包含文件会继承包含语句所在位置的所有变量:

php
<?php
declare(strict_types=1);

// main.php
$pageTitle = "首页";
$contentTemplate = "home.php";

// $pageTitle 变量会传递给被包含的文件
include $contentTemplate;
php
<?php
declare(strict_types=1);

// home.php(被包含文件)
// $pageTitle 从 main.php 继承而来
?>
<h1><?= htmlspecialchars($pageTitle) ?></h1>
<p>这是首页内容。</p>

如果 include 语句在函数内部执行,被包含文件只继承函数作用域内的变量:

php
<?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
<?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
<?php
declare(strict_types=1);

// 危险!这行中的 ?> 会被当作结束标签
// $file_contents = '<?php die(); ?>' . "\n";

// 下面这行在 ?> 之后会被当作 HTML 原样输出
// 导致 ' . "\n"; 出现在页面上

// 安全的写法 - 使用多行注释
/*
$file_contents = '<?php die(); ?>' . "\n";
*/

// 或者拼接方式避免
$fileContents = '<' . '?php die(); ?' . '>' . "\n";
echo $fileContents;

实战示例

一个完整的模板页面

php
<?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>&copy; <?= date("Y") ?> 我的博客</p>
    </footer>
</body>
</html>

可选 HTML 属性的动态生成

php
<?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
<?php
declare(strict_types=1);

// 以下代码中的 PHP 仍会执行!
// <!-- <?php echo "hidden"; ?> -->

// 如果确实需要注释掉 PHP 代码,使用 PHP 注释
// <?php // echo "truly commented"; ?>

2. 空白字符的隐式输出

结束标签 ?> 后紧跟的换行符会被隐式吃掉(PHP 会忽略紧跟 ?> 的第一个换行符),但其他空白字符(如空格、制表符)不会:

php
<?php
declare(strict_types=1);

echo "A";?>
B
// 输出:AB(换行符被吃掉)

echo "A";?>
    B
// 输出:A    B(空格保留)

3. 嵌套包含文件中的分离

在多层 include 的场景中,每个被包含文件都独立处理自己的 PHP 标签:

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
<?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
<?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;

最佳实践

  1. 模板逻辑与业务逻辑分离:在控制器中准备数据,在模板中只做展示逻辑
  2. 始终对输出进行转义:使用 htmlspecialchars() 防止 XSS 攻击
  3. 使用替代语法:在模板中优先使用 if:/endifforeach:/endforeach 替代语法
  4. 大段静态文本用标签外方式:比 echo 更高效且可读性更好
  5. 避免复杂的业务逻辑:模板中只保留简单的条件判断和循环
  6. 考虑使用模板引擎:对于复杂项目,推荐使用 Twig、Blade 等模板引擎
  7. 注意编码一致性:确保 PHP 文件、HTML 输出和数据库连接使用相同的字符编码

下一节

了解了 PHP 如何与 HTML 混合编写之后,接下来将学习 PHP 的指令分隔符——分号的使用规则,以及语句块的正确写法。请阅读 指令分隔符

参考链接