PHP $_POST 和 $*GET 怎么区别?实际网站开发中应该怎样使用
图片说明:示意图 图片来源:Public Domain(公有领域)
在PHP网站开发中,$_GET和$_POST几乎每天都会出现。搜索框、分页、筛选条件、文章ID、登录表单、用户注册、后台编辑页面以及API接口,都可能涉及这两个超全局数组。很多初学者把它们简单理解成“GET传普通数据,POST传敏感数据”,但这种理解并不完整。
$_GET和$_POST首先是PHP对HTTP请求参数提供的访问方式。它们背后的核心区别在于请求参数来自哪里,以及HTTP请求使用什么方法。至于数据是否安全、是否需要验证、是否应该进入数据库,则是另外一层问题。
$_GET的数据从哪里来
GET请求最常见的形式是URL查询字符串。例如:
https://example.com/search.php?q=php&page=2
PHP可以通过:
$q = $_GET['q'] ?? '';
$page = $_GET['page'] ?? 1;
读取这些参数。
在这个例子中,q和page都属于查询字符串参数。
GET非常适合表达“我要获取什么资源”或者“我要怎样筛选资源”。搜索、分页、分类、排序、文章ID等场景通常都非常适合使用GET。
例如新闻网站:
/news.php?category=technology&page=3
这种URL具有非常明显的优势。用户可以收藏、复制、分享,也可以直接重新访问同一个地址得到相同的查询结果。
搜索引擎也能够理解这种URL结构。
因此,如果一个新闻网站把搜索关键词、分类和分页全部放进POST表单,用户就很难直接保存一个搜索结果页面的URL。对于需要被链接、收藏或者索引的查询操作,GET往往更加自然。
$_POST的数据从哪里来
POST请求通常把表单数据放在HTTP请求体中。例如:
PHP可以读取:
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
这里最重要的一点是:POST数据虽然通常不会显示在浏览器地址栏中,但并不意味着“用户看不到”。
用户可以通过浏览器开发者工具看到请求内容,也可以使用浏览器扩展、代理工具、curl、Postman等工具自行构造POST请求。
因此,POST不是一种加密机制。
如果密码通过普通HTTP发送,攻击者仍然可能在传输过程中窃取密码。保护传输数据依靠HTTPS,而不是POST本身。
GET和POST最核心的区别并不是“安全与不安全”
这是PHP开发中非常容易形成的错误认识。
GET参数位于URL查询字符串中,因此可能出现在浏览器历史记录、服务器访问日志、代理日志、分析系统以及引用来源信息中。对于密码、身份认证令牌、支付数据等敏感信息,显然不适合放进URL。
POST的数据位于请求体中,在很多常见日志中不会像URL那样自动出现在访问记录里,但这并不代表它不会被记录,也不代表它自动受到保护。
所以更准确的理解是:
GET和POST解决的是HTTP请求语义和参数传递方式的问题;HTTPS解决的是传输过程中的加密和完整性问题。
两者不能混为一谈。
GET并没有固定的2048字节限制
很多PHP教程会告诉开发者:“GET最多只能传2048个字符。”
这个说法过于绝对。
HTTP协议本身并没有规定一个统一的“GET只能2048字节”限制。实际URL长度受到浏览器、Web服务器、反向代理、负载均衡器以及应用框架等多个环节影响。
不同环境的限制并不完全相同。
因此,正确的工程实践不是背一个“2048字节”的数字,而是根据应用场景设计请求。
如果参数是搜索关键词、分页、筛选条件等少量数据,GET非常合适。如果需要提交大量表单数据,则通常应该考虑POST或者其他适合的HTTP方法。
对于大型文件上传,更不能简单理解成“POST就没有大小限制”。PHP、Nginx、Apache、反向代理以及应用本身都可能设置请求大小限制。
POST也不是“容量无限大”
PHP处理POST请求时,至少需要考虑post_max_size等配置。
例如:
post_max_size = 32M
upload_max_filesize = 16M
其中upload_max_filesize主要限制单个上传文件,而post_max_size限制整个POST请求体能够达到的大小。
因此,如果用户上传文件时出现“文件明明没有超过upload_max_filesize,却还是上传失败”,就需要继续检查整个POST请求大小以及Web服务器、反向代理等其他限制。
大型文件系统还可能使用分块上传、断点续传或者对象存储的Multipart Upload机制,而不是单纯把PHP的POST限制不断调大。
GET特别适合查询和分页
假设网站有一个文章列表:
/news.php?page=3&category=technology
这里使用GET非常合理。
服务器可以读取:
$page = filter_input(
INPUT_GET,
'page',
FILTER_VALIDATE_INT
);
$category = trim(
$_GET['category'] ?? ''
);
if ($page === false || $page === null || $page < 1) {
$page = 1;
}
这种设计的好处是页面状态直接体现在URL中。
用户可以把这个地址复制给其他人,也可以刷新页面,浏览器前进和后退也能够正常工作。
对于搜索引擎,这种URL结构同样比隐藏在POST请求中的搜索状态更容易被理解。
修改服务器状态通常应该使用POST
如果操作会改变服务器上的数据,就不应该为了方便而使用GET。
例如删除文章:
/delete.php?id=100
虽然技术上可以实现,但设计上存在明显问题。
搜索引擎爬虫、预加载机制或者某些自动访问程序理论上都可能访问GET URL。如果这个GET请求具有删除数据的副作用,就可能产生严重问题。
更合理的是:
服务器收到POST之后,还需要验证当前用户身份、权限以及CSRF Token。
这里需要进一步说明,REST API中“创建、更新、删除”并不一定全部使用POST。现代HTTP API可能分别使用POST、PUT、PATCH和DELETE。PHP开发者不能把“所有修改都用POST”理解成HTTP协议的唯一标准,而应该根据接口语义选择合适的方法。
登录密码为什么通常使用POST
登录表单通常使用POST,因为密码不应该放进URL。
例如:
/login.php?username=brian&password=123456
这种设计会把密码直接放进URL,是非常糟糕的实践。
正确做法是使用POST:
然后通过HTTPS保护整个通信过程。
这里还有一个容易忽略的问题:即使使用POST,也不能把密码写进应用日志、调试日志或者错误日志。
真正安全的登录系统还应该使用密码哈希、会话保护、登录失败限制以及适当的CSRF和身份认证机制。
$_GET和$_POST都必须进行服务器端验证
不能因为GET用于搜索,就认为GET参数可以直接使用。
例如:
$id = $_GET['id'];
然后直接:
$sql = "SELECT * FROM articles WHERE id = $id";
依然存在SQL注入风险。
正确方式应该同时进行类型验证和参数化查询:
$id = filter_input(
INPUT_GET,
'id',
FILTER_VALIDATE_INT
);
if ($id === false || $id === null || $id < 1) {
http_response_code(400);
exit('Invalid article ID');
}
$stmt = $pdo->prepare(
'SELECT * FROM articles WHERE id = ?'
);
$stmt->execute([$id]);
这里做了两件不同的事情。
FILTER_VALIDATE_INT负责判断输入是否符合整数规则,PDO预处理语句负责防止参数破坏SQL结构。
这两者不能互相替代。
不要把filter_var当成“万能过滤器”
PHP提供了filter_var()和filter_input(),但它们并不是一个可以把所有用户输入变成“安全字符串”的万能工具。
例如:
$email = filter_input(
INPUT_POST,
'email',
FILTER_VALIDATE_EMAIL
);
适合验证邮箱格式。
对于整数:
$page = filter_input(
INPUT_GET,
'page',
FILTER_VALIDATE_INT
);
适合验证整数。
但用户名应该按照业务规则验证,HTML输出则需要HTML上下文的编码,SQL查询需要预处理语句,文件上传需要文件类型和存储位置控制。
不同问题必须使用不同的安全措施。
htmlspecialchars解决的是HTML输出问题
例如用户提交:
服务器将其保存到数据库以后,如果直接输出:
echo $username;
就可能产生XSS。
如果这个数据要作为HTML文本输出,可以:
echo htmlspecialchars(
$username,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
这与GET还是POST没有直接关系。
无论数据来自:
$_GET
还是:
$_POST
只要最终进入HTML输出环境,都需要根据上下文正确编码。
同样,也不能把htmlspecialchars()提前应用于所有输入,然后把编码后的字符串直接保存到数据库。通常更合理的设计是保存业务数据,在最终输出到HTML时进行HTML编码。
PHP没有“接收GET前先用filter_var转换UTF-8”这种规则
原稿中提到PHP默认使用ISO-8859-1处理GET参数,并建议接收参数前通过filter_var()进行编码转换,这个说法需要纠正。
filter_var()不是字符编码转换工具。
现代PHP网站应该从整个系统层面统一字符编码,通常使用UTF-8,包括HTML页面、数据库连接、数据库表、应用程序源码和JSON接口等。
如果实际数据出现乱码,需要检查HTTP请求编码、HTML文档声明、数据库连接字符集、数据库表字符集以及数据本身,而不是在所有$_GET参数上统一调用某个过滤器。
例如PDO连接MySQL时,可以明确使用:
$pdo = new PDO(
$dsn,
$username,
$password,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]
);
数据库连接的字符集则应该在DSN或连接初始化阶段正确配置,例如使用charset=utf8mb4。
GET和POST并不是简单的“缓存”和“不缓存”
原稿把GET描述成“更容易缓存”,把POST描述成“通常不会缓存”,方向上有一定背景,但如果作为工程规则就过于简单。
HTTP缓存主要由请求方法、响应头、Cache-Control、Expires、ETag、Vary等机制共同决定。
GET响应通常更容易参与HTTP缓存,因为GET主要用于获取资源;但是否实际缓存取决于响应的缓存策略。
POST响应也并非绝对不能缓存,HTTP规范允许在特定条件下缓存某些POST响应,只是实际Web基础设施通常对POST的缓存处理更加谨慎。
因此,开发者不应该使用“GET一定缓存、POST一定不缓存”来设计业务逻辑。
如果担心用户重复提交注册、订单或者支付表单,更重要的是使用幂等设计、唯一约束、请求ID或者支付平台提供的幂等键,而不是简单依赖POST“不缓存”。
表单重复提交不能靠POST解决
例如用户提交订单以后,浏览器因为网络延迟让用户连续点击两次提交按钮。
即使两个请求都是POST,也可能产生两个订单。
因此生产环境经常采用Post/Redirect/Get模式。
用户提交POST以后,服务器完成业务处理,然后返回重定向,让浏览器使用GET请求访问结果页面。
这样用户刷新结果页面时,不会再次提交原来的POST请求。
对于支付、订单和会员开通等关键业务,还应该在数据库和服务端增加幂等控制。
例如订单系统可以使用唯一订单号或者幂等键,确保同一业务请求重复到达时不会重复扣款或者重复创建权益。
GET和POST只是请求方式,真正的安全边界还在服务器
无论参数来自:
$_GET
还是:
$_POST
都不能相信它。
用户可以自己构造HTTP请求:
curl "https://example.com/article.php?id=100"
也可以手工发送POST:
curl -X POST \
-d "username=test&email=test@example.com" \
https://example.com/register.php
因此,网页上的HTML表单并不是安全边界。
服务器必须假设所有请求参数都可能被修改。
如果一个后台页面要求:
user_id=100
服务器不能因为这个字段来自隐藏表单就相信它。
用户完全可以改成:
user_id=101
真正的权限判断必须在服务器端完成。
API设计中的GET和POST
在API设计中,GET通常用于读取资源,例如:
GET /api/articles/100
POST则经常用于创建新的资源:
POST /api/articles
如果需要修改资源,则可能使用PUT或者PATCH;删除资源则通常使用DELETE。
例如:
GET /api/articles/100
POST /api/articles
PATCH /api/articles/100
DELETE /api/articles/100
这比把所有操作都设计成POST /api.php?action=xxx更加清晰。
不过,实际项目不一定必须严格实现完整REST架构。最重要的是接口语义清晰、权限明确、参数验证充分,并且能够正确处理重复请求和异常情况。
文件上传为什么必须使用POST
文件上传一般需要:
喜欢这篇报道?
使用下面的功能,方便以后继续阅读和分享 MNewsTV
关于文章收藏
收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。
删除收藏请进入「我的收藏」进行管理。
捐助(Paypal): https://www.paypal.me/observeccp 订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP