PHP 中的 PSR 規範

PSR 是 Proposing a Standards Recommendation (提出標準建議) 的縮寫,這個是 php-fig 組織制定的一套規範。
至今,php-fig已經發布了5個規範:

  • PSR-0 自動加載標準,2014-10-21該標準已經被廢棄,使用PSR-4替代
  • PSR-1 基本的編碼風格
  • PSR-2 編碼風格(更嚴格)
  • PSR-3 日誌記錄器接口
  • PSR-4 自動加載

PSR-1

  • PHP標籤:
    PHP代碼必須放在 <?php 標籤或 <? 標籤中

  • 編碼:
    PHP文件必須使用無BOM的 UTF-8 編碼

  • 副作用:
    一個PHP文件可以定義符號(類、函數、常量等),或者執行只有唯一副作用的操作(輸出結果、處理數據),但是不能同時做這兩件事,儘量是一個PHP文件的功能單一。在操作的時候儘量把變量、類、函數的聲明分開,通過 include 或 require 文件的方式來使用。

  • 命名空間和類:
    命名空間和類必須 遵循 PSR-4 自動加載器標準

  • 類的名稱:
    每個類都有自己的命名空間,且都在頂級命名空間下,類名必須使用駝峯式(CamelCase)

  • 常量:
    常量必須全部是用大寫,並且使用下劃線(_)分開

  • 類的方法:
    類的方法必須使用小寫字母開頭的駝峯(camelCase)命名。

PSR-2

  • 貫徹 PSR-1:
    使用 PSR-2 代碼標準之前要先貫徹 PSR-1 的代碼標準。

  • 文件和代碼行:
    PHP文件必須使用 Unix 風格的換行符(LF, linefeed),最後要有一個空行,僅包含PHP代碼的文件而且不能使用PHP關閉標籤?>,每行代碼不應該超過80個字符,每行末尾不能有空格,每行只能有一條語句,可以在適當的地方添加空行提高代碼的閱讀性。
    不加上?>關閉標籤,可以避免意料之外的輸出錯誤,如果加上關閉標籤,且在關閉標籤後有空行,那麼空行會被當成輸出,導致意想不到的錯誤。

  • 縮進:
    必須以4個空格爲縮進,不能使用製表符(Tab鍵)縮進。
    不同的編輯器中,空格的渲染效果基本一致,而製表符的寬度各有差異。

  • 關鍵字:
    PHP 的關鍵字必須使用小寫,而且 true, false, 和 null 也必須小寫。

  • 命名空間和use聲明:
    現在,namespace 聲明之後必須要有一個空行,而且 use 聲明必須放在 namespace 之後,必須分別使用 use 引入命名空間,而且 use 後要有空行,例如:

<?php
namespace Vendor\Package;

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

// ... additional PHP code ...
  • 類的繼承和實現:
    extends 和 implements 關鍵字必須和類名在同一行,類、接口和 Traits 定義體的起始括號應該在類名之後新起一行,結束括號也必須新起一行,例如:
<?php 
namespace Vendor\Package; 

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

class ClassName extends ParentClass implements \ArrayAccess, \Countable 
{ 
    // constants, properties, methods 
}

如果implements後面後很多類導致一行很長,可以依次將需要的類另起新行並縮進4個空格。

  • 可見性:
    類中的每個屬性和方法都要聲明可見性,有 public、private 和 protected,不能使用 var 關鍵詞來聲明,老版本的PHP會在私有屬性前加上_,一行只能聲明一個屬性,例如:
<?php
namespace Vendor\Package;

class ClassName
{
    public $foo = null;
}
  • 方法:
    類中的所有方法也應該定義可見性,方法名後面不能有空格,方法體的括號位置和類定義體的括號位置一樣,都要新起一行,結束括號也要新起一行。方法參數的起始圓括號之後沒有空格,結束括號之前也沒有空格,有多個參數是,每個參數的逗號後面加一個空格,例如:
<?php
namespace Vendor\Package;

class ClassName
{
    public function fooBarBar($arg1, $arg2, $arg3 = [])
    {
        // method body
    }
}
  • PHP的控制結構:
    PHP的控制結構包括 if、else、elseif、switch、case、while、do while、for、foreach、try和catch。如果這些關鍵詞後面有一對原括號,開始括號前必須有一個空格,與方法和類的定義體不同,控制結構關鍵詞後面的起始括號應該和控制結構關鍵詞寫在同一行,例如:
<?php

if ($global->isValue() === true) {
    do {
        $global->goods();
    } while ($libs->isArray() === true);
    
    $libs->rea();
}
  • PHP閉包函數:
    閉包函數在聲明時, function 關鍵詞後必須有一個空格,同時 use 關鍵詞前後也必須有一個空格,起始大括號不需要另起新行
<?php
$closure = function ($arg1, $arg2) {
    // body
}

$closureVars = function ($arg1, $arg2) use ($var1, $var2) {
    // body
}

PSR-3

  與 PSR-1 和 PSR-2 不同,PSR-3 規定了一套通用的日誌記錄器接口Psr\Log\LoggerInterface),爲了符合 PSR-3 規範,框架必須實現該規範中的接口,這樣可以更多的兼容第三方應用。

  PSR-3 規範中包含了9個方法,每個方法都對應了 RFC 5424 協議的一個日誌級別,而且都接受兩個參數 $message 和 $context,如下:

<?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 void
     */
    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 void
     */
    public function alert($message, array $context = array());

    /**
     * Critical conditions.
     *
     * Example: Application component unavailable, unexpected exception.
     *
     * @param string $message
     * @param array $context
     * @return void
     */
    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 void
     */
    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 void
     */
    public function warning($message, array $context = array());

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

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

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

    /**
     * Logs with an arbitrary level.
     *
     * @param mixed $level
     * @param string $message
     * @param array $context
     * @return void
     */
    public function log($level, $message, array $context = array());
}

關於 message 參數:
  $message 必須是一個字符串或者是含有 __toString() 方法的對象,$message 應該包含佔位符,例如 {placeholder_name},佔位符由{、佔位符名稱和}組成,不能包含空格,佔位符名稱可以由 A-Z, a-z, 0-9, _ 組成。

  第三方實現可以用 $context 參數來替換佔位符,佔位符名稱必須和 $context 數組的 key 對應。如下例子是使用 $context 中的值替換 $message 中的佔位符:

<?php

/**
 * 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參數:
   $context 是一個數組參數,用於構造複雜的日誌消息,$context 中的值不能跑出任何PHP異常或錯誤。如果 $context 中包含Exception對象,則該對象的 key 必須爲 exception。

PSR-3日誌記錄器的使用
  推薦使用 monolog/monolog,這樣可以讓我們不需要浪費更多的時間在編寫一個日誌記錄器了。Monolog組建完全實現了PSR-3接口,而且便於使用自定義的消息格式化程序和處理程序擴展功能,通過Monolog可以把日誌消息寫入文本文件、系統日誌和數據庫中,還能通過電子郵件發送,並且還支持Slack和遠程服務器。如下展示瞭如何設置Monolog,並把日誌消息寫入文本文件:

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

  PSR-4 規範描述了一個標準的自動加載器策略,指在運行時按需查找PHP類、接口或 Traits。支持 PSR-4 自動加載器標準的PHP組建和框架,使用同一個自動加載器就能找到相關代碼,然後將其載入PHP解釋器。有了這個功能,就可以把現代PHP生態系統中很多客戶操作的組件聯繫起來。

編寫一個PSR-4自動加載器

  PSR-4 規範不要求改變代碼的實現方式,只建議如何使用文件系統目錄結構和PHP命名空間組織代碼,PSR-4 規範以來PHP命名空間和文件系統目錄結構查找並加載PHP類、接口和Traits,這正是PSR-4的精髓所在。下面我們來自己手動實現一個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;
    }
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章