301页PHP入门笔记:HTML表格表单与GET/POST接收端实战
2026/9/17 23:28:01 网站建设 项目流程

简介:这份《PHP从入门到精通完整笔记》以单份301页PDF形式呈现,面向零基础想系统学习Web开发的初学者,以及需要完整课堂讲义用于复习巩固的在校学生。内容按课程进度编排,前22讲梳理HTML与CSS基础,从超文本标记语言的历史沿革、W3C标准、本地运行与远程访问原理讲起,逐步深入到html、head、body基本结构,成对标记与单标记、字符实体、超链接target属性、图片宽高设置以及table表格的行列合并与对齐控制;第23至149讲转入PHP部分,覆盖变量、条件判断、循环、数组、函数、类与对象,并延伸至与MySQL数据库交互的动态网页开发。压缩包内仅1个PDF文件,体积约2.14MB,轻便易存,手机与电脑均可随时翻阅。目前已有1083人学习,适合按讲次顺序精读,也可作为查漏补缺的速查手册。

1. 301 页笔记摊开来看:先啃 HTML 表格与表单,再进 PHP 接收端

很多人上手 PHP 的第一件事是找一套 php网站源码,改改数据库配置就跑起来,等到要自己加一个注册框、把数据存进库,才发现卡住的地方不在 PHP 语法,而在 HTML 表单的 name 怎么命名、method 为什么前后端对不上、复选框为什么取不到值。这份《PHP从入门到精通完整笔记》的编排恰好对症:前 22 讲铺 HTML 与 CSS,从 html/head/body 骨架讲到表格的 colspan、rowspan,再到 frameset、iframe 与表单控件族,第 23 讲往后才切进 PHP,把「页面长什么样」和「数据往哪送」接成了一条线。零基础跟着走一遍不吃力,半路出家的运维、测试也能拿它补表单这块短板。301 页不用一次读完,值得反复翻的只有三处:表格合并、表单控件、提交方法,其余当手册查。

2. HTML 文档结构与表格布局:head/body、单标记、colspan 与 rowspan 的实战

浏览器解析一份文档,先看结构,再看内容,最后才谈样式。笔记里 HTML 部分的开头几讲没急着上 CSS,而是把「结构」这件事讲透,这个顺序对新手是对的:结构错了,后面写多少样式都救不回来。表格这块尤其典型,它是这套笔记里代码量最大的一节,也是后面表单排版的基础。

2.1 文档骨架、单双标记与字符实体的最小约定

一份 HTML 文件的骨架就三层:<html>是根,<head>装元信息(标题、编码、外链资源),<body>装用户在页面上看得见的东西。标记分两类,成对出现的叫双标记,比如<head></head><b></b>;只有一个标签的是单标记,比如<br/><img/><input/><hr/>。属性统一写成属性名="值",值加双引号是习惯,不加多数浏览器也认,但遇到带空格的值就会翻车。

<!DOCTYPE html> <html> <head> <!-- head 里的 charset 决定中文会不会乱码,必须写在最前 --> <meta charset="utf-8" /> <title>我的第一个页面</title> </head> <body> <!-- 双标记:有开始有结束 --> <b>横看成岭侧成峰</b><br/> <!-- 单标记:自闭合,没有结束标签 --> <font color="red">远近高低各不同</font><br/> <!-- 内联样式优先级高于 font 的 size 属性 --> <font style="font-size:30px;">不知庐山真面目</font><br/> <!-- 字符实体:把保留字符当普通文本显示 --> <p>&lt;table&gt; 是块级元素&nbsp;&nbsp;这里有两个空格</p> </body> </html>

这段代码里有两处值得单独记:一是style="font-size:30px"<font size="1..7">同时存在时,内联样式生效,fontsize只能取 1 到 7 这七个档位,粒度太粗,所以笔记里同时给了两种写法;二是字符实体,&lt;输出左尖括号,&nbsp;输出不换行空格,&copy;输出版权符。只要是 HTML 的保留字符(<>&)要显示成文字,就必须走实体,否则浏览器会当成标签解析。

顺带说一个常被当面试题的点:.html.htm后缀有什么区别。同一目录下同时存在 index.html 和 index.htm,服务器默认优先返回.html.htm的存在是早期 DOS 系统 8.3 命名规范的遗留,那时主文件名不能超过 8 个字符,扩展名不能超过 3 个。今天写.htm不会有问题,但没必要。

运行方式上,本地直接双击是file://协议,把文件丢到 Web 服务器目录再访问是http://协议。W3C 负责给 HTML、XHTML、CSS、XML 这些定标准,但各家浏览器实现有差异,同一个页面在不同内核下渲染结果可能不一致,所以才有了历史上的 hack 写法。这块在笔记里只是一笔带过,但它是理解后面 CSS 兼容问题的前提。

2.2 表格属性速查与 colspan/rowspan 合并规则

笔记里表格的入门例子直接堆了一串属性,初看很杂,其实是三组:控制整体外观的、控制行内对齐的、控制单元格合并的。把下面这张表记住,再看代码就不乱了。

属性作用常见取值写在哪个标签上
align水平对齐方式left / center / righttable、tr、td
border边框宽度0 或像素值table
bordercolor边框颜色#E76BFF或颜色名table
bgcolor背景色yellow /#RRGGBBtable、tr、td
width/height宽高400px或百分比table、td
cellpadding单元格内容与边框的内边距像素值table
cellspacing单元格之间的间距0 表示紧贴table
colspan横向合并几列数字td、th
rowspan纵向合并几行数字td、th

把属性套进一个课表结构里,colspanrowspan的作用立刻清楚:

<table border="1" bordercolor="#579AFE" cellspacing="0" cellpadding="4" width="500px" align="center"> <tr align="center"> <td>项目</td> <!-- 横跨 5 列:上课 --> <td colspan="5">上课</td> <!-- 横跨 2 列:休息 --> <td colspan="2">休息</td> </tr> <tr bgcolor="pink" align="center"> <th>星期</th><th>星期一</th><th>星期二</th><th>星期三</th> <th>星期四</th><th>星期五</th><th>星期六</th><th>星期日</th> </tr> <tr align="center"> <!-- 纵跨 4 行:上午 --> <td rowspan="4">上午</td> <td>语文</td><td>数学</td><td>英语</td><td>英语</td><td>物理</td> <td>计算机</td> <!-- 纵跨 4 行:休息 --> <td rowspan="4">休息</td> </tr> <tr align="center"> <td>数学</td><td>数学</td><td>地理</td><td>历史</td><td>化学</td><td>计算机</td> </tr> <tr align="center"> <td>化学</td><td>语文</td><td>体育</td><td>计算机</td><td>英语</td><td>计算机</td> </tr> <tr align="center"> <td>政治</td><td>英语</td><td>体育</td><td>历史</td><td>地理</td><td>计算机</td> </tr> </table>

cellspacing="0"让单元格紧贴,这是做细边框表格的常规操作,不写就是默认的两像素缝。align="center"写在 table 上是整表居中,写在 tr 上是该行内容居中,写在 td 上是该格内容居中,作用范围逐层收窄,td 上的设置会覆盖 tr。

2.2.1 合并单元格最容易踩的三个坑

第一个坑是行内 td 数量算错。rowspan占掉的行,在后续的行里不能再写对应的 td,否则整张表会往右错位。判断方法很简单:每一行实际写的 td 数加上被上面 rowspan 占用的列数,必须等于总列数。上面例子里表头是 8 列,第二行写了 8 个 td,第三行只有 1 加 6 加 1 共 8 个,后面的行因为被第一列的rowspan="4"和最后一列的rowspan="4"各占一格,所以只写 6 个 td。

第二个坑是colspanrowspan同时用时的交叉覆盖,这种结构在真实项目里基本只出现在复杂表头,建议拆成嵌套表或改用 CSS Grid,别硬扛。

第三个坑是把 table 当布局工具。笔记里明确写了,早年页面布局要求不高时确实用表格排版,但今天横向对齐用display:flex,纵向栅格用 Grid,表格只负责展示二维数据,混用会让移动端适配直接失效。

2.3 列表、超链接与图片的属性细节

列表分无序和有序两种。无序列表<ul>type可以取 disc(实心圆,默认)、circle(空心圆)、square(方块);有序列表<ol>type可以取 1、A、a、I、i,start指定从第几项开始编号,做条款类文档时很有用。超链接的核心是hreftargethref写相对路径就站内跳转,写完整 URL 就跳外站,写mailto:就唤起邮件客户端。

<ul type="circle"> <li>英雄</li> <li>精武门</li> </ul> <ol type="I" start="3"> <li>卢俊义</li> <li>吴用</li> </ol> <!-- target 的四个保留值:_self 本窗口、_blank 新窗口、 _top 跳出所有框架、_parent 上一级框架 --> <a href="a.html" target="_blank">新窗口打开 a.html</a> <a href="http://www.example.com">跳转到外站</a> <a href="mailto:admin@example.com">联系管理员</a> <!-- 相对路径优先,width/height 建议只写一个让浏览器等比缩放 --> <img src="images/logo.png" width="120" />

target除了四个下划线开头的保留值,还能直接写某个 frame 或 iframe 的 name,这个特性在第 3 章会用到。图片这里有个老笔记里常见的写法<img src="F.GIF" dynsrc="clock.avi" loop="2" start="mouseover"/>,用图片占位、鼠标移上去才播放视频,这是早期 IE 的私有行为,现在只有历史价值,知道它存在就行,别往新项目里抄。

3. frameset、iframe 与图像映射:老派页面拼装技术在后台页面的复用

框架集、画中画、图像热区,这三样东西在今天的前端教程里基本被删干净了,但后台管理系统、旧项目维护、以及需要嵌第三方页面的场景里,它们还在跑。笔记里这几节代码给得比较全,值得花时间过一遍,至少做到能读懂、能改。

3.1 frameset 分割页面与 target 定向

frameset 的作用是把一个浏览器窗口切成几块,每块装一个独立页面。它和 body 是互斥的:用了<frameset>的页面里不能再出现<body>,否则整个框架结构会失效,这是新手最常犯的错。

<!-- A2.html:左侧 30% 是菜单,右侧自适应 --> <frameset cols="30%,*"> <!-- noresize 去掉才能拖动分割线,frameborder="0" 去边框 --> <frame name="frame1" src="b.html" noresize frameborder="0" /> <frame name="frame2" src="c.html" frameborder="0" /> </frameset>
<!-- b.html:菜单页,点击后内容在 frame2 里打开 --> <body bgcolor="pink"> <!-- target 直接写 frame 的 name,就把内容定向到那一格 --> <a href="zjl.html" target="frame2">周杰伦</a><br/> <a href="qq.html" target="frame2">齐秦</a><br/> </body>

多级分割靠嵌套:外层用rows="20%,*"切成上下两块,下面那块再嵌一个cols="20%,*"切成左右两块,就构成了「上标题、左菜单、右内容」的经典后台布局。注意rowscols的值可以是百分比,也可以是像素,还可以用*表示剩余空间;写20%,*时后面的星号会吃掉剩余全部宽度。scrolling="no"关掉滚动条,noresize锁住分割线,这两个属性在处理标题栏和菜单栏时用得最多。

要提醒的是,frameset 页面没法做 SEO,浏览器的前进后退和收藏行为也会变乱,URL 里只显示框架集页面地址,内容页地址用户看不到。它只适合内部系统,不适合对外站点。

3.2 iframe 画中画:有 body 的页面里嵌页面

frameset 是把整页切了,iframe 是在一个正常页面里挖一块窗口。它必须有 body,用法和普通元素一样,src指定要嵌的页面,widthheight控制尺寸,name供 target 引用。

<html> <body> <h1>hello,world</h1> <!-- 点击链接,目标页面在 iframe1 里打开,页面本身不跳转 --> <a href="http://www.example.com" target="iframe1">在浮动窗口打开示例站</a><br/> <a href="detail.html" target="iframe1">在浮动窗口打开详情页</a><br/> <iframe name="iframe1" src="welcome.html" width="500px" height="400px"></iframe> </body> </html>

target="iframe1"这个写法,在「列表页 + 详情页」的后台里非常常见:左边点一条记录,右边 iframe 里刷详情,页面不整体刷新。如果嵌的是外部站点,很多站点会返回X-Frame-OptionsContent-Security-Policy响应头阻止被嵌入,表现为 iframe 一片空白,这时打开 F12 看 Console 会直接报拒载信息,属于对方站点的策略,不是自己代码写错了。

维度framesetiframediv + CSS 布局
能否有 body不能有必须有必须有
分割范围整页分割局部窗口局部窗口
地址栏与前进后退容易混乱影响较小正常
移动端适配一般
常见场景老式后台菜单嵌外部页、预览、详情现代页面结构

3.3 图像映射 area 的 coords 与 fieldset 表单分组

图像映射解决的是「一张图上有多个可点区域」的问题,比如楼层平面图、身体部位图。写法是给imgusemap="#名字",再用<map name="名字">定义若干<area>

<html> <head> <script language="javascript"> function show(){ window.alert('点击了矩形区域'); } </script> </head> <body> <img src="mapimg.gif" usemap="#abc" /> <map name="abc"> <!-- shape=rect 时,coords 是 x1,y1,x2,y2 左上角与右下角 --> <area shape="rect" coords="140,20,280,60" onclick="show();" href="#" /> </map> </body> </html>

coords的含义随shape变化:rect是左上角与右下角两个坐标点,circle是圆心 x、圆心 y、半径,poly是一串首尾相连的顶点坐标。调试热区最土也最好用的办法,是在图片上临时加一层半透明遮罩把坐标网格画出来,或者直接用图片编辑工具读像素坐标,比反复改数字试快得多。

表单分组用fieldsetlegend,视觉上是一个带标题的框,语义上也把一组相关控件圈在一起:

<fieldset style="width:300px"> <legend><font color="blue">审核状态</font></legend> <form> <input type="radio" name="state" value="1">已经审核 <input type="radio" name="state" value="0">没有审核 <input type="radio" name="state" value="all">全部 </form> </fieldset>

同一组单选框必须共用同一个name,浏览器才会把它们当成互斥选项;value是提交到服务端的真实值,页面上显示的文字和提交值不一定一致,这一点在接 PHP 时经常出事。

4. 从表单控件到 PHP 接收端:GET/POST 提交链路的完整跑通

HTML 部分学完,真正见分晓的是数据能不能送进 PHP。笔记在表单这一节给了一个登录页的例子,但只到静态页面就停了,缺了接收端那一半。补上这一半,整条链路才算闭环。

4.1 GET 与 POST 的语义差异与表单写法

表单的本质是「浏览器把控件里的值打包,按指定方法发给一个 URL」。打包方式、参数位置、长度限制,GET 和 POST 完全不一样。

对比项GETPOST
参数位置拼在 URL 的?后面放在请求体里
可见性地址栏、日志、历史记录都能看到不显示在地址栏
长度限制受 URL 长度限制基本只受服务端配置限制
中文与特殊字符需要 URL 编码表单默认编码,相对省心
幂等 / 缓存幂等,可被缓存、可收藏非幂等,一般不缓存
PHP 超全局$_GET$_POST
典型用途查询、分页、筛选登录、注册、写入、上传

登录页的正确写法是把action指向接收脚本,method用 post:

<!-- login.html --> <form action="login.php" method="post"> <!-- name 的值就是 PHP 端 $_POST 的键名,不要用关键字 --> 用户名:<input type="text" name="username" /><br/> 密 码:<input type="password" name="password" /><br/> <input type="submit" value="登录系统" /> <input type="reset" value="重新填写" /> </form>

action写相对路径时,相对的是当前页面所在目录;method不写默认是 GET,很多人调试时发现密码出现在地址栏,就是这里漏了。submitvalue是按钮上显示的字,reset把表单恢复成初始值。

4.2 PHP 接收端:isset、trim 与输出转义

接收端第一件事是判断键存在,而不是直接取值。用户可能通过 curl、或者禁用 JS 后绕过页面直接提交,未勾选的复选框压根不会出现在数组里,硬取值会直接报未定义索引。

<?php // login.php:接收 login.html 用 POST 提交的数据 header('Content-Type: text/html; charset=utf-8'); // isset 先判存在,再 trim 去首尾空白 $username = isset($_POST['username']) ? trim($_POST['username']) : ''; $password = isset($_POST['password']) ? trim($_POST['password']) : ''; if ($username === '' || $password === '') { http_response_code(400); exit('用户名或密码为空'); } // 输出到页面时必须转义,否则用户名里写脚本会被执行 echo '收到用户名:' . htmlspecialchars($username, ENT_QUOTES, 'UTF-8');

isset()返回布尔值,比array_key_exists轻,配合三元表达式是最常见的写法。trim()去掉用户误输的首尾空格,密码两边不加空格很少有人故意这么干,去掉利大于弊。htmlspecialchars()<>"'转成实体,参数ENT_QUOTES表示单双引号都转,第三个参数指定字符集,漏掉它在 utf-8 页面上可能出现乱码或转义失效。真正存密码时不能明文入库,常见做法是用password_hash($password, PASSWORD_DEFAULT)生成哈希,校验时用password_verify()比对。

header()那一行必须在任何输出之前调用,前面多一个空格或 BOM 都会报 headers already sent。表单页和接收页的字符集要一致,<meta charset="utf-8">与 PHP 端的Content-Type都写 utf-8,中文才不会变成问号。

4.3 复选框数组、下拉多选与文件上传的接收写法

同一个name的多个复选框,服务端只会拿到最后一个值,要收成数组必须在 name 后面加[]。下拉列表的multiple同理。文本域用textarea,值放在标签之间而不是 value 属性里。

<!-- form.html:注意文件上传必须改 enctype,且 method 只能是 post --> <form action="submit.php" method="post" enctype="multipart/form-data"> 您最喜欢哪些城市: <input type="checkbox" name="cities[]" value="beijing">北京 <input type="checkbox" name="cities[]" value="shanghai">上海 <input type="checkbox" name="cities[]" value="london">伦敦<br/> 您的性别是: <input type="radio" name="sex" value="man">男 <input type="radio" name="sex" value="woman">女<br/> <!-- 隐藏域:用户看不见,但会随表单一起提交 --> <input type="hidden" name="source" value="web" /><br/> <select name="address[]" size="3" multiple> <option value="sichuan">四川</option> <option value="beijing">北京</option> </select><br/> <textarea name="remark" cols="40" rows="10"></textarea><br/> <input type="file" name="myfile" />上传文件<br/> <input type="submit" value="测试" /> </form>

接收端对应处理:

<?php // submit.php // 复选框可能一个都没勾,先给默认空数组再转成数组类型 $cities = isset($_POST['cities']) ? (array)$_POST['cities'] : []; echo '城市:' . implode('、', array_map(function ($v) { return htmlspecialchars($v, ENT_QUOTES, 'UTF-8'); }, $cities)) . '<br/>'; $sex = isset($_POST['sex']) ? $_POST['sex'] : '未选择'; echo '性别:' . htmlspecialchars($sex, ENT_QUOTES, 'UTF-8') . '<br/>'; // 文件上传:先看 error 码,再判断是不是真的上传文件 if (isset($_FILES['myfile']) && $_FILES['myfile']['error'] === UPLOAD_ERR_OK) { $tmp = $_FILES['myfile']['tmp_name']; // 服务器临时文件 $name = basename($_FILES['myfile']['name']); // 只取文件名,防目录穿越 $size = $_FILES['myfile']['size']; if ($size > 2 * 1024 * 1024) { exit('文件超过 2MB'); } // 第 4 个参数传 true,会检查是不是真的通过 HTTP 上传的 move_uploaded_file($tmp, __DIR__ . '/uploads/' . $name); echo '上传成功:' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); } else { echo '未选择文件或上传失败,错误码:' . ($_FILES['myfile']['error'] ?? 'none'); }

$_FILES的五个键是固定结构:name原始文件名、typeMIME 类型、tmp_name临时路径、error错误码、size字节数。error为 0(UPLOAD_ERR_OK)才算成功,1 表示超过 php.ini 里的upload_max_filesize,2 表示超过表单里MAX_FILE_ORDER的限制,4 表示没选文件。上传目录一定要单独放,别和可执行脚本混在一起,文件名也要过滤掉../这类路径片段,basename()是最低成本的防线。

4.4 用内置服务器与 curl 跑通提交链路

不用配 Apache 或 Nginx,PHP 自带的开发服务器足够验证表单逻辑。

# 在项目目录的上一级执行,-t 指定站点根目录 php -S 127.0.0.1:8000 -t D:\web\demo # 另开一个终端,用 curl 直接模拟 POST 提交,绕过浏览器 curl -i -X POST http://127.0.0.1:8000/login.php \ -d "username=admin&password=123456" # 查看已加载的扩展,确认 curl、mbstring、fileinfo 这些是否可用 php -m

-S启动的是单进程服务器,改完 PHP 文件刷新即可生效,不用重启。-t的路径写错会直接 404,注意写站点根目录而不是某个文件。curl 的-i让响应头一起输出,方便看状态码和Content-Type是否符合预期;-d默认以application/x-www-form-urlencoded编码发送,最接近浏览器默认表单的行为。文件上传就得换成-F "myfile=@D:\test.png",curl 会自动切成 multipart 编码。

Windows 下在编辑器里配 PHP 路径时,php.validate.executablePath指向 php.exe 的绝对路径,写完带反斜杠要转义或者直接用正斜杠,否则校验器静默失效,看着像插件坏了。

5. 表单与请求的验证技巧:用 curl、DevTools 和 PHP 调试脚本定位提交问题

链路跑通一次不难,难的是出问题时知道先看哪。踩得最多的三类症状是:提交后页面没反应、PHP 端取到的值是空的、中文变乱码。这三类问题有一套固定的排查顺序,按顺序走基本不会绕远路。

5.1 三层排错顺序:先看提交了什么,再看收到什么,最后看输出编码

第一层看请求本身。浏览器按 F12 打开 Network 面板,勾上 Preserve log,提交一次表单,点开那条请求,看 Payload 或者 Form Data 区域里到底带了哪些键值。常见发现是:复选框一个没勾所以完全没出现、name拼错了(比如写成user_name而 PHP 里取的是username)、或者 method 写成了 GET 所以数据跑到了 Query String 里。这一步能解决掉大约七成的「取不到值」。

第二层看 PHP 收到了什么。在接收脚本最开头插一段临时调试代码,把超全局变量原样打印出来然后终止:

<?php // debug.php:只在本地开发时用,上线前必须删掉 header('Content-Type: text/plain; charset=utf-8'); // 纯文本输出,避免标签被解析 echo "GET:\n"; var_dump($_GET); echo "POST:\n"; var_dump($_POST); echo "FILES:\n"; var_dump($_FILES); exit; // 终止后续逻辑,避免带出业务副作用

text/plain输出是为了让var_dump的结构不被 HTML 吞掉,exit是防止后面的写库逻辑真的跑起来。等键名和类型都对上了,再把这段删掉。第三层才是编码:页面<meta charset>、PHP 的header()、数据库连接字符集这三处任意一处不一致,中文就会变问号或乱码,三处统一成 utf-8 是唯一稳妥的做法。

5.2 建立「控件名 → 服务端键名」映射表

表单控件一多,最容易失控的是命名。建议每做一个表单就顺手记一张映射表,前端改动时对照着改后端,比翻代码快得多。

控件类型HTML 写法服务端取值注意点
文本框name="username"$_POST['username']必填要做非空校验
密码框name="password"$_POST['password']存哈希,不存明文
单选框name="sex"$_POST['sex']未选中时键不存在
复选框name="cities[]"$_POST['cities'](数组)一个都没勾时键不存在
下拉多选name="address[]"+multiple$_POST['address'](数组)同复选框
隐藏域name="source"$_POST['source']值可被篡改,不能当凭证
文本域<textarea name="remark">$_POST['remark']值写在标签之间
文件域name="myfile"+enctype$_FILES['myfile']method 必须 post
图片按钮<input type="image" name="btn">$_POST['btn_x']$_POST['btn_y']会额外带点击坐标

这张表里最后一行很多人不知道:图片按钮提交时除了自身 name,还会带name_xname_y两个坐标值,接收端如果只找btn会一直取不到。上传那行的enctype漏写,$_FILES会是空数组,浏览器不会报任何错,只是文件永远传不上去。

一个可以马上动手的验证习惯:每写一个表单,先用 curl 手动发一次请求,确认服务端逻辑独立于浏览器也能跑通,再去页面上点。这样一旦出问题,就能立刻判断是前端少传了字段,还是后端逻辑本身有问题,把排查范围缩小一半。curl 返回的响应头里Content-Type如果没有charset=utf-8,中文乱码的根源也就顺手定位了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询