简介:这份《PHP从入门到精通完整笔记》面向零基础或初学Web开发的人员,以HTML、CSS与PHP三门技术为主线,帮助读者建立从静态页面到动态网页的完整认知。笔记共301页,内容安排上先用22讲打牢HTML与CSS基础,再以第23至149讲系统讲解PHP核心知识,涵盖数据类型、条件与循环、数组、函数、类与对象,以及与MySQL数据库交互等常见应用。其中HTML部分讲解标记语言运行原理、文档基本结构、超链接与图片插入、表格布局与合并单元格、字符实体等要点,并穿插html与htm后缀区别一类面试题。资源包为1个pdf文件,体积约2.14MB,单文件便于随身携带、离线查阅与打印标注。目前已有1083人学习下载,适合作为课堂笔记式的入门参照,也可用于复习查漏补缺。
1. PHP 从入门到精通完整笔记 301 页:先把学习路径和验证方式定下来
很多人拿到一份 301 页的 PHP 笔记,第一反应是从第一页翻到最后一页,结果翻到运算符那一章就卡住了,因为前面全是概念,运行环境还没搭起来。这类笔记的价值不在于“读完”,而在于把它当成一份可检索的对照表:语法章节用来查写法,实操章节用来抄命令,安全章节用来避坑。PHP 入门门槛低,但真正让人卡住的往往不是语法本身,而是环境配置、文件上传、序列化中文处理、接口返回结构这些具体问题。所以看这类材料比较合理的顺序是:先让一个 hello world 跑起来,再回到语法章节逐个对号入座,最后用一个小项目把队列、缓存、图片生成这些场景串起来。它适合零基础跟着走,也适合写过几年 PHP、但环境一换就出问题的人拿来查漏。
2. Windows 与 Docker 两套 PHP 环境:从 phpstudy 到 nginx 配置的最小可跑步骤
2.1 为什么先定环境再学语法
PHP 代码在不同环境下的表现差异很大。同一段上传处理,在 Apache 下和 nginx 下可能一个正常一个报 500。所以学习路径的第一步不是背语法,而是确定一套自己能反复启动、能看日志、能改配置的环境。常见做法是两条路:Windows 本地手动搭配 nginx 加 PHP,或者用 Docker 镜像做容器化。前者适合课设和学习阶段,后者适合团队协作和部署一致性。本节把两条路的最小可用步骤写清楚。
2.2 Windows 下 nginx + PHP 的最小配置
在 Windows 10 上手动搭 nginx 加 PHP,先把 nginx 和 PHP 的 Windows 版本解压到没有中文和空格的路径。PHP 目录下把 php.ini-development 复制为 php.ini,然后改几个关键项。
; php.ini 关键项 extension_dir = "ext" extension=mysqli extension=curl extension=fileinfo cgi.fix_pathinfo=0 date.timezone = Asia/Shanghai然后是 nginx 的 server 块,重点是 fastcgi_pass 指向 PHP 的 9000 端口,以及 SCRIPT_FILENAME 的拼接方式。
server { listen 80; server_name localhost; root D:/webroot; index index.php index.html; location / { try_files $uri $uri/ /index.php?$query_string; } location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } }配置里cgi.fix_pathinfo=0属于安全项,防止不存在的 PHP 文件被错误解析成脚本。SCRIPT_FILENAME如果不带$document_root而写死绝对路径,换目录时就会找不到文件。启动顺序是先开 PHP 的 FastCGI 进程,再开 nginx。
# Windows 下启动 PHP FastCGI php-cgi.exe -b 127.0.0.1:9000 -c php.ini # 启动 nginx start nginx.exe # 改了配置之后重新加载 nginx.exe -s reload在 webroot 下放一个 index.php,内容写<?php phpinfo();,访问 localhost 能看到 PHP 信息页,说明环境通了。看到 502 就先检查 php-cgi 是否在监听 9000;看到浏览器直接下载文件,说明 nginx 没把 .php 交给 FastCGI 处理。
2.3 用 Docker 打包 PHP 镜像
更干净的做法是用 Docker。写一个 Dockerfile,基于 php:fpm 镜像,把扩展装好,再配一个 nginx 容器做反向代理。
FROM php:8.2-fpm RUN docker-php-ext-install mysqli pdo pdo_mysql WORKDIR /var/www/html COPY . /var/www/html RUN chown -R www-data:www-data /var/www/htmldocker-compose.yml 里把 php 和 nginx 拆成两个服务,通过卷挂载同一份代码目录。
services: php: build: . volumes: - ./src:/var/www/html nginx: image: nginx:alpine ports: - "8080:80" volumes: - ./src:/var/www/html - ./nginx.conf:/etc/nginx/conf.d/default.conf depends_on: - php这套结构的好处是环境可复制,换一台机器只要docker compose up -d。代价是调试稍微绕一点,日志要分容器看。
| 方式 | 启动速度 | 调试便利 | 适合场景 |
|---|---|---|---|
| Windows 手动 | 慢,需逐个进程启动 | 日志直观 | 学习、课设 |
| phpstudy 类集成包 | 快 | 面板操作 | 快速验证 |
| Docker | 首次慢,之后快 | 需分容器看日志 | 协作、部署 |
提示:先确认
php -v能输出版本,再确认nginx -t配置语法通过,最后才去访问页面。顺序反了,排错会多花不少时间。
3. PHP 基本语法到数组对象:运算符、序列化中文与接口返回结构
3.1 变量、运算符与类型转换的易错点
PHP 的弱类型在入门阶段很友好,但也埋了不少坑。比如==和===的区别,在比较字符串和数字时会出现和直觉不一致的结果。
<?php var_dump(0 == "a"); // PHP 7 及以前为 true,PHP 8 为 false var_dump("1" == "01"); // true,按数值比较 var_dump("10" == "1e1"); // true var_dump(100 == "1e2"); // true // 稳妥写法 var_dump("1" === "01"); // false,类型和值都不同运算符里还有一个常见问题是+对数组的行为。两个数组相加不是合并覆盖,而是保留左边键、补充右边不存在的键。
<?php $a = ['x' => 1, 'y' => 2]; $b = ['y' => 3, 'z' => 4]; print_r($a + $b); // x=1, y=2, z=4,y 不会被覆盖如果要合并并让右边覆盖左边,用array_merge,但注意数字键会被重新索引。这个区别在做接口参数合并时经常踩到。
3.2 数组与对象转换和接口返回
PHP 接口返回 JSON 时,数组和对象的边界要理清楚。json_encode对索引数组输出方括号,对关联数组输出花括号。如果后端想统一返回对象结构,可以用JSON_FORCE_OBJECT。
<?php $data = ['name' => 'test', 'tags' => ['a', 'b']]; echo json_encode($data, JSON_UNESCAPED_UNICODE); // {"name":"test","tags":["a","b"]} echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_FORCE_OBJECT); // {"name":"test","tags":{"0":"a","1":"b"}}JSON_UNESCAPED_UNICODE是中文接口的必加项,不加会把中文转成\uXXXX,前端虽然能解析,但日志里很难读。前端如果要用 JSONP 跨域,还需要在输出前包一层回调名,并且对回调名做白名单校验,否则就是典型的注入入口。
3.3 序列化与反序列化中的中文处理
PHP 的serialize对中文是按字节长度算的,中文字符串长度不等于字符数。serialize("中")得到的是s:3:"中",因为 UTF-8 下一个汉字占 3 个字节。这点在手工拼接序列化字符串或者做长度校验时特别容易出错。
<?php $str = "中"; echo strlen($str); // 3,字节数 echo mb_strlen($str); // 1,字符数 echo serialize($str); // s:3:"中" $arr = ['name' => '张三', 'age' => 20]; $ser = serialize($arr); echo $ser; // a:2:{s:4:"name";s:6:"张三";s:3:"age";i:20;} $back = unserialize($ser); print_r($back);反序列化有一条关键安全边界:不要对用户可控的输入直接调用unserialize。常见风险是构造带魔术方法的对象链,触发意外行为。稳妥做法是用json_decode替代跨系统传输,或者在反序列化前用allowed_classes限制可实例化的类。
<?php $input = $_POST['data'] ?? ''; $data = unserialize($input, ['allowed_classes' => false]);第二个参数从 PHP 7 开始支持,传false表示不允许实例化任何类,只还原数组和基础类型,能挡掉大部分对象注入链。
| 函数 | 中文长度 | 安全风险 | 适用场景 |
|---|---|---|---|
| serialize | 按字节 | 高,对象注入 | 内部缓存、会话 |
| json_encode | 按字符 | 低 | 接口传输 |
| var_export | 按字符 | 低 | 配置导出 |
注意:从数据库读出的序列化字段,如果历史上用 serialize 存过,迁移时要先反序列化再转 JSON,不能直接字符串替换,否则中文字节长度会对不上。
4. PHP 文件上传与错误处理:上传漏洞、一句话木马的防范与调试实操
4.1 文件上传的完整校验链路
文件上传是 PHP 项目里出安全问题最多的地方。一个能用的上传处理至少要做四层校验:请求层判断上传状态、大小层限制体积、类型层校验 MIME 和扩展名、存储层重命名并移出 web 根目录。
<?php $uploadDir = '/data/uploads/'; // 放在 web 根目录之外 $maxSize = 2 * 1024 * 1024; if (!isset($_FILES['file'])) { exit('没有文件'); } $file = $_FILES['file']; if ($file['error'] !== UPLOAD_ERR_OK) { exit('上传错误码:' . $file['error']); } if ($file['size'] > $maxSize) { exit('文件过大'); } $finfo = new finfo(FILEINFO_MIME_TYPE); $mime = $finfo->file($file['tmp_name']); $allow = ['image/jpeg', 'image/png', 'image/gif']; if (!in_array($mime, $allow, true)) { exit('类型不允许'); } $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $allowExt = ['jpg', 'jpeg', 'png', 'gif']; if (!in_array($ext, $allowExt, true)) { exit('扩展名不允许'); } $newName = bin2hex(random_bytes(16)) . '.' . $ext; $target = $uploadDir . $newName; if (!move_uploaded_file($file['tmp_name'], $target)) { exit('保存失败'); } echo '保存为:' . $newName;这段代码里几个关键点:$_FILES['file']['error']要先判断,不判断的话后面拿到的临时文件可能是空的;MIME 用finfo读实际内容,不能只信浏览器传来的 type;扩展名和 MIME 要同时校验;文件名要重命名,不能用原始名字,否则可能覆盖已有文件或者带路径穿越。move_uploaded_file比copy安全,因为它会检查文件是否真的是通过 HTTP 上传的。
4.2 常见上传绕过方式与对应堵法
了解常见的绕过方式,才知道校验为什么要写全。
| 绕过方式 | 原理 | 堵法 |
|---|---|---|
| 改 Content-Type | 浏览器传来的 type 可伪造 | 用 finfo 读真实 MIME |
| 双扩展名 | shell.php.jpg 在某些配置下被解析为 php | 校验最后一个扩展名并重命名 |
| 上传点开头的配置文件 | 改变目录解析规则 | 禁止上传点开头的文件 |
| 图片马 | 图片里嵌入 PHP 代码 | 图片二次渲染,关闭错误解析 |
所谓一句话木马,本质是一段接收外部参数的 PHP 代码。防御核心不是去识别某段特征字符串,而是让上传目录不可执行。nginx 里可以单独对上传目录关掉 PHP 解析:
location ^~ /uploads/ { location ~ \.php$ { return 403; } }这样即使有文件被传上去,也无法当作脚本执行。再把上传目录和 web 根目录分离,就是双保险。
4.3 错误处理与日志排查
PHP 的错误处理分显示和记录两条线。开发环境打开显示,生产环境只记录不显示。
<?php // 开发环境 ini_set('display_errors', '1'); error_reporting(E_ALL); // 生产环境 ini_set('display_errors', '0'); ini_set('log_errors', '1'); ini_set('error_log', '/var/log/php/error.log'); error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);生产环境不显示错误是为了避免把路径、SQL、配置暴露给用户,但记录必须开,否则出问题没有线索。排查上传问题时,先看error_log里有没有File upload error或者Unable to move,再看upload_max_filesize和post_max_size两个值是否够大。
upload_max_filesize = 8M post_max_size = 10M max_file_uploads = 20这两个值一个限制单个文件,一个限制整个 POST 体,改的时候要一起改。只改一个会出现“文件没超限但提交后为空”的情况。
提示:修改 php.ini 后要重启 PHP 进程或者重新加载配置,改完直接刷新页面是不生效的。用
phpinfo()确认配置真的加载了。
5. PHP 进阶技巧:Redis 消费组队列、vscode 配置与排错清单
5.1 用 Redis 消费组做 PHP 队列
PHP 做异步任务时,Redis 的 Stream 消费组是一个轻量选择。相比 list 的 lpush 加 brpop,消费组支持多消费者、消息确认和重试。
<?php $redis = new Redis(); $redis->connect('127.0.0.1', 6379); $stream = 'tasks'; $group = 'workers'; // 创建消费组,从最新消息开始;已存在会抛错,可忽略 try { $redis->xGroup('CREATE', $stream, $group, '$', true); } catch (RedisException $e) { } $consumer = 'worker-' . getmypid(); while (true) { $res = $redis->xReadGroup($group, $consumer, [$stream => '>'], 1, 5000); if (empty($res)) { continue; } foreach ($res[$stream] as $id => $msg) { echo "处理 {$id}\n"; $redis->xAck($stream, $group, [$id]); // 处理成功后确认 } }xReadGroup的>表示只读新消息,阻塞 5000 毫秒。消费完成后要xAck确认,没确认的消息会留在 pending 列表里,可以用xPending查看积压。如果消费者崩溃,pending 消息需要另一个消费者用xClaim接管,否则任务会卡住。这套机制比简单的 brpop 多了一层可靠性,代价是代码复杂度上升。
5.2 vscode 配置 PHP 调试
在 vscode 里调试 PHP,需要装 PHP Debug 扩展,然后配 launch.json。
{ "version": "0.2.0", "configurations": [ { "name": "Listen for XDebug", "type": "php", "request": "launch", "port": 9003, "pathMappings": { "/var/www/html": "${workspaceFolder}/src" } } ] }port 要和 php.ini 里的xdebug.client_port一致。pathMappings 在 Docker 场景里必须配,否则断点位置对不上。php.ini 里还要开xdebug.mode=debug,改完重启 PHP 进程。打断点后访问页面,vscode 会停在断点处,可以看变量和调用栈。
注意:XDebug 会明显降低 PHP 执行速度,生产环境不要开 debug 模式,可以设成 off,或者只在测试环境开 coverage。
5.3 排错时的检查顺序
遇到 PHP 页面白屏或者接口返回空,按这个顺序查能少走弯路:一看display_errors是否打开,开发环境先打开看具体报错;二看 PHP 错误日志最后几十行;三看 nginx 或 Apache 的错误日志,确认请求有没有到 PHP;四用php -l检查语法;五确认文件权限和目录是否存在。白屏大多是致命错误被藏起来了,日志是唯一线索。把php -l和nginx -t固定成提交前的手动检查项,能挡掉相当一部分低级错误。
本文还有配套的精品资源,点击获取