PHP资料整理:从入门语法到实战开发的学习路线

PHP资料整理:从入门语法到实战开发的学习路线
PHP 是一种常用于服务器端开发的脚本语言。它可以嵌入 HTML,也可以通过命令行运行;配合数据库、Web 服务器和 Composer 等工具,能够开发网站、API、后台系统以及命令行任务。PHP 上手门槛不高,但真正写出可靠程序,不能只记住语法,还要理解输入校验、数据库操作、输出转义、错误处理和部署配置。
本文按“建立环境—掌握语法—读写数据—开发 Web 功能—测试与部署”的顺序组织。示例面向 PHP 8.x;实际项目应查看所用框架和依赖的版本要求,并在目标运行环境中验证代码。
一、PHP 适合做什么
PHP 常见于内容管理系统、电商网站、企业后台、表单处理、REST 风格接口和数据导入脚本。它与 MySQL、PostgreSQL 等数据库集成成熟,主流框架和托管环境较多,适合快速开发以 HTTP 请求为入口的服务。WordPress、Laravel、Symfony 等项目也让 PHP 在内容平台和业务应用中有广泛实践。
是否选 PHP,取决于团队经验、现有系统、部署平台、依赖生态和性能要求,不宜只按语言热门程度判断。它并不只适合小网站,也不意味着写 PHP 就会自动安全或易维护。复杂系统仍需清晰的模块边界、测试、监控和版本管理。
二、准备运行环境
先安装 PHP,再确认命令行能够找到它。不同操作系统的安装方式不同,可使用包管理器、官方发行包或开发环境工具。开发 Web 程序时还需要 Web 服务器或框架自带的开发服务器,以及项目实际使用的数据库扩展。安装完成后检查版本和扩展:
php -v
php -m
php --ini
第一个命令查看版本,第二个列出已加载模块,第三个显示配置文件位置。开发环境与线上环境可能加载不同的 php.ini,因此排查问题时不要只看本机的命令行配置。
建立 hello.php:
<?php
declare(strict_types=1);
echo "Hello, PHP!\n";
在终端执行 php hello.php 即可运行。也可以在项目目录运行 PHP 内置开发服务器:
php -S 127.0.0.1:8000
然后访问 http://127.0.0.1:8000/hello.php。内置服务器适合本地开发和简单验证,不是生产环境的 Web 服务器。实际部署通常由 Nginx 或 Apache 等接收请求,再交给 PHP-FPM 等运行 PHP 的服务。
三、基础语法与数据类型
1. 变量、常量与字符串
PHP 变量以美元符号 $ 开头,变量名区分大小写。字符串可以用单引号或双引号;双引号支持变量插值。常量适合保存不会在运行过程中重新赋值的配置标识,但密钥等敏感值不应硬编码在代码仓库里。
<?php
$name = "Lin";
$count = 3;
$enabled = true;
$message = "你好,{$name},当前有 {$count} 条记录";
echo $message;
const APP_NAME = "示例应用";
echo APP_NAME;
不要把“变量里恰好是数字”与整数混为一谈。表单、查询参数和 JSON 输入经常以字符串形式到达程序,应按预期类型校验或转换。强制转换可能掩盖无效输入,不应代替校验。
2. 常用类型与比较
常见类型包括整数、浮点数、字符串、布尔值、数组、对象、null 等。PHP 支持动态类型,也支持参数、返回值和属性类型声明。对业务代码使用类型声明,可以尽早暴露一部分错误,但不能代替对外部输入的验证。
<?php
function add(int $a, int $b): int
{
return $a + $b;
}
echo add(2, 5);
declare(strict_types=1) 可以影响当前文件中标量类型声明的参数类型强制转换行为;它不是全局校验开关,也不能保证数据库或用户输入符合业务要求。调用边界处仍要检查数据。
比较时,优先用 === 和 !== 判断类型和值都相同,避免松散比较带来的意外类型转换。处理金额时,避免依赖二进制浮点数进行精确财务计算;可使用以最小货币单位存储的整数,或采用明确处理精度的方案。
<?php
$input = "0";
var_dump($input == false); // 松散比较,可能产生不直观结果
var_dump($input === false); // 严格比较,类型也必须一致
3. 条件、循环与数组
条件分支使用 if、elseif、else;遍历数组常用 foreach。PHP 数组既可作为列表,也可作为键值映射,实际使用时应明确它表达的结构。
<?php
$users = [
["id" => 1, "name" => "安"],
["id" => 2, "name" => "宁"],
];
foreach ($users as $user) {
if ($user["id"] > 1) {
echo $user["name"] . PHP_EOL;
}
}
从数组读取数据前应考虑键是否存在。直接访问缺失键可能产生警告,并在后续版本或错误配置下更难排查。可用 array_key_exists() 判断键是否存在;若只关心值是否为非 null,可考虑 isset(),两者语义不同。
<?php
$data = ["name" => null];
var_dump(isset($data["name"])); // false
var_dump(array_key_exists("name", $data)); // true
4. 函数与作用域
把重复逻辑封装为函数,并通过参数明确依赖。函数应尽量职责单一,避免读取大量隐式全局变量。返回类型有助于说明函数结果;对于失败情形,可以采用异常、结果对象或明确的返回值约定,不要让调用方猜测。
<?php
function formatName(string $first, string $last): string
{
return trim($first . " " . $last);
}
echo formatName("Lin", "Chen");
四、读写请求数据:先分清来源,再做验证
Web 程序常从 $_GET、$_POST、$_COOKIE、$_FILES 和请求正文中读取数据。它们都属于不可信输入:客户端可以自行构造请求,不应因为页面上有输入框、前端做过校验,或参数名看起来可信,就跳过服务端验证。
例如,下面的代码只在参数缺失时使用默认值,并将结果转成整数。若业务要求参数必须是正整数,还要明确检查范围:
<?php
$rawId = $_GET["id"] ?? null;
$id = filter_var($rawId, FILTER_VALIDATE_INT);
if ($id === false || $id === null || $id < 1) {
http_response_code(400);
echo "无效的 id";
exit;
}
echo "请求的记录编号是 " . $id;
FILTER_VALIDATE_INT 只能帮助验证整数形式,不能判断这条记录是否存在、当前用户是否有权访问,也不能替代业务规则。校验通常包括格式、范围、必填性、长度和授权等不同层次。
读取 JSON 请求时,要处理正文为空或格式错误的情况:
<?php
$body = file_get_contents("php://input");
$data = json_decode($body, true);
if (!is_array($data) || json_last_error() !== JSON_ERROR_NONE) {
http_response_code(400);
echo "JSON 格式无效";
exit;
}
echo json_encode(
["received" => true],
JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR
);
对外接口还应限制请求体大小、字段数量和字符串长度,并设置合理的请求处理超时。PHP 与 Web 服务器的限制项会因版本、配置和部署方式而异;应检查实际生效配置,而不是假设所有环境都有相同默认值。
五、输出 HTML 与安全边界
把不可信字符串直接拼进 HTML,可能造成跨站脚本攻击(XSS)。在 HTML 文本或适用的 HTML 属性值上下文中,可使用 htmlspecialchars() 转义特殊字符,并明确字符编码:
<?php
$name = $_GET["name"] ?? "";
$safeName = htmlspecialchars(
$name,
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
echo "<p>你好,{$safeName}</p>";
转义应发生在输出边界,且要匹配具体上下文。HTML 文本、HTML 属性、JavaScript、CSS、URL 都有不同规则;htmlspecialchars() 不是对所有上下文都通用的“安全化”方法。不要把它用于防 SQL 注入,也不要先把数据库内容永久转义再保存。应按用途存储原始业务值,呈现时再正确编码。
如果输出是链接地址,除 HTML 属性转义外,还要校验协议和允许的目标。用户提供的内容若要作为富文本展示,应使用经过审查的 HTML 清理方案,而不是仅靠字符替换。
六、数据库访问:使用 PDO 和预处理语句
PDO 提供统一的数据库访问接口。先在 PHP 环境中启用对应数据库驱动,再建立连接。下面是 MySQL 示例;数据库名、用户名和密码应来自环境配置或安全的密钥管理方式,不能提交真实凭据到公开仓库。
<?php
$dsn = "mysql:host=127.0.0.1;dbname=demo;charset=utf8mb4";
$pdo = new PDO($dsn, getenv("DB_USER"), getenv("DB_PASSWORD"), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$stmt = $pdo->prepare(
"SELECT id, name FROM users WHERE id = :id"
);
$stmt->execute(["id" => 1]);
$user = $stmt->fetch();
if ($user === false) {
echo "未找到记录";
} else {
echo htmlspecialchars(
$user["name"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
);
}
预处理语句用于把数据值与 SQL 结构分开,减少因数据拼接造成的 SQL 注入风险。它并不能自动保护所有动态 SQL:表名、列名和排序方向通常不能像普通值一样用占位符绑定。动态标识符应从代码内的白名单选择,而不是直接接受用户输入。
<?php
$allowedSorts = [
"name" => "name",
"created" => "created_at",
];
$requestedSort = $_GET["sort"] ?? "name";
$sortColumn = $allowedSorts[$requestedSort] ?? "name";
$sql = "SELECT id, name FROM users ORDER BY " . $sortColumn;
$rows = $pdo->query($sql)->fetchAll();
上例中拼接的列名来自固定白名单,而不是原样使用请求参数。数据库错误信息也不应直接返回给普通用户;异常日志中可记录必要诊断信息,但要避免把密码、令牌等敏感数据写入日志。
涉及多条写操作时,考虑使用事务,保证成功时一起提交、失败时一起回滚:
<?php
$pdo->beginTransaction();
try {
$debit = $pdo->prepare(
"UPDATE accounts SET balance = balance - :amount WHERE id = :id"
);
$debit->execute(["amount" => 100, "id" => 1]);
$credit = $pdo->prepare(
"UPDATE accounts SET balance = balance + :amount WHERE id = :id"
);
$credit->execute(["amount" => 100, "id" => 2]);
$pdo->commit();
} catch (Throwable $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
throw $e;
}
事务是否可用、隔离行为如何,取决于数据库、存储引擎和配置。真实转账等业务还需要检查余额、并发更新、幂等性和授权;仅有事务并不足以保证业务正确。
七、表单、会话与跨站请求伪造
会话可用于在多个请求之间保存登录状态。PHP 常通过会话标识符关联服务端会话数据。登录成功后应更新会话标识,Cookie 应根据部署情况设置合适的 Secure、HttpOnly 和 SameSite 属性;HTTPS 网站应启用安全传输。会话配置应在调用 session_start() 前完成。
Cookie 属性不能替代身份验证、权限检查或 CSRF 防护。对于会改变状态的请求,如修改资料、创建订单或删除数据,不能只依赖“用户已经登录”。常见做法是生成不可预测的 CSRF 令牌,将其放入表单,并在服务端验证提交值。令牌要与用户会话关联,不能只靠隐藏字段就认为安全。
<?php
session_start();
if (empty($_SESSION["csrf_token"])) {
$_SESSION["csrf_token"] = bin2hex(random_bytes(32));
}
if ($_SERVER["REQUEST_METHOD"] === "POST") {
$submitted = $_POST["csrf_token"] ?? "";
if (!is_string($submitted)
|| !hash_equals($_SESSION["csrf_token"], $submitted)) {
http_response_code(403);
exit("请求校验失败");
}
// 在这里进行授权检查和业务处理
}
?>
<form method="post">
<input
type="hidden"
name="csrf_token"
value="<?= htmlspecialchars(
$_SESSION["csrf_token"],
ENT_QUOTES | ENT_SUBSTITUTE,
"UTF-8"
) ?>"
>
<button type="submit">提交</button>
</form>
示例使用了 PHP 结束标签,因为随后需要展示 HTML;纯 PHP 文件通常省略结束标签,可减少意外输出空白导致响应头无法发送的问题。真实应用还要在处理前确认用户有权执行相应操作,并在成功后按业务需要更新令牌或重定向。
八、文件上传的限制与安全做法
上传文件不能只根据客户端提交的文件名或 $_FILES 中的 MIME 字段决定是否可信。应检查上传错误码、服务端检测到的文件类型、扩展名白名单和大小,并生成服务端文件名。上传目录应避免让用户上传的脚本被服务器执行;私有文件更适合存放在 Web 根目录之外,由应用鉴权后提供下载。
<?php
if (!isset($_FILES["avatar"])) {
http_response_code(400);
exit("缺少文件");
}
$file = $_FILES["avatar"];
if ($file["error"] !== UPLOAD_ERR_OK) {
http_response_code(400);
exit("上传失败");
}
$maxBytes = 2 * 1024 * 1024;
if ($file["size"] > $maxBytes) {
http_response_code(400);
exit("文件过大");
}
$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($file["tmp_name"]);
$allowed = [
"image/jpeg" => "jpg",
"image/png" => "png",
];
if (!isset($allowed[$mime])) {
http_response_code(400);
exit("不支持的文件类型");
}
$targetDir = __DIR__ . "/uploads";
if (!is_dir($targetDir)) {
mkdir($targetDir, 0750, true);
}
$filename = bin2hex(random_bytes(16)) . "." . $allowed[$mime];
$target = $targetDir . "/" . $filename;
if (!move_uploaded_file($file["tmp_name"], $target)) {
http_response_code(500);
exit("无法保存文件");
}
echo "上传成功";
代码中的大小检查只是应用层检查,文件在到达 PHP 前还可能受 Web 服务器、PHP 配置和请求体限制影响。应同时核对 upload_max_filesize、post_max_size、Web 服务器请求体上限等实际配置;它们的值和生效范围由部署环境决定。生产环境还应限制上传频率和总存储量,并评估恶意内容、压缩炸弹及图片处理库的风险。若要接收 SVG 等可能包含主动内容的格式,不能简单按图片类型放行。
九、错误处理、日志与调试
开发时可以开启详细错误报告,线上则不应把堆栈、SQL、文件路径或环境变量展示给访问者。可配置错误写入日志,由监控或日志系统收集。对可预期的业务失败,返回清楚的状态和错误信息;对意外异常,记录上下文后返回通用错误响应。
<?php
try {
// 执行业务操作
echo "处理完成";
} catch (Throwable $e) {
error_log((string) $e);
http_response_code(500);
echo "服务器暂时无法完成请求";
}
不要把所有异常都静默吞掉,也不要把原始异常内容无条件返回给客户端。日志应能帮助定位问题,但必须避免记录密码、完整支付信息、会话令牌等敏感内容。调试工具可以帮助开发,但不应在生产环境暴露调试页面或交互式控制台。
十、项目结构、Composer 与代码质量
PHP 项目通常通过 Composer 管理依赖和自动加载。开始学习时,可以先读懂 composer.json、依赖锁定文件和命名空间,再逐步采用项目需要的库。安装依赖前查看项目维护情况、许可证、安全更新和版本兼容性;锁定文件应按团队约定纳入版本管理,以便不同环境安装一致的依赖版本。
composer init
composer require monolog/monolog
一个小型应用也可以按职责拆分目录,例如入口、路由、业务服务、数据访问和模板。不要把所有代码堆在单个脚本里,也不要为“看起来专业”过早构造复杂架构。路由层负责解析请求,业务层执行规则,数据访问层处理持久化,模板层负责呈现,边界清楚后更容易测试和替换实现。
代码检查可以从语法检查开始:
php -l src/Service.php
随后为关键业务补充自动化测试。测试应覆盖正常输入、边界值、无权限访问、缺失字段、数据库异常和重复请求等情况。测试数据库与生产数据库要隔离,测试代码不要连接真实用户数据。
十一、部署前检查清单
- 生产环境使用受支持的 PHP 版本,并安装项目需要的扩展。
- Web 根目录只暴露公开入口和静态资源,配置文件、依赖源码和上传私有文件不应直接公开。
- 通过环境配置管理数据库凭据和第三方密钥,不把秘密提交到代码仓库。
- 关闭面向用户的详细错误输出,开启适当日志、监控和备份。
- 使用 HTTPS,按实际架构配置 Cookie 安全属性、代理信任和会话管理。
- 对所有写操作检查登录状态、资源归属和业务权限;验证输入并按输出上下文转义。
- 限制上传、请求体、执行时间和资源消耗,并结合 Web 服务器配置验证实际效果。
- 部署前运行测试、语法检查和依赖安全检查;上线后验证健康状态和回滚方案。
十二、建议的学习路线
- 熟悉运行方式。 学会使用终端运行 PHP,查看版本、扩展和配置;分清 CLI 与 Web 请求环境。
- 掌握语言基础。 练习变量、类型、条件、循环、数组、函数、异常和文件操作,理解严格比较与类型声明的作用。
- 学习 HTTP 与 HTML 输出。 认识请求方法、状态码、表单、JSON、Cookie 和会话,练习服务端验证与上下文转义。
- 连接数据库。 使用 PDO 完成增删改查、预处理语句和事务,理解白名单、索引、并发与权限检查。
- 完成小项目。 例如做一个带登录的任务清单:包含用户授权、数据校验、分页、CSRF 防护、错误处理和测试。
- 再学习框架与工程实践。 阅读 Composer、自动加载、依赖注入、路由、配置管理、测试和部署。理解基础之后再使用框架,更容易判断抽象的边界。
常见错误速查
- 把输入当成可信数据:前端验证可改善体验,但服务端必须重新验证格式、范围和权限。
- 把数据拼进 SQL:查询值使用预处理语句;动态表名、列名等从白名单选择。
- 认为转义一次就处处安全:HTML、JavaScript、URL 等上下文不同,应在对应输出位置采用合适编码。
- 假设配置没有上限:请求大小、上传大小、执行时间和内存限制由多个组件共同影响,需检查实际环境。
- 错误信息直接显示给用户:详细信息写入受控日志,客户端返回适当状态和通用提示。
- 把认证当作授权:用户已登录不等于有权访问某条记录,服务端还要检查资源归属与操作权限。
- 只靠本机验证:命令行和 Web 运行环境可能配置不同;上线前应在接近生产的环境测试。
学习 PHP 的关键,不是尽可能多地背函数,而是把语法放进完整的请求链路中理解:请求数据从哪里来、如何验证、怎样访问数据库、如何安全输出、发生错误时如何处理,以及代码最终如何运行在目标环境中。掌握这条链路之后,再学习具体框架、库和业务领域,会更容易判断工具适不适合当前问题,也更容易写出可维护、可测试的程序。




