在 2012 年以前,PHP 套件管理工具 Composer 還沒問世,安裝套件的方式的是從網路上下載,然後放到專案中的某個目錄,接著用 include
或 require
關鍵字來載入套件。
但在 2020 年的今天,當我們完成了一個 PHP 套件,很開心地想要發表這個作品給社群裡的大家知道,如果不支援 Composer,恐怕會乏人問津,很少會有開發者會對安裝個套件還得使用遠古時代安裝方式的套件感到興趣,主要原因有三項:
- 安裝步驟不如使用 Composer 方便。
- 不支援自動載入的話,實例化物件之前還得先行載入所需檔案。
- 如有和其它套件有依賴關係,最好由 Composer 管理,而非在套件專案裡內含依賴套件的檔案。
讓作品支援 PSR-4 自動載入的功能是最好的方式唷。
PSR-4 規格
名詞說明
ClassName
指的是類別 (class)、特性 (traits)、介面 (interface) 及其它類似的結構。
完整領域類別名稱
完整領域類別名稱 (fully qualified class name) 為以下形式:
例:
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
<NamespaceName>
指的是命名空間。
(\<SubNamespaceNames>)*
指的是下一層的命名空間。(不一定需要,如有使用也無限制層數)
- 完整領域名稱必須有一個頂層的命名空間名稱,也就是廣為人知的供應商命名空間 (vendor namespace)
- 完整領域名稱或許有一個或多個次層的命名空間名稱。
- 完整領域名稱必須有一個結尾的類別名稱。
- 下底線 (unserscore) 在完整領域名稱中沒有特別意義。
- 補充說明:已棄用的 PSR-0 中規範類別名稱的下底線在載入時轉成目錄分隔符,已棄用,可不理會。
- 完整領域名稱可以是任意字母的大小寫的組合。
- 類別的引用有區分大小寫。
載入符合完整領域類別名稱的檔案
- 一個連續系列的一個或多個命名空間名稱及次層的命名空間名稱,應對應到至少一個基本目錄。
- 一個連續的次層的命名空間名稱在命名空間前輟之後,對應到基本目錄的子目錄,命名空間的分隔符就是目錄的分隔符。子目錄的大小寫名稱應與次層的命名空間一致。
- 結尾的類別名稱及其大小寫,與
.php
檔案名稱一致。
禁止事項
自動載入程式 (autoloader) 不得丟出任何異常 (exception)、不得產生任何級別的錯誤訊息,也不能有任何的回傳值。
範例
完整領域類別名稱 | 命名空間前輟 | 基本目錄 | 結果檔案路徑 |
---|---|---|---|
\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 |
實作自動載入機制
利用 spl_autoload_register
這個函式來註冊自己的自動載入機制。配合 PSR-4 的規範,我們來實作一個自動載入程式,在後續建立自己作品並把作品要發佈到 Composer 的時候,會用在 composer.json
這個檔案設定載入方式為 PSR-4。
- 首先,建立一個檔案名為
autoload.php
- 要建立一個要註冊到
spl_autoload_register
的函式。 - 將該函式註冊進
spl_autoload_register
。
1. 建立 autoload.php
- 打開空白檔案,將接下來的步驟程式碼貼到該檔案,並存檔為
autoload.php
。 - 在要使用此套件的專案中,載入
autoload.php
。
2. 建立要註冊的函式
範例:
function psr_http_autoload($className)
{
$prefix = 'Shieldon\\';
$dir = __DIR__ . '/src';
if (0 === strpos($className, $prefix . 'Psr')) {
$parts = explode('\\', substr($className, strlen($prefix)));
$filepath = $dir . '/' . implode('/', $parts) . '.php';
echo $filepath . '<br >';
if (is_file($filepath)) {
require $filepath;
}
}
}
3. 將 psr_http_autoload 函式註冊
範例:
function psr_http_register()
{
spl_autoload_register('psr_http_autoload', true, false);
}
psr_http_register();
機制解說
這個範例取自筆者的作品,也是明天筆者會介紹的 PSR-7, PSR-15, PSR-17 的內容。
這個套件的目錄結構如圖:
在這個例子中:
- 命名空間前輟為
Shieldon
- 基本目錄為
src
- 次目錄對應到命名空間,也就是
Psr7
,Psr15
,Psr17
(大小寫要一致) - PHP 的檔案名稱要和「完整領域類別名稱」的結尾類別名稱一致。(大小寫要一致)
例如 ServerRequest.php 它的「完整領域類別名稱」為:
Shieldon\Psr7\ServerRequest
命名空間為:
Shieldon\Psr7
完整的範例:
<?php
/*
* This file is part of the Shieldon package.
*
* (c) Terry L. <contact@terryl.in>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
/**
* Register to PSR-4 autoloader.
*
* @return void
*/
function psr_http_register()
{
spl_autoload_register('psr_http_autoload', true, false);
}
/**
* PSR-4 autoloader.
*
* @param string $className
*
* @return void
*/
function psr_http_autoload($className)
{
$prefix = 'Shieldon\\';
$dir = __DIR__ . '/src';
if (0 === strpos($className, $prefix . 'Psr')) {
$parts = explode('\\', substr($className, strlen($prefix)));
$filepath = $dir . '/' . implode('/', $parts) . '.php';
if (is_file($filepath)) {
require $filepath;
}
}
}
psr_http_register();
逐行說明:
if (0 === strpos($className, $prefix . 'Psr')) {
實作載入檔案時,先檢查 $className 是否含有 Shieldon\Psr
字串,如果有的話,再進入下面的載入流程。
$parts = explode('\\', substr($className, strlen($prefix)));
$filepath = $dir . '/' . implode('/', $parts) . '.php';
把「結尾的類別名稱」取出,並得到該 .php
檔案的絕對路徑。
if (is_file($filepath)) {
require $filepath;
}
如果該路徑是一個檔案,就載入它。
這是筆者的套件 psr-http 的自動載入程式。授權條款為 MIT,您可以稍加修改用在自己的專案作品。
總結
PSR-4 的規範重點在於,類別的名稱與命名空間名稱與檔案目錄名稱的對應,只要保持名稱及大小寫一致即可。藉由引用筆者的實際作品,希望能幫助大家更快理解並採用 PSR-4 在自己的專案作品中喔!
留言