PHP Standards Recommendations [0-4]

2016年12月13日 沈伯伟

PSR 是 PHP Standards Recommendations 的简写,由 PHP FIG 组织制定的 PHP 规范,是 PHP 开发的实践标准。 PHP FIG,FIG 是 Framework Interoperability Group(框架可互用性小组)的缩写,由几位开源框架的开发者成立于 2009 年,从那开始也选取了很多其他成员进来 (包括但不限于Laravel, Joomla, Drupal, Composer, Phalcon, Slim, Symfony, Zend Framework等),虽然不是「官方」组织,但也代表了大部分的 PHP 社区。 组织的目的在于:以最低程度的限制,来统一各个项目的编码规范,避免各家自行发展的风格阻碍了程序设计师开发的困扰,于是大伙发明和总结了PSR。

总结一句话就是PSR是编写PHP代码需要遵守的规范。目前官网上公布的规范共有18条(PHP Standards Recommendations)。 其中处于ACCEPTED状态的共有7条,下面主要学习这7条规范(附加已经弃用的PSR-0,辅助理解替代它的PSR-4)。

PSR-6,7,13在:PSR 6,7,13

P.S:PSR规范的状态可以分为如下几个阶段:

Stage : Pre-Draft -> Draft -> Review -> Accepted (-> DEPRECATED)

本文的翻译或者理解有问题,欢迎留言指正~

PSR-1 Basic Coding Standard

  • PHP文件必须只能使用<?php<?=标签。

  • PHP文件必须只能使用UTF-8 without BOM编码。

  • 一个PHP文件中应该或声明符号(类,函数,常量等), 或其他会产生“副作用”(side-effects)的操作(如:生成文件输出以及修改.ini配置文件等)。 但是,不应该同时做。

  • 命名空间和类必须遵从“自动加载”规范[PSR-0,PSR-4]。

  • 类的名字必须使用StudlyCaps大写开头的驼峰命名方式声明。

  • 类常量必须使用全大写方式,单词见使用_分割的方式声明(all upper case with underscore separators)。

  • 方法的名字必须使用camelCase小写开头的驼峰命名方式声明。

具体解释和示例:PSR-1: Basic Coding Standard

PSR-2 Coding Style Guide

  • 代码必须遵从PSR-1。

  • 代码必须使用4个空格作为缩进,而不是tab

  • 在每一行代码的长度上必须不能有硬性约束。软性约束是必须小于120字符,应该小于80字符。

  • 在声明namespace必须留一个空行,同样use命名空间后也必须留一空行。

  • 类的开始花括号({)必须在单独一行,结束括号(})也必须在主体后单独一行。

  • 方法的开始花括号({)必须在单独一行,结束括号(})也必须在主体后单独一行。

  • 访问修饰符(privateprotected或者public必须添加到所有的属性和方法前, abstract以及final必须声明在访问修饰符之前,static必须声明在访问修饰符之后。

  • 控制结构的关键字必须要有一个空格,方法或函数调用后必须不能有空格。

  • 控制结构的开始花括号({)必须同一行,结束括号(})也必须在主体后下一行

  • 控制结构的开始圆括号(必须不能有空格,结束圆括号)必须不能有空格。

一个符合上述部分示例的示例:

<?php
namespace Vendor\Package;

use FooInterface;
use BarClass as Bar;
use OtherVendor\OtherPackage\BazClass;

class Foo extends Bar implements FooInterface
{
    public function sampleMethod($a, $b = null)
    {
        if ($a === $b) {
            bar();
        } elseif ($a > $b) {
            $foo->bar($arg1);
        } else {
            BazClass::bar($arg2, $arg3);
        }
    }

    final public static function bar()
    {
        // method body
    }
}

具体解释和示例:PSR-2: Coding Style Guide

PSR-3 Logger Interface

本条规范制定了日志接口规范,目的是简化、通用日志接口。下面以Psr\Log\LoggerInterface为例介绍:

  • 基本 Basics

    • LoggerInterface接口对外定义了八个方法,分别用来记录RFC 5424中定义的八个等级的日志: debuginfonoticewarningerrorcriticalalertemergency

    • 第九个方法为log,第一个参数为记录日志的等级(也就是上边说的八个等级之一)。可使用一个预先定义的等级常量作为此参数来调用log方法, 且必须与直接调用以上八个方法具有同样的结果。如果传入的等级常量参数没有预先定义,必须抛出Psr\Log\InvalidArgumentException类型的异常。 在不确定当前接口支持的情况下,使用者不应该使用未支持的等级常量来调用此方法。

  • 消息 Message

    • 每个方法接收一个string或者具有一个__toString()方法的对象作为消息参数。这样是为了让实现者(Implementors)可以直接处理传来的对象。 如果不是这样,实现者必须把对象转换为string

    • 消息可以包含占位符(placeholders ),这些占位符可以被实现者用上下文(context)数组的值替换掉。(类似于模板)

      关于占位符的更多描述:

      • 占位符的名称必须与上下文数组中的键名保持一致。

      • 占位符的名称必须由一个左花括号{以及一个右括号}包含。花括号与名称之间必须不能有空格符。

      • 占位符的名称应该只由A-Za-z,0-9、下划线_、以及英文句号.组成,其它字符作为将来占位符规范的保留。

      • 实现者可以通过对占位符采用不同的转义和转换策略来生成最终记录的日志。 而使用者(Users)在不知道上下文的数据时,不应该提前转义占位符。

      如下是一个上下文替换的实现例子:

        /* *
        * Interpolates context values into the message placeholders.
        */
        function interpolate($message, array $context = array())
        {
          // build a replacement array with braces around the context keys
          $replace = array();
          foreach ($context as $key => $val) {
              // check that the value can be casted to string
              if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
                  $replace['{' . $key . '}'] = $val;
              }
          }
      
          // interpolate replacement values into the message and return
          return strtr($message, $replace);
        }
      
        // a message with brace-delimited placeholder names
        $message = "User {username} created";
      
        // a context array of placeholder names => replacement values
        $context = array('username' => 'bolivar');
      
        // echoes "User bolivar created"
        echo interpolate($message, $context);
      
  • 上下文 Context

    • 每个方法(LoggerInterface的九个方法)都接受一个上下文数组参数,用来装载字符串不好表示的外部信息。 这个数组可以包含任何信息,所以实现者必须确保能尽可能“宽容”地处理其上下文的信息, 对于上下文的数据,必须不能 抛出异常,或产生任何PHP错误、警告或提醒信息(errorwarningnotice)。

    • 如需通过上下文参数传入了一个Exception对象,它必须'exception'作为键名。 记录异常信息是很普遍的,而且允许实现者提取异常栈轨迹(stack trace)在日志后台支持的情况下。 实现者仍然必须验证键名为'exception'的键值是否真的是一个Exception,毕竟它可以包含任何信息。

  • 相关辅助类和接口 Helper classes and interfaces

    • Psr\Log\AbstractLogger类让你能够很轻易地实现LoggerInterface接口,只需继承它和实现其中的log方法,而另外八个方法就能够把记录信息和上下文信息传给它。

    • Psr\Log\LoggerTraittrait同样也只需实现其中的log方法。 需要特别注意:因为在traits不能实现接口,所以你仍然不得不去实现LoggerInterface

    • Psr\Log\NullLogger类和接口一同提供。在没有日志提供的情况下,使用者可以把它当作一个“低效运行的黑洞”(fall-back “black hole” )实现。 不过,当上下文的构建非常消耗资源时(虽然是NullLogger,也要通过一步context构建才生成最终日志),带条件检查的日志记录或许是更好的办法(绕过context构建)。

    • Psr\Log\LoggerAwareInterface类只包含setLogger(LoggerInterface $logger)方法,框架可以使用它实现自动装载任意的日志记录实例。

    • Psr\Log\LoggerAwareTraittrait可以被用来实现等价的接口在任何的类中,它提供你访问$this->logger

    • Psr\Log\LogLevel类保存八个记录等级常量。

psr/log包中的部分内容示例:

  • Psr\Log\LoggerInterface
<?php

namespace Psr\Log;

/**
 * Describes a logger instance
 *
 * The message MUST be a string or object implementing __toString().
 *
 * The message MAY contain placeholders in the form: {foo} where foo
 * will be replaced by the context data in key "foo".
 *
 * The context array can contain arbitrary data, the only assumption that
 * can be made by implementors is that if an Exception instance is given
 * to produce a stack trace, it MUST be in a key named "exception".
 *
 * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
 * for the full interface specification.
 */
interface LoggerInterface
{
    /**
     * System is unusable.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function emergency($message, array $context = array());

    /**
     * Action must be taken immediately.
     *
     * Example: Entire website down, database unavailable, etc. This should
     * trigger the SMS alerts and wake you up.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function critical($message, array $context = array());

    /**
     * Runtime errors that do not require immediate action but should typically
     * be logged and monitored.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function error($message, array $context = array());

    /**
     * Exceptional occurrences that are not errors.
     *
     * Example: Use of deprecated APIs, poor use of an API, undesirable things
     * that are not necessarily wrong.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function warning($message, array $context = array());

    /**
     * Normal but significant events.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function notice($message, array $context = array());

    /**
     * Interesting events.
     *
     * Example: User logs in, SQL logs.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function info($message, array $context = array());

    /**
     * Detailed debug information.
     *
     * @param string $message
     * @param array $context
     * @return null
     */
    public function debug($message, array $context = array());

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     * @return null
     */
    public function log($level, $message, array $context = array());
}
  • Psr\Log\LoggerAwareInterface
<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object
     *
     * @param LoggerInterface $logger
     * @return null
     */
    public function setLogger(LoggerInterface $logger);
}
  • Psr\Log\LogLevel
<?php

namespace Psr\Log;

/**
 * Describes log levels
 */
class LogLevel
{
    const EMERGENCY = 'emergency';
    const ALERT     = 'alert';
    const CRITICAL  = 'critical';
    const ERROR     = 'error';
    const WARNING   = 'warning';
    const NOTICE    = 'notice';
    const INFO      = 'info';
    const DEBUG     = 'debug';
}

相关英文原文请查看:PSR-3: Logger Interface

PSR-4 Autoloading Standard

本条规范描述如何从文件路径自动加载类。本规范可以作为其他自动载入规范的补充,包括PSR-0。 此外,本规范还描述自动载入的类对应的文件存放路径规范。

  1. 本规范中所说的“类”包含:类,接口,traits以及其他类似的结构。

  2. 一个完全合格的类名需具有以下形式:

     \<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
     \<命名空间>(\<子类名>)*\<类名>
     其中子类名可为0或多个
    
    1. 完全合格的类名必须要有一个顶级命名空间(top-level namespace),称为 “vendor namespace”。

    2. 完全合格的类名可以有一个或多个子命名空间。

    3. 完全合格的类名必须有一个最终的类名。

    4. 完全合格的类名中的下滑线_在任一部分都是没有特殊含义(废除了PSR-0_就是目录分割符的写法)。

    5. 完全合格的类名可以由任意大小写字母组成。

    6. 所有类名必须都是大小写敏感的。

  3. 当根据完全合格的类名载入对应文件时……

    1. 在一个完全合格的类名中,前面连续的一个或多个命名空间和子命名空间(不包含最前面的命名空间分隔符), 即“命名空间前缀”,其至少对应一个“文件基目录”。

    2. 紧接“命名空间前缀”后的一系列子命名空间名对应”文件基目录“里的子目录, 其中的命名空间分隔符作为目录分隔符。子文件目录的名字必须匹配子命名空间的名字(大小写敏感)。

    3. 结尾的类名对应一个.php为后缀的文件,这个文件的名字必须匹配结尾类名(大小写敏感)。

    理解:上边总结一下就是:\Aa\Bb\Cc\Dd这个类,其中若命名空间前缀\Aa\Bb对应“文件基目录”为/path/src/。 那么这个类对应的文件就是:/path/src/Cc/Dd.php

  4. 自动加载的实现必须不能抛出异常,必须不能引起任何级别的错误,应该不能有返回值。

下表展示了符合规范类名、命名空间前缀和文件基目录所对应的文件路径。

Fully Qualified Class Name Namespace Prefix Base Directory Resulting File Path
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php

相关英文原文请查看:PSR-4: Autoloader

Example Implementations of PSR-4

弃用:PSR-0 Autoloading Standard

2014年10月21日起PSR-0已经被标记为“弃用”,PSR-4成为其的替代。

下面描述了一些强制性的要求,这些要求在自动加载交互中必须被坚持遵守。

  • Mandatory 强制

    • 一个完整合格的命名空间和类必须具备如下的结构:

        \<Vendor Name>\(<Namespace>\)*<Class Name>
      
    • 每一个命名空间必须有一个顶级的命名空间(”Vendor Name”)。

    • 每一个命名空间可以有多个子命名空间。

    • 在从文件系统加载时,每一个命名空间分隔符(\\)被转化为一个目录分割符(DIRECTORY_SEPARATOR)。

    • 类名中的每一个_符也被转化为一个目录分割符(DIRECTORY_SEPARATOR), 而命名空间中的_没有特殊含义。(与PSR-4最大区别)

    • 在从文件系统中加载的时候,完全合格的命名空间和类名的后缀都是.php

    • vendor names,命名空间(namespaces)和类名(class names )均可以由大小写字符组成,大小写敏感。

  • 例子

    • \Doctrine\Common\IsolatedClassLoader => /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php

    • \Symfony\Core\Request => /path/to/project/lib/vendor/Symfony/Core/Request.php

    • \Zend\Acl => /path/to/project/lib/vendor/Zend/Acl.php

    • \Zend\Mail\Message => /path/to/project/lib/vendor/Zend/Mail/Message.php

  • 命名空间和类名中有下划线_时

    • \namespace\package\Class_Name => /path/to/project/lib/vendor/namespace/package/Class/Name.php

    • \namespace\package_name\Class_Name => /path/to/project/lib/vendor/namespace/package_name/Class/Name.php

这些标准应该成为自动加载实现的最低标准。你可以利用PHP 5.3中加载类的样例SplClassLoader来测试加载遵从这些标准的类。

  • 例子的实现

    下面的样例函数将简单地展示上述的标准是如何实现自动加载的:

      <?php
    
      function autoload($className)
      {
          $className = ltrim($className, '\\');
          $fileName  = '';
          $namespace = '';
          if ($lastNsPos = strrpos($className, '\\')) {
              $namespace = substr($className, 0, $lastNsPos);
              $className = substr($className, $lastNsPos + 1);
              $fileName  = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
          }
          $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
    
          require $fileName;
      }
      spl_autoload_register('autoload');
    
  • SplClassLoader的实现

    下面的gist是一个简单的SplClassLoader实现,它可以加载你遵从上述自动加载标准所定义命名的类。 目前推荐使用这套标准加载PHP 5.3的类。

    http://gist.github.com/221634

相关英文原文请查看:PSR-0: Autoloading Standard

后续:PSR 6,7,13

参考文献

PHP Standards Recommendations

PHP PSR-1 基本代码规范(中文版)

PHP中PSR-(0-4)规范


评论