PSR 是由 PHP-FIG (PHP Framework Interop Group) 編撰的 PHP 標準建議。
其目的是編撰公開的建議,以保證專案的相容性,並確保對某些概念的共同理解。
目前有從 PSR-0 到 PSR-22,其中有些已被標示為 不再建議使用、已被棄用,或仍在開發中。
程式碼風格
在所有 PSR 中可以看到一些針對程式碼風格的建議。
目標是讓那些在多專案工作的開發者更容易閱讀有相同寫作規則的程式碼。當然不需完全遵循,但對開源專案來說,採用公開標準可能會吸引更多貢獻者,並在開源工具的程式碼中保持一致性的風格。
因此,我們可以看到 PSR-1 或 PSR-12,要我們應該遵循的規則,例如:
- 如果一個 PHP 檔案裡面只有 PHP,不要用
?>結尾 - PHP 檔案結尾留一行空白
abstract關鍵字必須出現在類別元素前- 必須加上類別元素的存取修飾詞 (public、protected、private)等……
要學習和記住所有規則是很困難的。因此,有一些工具可以自動化檢查並格式化程式碼。例如,PHP-CS-Fixer (Coding Standards Fixer) 就可用於這項任務。
自動載入 (autoloading)
另一項標準建議是於載入類別有關。
平常載入類別的方法是簡單地用 require_once 載入檔案。但這樣的組織方式很快就會變得十分複雜且令人頭痛。
為了解決這個問題,我們會使用 PSR-4 的建議,它靠檔案的階層結構來組織類別。
命名空間
PSR-4 建議我們將類別依照不同的命名空間(namespaces)來組織。
因此,我們可以看到像是 Symfony\Component\Mailer\Mailer 或 App\Shop\OrderService 這樣的類別。
以上提到的類別分別是 Mailer 和 OrderService。它們只是位於不同的命名空間之下。
因此,我們會為每個類別宣告一個命名空間:
<?php
namespace App\Shop;
//...
class OrderService {
/* ... */
}
在另一個檔案,如果想要使用 OrderService 的話,需要遵循以下程式碼:
<?php
// namespace... (命名空間的宣告)
use App\Shop\OrderService; // 引入類別
// ...
FQCN
對於應用程式中的每個類別,PSR-4 標準建議允許建立一個 FQCN (Fully Qualified Class Name)。這指的是從根命名空間到類別名稱的完整路徑。
怎麼組織我們的檔案 ?
PSR-4 使用檔案的階層結構來運作。
事實上,對於每個根命名空間,PSR-4 需要一個對應的路徑。接著,凡是位於其下層的命名空間,都會依照對應的階層結構一路對應下去,直到類別名稱為止。
範例
專案中
專案中,我們可以宣告及使用一個 PSR-4 的自動載入器。
為此,composer.json 檔案宣告 autoload 區段中,只需設定適當結構即可。舉例來說,在 Symfony 框架中常用的方法是將根命名空間 App\ 對應到 src/ 資料夾。如此,所有類別和邏輯程式都會放在這個 src/ 資料夾中。我們也可以自由建立新的資料夾,以組織不同的子命名空間。
// composer.json
{
//...
"autoload": {
"psr-4": {
"App\\": "src/"
}
}
//...
}
接下來,我們可以在 src 資料夾中建立我們的類別。
在 Symfony 的 Mailer 程式庫中
許多專案中,我們也能看到 PSR-4 的自動載入規則。
以 Symfony 的 Mailer 程式庫為例,如果我們查看其 composer.json 檔案:
{
"autoload": {
"psr-4": { "Symfony\\Component\\Mailer\\": "" }
//...
}
}
這項宣告顯示,所有位於 Mailer 專案根資料夾下的類別或介面,都會使用 Symfony\Component\Mailer 該命名空間。
介面
PSR 也可以定義一些通用的介面,任何人都可以選擇來實作之。
以 PSR-11 作為例子:它是一個名為 ContainerInterface 的介面,如果你想讓你的程式符合 PSR-11,就需要實作這個合約。正如該推薦標準頁面所說,這個標準的目的是規範各個函式庫與框架如何使用服務容器,在應用程式中獲取物件與設定值的方式。
在 Symfony 的 DependencyInjection 程式庫中,第一段宣告:
The DependencyInjection component implements a PSR-11 compatible service container that allows you to standardize and centralize the way objects are constructed in your application.
如果我們查看這個程式庫的程式碼,我們也會看到我們需要的所有資訊。
例子 : symfony/dependency-injection
我們可以來探索一下 Symfony 的相依注入程式庫。它遵循 PSR-4 的自動載入規範,因此瀏覽其程式碼會非常容易。
我們可以在 composer.json 檔案中看到一個對 psr/container 套件的依賴,這個套件包含了我們需要實作的 PSR-11 介面。
接下來,我們可以在專案的根目錄中看到 ContainerInterface 介面,它 繼承 了 PSR 的介面。此外,他們為 PSR 的介面分配了一個 別名,因為他們想要創建自己的 ContainerInterface,與 PSR 的介面同名:
namespace Symfony\Component\DependencyInjection;
use Psr\Container\ContainerInterface as PsrContainerInterface;
//...
interface ContainerInterface extends PsrContainerInterface
{
//...
}
因此,你會看到方法如 get 和 has,同時也會有其他函式,比如 getParameter 或 set。
最後,如果我們查看 Container 類別,我們會看到它宣告實作了 ContainerInterface。
如果我們現在前往 PHP-DI 的原始碼庫,在 PHP 生態系統中另一個知名的相依注入容器,我們會發現它同樣依賴 psr/container,而它主要的類別也實作了同樣的介面合約:
namespace DI;
//...
use Psr\Container\ContainerInterface;
class Container implements ContainerInterface, FactoryInterface, InvokerInterface
{
//...
}
PSR 中還有其他介面,例如:
- PSR-3:用於記錄(logging)資訊(範例套件:monolog/monolog)
- PSR-6 或 PSR-16:用於實作快取系統(範例套件:symfony/cache、beste/in-memory-cache、laminas/laminas-cache)
總之,未來當我們看到有函式庫標示「相容 PSR-XX」時,就知道這是什麼意思了。