十天学会php怎么用:从准备到完成的实操步骤

星座3小时前发布 jdkjadmin
0 00
十天学会php怎么用:从准备到完成的实操步骤

十天学会php怎么用:从准备到完成的实操步骤

PHP 常用于生成动态网页、处理表单、开发内容管理系统和提供 Web API。它可以嵌入 HTML,也能在命令行运行。本文用十天把基础知识串成一个小项目:用户提交任务,任务保存在数据库中,页面可以新增、查看和删除自己的任务。

十天不是掌握 PHP 的保证,而是一份循序渐进的练习计划。每天建议投入一至两小时,先运行代码,再有意识地修改和观察结果。示例面向 PHP 8.x,使用内置 Web 服务器和 SQLite,适合本地学习;正式上线时还需要配置生产服务器、HTTPS、备份和监控。

先明确适用场景和边界

PHP 适合服务端网页渲染、表单处理、数据库读写、内容管理、后台系统及 HTTP 接口。它拥有成熟的 Web 框架和扩展生态,可以按需从简单脚本逐步发展成完整应用。学习 PHP 不等于只学语法:实际项目还要理解 HTTP、数据库、错误处理、输入校验和部署。

PHP 代码通常运行在服务器端,浏览器收到的是服务器返回的 HTML、JSON 等内容,而不是 PHP 源码。PHP 内置服务器方便本地开发和演示,但不应当直接作为面向公网的生产服务器。高并发、复杂业务或长期维护的项目,也要评估架构、框架、缓存、队列和团队已有技术栈;语言本身不能替代这些设计。

准备环境:装好 PHP 并验证

安装 PHP 时优先选择仍受维护的版本,并确认命令行可以使用 PHP。macOS、Linux、Windows 可以通过各自的软件包管理器、官方发行包或集成开发环境安装。不同系统的安装方式不同,安装后先打开终端验证:

php -v

输出应显示 PHP 版本信息。再检查 SQLite 扩展是否可用:

php -m

列表中应能找到 PDO、pdo_sqlite 或 sqlite3。如果缺少扩展,需要按当前系统的 PHP 安装方式启用它。后文的数据库示例依赖 PDO SQLite。生产环境使用 MySQL 或 PostgreSQL 时,应安装对应 PDO 驱动。

创建练习目录,例如 php-ten-days,在其中新建 index.php。PHP 文件通常以 <?php 开始;纯 PHP 文件末尾一般不必写关闭标签,以免多余空白字符影响响应头。

<?php
echo "PHP 已经运行\n";

进入目录并启动本地开发服务器:

php -S 127.0.0.1:8000

在浏览器访问 http://127.0.0.1:8000。内置服务器适合本机开发和临时演示,不具备完整生产 Web 服务器的安全与性能配置;不要为了让同一网络中的其他设备访问而随意绑定公网地址。

第一天:认识 PHP 的基本语法

PHP 变量以美元符号开头,语句通常以分号结束。字符串可用单引号或双引号;双引号字符串会解析变量和部分转义序列。字符串拼接使用点号:

<?php
$name = "小林";
$days = 10;

echo "你好,{$name}!\n";
echo "学习计划共 " . $days . " 天。\n";

类型包括字符串、整数、浮点数、布尔值、数组、对象、null 等。PHP 会进行一定的类型转换,但新手不应依赖隐式转换处理不可信输入。比较时优先使用严格比较运算符 === 和 !==,避免松散比较带来的意外结果。

<?php
$count = 3;

if ($count === 3) {
    echo "数量是整数 3";
} else {
    echo "类型或数值不匹配";
}

练习:把变量改成不同的值,分别用 == 和 === 比较,观察数值与类型的差异。完成这一步后,能读懂赋值、条件判断和输出,就可以进入常用数据结构。

第二天:数组、循环与函数

PHP 的数组既能按数字索引使用,也能按字符串键保存关联数据。遍历数组通常使用 foreach。函数能把重复逻辑集中起来;参数和返回值可以声明类型,让代码意图更清晰。

<?php
$tasks = [
    ["id" => 1, "title" => "安装 PHP"],
    ["id" => 2, "title" => "练习数组"],
];

function formatTask(string $title): string
{
    return "待办:" . $title;
}

foreach ($tasks as $task) {
    echo formatTask($task["title"]) . "\n";
}

数组键不存在时直接读取可能产生警告或错误。读取外部数据前,应检查键是否存在,或使用空合并运算符 ?? 提供默认值。

<?php
$nickname = $_GET["nickname"] ?? "访客";
echo $nickname;

上例只是展示默认值写法,不应把用户提供的内容直接输出到网页。对用户输入进行 HTML 页面输出时,还要做适当转义,后面会专门处理。

第三天:理解请求、响应与表单

浏览器通过 HTTP 请求服务器。PHP 可以从超全局变量读取请求数据:$_GET 常用于查询参数,$_POST 常用于表单提交。它们是外部输入,不能因为“来自表单”就默认可信。

新建一个表单示例,保存为 form.php。它接受昵称并在页面上显示。由于昵称可能包含 HTML,输出时使用 htmlspecialchars 转义:

<?php
$nickname = null;

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $nickname = trim($_POST["nickname"] ?? "");
}
?>
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<title>表单练习</title>
<body>
    <form method="post">
        <label>昵称
            <input name="nickname" maxlength="50" required>
        </label>
        <button type="submit">提交</button>
    </form>
    <?php if ($nickname !== null): ?>
        <p>你好,<?= htmlspecialchars(
            $nickname,
            ENT_QUOTES | ENT_SUBSTITUTE,
            "UTF-8"
        ) ?></p>
    <?php endif; ?>
</body>
</html>

这里的 maxlength 和 required 是浏览器端的便利约束,用户可以绕过它们直接构造请求。服务器端仍应自行验证输入的类型、长度和业务规则。htmlspecialchars 适用于把文本放入 HTML 文本或适当的属性上下文;它不是所有输出场景的通用安全函数。JavaScript、CSS、URL 等上下文需要各自正确的编码和处理方式。

第四天:验证输入并处理请求方法

验证的目标是确认输入符合应用规则,而不是只把它“清理干净”。以任务标题为例,要求它是字符串、去掉首尾空白后不为空,并限制长度。PHP 字符串函数的长度行为要留意:strlen 计算字节数,处理 UTF-8 中文字符时,字符数校验应使用 mb_strlen,并确保启用了对应扩展。

<?php
$title = $_POST["title"] ?? null;

if (!is_string($title)) {
    http_response_code(400);
    exit("标题格式错误");
}

$title = trim($title);

if ($title === "" || mb_strlen($title, "UTF-8") > 100) {
    http_response_code(400);
    exit("标题不能为空,且不能超过 100 个字符");
}

如果环境没有 mbstring 扩展,就应安装或启用它,或者使用明确适合当前字符集的校验方案;不要直接把 strlen 当作中文字符数。真实应用还应处理非字符串值、超长请求、无效编码和业务层限制,并在错误响应中避免泄露内部细节。

还要区分客户端校验与服务端校验:前者改善使用体验,后者才是业务规则的权威检查。对数字、日期、枚举值等输入,最好先验证格式,再转换为对应类型。

第五天:用 PDO 和 SQLite 保存任务

数据库让数据在请求结束后仍能保存。PDO 提供统一的数据库访问接口,预处理语句可以把 SQL 结构与参数值分开。下面的代码会创建 SQLite 数据库文件和任务表。SQLite 文件路径相对于当前工作目录;实际项目中应选定固定的数据目录,并设置合适的文件权限。

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

$pdo->exec(
    "CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
    )"
);

$statement = $pdo->prepare(
    "INSERT INTO tasks (title) VALUES (:title)"
);
$statement->execute(["title" => "学习 PDO"]);

echo "任务已保存";

预处理语句应当用于用户提供的值。不要把用户输入直接拼接进 SQL 字符串,否则可能造成 SQL 注入。参数占位符通常不能用来代替表名、列名或 SQL 关键字;这些结构若需动态选择,应通过固定白名单映射,而不是直接接收任意输入。

例如查询并显示任务:

<?php
$statement = $pdo->query(
    "SELECT id, title, created_at FROM tasks ORDER BY id DESC"
);
$tasks = $statement->fetchAll(PDO::FETCH_ASSOC);

foreach ($tasks as $task) {
    echo htmlspecialchars(
        $task["title"],
        ENT_QUOTES | ENT_SUBSTITUTE,
        "UTF-8"
    ) . "\n";
}

PDO 的异常模式便于发现数据库错误,但生产环境不应把异常堆栈或 SQL 细节原样展示给访客。应记录必要的服务端日志,并向用户返回通用错误信息。数据库文件也要加入适当的备份策略;SQLite 适合不少小型应用和本地工具,但并非所有并发、运维或多主机场景的理想选择。

第六天:完成一个可运行的待办页面

把页面、数据库连接和增删操作放在一个小文件中,能快速理解请求的完整流程。下面的示例提供任务新增、列表和删除功能。它只适合本地练习:删除操作使用 POST,但还没有加入 CSRF 防护、用户登录和多用户数据隔离,不能直接当作生产应用使用。

<?php
declare(strict_types=1);

$pdo = new PDO("sqlite:" . __DIR__ . "/tasks.sqlite");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec(
    "CREATE TABLE IF NOT EXISTS tasks (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
    )"
);

function escapeHtml(string $value): string
{
    return htmlspecialchars(
        $value,
        ENT_QUOTES | ENT_SUBSTITUTE,
        "UTF-8"
    );
}

$error = null;

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $action = $_POST["action"] ?? "";

    if ($action === "add") {
        $title = $_POST["title"] ?? null;

        if (!is_string($title)) {
            $error = "标题格式错误";
        } else {
            $title = trim($title);

            if ($title === "" || mb_strlen($title, "UTF-8") > 100) {
                $error = "标题不能为空,且不能超过 100 个字符";
            } else {
                $statement = $pdo->prepare(
                    "INSERT INTO tasks (title) VALUES (:title)"
                );
                $statement->execute(["title" => $title]);
                header("Location: /");
                exit;
            }
        }
    }

    if ($action === "delete") {
        $id = filter_input(INPUT_POST, "id", FILTER_VALIDATE_INT);

        if ($id === false || $id === null || $id < 1) {
            $error = "任务编号无效";
        } else {
            $statement = $pdo->prepare(
                "DELETE FROM tasks WHERE id = :id"
            );
            $statement->execute(["id" => $id]);
            header("Location: /");
            exit;
        }
    }
}

$tasks = $pdo->query(
    "SELECT id, title FROM tasks ORDER BY id DESC"
)->fetchAll(PDO::FETCH_ASSOC);
?>
<!doctype html>
<html lang="zh-CN">
<meta charset="utf-8">
<title>我的待办</title>
<body>
    <h1>我的待办</h1>

    <?php if ($error !== null): ?>
        <p><?= escapeHtml($error) ?></p>
    <?php endif; ?>

    <form method="post">
        <input type="hidden" name="action" value="add">
        <label>任务
            <input name="title" maxlength="100" required>
        </label>
        <button type="submit">添加</button>
    </form>

    <ul>
    <?php foreach ($tasks as $task): ?>
        <li>
            <?= escapeHtml($task["title"]) ?>
            <form method="post" style="display:inline">
                <input type="hidden" name="action" value="delete">
                <input type="hidden" name="id"
                       value="<?= (int) $task["id"] ?>">
                <button type="submit">删除</button>
            </form>
        </li>
    <?php endforeach; ?>
    </ul>
</body>
</html>

将代码保存为 index.php,在项目目录运行 php -S 127.0.0.1:8000,访问本地地址后添加和删除任务。代码使用了预处理语句和 HTML 转义,但仍存在重要边界:任何访问者都能操作同一份数据;删除请求可能被第三方页面诱导;没有用户权限控制,也没有完善的错误页面。这些缺失不是靠改一个按钮就能解决的。

第七天:会话、登录状态与密码

HTTP 请求彼此独立。PHP 会话可在多个请求间保存少量服务端状态,常用于登录状态和一次性提示。使用会话前调用 session_start();它可能发送响应头,因此要在输出 HTML 或空白之前执行。

<?php
session_start();

$_SESSION["user_id"] = 42;

if (isset($_SESSION["user_id"])) {
    echo "已登录";
}

上例只是展示会话接口,固定写入用户编号不构成真正的登录系统。真实登录流程应从数据库验证用户,成功后重新生成会话 ID,并按部署环境设置安全的会话 Cookie,例如启用 HTTPS 时使用 Secure,并考虑 HttpOnly 和合适的 SameSite 策略。退出登录时应清理服务端会话,并按 PHP 文档正确处理会话 Cookie。

密码不能以明文保存,也不要使用 MD5 或 SHA-1 直接存密码。PHP 提供专门的密码哈希接口:

<?php
$password = "用户提供的密码";
$hash = password_hash($password, PASSWORD_DEFAULT);

// 注册时保存 $hash 到数据库;登录时读取数据库中的哈希
$valid = password_verify($password, $hash);

if ($valid) {
    echo "密码匹配";
}

PASSWORD_DEFAULT 的具体算法可能随 PHP 版本变化,数据库字段应留出足够长度,例如使用可容纳较长哈希字符串的字段。哈希不是加密:通常无法还原原密码。还要设置合理的密码长度规则、登录失败限制和账户恢复流程。登录与权限控制需要服务器端完成,不能仅靠隐藏页面按钮实现。

第八天:理解常见 Web 安全风险

跨站脚本(XSS)

把用户输入输出到 HTML 前,应针对输出上下文正确编码。普通文本节点可使用前述 htmlspecialchars。不要把用户输入直接插入脚本、事件处理属性或 CSS;这类场景需要避免动态拼接或采用更严格的设计。

SQL 注入

数据库参数值通过 PDO 预处理语句绑定。预处理不能替代权限控制,也不能自动保护动态表名、列名等 SQL 结构。动态结构应使用固定白名单,并限制数据库账户权限。

跨站请求伪造(CSRF)

对会改变状态的请求,例如添加、删除、改密码,应考虑 CSRF 防护。仅把表单改成 POST 不足以防护 CSRF。常见方案是在服务端生成不可预测的令牌,放进表单并在提交时校验,同时设置合理的 Cookie 策略。令牌应与会话关联,不能把令牌当作授权凭据;仍需检查用户是否有权执行操作。

文件上传

上传文件不能只信任浏览器传来的文件名或 MIME 类型。应检查错误码、大小、允许的文件类型,使用服务端实际识别结果辅助判断;将文件保存到不可直接执行 PHP 的位置,采用随机生成的存储名,并避免覆盖已有文件。上传大小还受 PHP 配置、Web 服务器限制和应用自身校验共同影响,具体值要结合部署配置检查。

密钥和错误信息

数据库密码、API 密钥等机密不要提交到公开代码仓库,也不要写入可被 Web 直接访问的文件。开发环境可以显示详细错误以便排查,生产环境应关闭面向访客的详细错误展示,把必要信息写入受控日志,并配置日志轮转与访问权限。

第九天:调试、错误处理与代码管理

遇到“页面空白”时,先检查 PHP 错误日志、终端输出和浏览器网络面板。确认 PHP 版本、扩展、文件路径、数据库权限及请求方法。开发时可以打开错误报告,但不要把详细错误配置直接照搬到公网生产环境。

<?php
error_reporting(E_ALL);
ini_set("display_errors", "1");

这段配置只适合本地调试。生产环境应让错误进入服务端日志,并避免将堆栈、路径、SQL 和配置细节发送给用户。数据库连接、网络调用等可能失败的操作可用 try/catch 处理;捕获异常后要记录上下文,但不要把密码、令牌等秘密写进日志。

<?php
try {
    $pdo = new PDO("sqlite:" . __DIR__ . "/tasks.sqlite");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $exception) {
    error_log($exception->getMessage());
    http_response_code(500);
    exit("服务暂时不可用");
}

练习项目可以使用 Git 管理版本。提交代码前检查是否包含数据库文件、日志、私钥和本地配置。随着项目变大,可以使用 Composer 管理依赖与自动加载,采用框架组织路由、控制器、模板和数据库迁移。不要为了“用了框架”而忽略输入校验、权限检查和依赖更新。

第十天:整理项目并部署前检查

十天结束时,至少应能解释一条完整数据流:浏览器发请求,PHP 检查请求方法和参数,应用执行业务规则,PDO 访问数据库,PHP 对输出做上下文合适的编码,再返回响应。把练习项目整理成清晰结构,并为新增、删除、无效输入和数据库故障分别测试。

部署前逐项检查:

  • 使用仍受维护的 PHP 版本,并按需要安装扩展。
  • 使用正式 Web 服务器或托管平台;不要把 PHP 内置服务器作为公网生产服务。
  • 启用 HTTPS,检查会话 Cookie 和代理、负载均衡相关配置。
  • 关闭面向访客的详细错误显示,设置可访问范围受控的日志。
  • 检查数据库权限、备份与恢复流程,确保备份可实际还原。
  • 所有写操作都做服务端校验、权限检查和 CSRF 防护。
  • 所有 SQL 值使用参数绑定,所有用户可控的页面输出按上下文编码。
  • 检查上传限制、请求大小、超时及资源限制;这些值取决于 PHP 和服务器配置,不存在适用于所有站点的统一数值。
  • 更新依赖并检查许可、漏洞公告和不再维护的组件。

常见错误与排查顺序

浏览器提示无法连接

确认命令行是否仍在运行 PHP 内置服务器,端口是否正确,浏览器地址是否与启动地址一致。如果端口被占用,选择一个空闲端口。不要把“服务能在本机启动”误认为已经完成公网部署。

找不到 PDO SQLite 或 mbstring

确认当前运行 PHP 的版本与启用扩展的配置一致。有些机器同时装有多套 PHP,命令行和 Web 服务器可能使用不同配置。运行 php -m 查看命令行扩展;Web 环境则要查对应服务的 PHP 配置与日志。

数据库无法写入

检查数据库文件所在目录是否存在,以及运行 PHP 的系统用户是否有适当读写权限。不要为了图省事把整个项目目录设置成所有用户都可写。生产环境还要确认数据库文件不会通过 Web 静态路径直接下载。

中文长度判断不正确

strlen 统计字节数,UTF-8 中文字符通常占多个字节。字符数规则应使用启用的多字节字符串扩展,或选用明确适配的校验方法,并在服务端统一字符编码。

修改 PHP 后仍看到旧页面

先检查是否访问了正确文件和目录,再查看服务端日志。确认代码没有语法错误,必要时重新启动开发服务器。生产环境可能存在 OPcache 或代理缓存,需要按实际配置处理缓存刷新。

接下来学什么

完成待办应用后,可以为每个用户建立账户,并在任务表中增加用户编号;每次读取、修改或删除任务时,都要在数据库查询中同时限制所属用户,不能只依赖前端传来的编号。随后可以练习分页、数据库迁移、自动化测试、REST API 和表单 CSRF 令牌。

当项目涉及多用户、支付、个人信息或公开文件上传时,应把安全设计、访问控制、数据保护和运维作为项目的一部分,而不是上线前临时补丁。掌握 PHP 的关键,是能读懂请求如何变成数据、数据如何保存、页面如何安全返回,并知道示例代码适用到哪里、还缺少什么。

© 版权声明

相关文章