从零读懂 PSR-1:PHP 代码规范到底在约束什么?
我打算用一段时间系统性地学习 PSR(PHP Standard Recommendation)规范,借这个机会,把一些长期似懂非懂的 PHP 细节彻底理一遍。 本文聚焦 PSR-1。 PSR-1 要求 PHP 文件只能使用两种标签: 为什么? PHP 曾经支持: 这种“短标签”依赖服务器配置,换个环境就可能直接挂掉。 结论很简单: 这句话信息量很大,我们拆开讲。 ASCII 只解决了一件事: 它完全不考虑中文、日文、法语等语言中的特殊字母。 Unicode 的目标是: 比如: 但 如果按照 UTF-8 的设计非常聪明: 所以它成了目前互联网上最流行的标准。 注意:这里的 BOM 不是浏览器的 BOM,而是: BOM 是文件最前面的几个字节,用来标记编码类型。 问题在于: 结果就是: 不是 PHP 代码有问题,是: 所以 PSR-1 直接一刀切: 很多人第一次看到 BOM,以为和 DOM 有关系(比如我),这是个误区。 DOM(Document Object Model)本质是: HTML: 内存中的 DOM 结构: 类似于这种: 所以:JS 操作的不是字符串,而是对象。 BOM(Browser Object Model)是: 比如: 关系很简单: 而 PSR-1 里的 BOM,和浏览器毫无关系。 翻译过来是: 这个文件: 问题立刻就来了: PHP 是顺序执行的, 一句话总结: 如果没有自动加载,我们只能这样写: 问题有三个: PHP 提供了机制: 但前提是: 它们定义的不是语法,而是映射规则: 我们只需要: 剩下的交给自动加载器。 PSR-1 对 类名、常量、方法名 的格式约束,看起来很强制,实际上解决的是一个更底层的问题: PHP 是弱类型、动态语言,语法层面给的信息非常少,于是只能靠命名来补。 StudlyCaps(大驼峰)有一个核心目的: 对比一下: 我们不用读上下文,也不用看定义: 这在 PHP 里尤其重要,因为: 因此,类名的首字母大写,本质是在弥补语言层面的信息缺失。 类常量的语义不是变量,而是: 全大写的作用只有一个: 下划线而不是驼峰,是为了增加扫读效率: 我们可以不用拆词、不用脑补,直接读。 这和 SQL、环境变量、配置项的命名语义是完全一致的。 对比这两种: 如果方法也用大写开头,它在视觉上会和类混在一起,读代码时需要额外判断: 到这里,PSR-1 的所有规则就形成了一个完整闭环: 此前虽接触过 PHP 与 ThinkPHP 框架,但随着时间推移,不少语法细节与编码规范已渐渐模糊,唯独 MVC 这一核心开发思想留存下来。感谢这次梳理 PSR-1 规范的契机(也感谢潘老师的任务指引),让我得以跳出只记规则的浅层学习。 这次系统性复盘,不仅是重新熟悉 PHP 语法,更是理解规范存在的意义:好的编码规范从来不是束缚,而是降低协作成本、规避隐藏问题的底层逻辑。后续也会带着这种 “知其然更知其所以然” 的思路,继续梳理其他 PSR 规范,把 PHP 基础扎得更牢。
这不是背规范,而是搞清楚:这些规则为什么存在。一、PHP 标签:只准用
<?php 和 <?=Files MUST use only
<?php and <?= tags.<?php
<?= $name ?>历史遗留问题
<% 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:编码界的“祖师爷”
Unicode:只管编号,不管存储
给世界上每一个字符分配一个唯一编号
你 → U+4F60A → U+0041Unicode 不规定这些编号在内存里怎么存。UTF-8:最成功的实现方案
Unicode 全量存储,存英文会非常浪费空间。于是诞生了 UTF-8。BOM 是什么?为什么要禁止?
Byte Order Mark
session_start(); // 报错
header(); // 报错BOM 抢先输出了内容
PHP 文件必须是 UTF-8,但不能带 BOM
三、DOM ≠ BOM:别再搞混了
DOM 是什么?
浏览器把 HTML 转换成一棵可操作的对象树
<p>你好 <span>世界</span></p>p
├── 文本节点:你好
└── span
└── 文本节点:世界
BOM 又是什么?
浏览器对外暴露的能力接口
window
├── document (DOM)
└── location / history / navigator ...四、声明 ≠ 副作用:一个文件只能干一件事
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');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';自动加载解决了什么?
当你使用一个类时,才去加载它对应的文件
类名和文件路径之间必须有确定规则
PSR-0 / PSR-4 的作用
App\Service\UserService
↓
src/Service/UserService.phpnew UserService();六、命名不是审美,而是统一标准
人在读代码时,如何快速分辨这是什么东西。
类名:大驼峰,本质是类型声明
Class names MUST be declared in StudlyCaps.
class UserService {}
class OrderDetail {}
class HttpRequest {}让类在视觉上立刻和变量、函数区分开。
$userService = new UserService();类常量:全大写
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方法名:小驼峰,强调行为
Method names MUST be declared in camelCase.
public function getUserById() {}
public function calculateTotalPrice() {}
public function isValid() {}$user->getName();
User::getName();写在最后