二、接受标准

每个行业都有自己的标准。无论是正式的还是非正式的,他们都支配着做事的方式。软件行业倾向于将标准形式化为文档,这些文档建立了各种规范和程序,旨在确保产品和服务的质量和可靠性。它们进一步激发了兼容性和互操作性过程,否则这可能是不可能的。

将代码放在产品的上下文中,多年来出现了各种编码标准。它们的使用产生了更高的代码质量,并减少了对代码库的认知摩擦。代码质量是可持续软件开发的支柱之一,因此标准对任何专业开发人员来说都具有无可挑剔的重要性也就不足为奇了。

谈到 PHP,我们需要考虑几个层次的标准。有特定于语言本身的编码标准,也有特定于单个库、框架或平台的编码标准。虽然这些标准中有一些相互遵循,但其他标准有时往往会发生冲突。通常,这种冲突与一些小事情有关,例如将开始函数括号放在新行中或将其保留在同一行中。在这种情况下,特定的库、框架和平台标准应该优先于纯语言标准。

早在 2009 年,在芝加哥举行的php【tek】会议上,许多开发人员联手成立了php 标准小组。围绕standards@lists.php.net的邮件列表进行组织,最初的目标是建立适当的自动加载标准。自动加载对于框架和平台开发人员来说是一个严峻的挑战。不同的开发人员在命名类文件时使用不同的约定。这对互操作性产生了严重影响。代号为PSR-0PHP 标准建议旨在通过概述自动加载器互操作性必须遵循的实践和约束来解决此问题。在早期阶段,PHP 社区中的团体接受度是相当保守的。他们还没有赢得在社区眼中这样称呼自己的权利。两年后,该集团更名为框架互操作组,简称PHP-FIG。到目前为止,PHP-FIG 已经产生了几个 PSR,重申了它在开发人员中的地位。

PHP-FIG 及其 PSR 早于 PEAR 编码标准,PEAR 编码标准至今仍占主导地位。它主要关注 PHP 语言本身的元素。这些元素处理我们编写函数、变量、类等的方式。另一方面,PSR 主要关注事物的互操作性方面。PHP-FIG 和 PEAR 在 PSR-1 和 PSR-2 的边界内相交;这使得开发人员现在可以自由地遵循一组标准,即 PHP-FIG 小组提供的标准。

在本章中,我们将详细了解当前发布和接受的 PSR 标准:

  • PSR-1-基本编码标准
  • PSR-2-编码样式指南
  • PSR-3-记录器接口
  • PSR-4-自动加载标准
  • PSR-6-缓存接口
  • PSR-7-HTTP 消息接口
  • PSR-13-超媒体链接

Throughout the PSRs, there is an extensive use of the MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL keywords. The meaning of these keywords is described in more detail under RFC 2119 (http://www.ietf.org/rfc/rfc2119.txt).

PSR-1-基本编码标准

PSR-1 是基本的编码标准。它概述了我们的代码应该遵循的规则,正如 PHP-FIG 的成员所看到的。该标准本身非常简短。

文件只能使用<?php 和<=标签PHP 曾经支持几个不同的标签(<?php ?><? ?><?= ?><% %><%= %><script language="php"></script>)。部分的使用取决于配置指令short_open_tag<? ?>)和asp_tags<% %><%= %>)。PHP7 版本删除了 ASP 标记(<%<%=)和脚本标记(<script language="php">)。现在建议只使用<?php ?><?= ?>标签,以最大限度地提高兼容性。

对于 PHP 代码,文件必须仅使用无 BOM 的 UTF-8。字节顺序标记BOM)是一个 Unicode 字符,U+FEFF 字节顺序标记(BOM),出现在文档的开头。正确使用时,BOM 表不可见。HTML5 浏览器需要识别 UTF-8 BOM 并使用它来检测页面的编码。另一方面,PHP 可能会遇到 BOM 问题。位于文件开头的 BOM 与 PHP 标题冲突,导致页面在解释 header 命令之前开始输出。

文件应声明符号(类、函数、常量等)或引起副作用(例如,生成输出、更改.ini 设置等),但不应同时声明PHP 的简单性常常成为它的缺点。这种语言在使用上相当松散。我们可以很容易地从一个空白文件开始,在其中编写整个应用程序。这意味着要有几十个不同的类、函数、常量、变量、include、requires 和其他指令,它们都是一个接一个地堆叠在一起的。虽然这对于快速原型设计可能很有用,但它绝不是构建应用程序时要采用的方法。

以下代码行演示了要避免的示例:

<?php

// side effect: change ini settings
ini_set('error_reporting', E_ALL);

// side effect: loads a file
include 'authenticate.php';

// side effect: generates output
echo "<h1>Hello</h1>";

// declaration
function log($msg)
{
  // body
}

以下代码行演示了要遵循的示例:

<?php

// declaration
function log()
{
  // body
}

// conditional declaration is *not* a side effect
if (!function_exists('hello')) {
 function hello($msg)
 {
   // body
 }
}

名称空间和类必须遵循自动加载的 PSR:[PSR-0,PSR-4]。自动加载在 PHP 中扮演着重要角色。这个概念通过自动从各种文件中引入类和函数,减少了 require 构造的使用。默认情况下,语言本身提供了__autoload()spl_autoload_register()函数来帮助实现这一点。PHP-FIG 小组制定了两个自动加载标准。PSR-0 标准是第一个出现的 PSR,它很快被许多 PHP 框架广泛采用。截至 2014 年 10 月,PSR-0 已被标记为弃用,剩下 PSR-4 作为替代方案。稍后我们将更详细地讨论 PSR-4。目前,可以说为 PHP5.3 及其后版本编写的代码必须使用正式的名称空间。

以下代码行演示了要避免的示例:

<?php

class Foggyline_User_Model
{
  // body
}

以下代码行演示了要遵循的示例:

<?php

namespace Foggyline\Model;

class User 
{
  // body
}

类名称必须在StudlyCaps中声明。类名有时由多个单词组成。例如,想象一下,负责 XML 解析的类。合理地说,我们可以称之为Xml_ParserXmlParserXML_ParserXMLParser或类似的组合。有许多不同的规则用于压缩多个单词,以提高代码的可读性,例如 camel-case、kebab-case、snake-case 等等。本标准建议使用 StudlyCaps,其中字母的大小写任意变化。它们类似于,但可能以更随机的方式执行。

以下代码行演示了要避免的示例:

<?php

class xmlParser 
{
  // body
}

class XML_Parser 
{
  // body
}

以下代码行演示了要遵循的示例:

<?php

class XmlParser 
{
  // body
}

class XMLParser 
{
  // body
}

类常量必须用带下划线分隔符的所有大写字母声明。PHP 系统有两种类型的常量,一种是位于类外部并使用 define 构造定义的常量,另一种是位于类内部的常量。鉴于常数代表不可变变量,它们的名称应该很突出。本标准明确规定,任何类常量名称都应完全大写。但是,它避免了有关属性名称的任何建议。只要我们保持一致,我们可以自由使用以下任意组合($StudlyCaps$camelCase$under_score

以下代码行演示了要避免的示例:

<?php

class XmlParser 
{
  public const APPVERSION = 1.2;
  private const app_package = 'net.foggyline.xml.parser';
  protected const appLicence = 'OSL';
}

以下代码行演示了要遵循的示例:

<?php

class XmlParser 
{
  public const APP_VERSION = 1.2;
  private const APP_PACKAGE = 'net.foggyline.xml.parser';
  protected const APP_LICENCE = 'OSL';
}

方法名称必须在 camelCase 中声明。类中包含的函数称为方法。这里的命名模式不同于前面提到的 StudlyCaps,因为它使用的是 camelCase,不太随意。更具体地说,使用小写 camelCase,这意味着方法名称以小写字母开头。

以下代码行演示了要避免的示例:

<?php

class User 
{
  function say_hello($name) { /* … */ }
  function Pay($salary) { /* … */ }
  function RegisterBankAccount($account) { /* … */ }
}

以下代码行演示了要遵循的示例:

<?php

class User 
{
  function sayHello($name) { /* … */ }
  function pay($salary) { /* … */ }
  function registerBankAccount($account) { /* … */ }
}

The official, full-length PSR-1 Basic Coding Standard guide is available at http://www.php-fig.org/psr/psr-1/.

PSR-2-编码样式指南

PSR-2 是 PSR-1 的扩展。这意味着,当谈到 PSR-2 时,PSR-1 标准有点含蓄。不同之处在于 PSR-2 通过列举一组关于如何格式化 PHP 代码的规则,扩展了基本类和函数的格式化。概述的样式规则是从各个 PFP-FIG 成员项目中共享的相似性中派生出来的。

编码必须遵循编码样式指南 PSR(PSR-1)。可以说,每个 PSR-2 代码都隐式符合 PSR-1。

代码必须使用 4 个空格进行缩进,而不是制表符。在编程世界中,空格与制表符的矛盾由来已久。PHP-FIG 小组投票赞成使用空格,而 4 个空格通常表示单个制表符缩进。空格优于选项卡的好处是一致性。然而,选项卡可能根据环境显示为不同的列数,单个空间始终是一列。虽然这可能不是最有说服力的论点,但标准继续说,4 个空格构成一个缩进。把它想象成 4 个空格,用来表示曾经的单个缩进。现在大多数现代 IDE 编辑器,比如 PhpStorm,都会自动为我们处理这个问题。

线路长度不得有硬限制;软限制必须为 120 个字符;行应不超过 80 个字符。80 个字符的行长参数与编程本身一样古老。1928 年设计的 IBM 打孔卡有 80 列,每列有 12 个打孔位置,每列一个字符。这一每行 80 个字符的设计选择后来被传递到基于字符的终端。尽管显示设备的改进远远超出了这些限制,但即使在今天,一些命令提示仍设置为 80 列。这个标准基本上说,虽然我们可以使用我们想要的任何长度,但最好将其保持在 80 个字符以下。

名称空间声明后必须有一个空行,使用块声明后必须有一个空行。虽然这不是语言本身强加的技术要求,但标准要求它。要求本身更具装饰性。由此产生的使用会更好地影响代码的可读性。

以下代码行演示了要避免的示例:

<?php
namespace Foggyline\User\Model;
use Foggyline\User\Model\Director;

class Employee 
{
}

以下代码行演示了要遵循的示例:

<?php
namespace Foggyline\User\Model;
use Foggyline\User\Model\Director;

class Employee 
{
}

类的开始大括号必须放在下一行,结束大括号必须放在正文后的下一行。同样,这不是语言的技术要求,而是一种装饰性要求。

以下代码行演示了要避免的示例:

<?php

class Employee {
  // body
}

以下代码行演示了要遵循的示例:

<?php

class Employee 
{
  // body
}

方法的开启支架必须在下一行,关闭支架必须在主体后的下一行。同样,这是一种装饰性的要求,它并不是语言本身强加的。

以下代码行演示了要避免的示例:

<?php

class Employee {
  public function pay() {
    // body
  }
}

以下代码行演示了要遵循的示例:

<?php

class Employee 
{
  public function pay()
  {
    // body
  }
}

必须在所有属性和方法上声明可见性;抽象和最终必须在可见性之前声明;必须在可见性之后声明 static。可见性只是官方称之为访问修饰符的缩写。PHP 中的类方法可以使用多个访问修饰符。在这种情况下,访问修饰符的顺序不相关;我们可以很容易地说abstract public functionpublic abstract function或者final public functionpublic final function。当我们向混合中添加static访问修饰符时,情况也是如此,在一个方法中,我们实际上可能有三个不同的访问修饰符。本标准明确规定,若使用abstractfinal修饰符,则需要先设置,若使用static修饰符,则需要遵循publicprivate修饰符。

以下代码块演示了要避免的示例:

<?php

abstract class User
{
  public function func1()
  {
    // body
  }

  private function func2()
  {
    // body
  }

  protected function func3()
  {
    // body
  }

  public static abstract function func4();

  static public final function func5()
  {
    // body
  }
}

class Employee extends User
{
  public function func4()
  {
    // body
  }
}

下面的代码块演示了下面的示例:

<?php

abstract class User
{
  public function func1()
  {
    // body
  }

  private function func2()
  {
    // body
  }

  protected function func3()
  {
    // body
  }

  abstract public static function func4();

  final public static function func5()
  {
    // body
  }
}

class Employee extends User
{
  public static function func4()
  {
    // body
  }
}

控件结构关键字后面必须有一个空格;方法和函数调用不能被调用。这是一个相当表面化的要求,只会影响代码的可读性。

以下代码行演示了要避免的示例:

<?php

class Logger
{
  public function log($msg, $code)
  {
    if($code >= 500) {
      // logic
    }
  }
}

以下代码行演示了要遵循的示例:

<?php

class Logger
{
  public function log($msg, $code)
  {
    if ($code >= 500)
    {

    }
  }
}

控制结构的开启支撑必须在同一条线上,关闭支撑必须在主体后的下一条线上。

以下代码块演示了要避免的示例:

<?php

class Logger
{
  public function log($msg, $code)
  {
    if ($code === 500)
    {
      // logic
    }
    elseif ($code === 600)
    {
      // logic
    }
    elseif ($code === 700)
    {
      // logic
    }
    else
    {
      // logic
    }
  }
}

下面的代码块演示了下面的示例:

<?php

class Logger
{
  public function log($msg, $code)
  {
    if ($code === 500) {
      // logic
    } elseif ($code === 600) {
      // logic
    } elseif ($code === 700) {
      // logic
    } else {
      // logic
    }
  }
}

控制结构的左括号后面不得有空格,控制结构的右括号前面不得有空格。这里的右括号空间可能有点让人困惑,因为在前面,我们看到了标准的缩进空格,而不是制表符。这意味着在结束括号之前有空格。但是,应该只有足够的空间来表示闭合括号点处的实际缩进,仅此而已。

演示了一个要避免的示例(请注意第 7 行上的空格,在开始的花括号之后):

示例演示了要遵循的示例:

The official, full-length PSR-2 Coding Style guide is available at http://www.php-fig.org/psr/psr-2/.

PSR-3-记录器接口

记录不同类型的事件是应用程序的常见做法。虽然一个应用程序可能会将这些类型的事件分类为错误、信息性事件和警告,但其他应用程序可能会加入更详细的严重性级别日志记录。日志消息本身的实际格式也是如此。也就是说,每个应用程序都可能很容易拥有自己的日志机制。这在某种程度上阻碍了互操作性。

PSR-3 标准通过为实际记录器接口定义标准来解决这一问题。这样的标准化接口使我们能够以一种简单而通用的方式编写 PHP 应用程序日志。

互联网工程任务组IETF定义的系统日志协议(RFC 5424)区分了以下八个严重级别:

  • emergency:表示系统不可用
  • alert:表示必须立即采取行动
  • critical:表示临界条件
  • error:表示错误情况
  • warning:表示警告条件
  • notice:表示正常但重要的情况
  • info:表示信息性消息
  • debug:表示调试级消息

PSR-3 标准建立在 RFC 5424 的基础上,规定了LoggerInterface,这为八个严重性级别中的每一个级别公开了一种特殊方法,如下所示:

<?php

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());
}

我们还可以注意到第九个log()方法,其签名与前八个不同。log()方法是一种更方便的方法,其级别参数需要指示八个严重级别中的一个。调用此方法的结果必须与调用特定于级别的方法的结果相同。每个方法都接受一个字符串作为$message,或者一个对象作为__toString()方法。试图以未知的严重性级别调用这些方法必须抛出Psr\Log\InvalidArgumentException

$message字符串可能包含一个或多个占位符,接口实现者可以使用传递到$context字符串中的键值参数插入占位符,如以下抽象示例所示:

<?php

//...
$message = "User {email} created, with role {role}.";
//...
$context = array('email' => ‘john@mail.com', ‘role’ => 'CUSTOMER');
//...

不必详细讨论实现细节,就可以说 PSR-3 是一个简单的标准集,用于对记录器机制的重要角色进行排序。使用记录器接口,我们不必依赖特定的记录器实现。我们可以针对LoggerInterface键入应用程序代码以获取符合 PSR-3 的记录器。

如果我们在项目中使用Composer,我们可以很容易地将psr/log包包含进去。这将使我们能够通过以下方式之一将符合 PSR 的记录器集成到我们的项目中:

  • 实现LoggerInterface接口并定义其所有方法
  • 继承AbstractLogger类并定义log方法
  • 使用LoggerTrait并定义log方法

但是,使用现有的 Composer 包(如monolog/monologkatzgrau/klogger)要容易得多,并且避免编写我们自己的记录器实现。

The Monolog project is a nice example of a popular and robust PHP library that implements the PSR-3 logger interface. It can be used to send our logs to files, sockets, inboxes, databases, and various web services. The official, full-length PSR-3: Logger Interface guide is available at http://www.php-fig.org/psr/psr-3/.

PSR-4-自动加载标准

到目前为止,PHP-FIG 小组已经发布了两个自动加载标准。早于 PSR-4 的是 PSR-0。这是 PHP-FIG 小组发布的第一个标准。它的类命名具有某些向后兼容特性,与更旧的 PEAR 标准一致。然而,层次结构的每一层都用一个下划线分隔,表示伪名称空间和目录结构。PHP5.3 版本随后为该语言带来了官方名称空间支持。PSR-0 既允许旧的 PEAR 下划线模式,也允许使用新的名称空间表示法。允许下划线跟随一段时间,缓解了向名称空间的过渡,并鼓励了更广泛的采用。很快,作曲家来到了现场。

Composer is a popular dependency manager for PHP that deals with packages and libraries by installing them in a vendor/ directory of our project.

根据 Composer 的vendor/目录理念,PHP 源代码没有 PEAR 那样的单一主目录。PSR-0 成为瓶颈,并于 2014 年 10 月被标记为弃用。

PSR-4 是目前推荐的自动加载标准。

根据 PSR-4,完全限定类名现在的形式如下例所示:

\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>

这里的术语不仅仅指类。也指接口特征等类似结构。

为了把它放到上下文中,让我们看一看从 MyEnto 2 Maulto T1 商业平台获取的部分类代码,如下所示:

<?php

namespace Magento\Newsletter\Model;

use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;

class Subscriber extends \Magento\Framework\Model\AbstractModel
{
  // ...

  public function __construct(
    \Magento\Framework\Model\Context $context,
    \Magento\Framework\Registry $registry,
    \Magento\Newsletter\Helper\Data $newsletterData,
    \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
    \Magento\Framework\Mail\Template\TransportBuilder
      $transportBuilder,
    \Magento\Store\Model\StoreManagerInterface $storeManager,
    \Magento\Customer\Model\Session $customerSession,
    CustomerRepositoryInterface $customerRepository,
      AccountManagementInterface $customerAccountManagement,
    \Magento\Framework\Translate\Inline\StateInterface
      $inlineTranslation,
    \Magento\Framework\Model\ResourceModel\AbstractResource
      $resource = null,
    \Magento\Framework\Data\Collection\AbstractDb
      $resourceCollection = null,
    array $data = []
  ) {
   // ...
  }

  // ...
}

前面的Subscriber类是在vendor\Magento\module-newsletter\Model\Subscriber.php文件中定义的,相对于Magento项目的根。我们可以看到__construct使用了各种完全分类的类名。由于 Magento 平台处理依赖注入的方式,它的代码库中到处都有这些类型的健壮构造函数。我们可以想象,如果没有统一的自动加载标准,手动单独require所有这些类所需的额外代码量。

PSR-4 标准还规定,自动加载器实现不得引发任何级别的异常或错误。这是为了确保可能的多个自动加载器不会彼此中断。

The official, full-length PSR-4: Autoloader Standard guide is available at http://www.php-fig.org/psr/psr-4/.

PSR-6-缓存接口

性能问题一直是应用程序开发的热门话题。性能不佳的应用程序的影响有时会产生严重的财务影响。早在 2007 年,亚马逊报告说增加了 100 毫秒 https://www.amazon.com/ 装车时间及销量下降 1%。几项研究还表明,如果页面加载时间超过 3 秒,近一半的用户可能会放弃网站。为了解决性能问题,我们研究了缓存解决方案。

浏览器和服务器都允许缓存各种资源,如图像、网页、CSS/JS 文件。然而,有时这还不够,因为我们需要能够控制应用程序级别上各种其他位的缓存,例如对象本身。随着时间的推移,各种库推出了自己的缓存解决方案。这使得开发人员非常困难,因为他们需要在代码中实现特定的缓存解决方案。这使得以后不可能轻松地更改缓存实现。

为了解决这些问题,PHP-FIG 小组提出了 PSR-6 标准。

本标准定义了两个主要接口CacheItemPoolInterfaceCacheItemInterface,用于处理。池表示缓存系统中的项集合。鉴于,项表示池中存储的单个/值对。关键部分充当唯一标识符,因此它必须是不可变的

以下代码段反映了 PSR-6CacheItemInterface的定义:

<?php

namespace Psr\Cache;

interface CacheItemInterface
{
  public function getKey();
  public function get();
  public function isHit();
  public function set($value);
  public function expiresAt($expiration);
  public function expiresAfter($time);
}

以下代码段反映了 PSR-6CacheItemPoolInterface的定义:

<?php

namespace Psr\Cache;

interface CacheItemPoolInterface
{
  public function getItem($key);
  public function getItems(array $keys = array());
  public function hasItem($key);
  public function clear();
  public function deleteItem($key);
  public function deleteItems(array $keys);
  public function save(CacheItemInterface $item);
  public function saveDeferred(CacheItemInterface $item);
  public function commit();
}

实现 PSR-6 标准的库必须支持以下可序列化的 PHP 数据类型:

  • 整数
  • 漂浮
  • 布尔值
  • 无效的
  • 阵列
  • 对象

像数组和对象这样的复合结构总是很复杂的。该标准规定必须支持任意深度的索引、关联和多维数组。由于 PHP 中的数组不一定是单一的数据类型,因此这是一个需要注意的问题。对象可能使用 PHPSerializable接口、__sleep()__wakeup()魔术方法或类似的语言功能。重要的一点是,传递给实现 PSR-6 的库的任何数据都应该返回到传递的状态。

有几个 PSR-6 缓存实现可通过 Composer 获得,所有这些都支持标记。以下是最受欢迎的部分列表:

  • cache/filesystem-adapter:使用文件系统
  • cache/array-adapter:使用 PHP 数组
  • cache/memcached-adapter:使用 Memcached
  • cache/redis-adapter:使用 Redis
  • cache/predis-adapter:使用 Redis(Predis)
  • cache/void-adapter:使用 Void
  • cache/apcu-adapter:使用 APCu
  • cache/chain-adapter:使用链条
  • cache/doctrine-adapter:使用学说

只需使用Composer require new/package,我们就可以轻松地将这些缓存库中的任何一个添加到我们的项目中。PSR-6 法规遵从性使我们能够轻松地在项目中交换这些库,而无需更改其任何代码。

The Redis is an open source in-memory data structure store used as a database, cache, and message broker. It is quite popular with PHP developers as a caching solution. The official Redis page is available at https://redis.io/. The official, full-length PSR-6: Caching Interface guide is available at http://www.php-fig.org/psr/psr-6/.

PSR-7-HTTP 消息接口

HTTP 协议已经存在了相当长的一段时间了。早在 1989 年,欧洲核子研究所的 Tim Berners-Lee 就开始了它的开发。多年来,互联网工程任务组IETF)和万维网联盟W3C)为 it 定义了一系列标准,称为征求意见RFCs。HTTP/1.1 的第一个定义出现在 1997 年的 RFC2068 中,后来在 1999 年被 RFC2616 否决。十多年后,HTTP/2 在 2015 年实现了标准化。虽然 HTTP/2 现在得到了主要 web 服务器的支持,但 HTTP/1.1 仍然被广泛使用。

底层 HTTP 通信归结为请求和响应,通常称为HTTP 消息。这些抽象的消息形成了 Web 开发的基础,因此对每一个 Web 应用开发者来说都是有意义的。然而,当 RFC 7230、RFC 7231 和 RFC 3986 详细说明 HTTP 本身的细节时,PSR-7 描述了根据这些 RFC 表示 HTTP 消息的通用接口。

PSR-7 共定义了以下七个接口:

  • Psr\Http\Message\MessageInterface
  • Psr\Http\Message\RequestInterface
  • Psr\Http\Message\ServerRequestInterface
  • Psr\Http\Message\ResponseInterface
  • Psr\Http\Message\StreamInterface
  • Psr\Http\Message\UriInterface
  • Psr\Http\Message\UploadedFileInterface

它们可以通过 Composer 作为psr/http-message包的一部分获取。

以下代码块反映了 PSR-7Psr\Http\Message\MessageInterface的定义:

<?php

namespace Psr\Http\Message;

interface MessageInterface
{
    public function getProtocolVersion();
    public function withProtocolVersion($version);
    public function getHeaders();
    public function hasHeader($name);
    public function getHeader($name);
    public function getHeaderLine($name);
    public function withHeader($name, $value);
    public function withAddedHeader($name, $value);
    public function withoutHeader($name);
    public function getBody();
    public function withBody(StreamInterface $body);
}

前面的MessageInterface方法对于消息的请求和响应类型都是通用的。消息被认为是不可变的。实现MessageInterface接口的类需要通过为更改消息状态的每个方法调用返回一个新消息实例来确保这种不变性。

以下代码块反映了 PSR-7Psr\Http\Message\RequestInterface的定义:

<?php

namespace Psr\Http\Message;

interface RequestInterface extends MessageInterface
{
   public function getRequestTarget();
   public function withRequestTarget($requestTarget);
   public function getMethod();
   public function withMethod($method);
   public function getUri();
   public function withUri(UriInterface $uri, $preserveHost = false);
}

RequestInterface接口扩展了MessageInterface,用作传出客户端请求的表示。与前面提到的消息一样,请求也被认为是不可变的。这意味着应用相同的类行为。如果类方法要更改请求状态,则需要为每个此类方法调用返回新的请求实例。

以下Psr\Http\Message\ServerRequestInterface定义反映了 PSR-7 标准:

<?php

namespace Psr\Http\Message;

interface ServerRequestInterface extends RequestInterface
{
  public function getServerParams();
  public function getCookieParams();
  public function withCookieParams(array $cookies);
  public function getQueryParams();
  public function withQueryParams(array $query);
  public function getUploadedFiles();
  public function withUploadedFiles(array $uploadedFiles);
  public function getParsedBody();
  public function withParsedBody($data);
  public function getAttributes();
  public function getAttribute($name, $default = null);
  public function withAttribute($name, $value);
  public function withoutAttribute($name);
}

ServerRequestInterface的实现用作传入服务器端 HTTP 请求的表示。它们也被认为是不变的;这意味着状态更改方法的规则与前面提到的相同。

以下代码片段反映了 PSR-7Psr\Http\Message\ResponseInterface的定义:

<?php

namespace Psr\Http\Message;

interface ResponseInterface extends MessageInterface
{
  public function getStatusCode();
  public function withStatus($code, $reasonPhrase = '');
  public function getReasonPhrase();
}

由于只定义了三种方法,ResponseInterface的实现作为传出服务器端响应的表示。这些类型的消息也被认为是不可变的。

以下代码反映了 PSR-7Psr\Http\Message\StreamInterface的定义:

<?php

namespace Psr\Http\Message;

interface StreamInterface
{
  public function __toString();
  public function close();
  public function detach();
  public function getSize();
  public function tell();
  public function eof();
  public function isSeekable();
  public function seek($offset, $whence = SEEK_SET);
  public function rewind();
  public function isWritable();
  public function write($string);
  public function isReadable();
  public function read($length);
  public function getContents();
  public function getMetadata($key = null);
}

StreamInterface提供了常见 PHP 流操作的包装器,包括将整个流序列化为字符串。

以下代码反映了 PSR-7Psr\Http\Message\UriInterface的定义:

<?php

namespace Psr\Http\Message;

interface UriInterface
{
  public function getScheme();
  public function getAuthority();
  public function getUserInfo();
  public function getHost();
  public function getPort();
  public function getPath();
  public function getQuery();
  public function getFragment();
  public function withScheme($scheme);
  public function withUserInfo($user, $password = null);
  public function withHost($host);
  public function withPort($port);
  public function withPath($path);
  public function withQuery($query);
  public function withFragment($fragment);
  public function __toString();
}

此处的UriInterface接口表示根据 RFC 3986 的 URI。接口方法强制实现者为 URI 对象周围的大多数常见操作提供方法。URI 对象的实例也被认为是不可变的。

以下代码片段反映了 PSR-7Psr\Http\Message\UploadedFileInterface的定义:

<?php

namespace Psr\Http\Message;

interface UploadedFileInterface
{
  public function getStream();
  public function moveTo($targetPath);
  public function getSize();
  public function getError();
  public function getClientFilename();
  public function getClientMediaType();
}

UploadedFileInterface接口表示通过 HTTP 请求上传的文件,这是 web 应用程序的常见角色。少数方法强制类实现覆盖对文件执行的最常见操作。与前面的所有接口一样,类实现需要确保对象不变性。

Guzzle is a popular PSR-7 compliant HTTP client library that makes it easy to work with requests, responses, and streams. It is available at https://github.com/guzzle/guzzle, or as a Composer guzzlehttp/guzzle package. The official, full-length PSR-7: HTTP Message Interface guide is available at http://www.php-fig.org/psr/psr-7/.

PSR-13-超媒体链接

超媒体链接是任何 web 应用程序的重要组成部分,无论我们谈论的是 HTML 还是 API 格式。每个超媒体链接至少由一个表示目标资源的 URI 和一个定义目标资源与源之间关系的关系组成。目标链接必须是 RFC 5988 定义的绝对 URI 或相对 URI,或者可能是 RFC 6570 定义的 URI 模板。

PSR-13 标准定义了一系列接口,概述了通用超媒体格式以及表示这些格式之间链接的方法:

  • Psr\Link\LinkInterface
  • Psr\Link\EvolvableLinkInterface
  • Psr\Link\LinkProviderInterface
  • Psr\Link\EvolvableLinkProviderInterface

这些接口可以通过 Composer 作为psr/link包的一部分获取。

下面的代码片段反映了 PSR-13Psr\Link\LinkInterface定义,它表示单个可读链接对象:

<?php

namespace Psr\Link;

interface LinkInterface
{
  public function getHref();
  public function isTemplated();
  public function getRels();
  public function getAttributes();
}

下面的代码片段反映了 PSR-13Psr\Link\LinkProviderInterface定义,它表示单个链接提供程序对象:

<?php

namespace Psr\Link;

interface LinkProviderInterface
{
  public function getLinks();
  public function getLinksByRel($rel);
}

以下代码片段反映了 PSR-13Psr\Link\EvolvableLinkInterface定义,它表示一个可演化的链接值对象:

<?php

namespace Psr\Link;

interface EvolvableLinkInterface extends LinkInterface
{
  public function withHref($href);
  public function withRel($rel);
  public function withoutRel($rel);
  public function withAttribute($attribute, $value);
  public function withoutAttribute($attribute);
}

以下代码片段反映了 PSR-13Psr\Link\EvolvableLinkProviderInterface定义,它表示一个可演化的链接提供程序值对象:

<?php

namespace Psr\Link;

interface EvolvableLinkProviderInterface extends LinkProviderInterface
{
  public function withLink(LinkInterface $link);
  public function withoutLink(LinkInterface $link);
}

这里意味着这些接口的对象实例与 PSR-7 中的行为相同。默认情况下,对象需要是不可变的。当一个对象状态需要更改时,该更改应该反映到一个新的对象实例中。

The copy-on-write behavior is a built-in mechanism of PHP code, whereas PHP takes care of avoiding unnecessary variable duplicates. Until one or more bytes of variable are changed, the variable is not being copied.

The official, full-length PSR-13: Hypermedia Links guide is available at http://www.php-fig.org/psr/psr-13/.

总结

PHP-FIG 组通过其 PSR 解决了广泛的问题。其中一些关注代码的结构和可读性,另一些则通过定义大量接口来提高互操作性。这些 PSR 直接或间接地有助于提高我们的项目和我们可能使用的第三方库的质量。RFC 2119 标准是每个 PSR 的共同基础。它消除了可能、必须、应该和描述标准的类似词语之间的任何歧义。这确保了文档按照 PHP-FIG 的意图阅读。虽然我们可能不会每天接触每一个标准,但在为我们的项目选择库时,值得注意它们。符合标准的库(如 Monolog)通常意味着更大的灵活性,因为我们可以在项目的后期轻松地在不同的库之间切换。

接下来,我们将研究错误处理和日志记录背后的配置选项、机制和库。