PHP网站设计入门:从页面结构到动态内容展示

资讯16小时前发布 jdkjadmin
0 00
PHP网站设计入门:从页面结构到动态内容展示

PHP网站设计入门:从页面结构到动态内容展示

PHP 常用于生成网站页面、接收表单、读取数据库并返回 HTML。它运行在服务器端:浏览器请求页面后,服务器执行 PHP,再把结果发送给浏览器。浏览器通常看不到服务器上的 PHP 源码。理解这一点,就能把页面结构、动态数据和安全边界放在正确的位置。

本文从一个小型内容页开始,逐步介绍 PHP 文件组织、动态列表、表单处理和数据库查询,并说明示例适合什么场景、有哪些限制。示例以 PHP 8.1 或更新版本为参考;其中使用的基本语法在较早版本中也大多存在,但生产项目应选用仍受支持的 PHP 版本。

一、PHP 适合做什么

PHP 适合渲染服务端页面,也可用于处理表单、会话、文件上传、后台管理和 API。对于以页面为主、需要从数据库读取内容的网站,PHP 的部署方式和生态都比较成熟。WordPress 等内容管理系统也建立在 PHP 生态之上。

PHP 不会自动替代 HTML、CSS 或 JavaScript:HTML 负责页面语义和结构,CSS 负责样式,浏览器中的 JavaScript 负责客户端交互,PHP 则在服务器上处理请求和生成响应。若页面主要由前端应用构成,也可以让 PHP 只提供 API;若只是纯静态文件,则不一定需要 PHP。

二、准备运行环境

本地需要 PHP 命令行程序。终端执行以下命令检查是否已安装:

php -v

创建一个目录,例如 php-site,在其中新建 index.php。进入该目录后运行 PHP 自带的开发服务器:

php -S 127.0.0.1:8000

在浏览器访问 http://127.0.0.1:8000。这个命令用于本机开发和验证,不是面向公网的生产服务器方案。生产环境应使用配置妥当的 Web 服务器和 PHP 运行方式,并配合 HTTPS、日志管理、访问控制及部署流程。

最简单的 PHP 文件可以直接输出 HTML:

<?php
echo '<h1>你好,PHP</h1>';

也可以在 HTML 中嵌入 PHP 表达式:

<?php
$title = '欢迎来到我的网站';
?>
<h1><?= $title ?></h1>

<?= ... ?> 是输出表达式的简写,相当于 <?php echo ...; ?>。但不要把未经处理的用户输入直接放进输出标签;后文会介绍安全输出方法。

三、从页面结构开始

一张常见页面可以分为页头、导航、主体和页脚。先把结构写清楚,再考虑哪些内容需要动态生成。下面是可直接保存为 index.php 的完整示例:

<?php
$pageTitle = '站点首页';
?>
<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?= htmlspecialchars($pageTitle, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></title>
</head>
<body>
    <header>
        <h1>我的 PHP 网站</h1>
        <nav aria-label="主导航">
            <a href="/">首页</a>
            <a href="/about.php">关于</a>
        </nav>
    </header>

    <main>
        <h2>页面内容</h2>
        <p>这是由 PHP 服务器返回的页面。</p>
    </main>

    <footer>
        <p>&copy; 我的站点</p>
    </footer>
</body>
</html>

页面中的 PHP 先给变量赋值,再通过短标签输出。对可能来自用户、数据库或外部服务的数据,示例使用了 htmlspecialchars:它会把 HTML 中有特殊含义的字符转换为实体,降低数据被浏览器当作标记执行的风险。这里明确指定 UTF-8 编码,并使用 ENT_QUOTES 处理单双引号、ENT_SUBSTITUTE 在遇到无效编码序列时进行替代。

这类转义适用于 HTML 文本或适当的 HTML 属性值,不是所有输出场景的通用解决方案。例如,放入 JavaScript、CSS 或 URL 时,需要针对对应上下文处理;更稳妥的做法是尽量避免把不可信数据拼进脚本和样式代码。

四、把重复页面拆成模板

页面增多后,导航和页脚不应在每个文件里重复维护。可以创建 templates/header.php 和 templates/footer.php,并用 require 引入。一个简单目录如下:

php-site/
├── index.php
└── templates/
    ├── header.php
    └── footer.php

templates/header.php 可放置公共页面头部。以下示例假设调用方已经定义并转义标题:

<!doctype html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title><?= htmlspecialchars($pageTitle, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') ?></title>
</head>
<body>
<header>
    <a href="/">我的网站</a>
</header>
<main>

templates/footer.php 可放置页面主体结束标记和页脚:

</main>
<footer>
    <p>感谢访问。</p>
</footer>
</body>
</html>

主页面中先准备变量,再引入模板:

<?php
$pageTitle = '首页';
require __DIR__ . '/templates/header.php';
?>

<h1>欢迎</h1>
<p>这里是首页内容。</p>

<?php require __DIR__ . '/templates/footer.php'; ?>

__DIR__ 表示当前 PHP 文件所在目录,使用它拼接本地模板路径,比依赖当前工作目录更可靠。模板路径应由程序固定或经过严格控制,不能直接拿用户提交的文件名拼进 require,否则可能造成任意文件包含等问题。

五、用循环展示动态内容

动态页面的核心不是“PHP 自动生成内容”,而是程序根据变量、请求或数据库结果组合输出。先用数组模拟文章列表,可以观察循环和模板的关系:

<?php
$articles = [
    ['title' => 'PHP 页面结构入门', 'summary' => '从 HTML 骨架开始搭建页面。'],
    ['title' => '处理用户提交的数据', 'summary' => '了解表单请求和基础校验。'],
    ['title' => '数据库查询基础', 'summary' => '使用参数化查询读取记录。'],
];

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>

<ul>
    <?php foreach ($articles as $article): ?>
        <li>
            <h2><?= h($article['title']) ?></h2>
            <p><?= h($article['summary']) ?></p>
        </li>
    <?php endforeach; ?>
</ul>

foreach 会逐项遍历数组,冒号与 endforeach 是适合嵌入 HTML 的替代语法。示例中的 h 函数只是对 HTML 输出转义的简写,名称和用途都应在项目中保持明确。它不会验证业务规则,也不会把数据“变安全后永久存储”;在最终输出到 HTML 时仍要按上下文处理。

真实项目中,列表通常来自数据库。数组示例适合原型、配置项或少量固定内容,不适合承担需要多人编辑、搜索、权限控制或持久化的数据管理需求。

六、读取查询参数并限制输入

请求地址中的查询字符串可用于搜索、筛选或翻页,例如 /?q=php。参数来自客户端,不能因为它出现在 URL 中就默认可信。下面的示例读取关键词、限制长度,并转义后显示:

<?php
$q = $_GET['q'] ?? '';

if (!is_string($q)) {
    $q = '';
}

$q = trim($q);

// 这里按 UTF-8 字节数限制长度,不代表字符数。
if (strlen($q) > 200) {
    $q = '';
}

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>

<form method="get" action="/">
    <label for="q">搜索关键词</label>
    <input id="q" name="q" value="<?= h($q) ?>" maxlength="100">
    <button type="submit">搜索</button>
</form>

<p>当前关键词:<?= h($q) ?></p>

浏览器的 maxlength 是界面层限制,服务端仍要自行检查。示例使用 strlen 限制字节数,所以中文字符通常占多个字节;若业务要求限制 Unicode 字符数量,可以使用 mb_strlen($q, 'UTF-8'),但需要安装并启用 mbstring 扩展。长度限制用于控制输入规模,不等同于内容合法性校验。

如果后续把关键词用于搜索,应采用参数化 SQL,而不是直接拼进 SQL 字符串。若参数会被用作排序字段或表名,通常不能通过普通占位符绑定,应由程序从明确的允许列表中选择。

七、处理表单提交

表单通常通过 GET 或 POST 提交。GET 参数适合可分享、可收藏的查询条件;会改变服务器状态的操作通常使用 POST,但使用 POST 本身并不能防止伪造请求,也不代表数据已经加密。敏感传输需要 HTTPS。

下面是一个最小的 POST 表单示例,它检查请求方式、字段类型和内容长度,并在页面中安全显示提交结果:

<?php
$message = '';
$error = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $name = $_POST['name'] ?? '';

    if (!is_string($name)) {
        $error = '输入格式不正确。';
    } else {
        $name = trim($name);

        if ($name === '') {
            $error = '请填写姓名。';
        } elseif (strlen($name) > 200) {
            $error = '输入内容过长。';
        } else {
            $message = $name;
        }
    }
}

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>

<form method="post" action="">
    <label for="name">姓名</label>
    <input id="name" name="name" required maxlength="100">
    <button type="submit">提交</button>
</form>

<?php if ($error !== ''): ?>
    <p><?= h($error) ?></p>
<?php elseif ($message !== ''): ?>
    <p>收到:<?= h($message) ?></p>
<?php endif; ?>

这是用于说明流程的基础示例,不包含 CSRF 防护、数据库保存、重复提交控制或业务权限判断。涉及修改账户、发布内容、删除记录等操作时,应加入 CSRF token,并在服务端校验当前用户是否有权执行操作。仅检查请求来源、改用 POST 或隐藏按钮,都不能替代这些措施。

实际表单还要按照业务验证字段格式,例如邮箱、日期或枚举值。不要只依赖浏览器的 required、maxlength 等属性,因为客户端检查可以被绕过。对于密码,应使用 PHP 提供的 password_hash 和 password_verify 等专用函数处理,不能明文存储,也不应使用自行设计的普通哈希方案。

八、通过 PDO 查询数据库

生产网站的内容通常保存在数据库中。下面用 PDO 和 SQLite 演示创建表、写入一条记录并读取列表。运行环境必须启用 PDO 和 pdo_sqlite 扩展;数据库文件写在当前目录,因此该示例更适合本机演示,部署时应把数据文件放在 Web 根目录之外并正确设置访问权限。

<?php
$pdo = new PDO('sqlite:' . __DIR__ . '/site.sqlite');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

$pdo->exec(
    'CREATE TABLE IF NOT EXISTS articles (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        summary TEXT NOT NULL
    )'
);

// 演示用:确保至少有一条记录。
$count = (int) $pdo->query('SELECT COUNT(*) FROM articles')->fetchColumn();

if ($count === 0) {
    $insert = $pdo->prepare(
        'INSERT INTO articles (title, summary) VALUES (:title, :summary)'
    );
    $insert->execute([
        'title' => '数据库中的文章',
        'summary' => '这条内容由 PHP 从 SQLite 读取。',
    ]);
}

$statement = $pdo->prepare(
    'SELECT title, summary FROM articles ORDER BY id DESC LIMIT :limit'
);
$statement->bindValue(':limit', 20, PDO::PARAM_INT);
$statement->execute();
$articles = $statement->fetchAll(PDO::FETCH_ASSOC);

function h(string $value): string
{
    return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
?>

<ul>
    <?php foreach ($articles as $article): ?>
        <li>
            <h2><?= h($article['title']) ?></h2>
            <p><?= h($article['summary']) ?></p>
        </li>
    <?php endforeach; ?>
</ul>

把代码保存到 index.php 后,通过本地开发服务器访问即可。首次访问会创建数据库和演示数据;因为用记录数量决定是否插入,删除所有记录后再次访问会重新插入一条。正式应用不应依赖访问页面时自动初始化数据库,通常应通过迁移脚本或部署任务管理表结构和初始数据。

prepare 和 execute 把 SQL 结构与参数值分开,能够防止参数值被当作 SQL 语法的一部分,这是防止 SQL 注入的重要方式。参数化查询不是 HTML 转义的替代品:从数据库取出的标题仍需在输出到 HTML 时转义。

占位符通常用于绑定值,不能把表名或排序方向当作普通参数传入。需要动态排序时,可将用户选择映射到程序预先定义的列名,例如只允许 title 或 created_at,不要直接拼接任意输入。示例中的 LIMIT 使用整数绑定;不同数据库驱动对占位符的细节可能有差异,切换数据库时应查阅对应驱动文档并测试。

九、常见错误与边界

  • 直接输出用户输入:输入可能包含 HTML 标记或脚本内容。输出到 HTML 时使用适合该上下文的转义,并尽量不要把不可信数据插入脚本或样式。
  • 把数据库参数直接拼进 SQL:会增加 SQL 注入风险。查询值使用 PDO 参数化;动态表名、列名和排序方式通过允许列表控制。
  • 认为表单验证只要写在浏览器端:客户端代码可以被绕过。服务端必须检查类型、格式、长度和业务规则;前端验证主要改善使用体验。
  • 把 PHP 页面当成静态 HTML 打开:PHP 必须由 PHP 运行环境执行。直接通过文件管理器打开文件不会执行 PHP,需使用本地服务器或配置好的 Web 服务器。
  • 在页面中显示详细报错:开发时可以查看错误日志和诊断信息;生产环境应避免向访客展示路径、SQL、密钥等内部细节,并将错误写入受保护的日志。
  • 用开发服务器上线:php -S 便于本机测试,但不是完整的生产部署方案。上线还要考虑 HTTPS、Web 服务器配置、文件权限、备份、日志和更新策略。
  • 把配置和密钥提交到公开仓库:数据库密码、API 密钥等应通过受保护的环境配置提供,并限制读取权限;示例为简化而直接写明演示数据,不应照搬为生产凭据管理方式。

十、下一步怎么扩展

从单文件开始学习有助于看清请求、变量和模板的关系,但随着功能增加,建议逐步把路由、业务逻辑、数据访问和视图分开。还可以增加分页、搜索、登录授权、错误页和自动化测试,并使用 Composer 管理第三方依赖。升级结构时,应继续遵循同一条原则:输入先验证,数据库操作使用参数化查询,输出按目标上下文处理。

一个 PHP 页面并非越复杂越好。小型展示页可以直接用模板和少量 PHP 完成;涉及多人协作、复杂权限或大量业务规则时,使用成熟框架或内容管理系统通常更易维护。选择方案时应结合团队经验、部署环境和长期维护成本,而不是只看代码能否在本机运行。

© 版权声明

相关文章