Learning Objectives
- 說明為什麼需要命名空間:不同套件的類別、函式可能同名
- 用 namespace 宣告命名空間,分辨非限定名稱、限定名稱與以 \ 開頭的完整名稱
- 用 use 匯入類別、用 as 取別名,用 use function/use const 匯入函式與常數
- 說明在命名空間裡呼叫函式找不到時會退回全域,類別則不會(要寫 \Exception 或 use)
- 比較 include、require 與 _once 版本,用 spl_autoload_register 依 PSR-4 規則自動載入類別
- 用 Composer 建立專案、安裝套件,並載入 vendor/autoload.php
兩個 User 類別放在同一個專案
程式變大以後,會分成很多檔案,也會用到別人寫的套件。這時很容易撞名:
你的網站有一個 User 類別(會員),你裝的部落格套件也有一個 User 類別(作者);
你寫了一個 format() 函式,另一個套件也有 format()。同一個名稱定義兩次,PHP 會直接停下來:
Fatal error: Cannot declare class User, because the name is already in use
命名空間(namespace)的作法和電腦的資料夾一樣:不同資料夾裡可以各有一個 readme.txt,
因為它們的完整路徑不同。類別、函式、常數放進命名空間以後,名稱就多了一段「前綴」:
| 簡短名稱 | 完整名稱 |
|---|---|
User(你的會員) |
App\Models\User |
User(部落格套件的作者) |
Blog\Models\User |
PHP 的命名空間用反斜線 \ 分隔(不是 / 也不是 .)。你在 Laravel、Symfony 等框架看到的程式,第一行幾乎都是 namespace …;,
本課學會讀懂這些名稱,以及 PHP 怎麼找到每個類別所在的檔案。
宣告命名空間與三種名稱
在檔案開頭寫 namespace App\Models;,這個檔案之後定義的類別、介面、trait、enum、函式、常數都屬於 App\Models
(變數不受命名空間影響)。namespace 必須是檔案裡第一個敘述(前面只能有 <?php 和 declare(strict_types=1);)。
在程式裡寫名稱時有三種寫法,就像檔案的相對路徑與絕對路徑:
| 寫法 | 例子 | 在 namespace App\Models; 裡實際找的是 |
|---|---|---|
| 非限定名稱(unqualified) | User |
App\Models\User |
| 限定名稱(qualified) | Sub\User |
App\Models\Sub\User |
完整名稱(fully qualified),以 \ 開頭 |
\Blog\Models\User |
\Blog\Models\User(就是它) |
兩個好用的工具:
類別名稱::class:取得類別的完整名稱字串(例如寫User::class得到'App\Models\User'),不需要建立物件__NAMESPACE__:目前所在的命名空間名稱
在逐步執行動畫裡,函式與方法的框架名稱會顯示完整名稱,例如 App\Models\label。
宣告命名空間:名稱自動加上前綴
整個檔案都在 App\Models 命名空間裡。第 20 行只寫 User,PHP 會補上目前的命名空間;第 24、25 行以 \ 開頭的完整名稱,不管寫在哪裡都指向同一個東西。
use:匯入名稱與取別名
每次都寫 \Blog\Models\User 很長。在檔案開頭用 use 匯入,之後就能用最後一段名稱:
use App\Models\User; // 之後寫 User=App\Models\User
use Blog\Models\User as Author; // 名稱撞了:取別名,寫 Author=Blog\Models\User
use function App\Support\money; // 匯入函式
use const App\Support\TAX; // 匯入常數
use App\Models\{Order, Product}; // 同一個命名空間的多個類別,用大括號一次匯入
use寫的一律是完整名稱(開頭的\可以省略),它只是「取一個短名字」,不會載入檔案use只對目前這個檔案有效,每個檔案要各自寫- 一個檔案裡也可以用大括號寫好幾個命名空間(下面的範例為了放在同一個檔案裡才這樣寫);實務上一律一個檔案一個命名空間、一個類別
兩個 User 類別同時使用
Shop\Models 和 Blog\Models 各有一個 User。最下面的程式(全域命名空間)用 use 匯入前者、用 as Author 為後者取別名。看右邊兩個物件的標題:類別名稱不同。
想一想:名稱怎麼解析
下面的程式會印出什麼?
<?php
namespace Shop;
class Cart {}
echo Cart::class, ' ', \Shop\Cart::class;
下面的程式寫在 App\Report 命名空間,Summary 是 use 匯入的。執行 new Summary() 時,PHP 要找哪一個類別?
<?php
namespace App\Report;
use App\Models\Order as Summary;
$s = new Summary();
內建函式為什麼不用加 \:退回全域
strlen、count、DateTime、Exception 這些內建的名稱都在全域命名空間。那在 namespace App; 裡寫 strlen('abc'),
不是應該去找 App\strlen 嗎?PHP 的規則是:
| 種類 | 非限定名稱找不到時 |
|---|---|
| 函式、常數 | 先找目前命名空間(App\strlen),找不到就退回全域(strlen) |
| 類別、介面、enum | 不會退回:只找 App\DateTime,找不到就丟出 Error |
所以在命名空間裡使用內建類別,要寫完整名稱 \DateTime、\Exception、\PDO,或在檔案開頭 use DateTime;。
這是初學者最常遇到的錯誤訊息之一:
Fatal error: Uncaught Error: Class "App\DateTime" not found
catch (Exception $e) 也要小心:在命名空間裡它指的是 App\Exception,不存在的類別不會被比對到,例外就接不住。要寫 catch (\Exception $e)。
函式退回全域,類別不會
這個檔案在 App 命名空間,而且自己定義了一個 App\strtoupper。注意第 11、12 行的差別,以及第 16 行的 new DateTime 為什麼會出錯。
一個檔案一個類別:include 與 require
慣例是一個檔案放一個類別,檔名和類別名稱相同(User 放在 User.php)。要使用另一個檔案的程式,
用 include 或 require 把它載入,PHP 會在這個位置執行那個檔案的內容:
| 寫法 | 找不到檔案時 | 同一個檔案載入第二次 |
|---|---|---|
include 'a.php' |
Warning,回傳 false,程式繼續執行 |
再執行一次 |
require 'a.php' |
丟出 Error,沒有接住就停止 | 再執行一次 |
include_once、require_once |
同上 | 略過(不會重複定義函式或類別) |
- 程式少了那個檔案就沒辦法正確執行(類別、函式、設定),用
require_once;include適合「沒有也沒關係」的片段(例如網頁的頁尾) - 被載入的檔案可以
return一個值,例如設定檔回傳一個陣列:$config = require 'config.php'; - 相對路徑會依 include_path 與目前工作目錄尋找,容易出錯;寫成
__DIR__ . '/src/helpers.php'以目前檔案所在的資料夾為準最穩定
逐步執行動畫只畫 index.php 的每一步:被載入的檔案、以及在那些檔案裡定義的函式與類別,執行時一次跑完,不逐行顯示,它們印出的內容一樣會出現在輸出裡。
載入其他檔案
網站根目錄還有兩個檔案:config.php(return 一個設定陣列)和 src/helpers.php(第 2 行 echo "載入 helpers.php\n";,接著定義函式 price_tag)。注意 require_once 寫了兩次,「載入 helpers.php」只印一次;以及 include 與 require 找不到檔案時的差別。
自動載入與 PSR-4
專案有幾十個類別時,在每個檔案開頭寫一長串 require_once 既麻煩又容易漏。自動載入(autoloading)讓 PHP 在
第一次用到某個還沒定義的類別時,呼叫你註冊的函式,由它負責 require 對應的檔案:
spl_autoload_register(function (string $class) {
// $class 是完整類別名稱,例如 'App\Models\User'
});
要從類別名稱推算出檔案位置,大家約定了一套規則 PSR-4(PHP-FIG 制定的自動載入標準): 指定一個命名空間前綴對應一個資料夾,前綴之後的每一段命名空間就是一層子資料夾,類別名稱就是檔名。
| 前綴對應 | 類別 | 檔案 |
|---|---|---|
App\ → src/ |
App\Models\User |
src/Models/User.php |
App\ → src/ |
App\Http\Controllers\HomeController |
src/Http/Controllers/HomeController.php |
Blog\ → vendor/blog/src/ |
Blog\Models\User |
vendor/blog/src/Models/User.php |
照這個規則放檔案,類別就能自動找到。注意 use 只是取名字,不會觸發自動載入;參數的型別宣告、instanceof 也不會。
要到 new、呼叫靜態方法、讀取類別常數這類真正用到類別的時候,才會呼叫載入函式。
用 spl_autoload_register 實作 PSR-4
網站裡有 src/Models/User.php(namespace App\Models; class User)和 src/Services/Greeter.php(namespace App\Services; class Greeter)。主程式沒有任何 require:看第一次 new User 時,PHP 怎麼呼叫載入函式(框架名稱 {closure}),以及第二次 new User 為什麼沒有再呼叫。左邊的字串值用程式碼的寫法顯示,"App\\Models\\User" 裡的 \\ 代表一個反斜線,實際的值是 App\Models\User。
Composer:安裝套件與自動載入
實務上不需要自己寫載入函式。Composer 是 PHP 的套件管理工具:它依照 composer.json 下載套件到 vendor/,
並產生一個 vendor/autoload.php,同時負責你自己的類別與所有套件的自動載入。
下面的指令要在自己的電腦上執行(需要先到 getcomposer.org 依說明安裝 Composer);套件的版本號依你執行的時間而定。
-
1
建立 composer.json
composer init會一題一題問你專案名稱、作者等資料;加上--no-interaction就用參數直接建立。--autoload=src/設定 PSR-4:命名空間Demo\Shop\對應src/資料夾(由專案名稱demo/shop推算)。 -
2
安裝套件
composer require 套件名稱會把套件與它需要的其他套件下載到vendor/,並記錄在composer.json的require與composer.lock。這裡以記錄日誌的套件 monolog 為例(輸出省略了部分行;版本號是 2026 年 10 月的最新版)。 -
3
載入 vendor/autoload.php
程式的入口只需要
require一次vendor/autoload.php,之後自己的類別(src/裡)和套件的類別都會自動載入。 -
4
換一台電腦:composer install
vendor/不放進 Git(在.gitignore加一行/vendor/),只提交composer.json與composer.lock。別人取得專案後執行composer install,就會依composer.lock安裝完全相同的版本。之後改了composer.json的 autoload 設定,執行composer dump-autoload重新產生載入程式。
想一想:載入與自動載入
a.php 的內容是 <?php echo 'A';。下面的程式會印出什麼?
<?php
include_once 'a.php';
include 'a.php';
require_once 'a.php';
echo "\n";
下面的程式會以哪一種錯誤結束?
<?php
namespace App;
echo strlen('abc'), "\n";
throw new Exception('設定檔不存在');
composer.json 設定 "psr-4": {"App\\": "src/"}。類別 App\Http\Controllers\HomeController 應該放在哪個檔案?
動手做:寫一個自動載入器
網站裡已經有 src/Shop/Product.php(namespace Shop; class Product,建構子參數 name、price)和 src/Shop/Cart.php(namespace Shop; class Cart,方法 add(Product $p, int $qty)、count()、total())。請完成兩個 TODO,讓主程式不寫任何 require 就能使用這兩個類別:類別 Shop\Cart 對應檔案 src/Shop/Cart.php。
重點回顧
- 命名空間讓同名的類別、函式、常數可以並存;用反斜線
\分隔,例如App\Models\User namespace App\Models;寫在檔案最前面;之後的非限定名稱User會補上前綴,以\開頭的是完整名稱use 完整名稱;匯入、as取別名;函式與常數用use function、use const。use 只取名字,不載入檔案,只對目前檔案有效類別::class取得完整類別名稱,__NAMESPACE__是目前的命名空間- 在命名空間裡:函式、常數找不到會退回全域(
strlen照常可用);類別不會,要寫\DateTime、\Exception或 use include找不到檔案是 Warning;require找不到丟出 Error;_once版本不會重複載入同一個檔案spl_autoload_register在第一次用到類別時才載入檔案;PSR-4 規則:前綴對應資料夾、命名空間每一段對應一層資料夾- 實務上用 Composer:
composer require安裝套件,程式開頭require 'vendor/autoload.php';;vendor/不進 Git,用composer install還原