PHP推荐标准方面的概念,也就是PSR代码规范,从而掌握更加规范的编码方式。
一.暴露问题
当我们做PHP快速开发时,必然要选择各种合适我们当前项目的框架。但是,不同的框架开发年代、方式、思维都有所不同。导致的结果:不能与其它框架实现共享代码。比如A框架的某一个功能库很棒,但是现在用的B框架,移植的成本就变的很大。
所以,框架与框架之间并没有考虑过互相通信。对于开发者来说,这么做的效率非常的低。当大家意识到这种问题时,一个自发的组织PHP-FIG讨论如何提升框架之间的通信以及如何提升开发者的开发效率。进而制定了一系列的推荐规范,来加大代码之间的联系,改进框架之间的共享能力。
二.PSR诞生
PSR即:PHP推荐标准。目前通过审核的有PSR1-PSR4,还有最近的6和7。重点研究已经成熟的前四个,对于初学者来说,可以起到一个很好的代码规范作用。早些时候还有一个PSR0规范,但已经被PHP-FIG废弃从而被PSR4取代。
三.PSR1-4风格详解
PSR-1:基本的代码风格
PSR-1 是最为基础的 PHP 代码规范,也是最容易遵守的标准。
PSR-1 编码规范:
1.标签风格
必须严格的把PHP代码放在<?php ?>或<?= ?>标签中,不可以使用其它任何自定义的标签句法。
<!doctype html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
//多行显示方式
<?php
echo '多行';
?>
//单行显示方式
<?='单行'?>
</body>
</html>
打印显示:
多行 单行
2.字符编码
PHP文件必须严格使用无BOM的UTF-8编码,在PHP专用的IDE上,设置的UTF-8编码基本都是无BOM的。在文本编辑器上,UTF-8编码有BOM和无BOM的选择。
3.副作用
在一个PHP文件中应该(并不强制)只定义新的声明(包括:类、函数或常量),或者只书写产品的逻辑操作。不应该同时具有两种,否则将会产生副作用。
换言之:不去在直接在执行的业务操作的文件中声明类、函数和常量等,而是通过包含文件将声明引入进来。也就是说,一个文件只做一件事,尽可能让它功能单一,而不要添加其它的“副作用”。
所以现在流行的开发模式为:程序入口+引导文件+自动加载+大量类库+开发者的MVC层。
可能产生副作用的有如下:
(1)生成输出;
(2)显现直接的加载文件:require或include;
(3)连接外部服务;
(4)修改ini配置;
(5)抛出错误或异常;
(6)修改全局或静态变量;
(7)读写文件等。
<?php
//这个就是一个副作用
require '1.php';
//这又是一个副作用
echo '<strong>';
//函数
function fnTest()
{
//函数主体
}
上面的代码,本身是一个函数创建的文件,却有引入文件和HTML输出的操作,产生了两个副作用。这种构建代码的方式,是不推荐的。
//判断函数是否存在,不属于副作用
<?php
//函数
function fnTest()
{
//函数主体
}
//这是一个主体,不属于副作用
if (!function_exists('fnFoo')) {
function fnFoo()
{
//函数主体
}
}
如果这个页面是声明函数相关的,附加了判断函数是否存在再创建函数,这种情况下,不算作“副作用”。
4.命名空间和类
(1)命名空间以及类的命名必须严格遵循PSR-4(自动加载控制器规范);
(2)每个类都独立为一个文件,且命名空间至少有一个层次:顶级的组织名称(vendorname);
(3)类的命名必须遵循大写开头的驼峰是规范,比如:Test;
(4)PHP5.3及以后的版本代码必须使用正式的命名空间。
//命名空间
namespace Vendor\Model;
//类
class Test
{
}
关于常量:类的常量所有字符必须大写,词间用下划线分割。
//常量命名规范
const PI = 3.14;
const BATE_VERSION = '2.1.3';
关于属性:类的属性命名可以遵循(不做强制要求,但选择一种模式后,团队开发时必须统一规范风格):
(1)大写开头的驼峰式($WebName);
(2)小写开头的驼峰式($webName);
(3)下划线分割式($web_name);
//属性命名规范
protected $WebName = '西西欢迎你们';
关于方法:方法名称必须严格符合小写开头的驼峰式命名规范。
//方法命名规范
public function startApp()
{
//方法主体
}
举例:
<?php
//命名空间
namespace Psr\Model;
//创建一个类Test
class Test
{
//属性命名规范(受保护的)
protected $WebName = '西西欢迎你们';
protected $webName = 'xxx';
protected $web_name = 'xxx';
//常量命名规范
const PI = 3.14;
const BATE_VERSION = '2.1.3';
//方法命名规范
public function index()
{
}
//方法主体
public function startApp()
{
}
}
PSR-2:严格的代码风格
一.PSR-2概述
1.PSR-2其实是PSR-1的继承和扩展。和PSR-1不同的是,PSR-2更加的严格。当然,严格并不代表不容易,写到一定的量,就非常的好驾驭了。
二.PSR-2编码规范
1.编码准则
PHP代码必须严格符合PSR-1的所有规范。
2.文件准则
(1)PHP文件必须要以一个空白行作为结束;
(2)纯PHP代码文件必须省略最后的?>结束标签。
3.行准则
(1)代码每一行应该保持在80个字符以内;
(2)理论上一定不能超过120个字符;
(3)大于80个字符应该换成多行;
(4)非空行后面一定不可以有多余的空格符;
(5)空行可以有助于代码的可读性以及分块;
(6)每行一定不可以存在多条语句。
4.缩进准则
代码必须使用4个空格符的缩进,一定不可以使用tab键。这样可以避免不同环境或平台导致的代码差异,使之混乱。
注意:phpstorm等专用IDE会默认将tab键转换为4个空格符,所以,大胆敲tab键。具体测试,可以使用记事板测试便知。
5.关键字准则
(1)PHP所有的关键字必须全部小写;
(2)true、false和null也必须全部小写。
6.命名空间准则
(1)namespace声明后必须插入一个空白符;
(2)所有use必须在 namespace后声明;
(3)每条use声明语句必须只有一个use关键字;
(4)use声明语句块后必须要有一个空白行。
<?php
namespace Psr\Model;
use Controller;
use AbcAccess as Abc;
//下面开始编写PHP代码
7.继承与实现准则
(1)关键字extends和implements必须写在类名称的同一行;
(2)类的开始花括号必须独占一开,结束的花括号也必须独占一行。
//继承和实现
class MyPerson extend Person implements Action
{
//类主体
}
(3)implements的实现列表也可以分成多行,分成多行时,每个实现接口必须独立成行,包括第一个。
//实现多个接口
class Person extends Per implements
\Action,
\Abc,
\Dec
{
//类主体
}
8.属性准则
(1)每个属性都必须添加访问修饰符;
(2)定不可以使用关键字var声明一个属性;
(3)每条语句一定不可以超过一个属性;
(4)不该使用下划线作为前缀区分是protected或private。
//标准的属性
public $name = 'Mr.Wang';
9.方法准则
(1)所有方法都必须添加修饰符;
(2)不改使用下划线作为前缀,来区分protected或private;
(3)方法名后一定不可以有空格符,其开始花括号必须独占一行,结束花括号也必须独占一行;
(4)参数左括号后和右括号前,一定不可以有空格。
//标准方法
public function run()
{
//方法主体
}
10.参数准则
(1)参数列表中,每个逗号后面必须要有一个空格,而逗号前面不可以有空格;
(2)有默认值的参数,必须放在参数列表的末尾。
//标准参数
public function run($key, $value, $arr = [])
{
//方法主体
}
(3)参数列表可以分列成多行,这样包括第一个参数在内的每个参数必须独立成行。
(4)拆分成多行的参数列表后,结束括号以及方法开始花括号必须写在同一行,中间用一个空格分隔。
//拆分参数
public function run(
$key,
$value,
$arr = []
) {
//方法主体
}
11.abstract、final和static准则
需要添加abstract和final声明时,必须写在访问修饰符前面,而static则必须写在其后。
//抽象类
<?php
namespace Psr\Model;
抽象类必须写在修饰符前面
abstract class Computer
{
//在修饰符后面
protected static $mode;
//抽象写在在修饰符前面
abstract public function run();
//在修饰符后面
final public static function bar()
{
//方法主体
}
}
12.方法及函数调用准则
(1)方法及函数调用时,方法名或函数名与参数左括号之间一定不可以有空格,参数右括号前也一定不可以有空格。
(2)每个参数前一定不可以有空格,但其后必须有一个空格。
//标准调用
$p->run($key, $value);
(3)参数可以分成多行,此时第一个参数在内的每一个都必须独立成行。
//独立成行的参数
$p->run(
$key,
$value
);
13.控制结构准则
(1)控制结构关键字后必须要有一个空格;
(2)左括号(后面一定不可以有空格;
(3)右括号)前面也一定不可以有空格;
(4)右括号)与开始花括号{之间必须要有一个空格;
(5)结构体主体必须要有一个缩进;
(6)结束花括号}必须在结构体主体后单独成行。
(7)每个结构体的主体都必须包含在成对的花括号之中,这能让结构体更加结构化,避免后期加入新行时出错的几率。
<?php
//结构体
if ($flag) {
//结构体内部
}
14.if、elseif和else
(1)else和elseif都与前面的结束花括号在同一行;
(2)elseif代替else if,让一个单词控制。
<?php
if ($flag) {
//结构体内部
} elseif ($flag2) {
//elseif
} else {
//else
}
15.switch和case
(1)case语句必须相对于switch进行一次缩进;
(2)break语句以及case内部的其它语句都必须相对case进行一次缩进;
(3)非空case直穿语句,主体里必须有类似//no break的注释。
<?php
switch ($flag) {
case 0:
echo '开始阶段';
break;
case 1:
echo '常规运行';
//不需要break
case 2:
case 3:
case 4:
echo '结束阶段';
break;
default:
echo '发生意外';
break;
}
16.while和do while
while和do while结构体基本和if语句一致。
<?php
//while标准格式
while ($flag) {
//
}
do {
//
} while ($flag);
17.for、foreach和try catch
这三种语法和if结构体规范要求基本一致。
<?php
//for循环
for ($i = 0; $i < 10; $i++) {
///for结构体
}
//foreach遍历
foreach ($array as $key => $value) {
//foreach结构体
}
//try catch
try {
//try
} catch (Exception $e) {
//catch
}
18.闭包
(1)闭包声明时,关键字function后以及关键字use的前后都必须要有一个空格;
(2)开始花括号必须写在声明的同一行,结束花括号必须紧跟主体结束的下一行;
(3)参数列表和变量列表的左括号后以及右括号前,一定不可以有空格;
(4)参数和变量列表中,逗号前一定不可以有空格,而逗号后必须要有空格。
<?php
//闭包
$myFn = function ($arg1, $arg2) {
//匿名函数代码
};
//闭包
$myFn = function ($arg1, $arg2) use ($var1, $var2) {
//匿名函数代码
};
//在分行显示时,和属性方法传参规则一样。
$myFn = function (
$arg1,
$arg2
) use
(
$var1,
$var2
) {
//匿名函数代码
};
PSR-3:日志记录器接口;
一.PSR-3概述
PSR-3主要是定义一个日志接口,规定PHP日志记录器组件可以实现的方法。
二.PSR-3 编码规范
(1)必须包含一个实现Psr\Log\LoggerInterface(规范路径)接口的PHP类;
(2)需要实现九个方法。
//符合PSR-3的日志接口
<?php
//LoggerInterface.php路径
namespace Psr\Log;
/**
* 日志记录实例 *
* 日志信息变量 —— message,**必须** 是一个字符串或是实现了 __toString() 方法的对象。
*
* 日志信息变量中 **可以** 包含格式如 “{foo}” (代表 foo) 的占位符,
* 它将会由上下文数组中键名为「foo」的键值替代。
*
* 上下文数组可以携带任意的数据,唯一的限制是,当它携带的是一个 exception 对象时,它的键名 **必须** 是 "exception"。
*/
interface LoggerInterface
{
/**
* 系统不可用
*
* @param string $message
* @param array $context
* @return null
*/
public function emergency($message, array $context = array());
/**
* 必须立刻采取行动
*
* 例如:在整个网站都垮掉了、数据库不可用了或者其他的情况下, **应该** 发送一条警报短信把你叫醒。
*
* @param string $message
* @param array $context
* @return null
*/
public function alert($message, array $context = array());
/**
* 紧急情况
*
* 例如:程序组件不可用或者出现非预期的异常。
*
* @param string $message
* @param array $context
* @return null
*/
public function critical($message, array $context = array());
/**
* 运行时出现的错误,不需要立刻采取行动,但必须记录下来以备检测。
*
* @param string $message
* @param array $context
* @return null
*/
public function error($message, array $context = array());
/**
* 出现非错误性的异常。
*
* 例如:使用了被弃用的API、错误地使用了API或者非预想的不必要错误。
*
* @param string $message
* @param array $context
* @return null
*/
public function warning($message, array $context = array());
/**
* 一般性重要的事件。
*
* @param string $message
* @param array $context
* @return null
*/
public function notice($message, array $context = array());
/**
* 重要事件
*
* 例如:用户登录和SQL记录。
*
* @param string $message
* @param array $context
* @return null
*/
public function info($message, array $context = array());
/**
* debug 详情
*
* @param string $message
* @param array $context
* @return null
*/
public function debug($message, array $context = array());
/**
* 任意等级的日志记录
*
* @param mixed $level
* @param string $message
* @param array $context
* @return null
*/
public function log($level, $message, array $context = array());
}
然后可以创建一个具体实现这个接口的类,来编写日志管理类。比如编写一个Logger.php来实现这个接口(LoggerInterface)即可。
<?php
//Logger.php路径
namespace Model\Db;
use Psr\Log\LoggerInterface;
class Logger implements 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())
{
//debug 详情
}
public function log($level, $message, array $context = array())
{
//任意等级的日志记录
echo '日志等级为:' . $level . '<br>';
//打印日志信息
echo '日志信息为:' . $message . '<br>';
}
}
test.php
<?php
require 'Psr/Log/LoggerInterface.php';
require 'Model/Db/Logger.php';
$logger = new Model\Db\Logger();
$logger->log('ERROR', '一条错误');
执行:http://192.168.3.62/Psr/test.php
日志等级为:ERROR
日志信息为:一条错误
目前来说没有编写细节方面的东西,而符合PSR-3规范的日志记录器,已经有相关组件可以直接使用了。比如:monolog。这个产品完全在LoggerInterface接口下开发,非常方便。
PSR-4:自动加载器
一.PSR-4 概述
PSR-4是关于由文件路径自动载入对应类的相关规范,在不要求改变代码的实现方式,只建议如何使用文件系统目录结构和PHP命名组织代码。
二.PSR-4编码规范
首先,先自行设计一个自动加载器,然后对照规范来检验。
目录结构如下:
1.加载文件:psr/Home/Model/Db/File.php
<?php
//File.php
namespace My\Think\Db;
class File
{
public function run()
{
echo 'model file running...';
}
}
2.执行文件:psr/auto.php
进行自动载入File.php文件
<?php
//auto.php
require '/Home/Model/Db/File.php';
$file = new \My\Think\Db\File();
$file->run();
执行:http://192.168.3.62/Psr/auto.php
model file running…
注意:命名空间最后的Db和类文件目录Db是一样的;而命名空间前缀和文件路径毫无关系。当然,你可以将命名空间和文件路径也完全对应起来,那样更加简单。
进行自动载入File.php文件
<?php
spl_autoload_register(function ($class) {
//命名空间前缀
$prefix = 'My\\think\\';
这个命名空间对应的目录(父路径)
$base_dir = __DIR__.'/Home/Model/';
//得到长度
$len = strlen($prefix);
//获取去掉前缀后的类名
$relative_class = substr($class, $len);
//完整类路径
$path = $base_dir.$relative_class.'.php';
//判断路径是否存在,如果存在引入过来
if (file_exists($path)) {
require $path;
}
});
//实例化
$file = new \My\Think\Db\File();
$file->run();
运行:http://192.168.3.62/Psr/auto.php
model file running…
一个完整的类名需具有以下结构:
<命名空间>(<子命名空间>)*<类名>
(1)完整的类名必须要有一个顶级命名空间;
(2)完整的类名可以有一个或多个字命名空间;
(3)完整的类名必须有一个最终的类名;
(4)完整的类名中任意一部分中的下划线都是没有特殊含义的;
(5)完整的类名可以由大小写字母组成;
(6)所有类名都必须是大小写敏感的。
当根据完整的类名载入相应的文件
(1)完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为「命名空间前缀」,其必须与至少一个「文件基目录」相对应;
(2)紧接命名空间前缀后的子命名空间必须与相应的「文件基目录」相匹配,其中的命名空间分隔符将作为目录分隔符。
(3)末尾的类名必须与对应的以.php 为后缀的文件同名。
(4)自动加载器(autoloader)的实现一定不可抛出异常、一定不可触发任一级别的错误信息以及不应该有返回值。
实际中,你根本不需要自行编写符合PSR-4规范的autoload自动加载器。可以使用composer来自动生成PSR-4自动加载器。