PSR是什么?
PSR是 PHP Standards Recommendation(PHP推荐标准)的简称/如果最近读过关于
PHP的博客,你或许见过PSR-1、PSR-2和PSR3等术语。这些都是 PHP-FIG制定的推荐 规范。这些规范的名称以PSR-开头,后面跟着一个数字。PHP-FIG制定的每个推荐规范
用于解决大多数PHP框架经常会遇到的某个具体问題。PHP框架无需频繁解决相同的问
题,它们可以遵守PHP-FIG制定的推荐规范,使用共用的方案来解决。
截至本书出版时,PHP-FIG发布了五个推荐规范
PSR-1:基本的代码风格(https://www.php-fig.org/psr/psr-1/)
PSR-2:严格的代码风格(https://www.php-fig.org/psr/psr-2/)
PSR-3:日志记录器接口(http://www.php-fig.org/psr/psr-3)
PSR-4:自动加载(http://www.php-fig.org/psr/psr-4)
注意:如果你数了、发现只有四个推荐規范、你数得对、PHP-FIG放弃了第一份推规范
PSR-0 (http://www.php-fig.org/psr/psr-0)。第一位推荐规范被新发布的PSR-4替代了。
注意、这些 PHP-FIG:推荐規范与我前面提到的三种实现互操作性的方法(接口、自动加
线和代码风格)是一一对应的、这可不是巧合。
PHP-FIG发布的这些推落規范让我特别高兴、它们是现代PP生态系的牢固基石,定义了PHP组件和框架实现互操作性的方式。我承认,PHP标准不是最吸引人的话题,但
(我觉得)这些标准是理解现代PHP的前提。
PSR-1:基本的代码风格
如果想写符合社区标准的PHP代码、首先要连守PSR。这是最容易守的PHP标准
簧单到你可能已经使用了・PSR-1提出了简单的指导方针・这方针易于实现,只 要极少的工作量。PSR1的目的是为遵守这一标准的PHP概架提供代码风格基准。符合
PSR-1的代码必須满足以下要求:
PHP标签
必须把PHP代码放在<php ?>或 <?= ?>标签中。不得使用其他PHIP标签句法。
编码*
所有PHP文件都必须使用UTF-8字符集编码,而且不能有字节顺序标记(Byte
Order Mark,BOM)。这个要求听起来很复杂,共实文本编辑器或IDE都能自动做
到这一点。
目的
个PHP文件可以定义符号(类、性状、函数和常量等),或者执行有副作用的操
作(例如,生成结果或处理数据)、但不能同时做这两件事。这是一个简单的要
求,我们只需稍微深谋远虑一些。
自动加载
PHP命名空间和类必须違守PSR+自动加器标准,我们只需为PHP符号选择合适
的名称,并把定义符号的文件放在预期的位置。稍后我们会讨论PSR-4
类的名称
PHP类的名称必须一直使用驼峰式( CamelCase)。这种格式也叫标题式
(TitleCase)。例如 CoffeeGrinder、CoffeeBean 和 PourOver。
常量的名称
PHP常量的名称必须全部使用大写字母。如果需要,可以使用下划线把单词分开。
例如WOOT、LET_OUR_POWERS COMBINE和 GREAT SCOTT。
方法的名称
PHP方法的名称必须一直使用cameLCase 这种驼峰式。也就是说,方法名的首字
母是小写的,后续单词的首字母都是大写的。例如 phpIsAwesome、 iLoveBacon和
tennantIsMyFavoriteDoctor。
PSR-2:严格的代码风格
贯彻PSR-1之后,下一步要实施PSR-2。PSR-2使用更严格的指导方针进一步定义PHP的
代码风格。
PSR-2制定的代码风格对有多个来自世界各地的贡献者的PHP框架来说,可谓是及时雨。因为每个贡献者都有自己独特的风格和偏好,严格的通用代码风格能让开发者轻易地编写代码,而且其他贡献者能快速理解代码的作用。
与PSR-1不同,PSR-2推荐规范中的指导方针更严格。你可能不喜欢PSR-2中的某些指导
方针,可这是很多PHP流行框架选择使用的代码风格。你没必要非得遵守PSR-2,但是
如果遵守的话,其他开发者能轻易地理解你的PHP代码,会有更多的人使用你的代码,为代码做贡献。
建议:你应该使用更为严格的PSR-2代码风格。虽然名称中“严格”两字,其实代码写起来十分容
易。写得多了,最终会变成第二天性。而且,有工具能把现有的PHP代码自动格式化成符
合PSR-2风格的代码。
贯彻PSR-1
使用PSR-2代码风格之前先要贯彻PSR-1代码风格
缩进
这个话题很热门,通常分为两个阵营:第一个阵营选择使用一个制表符缩进,第二
个阵营选择使用多个空格缩进(这样酷多了)。PSR-2推荐规范要求PHP代码使用
四个空格缩进。
建议:以我个人的经验来看,缩进更适合使用空格,因为空格最可靠,在不同的代码编辑器 中渲染的效果基本一致。而制表符的宽度各异,在不同的代码编辑器中渲染的效果也
不同。为了得到最好的外观一致性,请使用四个空格缩进代码。
文件和代码行
PHP文件必须使用UNIX风格的换行符(LF),最后要有一个空行、而且不
PHP关闭标签 ?>。每行代码不能超过80个字符,至少不能超过120个字答、每行末尾不能有空格。这些要求听起来需要做很多工作,其实不然。大多数代码编辑器都
能在指定的宽度处换行,删除行尾的空白,并使用UNIX风格的换行符、这些要求
都能自动实现,因此你无需担心。
建议:一开始我觉得不写PHP关闭标签 ?>很奇怪。其实,最好不写关闭标签,这样能避免意料之外的输出错误。如果加上关闭标签 ?>,而且在关闭标签后有空行,那么这个空
会被当成输出,导致出错(例如,设定HTTP首部时)
关键字
我认识很多PHP开发者,他们会使用全部大写的TRUE、 FALSE和NULL。如果你也是
这么写的,从现在开始摒弃这种写法,只使用小写字母形式。PSR-2推荐规范要求,PHP关健字都应该他用小写字
命名空间
每个命名空间声明语句后必须跟着一个空行。类似地,使用use关键字导入命名空
间或为命名空间创建别名时,在一系列use声明语句后要加一个空行。下面是一个示例:
<?php
namespace My\Component;
use Symfony\Components\HttpFoundation\Request;
use Symfony\Components\HttpFoundation\Response;
class App
{
// 类的定义体
}
类
与缩进方式一样,类定义体的括号位置是另一个引起激烈争论的话题。有些人选择
把起始括号和类名放在同一行,另一些人则选择在类名之后新起一行写起始括号
PSR-2推荐规范要求,类定义体的起始括号应该在类名之后新起一行写,如下述示例
所示。类定义体的结東括号必须在定义体之后新起一行写。如果你已经这么做
了,会觉得这没什么。如果类扩展其他类或实现接口, extends和 implements 关键
字必须和类名写在同一行。
<?php
namespace My\App;
class Administrator extends User
{
// 类的定义体
}
方法
方法定义体的括号位置和类定义体的括号位置一样:方法定义体的起始括号要在方
法名之后新起一行写;方法定义体的结東括号要在方法定义体之后新起一行写。要
特别注意方法的参数:起始圆括号之后没有空格,结束圆括号之前也没有空格。方
法的每个参数(除了最后一个)后面有一个返号和空格。
<?php
namespace Animals;
Class StrawNeckedibis
{
public function flapWings($numberOfTimes = 3, $speed ="fast")
{
// 方法的定义体
}
}
可见性
类中的每个属性和方法都要声明可见性。可见性由pub1ic、 protected或 private
指定,其作用是决定在类的内部和外部如何访问属性和方法。传统的PHP开发者
可能习惯在类的属性前加上vax关键字,在私有方法的名称前加上下划线(二)。
别这么做,我们应该使用前面列出的可见性关键字。如果把类属性或方法声明为
abstract或 final,这两个限定符必须放在可见性关键字之前。如果把属性或方法
声明为 static,这个限定符必须放在可见性关键字之后。
<?php
namespace Animals;
class StrawNeckedIbis
{
//指定了可见性的静态属性
public static $numberOfBirds = 0;
// 指定了可见性的方法
public function __construct()
{
static::$numberOfBirds++;
}
}
控制结构
我最常在这条指导方针上犯错。所有控制结构关键字后面都要有一个空格。控制
结构关键字包括:if、 elseif、else、 Switch、case、 while、 do while、for、
foreach、try和 catch。如果控制结构关键字后面有一对圆括号,起始圆括号后面
不能有空格,结東圆括号之前不能有空格。与类和方法的定义体不同,控结
键字后面的起始括号应该和控制结构关键字写在同一行。控制结构关键字后面的结
束括号必须单独写在一行。下面是一个简单的示例,展示了上述指导方针:
<?php
$gorilla = new \Animals\Gorilla;
$ibis = new \Animals\StrawNeckedIbis;
if ($gorilla->isAsleep() === true) {
do{
$gorilla->beatChest();
}while($ibis->isAsleep() === true);
$ibis->flyAway();
}
建议:我们可以自动实施PSR-1和PSR-2代码风格,很多代码编辑器都能根据PSR-1和PSR1.
自动格式化代码。有些工具还能根据PHP标准,帮你审核并格式化代码,例如PHP
Code Sniffer(也叫 phpcs,http://bit.ly/phpsniffer)。这个工具(可以直接在命令行中
使用,也可以集成到IDE中使用)能指出你的代码和指定PHP代码标准之间的差异
大多数包管理器(例如PEAR、 Homebrew、 Aptitude或Yum)都能安装 phpcs
你还可以使用法比安博滕斯尔开发的PHP-CS- Fixer(http://cs.sensiolabs.org/)自动
纠正大多数不符合标准的代码。这个工具并不完美,但是使用这个工具只需做少量工
作或无需做任何工作,就能让代码更符合PSR规范。
编写PSR-3:日志记录器接口
PHP-FIG发布的第三个推荐规范与前两个不同,不是一系列指导方针,而是一个接口,
规定PHP日志记录器组件可以实现的方法。
注意:日志记录器是对象,用于把不同重要程度的消息写入指定的输出。记录的消息用于诊断、
检査和排除应用中的操作、稳定性和性能方面的问题。例如,在开发过程中把调试信息
人文本文件;捕获网站的流量统计信息,写入数据库;把致命错误的诊断信息通过电子
件发给网站管理员。最受欢迎的PHP日志记录器组件是由乔迪·波哥阿诺开发的 monolog/monolog (https://packagist.org/packages/monolog/monolog)
大多数PHP框架都在某种程度上实现了日志功能。在PHP-FIG出现之前,每个框架使用
不同的方式实现日志功能,通常会使用专属的实现方式。为了实现互操作性和专业化
也就是现代PHP提倡的重复利用,PHP-FIG制定了PSR-3日志记录器接口。若想使用符合
PSR-3规范的日志记录器,框架要做到两件重要的事:日志功能委托给第三方库实现
最终用户能选择使用他们喜欢的日志记录器组件。这么做对所有人都好。
编写PSR-3日志记录器
符合PSR-3推荐规范的PHP日志记录器组件,必须包含一个实现 PSRILO8 \Loggerinter
face接口的PHP类。PSR-3接口复用了RFC5424系统日志协议 (hti/. tools, ietf. org/hrml
rfc5424),规定要实现九个方法:
<?php
namespace Psr\Log;
interface LoggerInterface
{
public function emergency($message, array $context =array());
public function alert($message, array $context=array());
public function critical($message, array $context =array());
public function error($message, array $context =array());
public function warning($message, array $context =array());
public function notice($message, array $context =array());
public function info($message, array $context =array());
public function debug($message, array $context =array());
public function log($level, $message, array $context =array());
}
每个方法对应RFC 5424协议的一个日志级别,而且都接受两个参数。第一个参数
$message必须是一个字符串,或者是一个有 _toString() 方法的对象。第二个参数
$context是可选的,这是一个数组,提供用于替换第一个参数中占位标记的值。
建议: c o n t e x t 参 数 用 于 构 造 复 杂 的 日 志 消 息 。 消 息 文 本 中 可 以 使 用 占 位 符 , 例 如 p l a c e h o l d e r n a m e 。 占 位 符 由 、 占 位 符 的 名 称 和 组 成 , 不 能 包 含 空 格 。 context 参数用于构造复杂的日志消息。消息文本中可以使用占位符,例如{ placeholder_name}。 占位符由{、占位符的名称和 } 组成,不能包含空格。 context参数用于构造复杂的日志消息。消息文本中可以使用占位符,例如placeholdername。占位符由、占位符的名称和组成,不能包含空格。contexta参数的值是一个关联
数组,键是占位符的名称(没有花括号),对应的值用于替换消息文本中的占位符。
如果想编写符合PSR-3规范的日志记录器,要创建一个实现 Psr\Log Vloggerinterfacef接
口的PHP类,而且要提供这个接口中每个方法的具体实现
使用PSR-3日志记录器
如果你正在编写自己的PSR-3日志记录器,停下来,想想自己是不是在浪费时间。我强
烈不建议你自己编写日志记录器。为什么呢? 因为已经有一些十分出色的PHP日志记录
器组件了。
如果需要符合PSR-3规范的日志记录器,使用 monolog/monolog(https://packagist.org
packages/monolog/monolog)即可,别浪费时间找其他组件了。 Monolog组件完全实现
了PSR-3接口,而且便于使用自定义的消息格式化程序和处理程序扩展功能。 Monolog
的消息处理程序可以把日志消息写入文本文件、系统日志和数据库,能通过电子邮件发
送,还能传给 Hip Chat、 Slack、网络中的服务器和远程AP。只要你能想到的日志处理
方式, Monolog几乎都提供了。如果非常不巧, Monolog没有提供你所需的处理程序、
自己编写消息处理程序并将其集成到 Monolog也特别容易。示例3-1展示了如何设量Monolog,把日志消息写入文本文件。你可以看出这个操作是多么简单。
示例3-1:使用 Monolog
<php
require 'vendor/autoload.php';
use Monolog\Logger;
use Monolog\Handler\StreamHandler;
// 准备日志记录器
$log = new Logger('myApp');
$log->pushHandler(new StreamHandler('logs/development.log', Logger::DEBUG));
$log->pushHandler(new StreamHandler('logs/production.log', Logger::WARNING));
// 使用日志记录器
$log->debug('This is a debug message');
$log->warning('This is a warning message');
PSR-4自动加载器
PHP-FIG发布的第四个推荐规范描述了一个标准的自动加裁器策略。自动加载器略是
载器标准的PHP组件和框架,使用同一个自动加载器就能找到相关代码,然后将其数入 指,在运行时按活查找PHP类、接口或性状、井将共载人PP解释器支特PSR4自动加
PHP解释器。这是了不起的功能,把现代PHP生态系统中很多可互操作的组件联系起来
为什么自动加载器很重要
在PHP文件的顶部你是不是经常看到类似下面的代码?
<?php
include 'path/to/file1.php';
include 'path/to/file2.php';
include 'path/to/file3.php';
太常见了,是吧? 你可能熟知 require()、 require_once()、 include()和 include_once()函数的作用:把外部PHP文件载入当前脚本。如果只需载入几个PHP脚本,使用
这些函数能很好地完成工作。可是,如果需要引入一百个PHP脚本,或者需要引入一千
个PHP脚本呢? 此时,require()函数和 include()函数无法胜任,这就是为什么PHP自
动加载器很重要的原因。有了自动加载器,我们无需像前面那样手动引入文件,自动加
载器策略能找到PHP类、接口或性状,然后在运行时按需将其载入PHP解释器。
在PHP-FIG发布PSR-4推荐规范之前,PHP组件和框架的作者使用 autoload()和sql
autoload_register()函数注册自定义的自动加载器策略。可是,每个PHP阻件和框架
都使用独特的自动加载器,而且每个自动加载器使用不同的逻辑査找并加载PHP类、
接口和性状。使用这些组件和框架的开发者,在引导PHP应用时必须调用每个组件各自
的自动加载器。我一直使用 Sensio Labs开发的Twig(http://twig.sensiolabs.org)模板组
件,这个组件很棒。可是,如果没有PSR-4,我要阅读Tvig的文档,弄清如何在应用的
引导文件中注册这个组件自定义的自动加载器,如下所示:
<?php
require_once “/path/to/lib/Twig/Autoloader.php”;
Twig_Autoloader::register();
试想一下,如果要研究每个PHP组件的自动加载器,然后在自己的应用中注册,那该有
多麻烦啊!PHP-FIG认识到了这个问题,推荐使用PSR-4自动加载器规范,促进组件实
现互操作性。得益于PSR-4,我们只需使用一个自动加載器就能自动加载应用中的所有
PHP组件。这太棒了!大多数现代的PHP组件和框架都符合PSR-4规范。如果你自己编写
并分发组件,确保你的组件也符合PSR-4规范。符合这一规范的组件包括: Symfony、
Doctrine、 Monolog、Twig、 Guzzle、 Swiftmailer、 PHPUnit和 Carbon等。
PSR-4自动加载器策略
与其他PHP自动加载器一样,PSR-4描述的策略用于在运行时査找并加载PHP类、接口和
性状。PSR-4推春规范不要求改变代码的实现方式,只建议如何使用文件系统目录结构
和PHP命名空间组织代码。PSR-4自动加载器策略依赖PHP命名空间和文件系统目录结本
査找并加载PHP类、接口和性状。
PSR-4的精髓是把命名空间的前缀和文件系统中的目录对应起来。例如,我可以告诉
PHP, \Orei1 lymmodernphp命名空间中的类、接口和性状在物理文件系统的src/目录
中,这样PHP就知道,前缀为 \Oreilly\ModernPHP的命名空间中的类、接口或性状对应
于src/目录里的目录和文件。例如,\Oreilly\ Modernphp\Chapter1命名空间对应于src/
Chapteri1目录, \Oreilly\Modernphp\Chapter1类对应于src/Example.php文件。
建议:PSR-4会把命名空间的前经和文件系统中的目录对应起来。命名空间的前缀可以是顶层命
名空间,也可以是顶层命名空间加上任意一个子命名空间,相当灵活。
还记得我们在第2章讨论的厂商命名空间吗? PSR-4自动加载器策略对开发组件和框架供
其他开发者使用的作者来说最有用。PHP组件的代码在唯一的厂商命名空间中,而且组
件的作者会指定厂商命名空间对应于文件系统中的哪个目录,这和我前面演示的做法完
全一样。我们在第4章会进一步探讨这个概念。
如何编写PSR-4自动加载器(以及为什么不该这么做)
我们知道,符合PSR-4规范的代码有个命名空间前缀对应于文件系统中的基目录;还知道,这个命名空间前缀中的子命名空间对应于这个基目录里的子目录。示例3-2实现了一个自动加载器,
这段代码摘自 PHP-FIG网站(http://bit.ly/php-fig)。 这个自动加载器会根据PSR-4自动加我器策路査找并加载类、接口和性状。
示例3-2:PSR-4自动加载器
<?php
/**
* 举例说明如何实现项目专用的自动加载器。
*
* 使用SPL注册这个自动加载函数后, 遇到下述代码时这个函数
* 会尝试从 /path/to/project/src/Baz/Qux.php 文件中加载
* \Foo\Bar\Baz\Qux类:
*
* new \Foo\Bar\Baz\Qux;
*
* @param string $class完全限定的类名。
* @return void
*/
spl_autoload_register(function ($class) {
// 这个项目的命名空间前缀
$prefix = 'Foo\\Bar\\';
// 这个命名空间前缀对应的基目录
$base_dir = __DIR__ . '/src/';
// 参数传人的类使用这个命名空间前缀吗?
$len = strlen($prefix);
if (strncmp($prefix, $class, $len) !== 0) {
// 不使用, 交给注册的下一个自动加载器处理
return;
}
// 获取去掉前绥后的类名
$relative_class = substr($class, $len);
// 把命名空间前缀替换成括目录,
// 在去掉前缀的类名中, 把命名空间分隔符替换成目录分隔符,
// 然后在后面加上.php
$file = $base_dir . str_replace('\\', '/', $relative_class) . '.php';
// 如果文件存在,将其导入
if (file_exists($file)) {
require $file;
}
});
复制上述代码,将其粘贴到你的应用中,然后修改变量$prefix和 $base_dir的的值,这样
你就有一个可用的PSR-4自动加我器了。不过,如果你发现自己在编写PSR-4自动加载
器,请停下来,然后问自己有必要这么做吗。为什么呢?因为我们可以使用依赖管理器
Composer自动生成的PSR-4自动加载器。很巧,接下来在第4章我们就会讨论这个话题。
来源: Modern PHP 第三章 标准