我打算用一段时间系统性地学习 PSR(PHP Standard Recommendation)规范,借这个机会,把一些长期似懂非懂的 PHP 细节彻底理一遍。
不是在背规范,而是想搞清楚:这些规则为什么存在。
本文聚焦 PSR-1。
一、PHP 标签:只准用 <?php 和 <?=
Files MUST use only<?phpand<?=tags.
PSR-1 要求 PHP 文件只能使用两种标签:
<?php
<?= $name ?>为什么?
历史遗留问题
PHP 曾经支持:
<% echo $a; %>这种“短标签”依赖服务器配置,换个环境就可能直接挂掉。
<?= 是安全的
<?= 是 <?php echo ?> 的语法糖,从 PHP 5.4 起默认开启、不可关闭。
echo 函数用于输出一个或多个字符串,可以快速在页面查看变量的值,类似于angular的插值(interpolation)表达式。
结论很简单:
为了代码在任何环境都能跑。
二、编码问题:UTF-8、Unicode、ASCII 和 BOM 到底啥关系?
Files MUST use only UTF-8 without BOM for PHP code.
这句话信息量很大,我们拆开讲:
ASCII:编码界的“祖师爷”
ASCII 只解决了一件事:
- 英文字母
- 阿拉伯数字
- 少量符号(一些标点符号、运算符号,控制字符等)
它完全不考虑中文、日文、法语等语言中的特殊字母。
Unicode:只管编号,不管存储
Unicode 的目标是:
给世界上每一个字符分配一个唯一编号
比如:
你→ U+4F60A→ U+0041
但 Unicode 不规定这些编号在内存里怎么存。
UTF-8:最成功的实现方案
如果按照 Unicode 全量存储,存英文会非常浪费空间。于是诞生了 UTF-8。
UTF-8 的设计非常聪明:
- 英文 → 1 字节
- 中文 → 3 字节
- 向下兼容 ASCII
- 空间效率高
所以它成了目前互联网上最流行的标准。
BOM 是什么?为什么要禁止?
注意:这里的 BOM 不是浏览器的 BOM,而是:
Byte Order Mark
BOM 是文件最前面的几个字节,用来标记编码类型。
问题在于:
- PHP 是 边读边执行
- BOM 会被当成普通字符
- 普通字符 = 输出
结果就是:
session_start(); // 报错
header(); // 报错不是 PHP 代码有问题,是:
BOM 抢先输出了内容
所以 PSR-1 直接一刀切:
PHP 文件必须是 UTF-8,但不能带 BOM
三、DOM ≠ BOM
很多人第一次看到 BOM,以为和 DOM 有关系(比如我),这是个误区。
DOM 是什么?
DOM(Document Object Model)本质是:
浏览器把 HTML 转换成一棵可操作的对象树
HTML:
<p>你好 <span>世界</span></p>内存中的 DOM 结构:
p
├── 文本节点:你好
└── span
└── 文本节点:世界类似于这种:
所以:JS 操作的不是字符串,而是对象。
BOM 又是什么?
BOM(Browser Object Model)是:
浏览器对外暴露的能力接口
比如:
- 窗口大小
- URL
- 历史记录
- 弹窗
关系很简单:
window
├── document (DOM)
└── location / history / navigator ...而 PSR-1 里的 BOM,和浏览器毫无关系。
四、声明 ≠ 副作用:一个文件只能干一件事
Files SHOULD either declare symbols (classes, functions, constants, etc.) or cause side-effects (e.g. generate output, change .ini settings, etc.) but SHOULD NOT do both.
翻译过来是:
要么定义东西,要么执行事情,别混着来。
反面示例
<?php
// util.php
function connectDb() {
// ...
}
echo "db ready";这个文件:
- 声明了函数
- 又产生了输出
问题立刻就来了:
include 'util.php';
header('Location: /login');PHP 是顺序执行的,include 的瞬间就输出了内容,HTTP Header 被污染。
正确方法
// util.php
function connectDb() {
// ...
}// bootstrap.php
connectDb();
echo 'db ready';一句话总结:
声明文件是被动的,执行文件是主动的。
五、自动加载:为什么 PSR 强制使用?
Namespaces and classes MUST follow an autoloading PSR (PSR-4,PSR-0)
如果没有自动加载,我们只能这样写:
require 'User.php';
require 'Order.php';
require 'Service/UserService.php';问题有三个:
- 顺序要自己维护
- 文件一多直接崩
- 重构成本极高
自动加载解决了什么?
PHP 提供了机制:
当你使用一个类时,才去加载它对应的文件
但前提是:
类名和文件路径之间必须有确定规则
PSR-0 / PSR-4 的作用
它们定义的不是语法,而是映射规则:
App\Service\UserService
↓
src/Service/UserService.php我们只需要:
new UserService();剩下的交给自动加载器。
六、命名不是只为审美,而是统一标准
PSR-1 对 类名、常量、方法名 的格式约束,看起来很强制,实际上解决的是一个更底层的问题:
人在读代码时,如何快速分辨这是什么东西。
PHP 是弱类型、动态语言,语法层面给的信息非常少,于是只能靠命名来补。
类名:大驼峰,本质是类型声明
Class names MUST be declared in StudlyCaps.
class UserService {}
class OrderDetail {}
class HttpRequest {}StudlyCaps(大驼峰)有一个核心目的:
让类在视觉上立刻和变量、函数区分开。
对比一下:
$userService = new UserService();我们不用读上下文,也不用看定义:
- 小写开头 → 变量
- 大写开头 → 类
这在 PHP 里尤其重要,因为:
- PHP 不要求文件名必须和类名一致
- 不强制一文件一类
- 没有编译期类型检查
因此,类名的首字母大写,本质是在弥补语言层面的信息缺失。
类常量:全大写
Class constants MUST be declared in all upper case with underscore separators.
class User
{
public const STATUS_ACTIVE = 1;
public const STATUS_DISABLED = 0;
}类常量的语义不是变量,而是:
- 枚举值
- 状态定义
- 业务规则
- 不应被修改的事实
全大写的作用只有一个:
强制我们在阅读时停一下:这个量是不可变的规则。
下划线而不是驼峰,是为了增加扫读效率:
STATUS_EMAIL_NOT_VERIFIED我们可以不用拆词、不用脑补,直接读。
这和 SQL、环境变量、配置项的命名语义是完全一致的。
方法名:小驼峰,强调行为
Method names MUST be declared in camelCase.
public function getUserById() {}
public function calculateTotalPrice() {}
public function isValid() {}对比这两种:
$user->getName();
User::getName();如果方法也用大写开头,它在视觉上会和类混在一起,读代码时需要额外判断:
- 这是构造对象?
- 还是在调用函数?
到这里,PSR-1 的所有规则就形成了一个完整闭环:
- 文件级:标签、编码、BOM、副作用
- 结构级:声明与执行分离
- 系统级:自动加载
- 语义级:命名即类型
写在最后
此前虽接触过 PHP 与 ThinkPHP 框架,但随着时间推移,不少语法细节与编码规范已渐渐模糊,唯独 MVC 这一核心开发思想留存下来。感谢这次梳理 PSR-1 规范的契机(也感谢潘老师的任务指引),让我得以跳出只记规则的浅层学习。
这次系统性复盘,不仅是重新熟悉 PHP 语法,更是理解规范存在的意义:好的编码规范从来不是束缚,而是降低协作成本、规避隐藏问题的底层逻辑。后续也会带着这种 “知其然更知其所以然” 的思路,继续梳理其他 PSR 规范,把 PHP 基础扎得更牢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用。你还可以使用@来通知其他用户。