我覺得php7之後的php應該就是namespace的世界。
此篇簡單整理autoload和namespace的結合使用。
過去的做法
過去,我們主程式index.php載入類別時myclass.php,一般會寫成這樣:
index.php
@include_once( 'myclass.php');
最前面的@是忽略錯誤的符號。假加有很多的類別,且放在不同的目錄下,我們都要寫很多行並且加上目錄路徑,例如寫成這樣:
index.php
@include_once( 'myclass1.php');
@include_once( '/var/www/html/class/myclass2.php'); <== 絕對路徑表示法
@include_once( 'class/myclass3.php'); <== 使用相對路徑
@include_once( __DIR__ .'/myclass4.php'); <== 本目錄下的檔案
引入後 再new還是幹嘛的都可以。
這樣的做法對於要引入很多檔案的情況,會寫長長的一串,所以我們可以使用 php5開始提供的autoload 功能,在php8以前autoload的實作方式是在程式最開始加入一個__autoload()函數,並把autoload的規則寫在此函數裡,這個函數像是黑手一樣的存在,可以自動載入你叫用的類別。很可惜這個方法在php8之後就被廢棄並報錯:
Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead in xxx
php8 之後的autoload
php8之後,可以採用 spl_ 相關的函數來實現autoload,並且能和 namespace 結合,spl是 Standard PHP Library 的縮寫,主要的函數只有一個
spl_autoload_register()
先看看檔案架構
├── module
│ └── shop
│ └── taipei.php
└── index.php
這裡只有兩個檔案,一個是索引用的 index.php 和類別檔 taipei.php
在傳統作法中,只要 include('module/shop/taipei.php) 然後就可以想幹嘛就幹嘛。
類別taipei沒做什麼,只有一個函數sell(),印出販賣的商品是蘋果:
taipei.php
<?php namespace module\shop; class taipei { public function sell(){ return "蘋果"; } }
但是想用autoload方法來實作的話,程式寫成這樣就可以了:
index.php
<?php spl_autoload_register(); $myshop = new module\shop\taipei; print $myshop->sell(); //蘋果
可以看到不過加了第2行的函數spl_autoload_register(),什麼路徑都不必指定,也不用叫用 include什麼的,他自然而然的會去路徑 module/shop/ 中找到 taipei.php 的這個類別並載入。
這簡直太神奇?!
到底spl_autoload_register()做了什麼事才會這麼神奇?
眼尖的人可能有發現,index.php第4行中的new 後面指定了namespace,namespace在此發揮了巨大的作用。
我們能猜測他內部是把namespace轉成路徑,並區分最後一個部分當成類別名。
同時要注意,類別中的namespace宣告要和初始化的一致(taipei.php第2行),這樣就漂亮的整合了namespace和autoload
自定義的autoload
如果上面預設的autoload規則不符合需求的話,只能自己來訂定,以下範例檔案架構變成這樣,我們增加一個system的目錄,假設裡面會放一些和公司本身規定有關的程式碼。
├── module
│ └── shop
│ └── taipei.php
├── index.php
├── autoload.php
└── system
└── promote
└── year2023.php
這裡多了一個類別 year2023.php,以及後面才會加入的autotload.php
我們可以用上面的做法,直接初始化
就能自動載入該類別。
但如果 promote/ 目錄下的活動很多,我們想全部都編成core這個namespace的話,就要改一下autoload的規則。先看程式碼:
year2023.php
<?php namespace core ; class year2023 { public function anniversary(){ return "3/31-4/5"; } }
注意上面第2行的namespace改成core。
index.php
<?php spl_autoload_register( function ($myclass){ $segment= explode("\\",$myclass,2); if( $segment[0]==="core"){ @include_once(__DIR__ . '/system/promote/' . str_replace('\\', '/', $segment[1]). '.php'); }else @include_once(__DIR__ . '/' . str_replace('\\', '/', $myclass) . '.php'); }); $activity= new core\year2023; print $activity->anniversary(); //3/31-4/5
上面在 spl_autoload_register()中放入了一個函數,第3行$myclass就是所有被叫用的namespace+類別。
第4行把字串$myclass拆開分成2段,第一個字節和剩下的其它部分。
第5-6行 namespace的第一個字節如果是core開頭的,就去/system/promote/裡面找類別
第7-8行 除此之外就是和原本的autoload一樣。
接下來第9行引用時,namespace直接可以用core來引用,假如這行寫成
$activity= new system\promote\year2023;
可不可以?當然不行,因為一個類別只能有一個namespace。
我們可以把自定義的部分拆出來,新增一個autoload.php程式,爾後所有的程式都可以叫用同樣的規則。
autoload.php
<?php spl_autoload_register( function ($myclass){ $segment= explode("\\",$myclass,2); if( $segment[0]==="core"){ @include_once(__DIR__ . '/system/promote/' . str_replace('\\', '/', $segment[1]). '.php'); }else @include_once(__DIR__ . '/' . str_replace('\\', '/', $myclass) . '.php'); });
index.php
<?php include_once(__DIR__ ."/autoload.php"); $activity= new core\year2023; print $activity->anniversary(); //3/31-4/5
use? 怎麼use?
如果你討厭每個函數之前都要帶著長長的namespace,你可以使用use
index.php
<?php include_once(__DIR__ ."/autoload.php"); use module\shop\taipei; use core\year2023; $myshop = new taipei; print $myshop->sell(); //蘋果 $activity= new year2023; print $activity->anniversary(); //3/31-4/5
第4-5指定了use,在第7和第10行就不必帶上namespace了。
這部分可參考2016末寫的文章[PHP] 命名空間 namespace及 use@新精讚至今剛好滿6年。
結論
1. namespace+ autoload是好兄弟,php8以上請採用這種寫法。
2. __autoload 方法已經在php8作廢。
3. 目錄不要使用php的關鍵字來取名,例如你不要把目錄叫作class或是use
4. 自訂義的autoload很方便。
參考資料
[1] https://justericgg.logdown.com/posts/196891-php-series-autoload
[2] https://ithelp.ithome.com.tw/articles/10134247