PHP 不是你記憶中的樣子:寫給現代開發者的 PHP 快速入門
本篇是「PHP/Laravel 完全指南」系列的第 1 / 15 篇。你可以從系列總覽開始閱讀,也可以直接接著看本文。
「PHP?那不是上個時代的語言嗎?」——如果你腦中閃過這個念頭,我完全理解。十年前的 PHP 確實混亂:沒有型別提示、沒有套件管理、一堆 mysql_* 全域函式散落各處。那個年代寫 PHP 的體驗,大概跟在泥地裡蓋房子差不多。
但 2026 年的 PHP 8.4+,已經是一門完全不同的語言了。它有嚴格的型別系統、Enum、Named Arguments、Property Hooks、Readonly Properties、Match Expression——這些特性放在任何現代語言裡都不會顯得突兀。2024 年 9 月,Laravel 拿到了 Accel 領投的 5,700 萬美金 A 輪融資,這是 PHP 框架生態系商業價值的最強信號。
這一章我們不會從零教你寫 echo "Hello World"。我假設你已經會至少一門程式語言(JavaScript、Python、Go 都行),所以我們用對照表的方式快速帶你過一遍 PHP 8.4+ 的核心語法。目標是讀完這章之後,你看得懂 Laravel 原始碼裡的 PHP,也知道怎麼用 Composer 管理套件。準備好了,我們開始。
2026 年了,PHP 還值得學嗎?
先看數字說話:
- 71.7%——根據 W3Techs 2026 年 3 月的統計,全球已知伺服器端語言的網站中,有 71.7% 使用 PHP。遙遙領先第二名的 Ruby(約 6.8%)。
- 42.2%——根據 W3Techs 2026 年 5 月的統計,所有網站中有 42.2% 跑在 WordPress 上,而 WordPress 是純 PHP 寫的。
- 454,000+——PHP 的套件管理器 Packagist 上有超過 45 萬個套件,累計安裝次數超過 1,800 億次。
- $57M——Laravel 在 2024 年 9 月完成了 Accel 領投的 A 輪融資,估值和商業前景都在成長。
「但 Stack Overflow 調查裡 PHP 用的人不多啊?」那個調查反映的是「誰在填問卷」,不是「誰在跑 production」。PHP 龐大到不需要被討論——它就是在那裡,安靜地服務著全世界七成以上的網站。
不過上面這幾個數字我得幫你補一下脈絡,不然會誤導你。那 71.7% 跟 42.2% 主要是「存量」——絕大多數是早就架好、跑了很多年的 WordPress 站和老站,不代表 2026 年的新專案都在選 PHP。而 Stack Overflow 那題我也不想全推給「誰在填問卷」:PHP 在「開發者想不想用」這個指標上確實偏弱,薪資中位數比不上 Go/Rust,AI、資料工程那一塊基本上是 Python 的天下,你拿 PHP 去硬擠不會太愉快。我的結論是:別把高市占當成「PHP 是新專案首選」的證據;但如果你要做的是 Web 後端、SaaS、接案、CMS,PHP 到今天仍然是非常務實、CP 值很高的選擇。適不適合,看你要解的是哪種問題,自己判斷比我幫你下定論好。
更重要的是,現代 PHP 和你記憶中的 PHP 完全不同。PHP 的每一個大版本都帶來實質的語法改進:
| 版本 | 發布年份 | 關鍵特性 |
|---|---|---|
| PHP 8.0 | 2020 | Named Arguments、Match Expression、Union Types、Nullsafe Operator |
| PHP 8.1 | 2021 | Enums、Fibers(協程)、Readonly Properties、Intersection Types |
| PHP 8.2 | 2022 | Readonly Classes、Disjunctive Normal Form Types、true/false/null 獨立型別 |
| PHP 8.3 | 2023 | Typed Class Constants、#[\Override]、json_validate() |
| PHP 8.4 | 2024 | Property Hooks、Asymmetric Visibility、array_find()/array_any()/array_all() |
| PHP 8.5 | 2025 | Pipe Operator |>、URI Extension、array_first()/array_last()、Clone With(目前最新穩定版) |
如果你的 PHP 印象停留在 5.x 時代,請直接跳到 8.4。那是一門不同的語言。
平反歸平反,現代 PHP 還是有幾個老問題你早晚會踩到:
- 標準函式庫 API 不一致——
in_array(needle, haystack)跟str_contains(haystack, needle)參數順序剛好相反,函式名有的加底線(str_replace)有的駝峰(array_map),這個亂象到 8.5 都還在。IDE 補全會救你大半,但別期待它像 Python 標準庫那樣整齊。- 沒有原生泛型——你寫不出
Collection<User>讓引擎幫你檢查型別,只能靠 PHPStan / Psalm 加註解,或在 docblock 標 array 形狀來補。==弱比較跟自動型別轉換的歷史包袱還在,習慣一律用===就能避掉大半的雷。- 生態系仍然新舊混雜——Google 隨手搜到的教學一大半是 PHP 5 的 procedural 老寫法,跟著抄你會學到一身壞習慣。
我把短板攤出來不是要打臉前面的平反,剛好相反——願意承認缺點的平反,才是有說服力的平反。
環境建置:安裝 PHP 8.4+ 與 Composer
macOS
最簡單的方式是用 Homebrew:
brew install php
php -v # 確認版本 >= 8.4
Homebrew 預設安裝最新穩定版。如果你需要特定版本:
brew install php@8.4
Linux (Ubuntu/Debian)
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php8.4 php8.4-cli php8.4-mbstring php8.4-xml php8.4-curl php8.4-zip
php -v
Windows
推薦使用 Laragon——一鍵安裝 PHP + Composer + MySQL + Nginx,比手動設定 WAMP 省心太多。
安裝 Composer
Composer 是 PHP 的套件管理器,等同於 npm(Node.js)或 pip(Python)。Laravel 本身就是一個 Composer 套件。
# macOS / Linux
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
composer --version
安裝完 PHP 和 Composer,你就準備好了。後面的章節會用 Composer 來安裝 Laravel。
確認安裝:你的第一行 PHP
建立一個 hello.php 檔案:
<?php
declare(strict_types=1);
echo "Hello from PHP " . PHP_VERSION . PHP_EOL;
執行它:
php hello.php
# Hello from PHP 8.4.x
declare(strict_types=1) 是現代 PHP 的第一行——它強制啟用嚴格型別檢查。為節省篇幅,後續範例省略這兩行(<?php 與 declare(strict_types=1)),但你實際寫檔案時都要加上。
型別系統:PHP 也有 Type Hints 了
PHP 從 7.0 開始引入型別宣告,到了 8.4 已經相當完整。如果你從 TypeScript 或 Python type hints 來的,會覺得很熟悉:
基本型別宣告
<?php
declare(strict_types=1);
function add(int $a, int $b): int
{
return $a + $b;
}
echo add(3, 4); // 7
echo add('3', 4); // ❌ TypeError(strict_types 開啟時)
可為 null 的型別
function findUser(int $id): ?User
{
// 回傳 User 或 null
return User::find($id);
}
Union Types(PHP 8.0+)
function formatId(int|string $id): string
{
return "ID: {$id}";
}
formatId(42); // ✅
formatId('abc'); // ✅
formatId(3.14); // ❌ TypeError
Intersection Types(PHP 8.1+)
function process(Countable&Iterator $items): void
{
// $items 必須同時實作 Countable 和 Iterator
foreach ($items as $item) {
// ...
}
}
跨語言對照
| 概念 | PHP 8.4 | TypeScript | Python |
|---|---|---|---|
| 基本型別 | int, string, float, bool | number, string, boolean | int, str, float, bool |
| 陣列 | array | Array<T> / T[] | list[T] |
| 可為 null | ?string | string | null | Optional[str] |
| Union | int|string | number | string | int | str |
| 回傳型別 | : int | : number | -> int |
| 無回傳值 | : void | : void | -> None |
| 嚴格模式 | declare(strict_types=1) | 預設嚴格 | mypy 靜態檢查 |
重點: PHP 的型別檢查是執行期(runtime)的,不像 TypeScript 只在編譯期。如果型別不符,PHP 會直接丟出
TypeError。
現代語法快速對照:PHP vs JavaScript vs Python
如果你已經會 JS 或 Python,下面這張表讓你五分鐘看懂 PHP 語法:
變數與常數
// PHP
$name = 'Bobo'; // 變數用 $ 開頭
$age = 35;
const TAX_RATE = 0.05; // 常數
define('APP_NAME', '揪好買'); // 另一種常數定義方式
// JavaScript
const name = 'Bobo';
let age = 35;
const TAX_RATE = 0.05;
# Python
name = 'Bobo'
age = 35
TAX_RATE = 0.05
字串插值
// PHP —— 雙引號才能插值,單引號不行
$name = 'Bobo';
echo "Hello, {$name}!"; // Hello, Bobo!
echo 'Hello, {$name}!'; // Hello, {$name}!(原樣輸出)
// JavaScript
console.log(`Hello, ${name}!`); // 反引號
陣列(Array)
PHP 的 array 同時扮演了 JS 的 Array 和 Object 的角色:
// 索引陣列(像 JS Array)
$fruits = ['apple', 'banana', 'cherry'];
echo $fruits[0]; // apple
// 關聯陣列(像 JS Object / Python dict)
$user = [
'name' => 'Bobo',
'age' => 35,
'city' => 'Taipei',
];
echo $user['name']; // Bobo
// JavaScript
const fruits = ['apple', 'banana', 'cherry'];
const user = { name: 'Bobo', age: 35, city: 'Taipei' };
# Python
fruits = ['apple', 'banana', 'cherry']
user = {'name': 'Bobo', 'age': 35, 'city': 'Taipei'}
條件判斷
// PHP —— 幾乎跟 JS/C 一樣
if ($age >= 18) {
echo '成年';
} elseif ($age >= 12) {
echo '青少年';
} else {
echo '兒童';
}
注意: PHP 用
elseif(連在一起),不是 Python 的elif也不是 JS 的else if(雖然else if分開寫也能用)。
迴圈
// foreach —— PHP 裡最常用的迴圈
$items = ['蔥油餅', '雞排', '珍奶'];
foreach ($items as $item) {
echo $item . PHP_EOL;
}
// 帶 key 的 foreach(像 Python 的 enumerate)
foreach ($items as $index => $item) {
echo "{$index}: {$item}" . PHP_EOL;
}
// 關聯陣列的 foreach
$prices = ['雞排' => 70, '珍奶' => 50];
foreach ($prices as $name => $price) {
echo "{$name} = \${$price}" . PHP_EOL;
}
函式
// PHP
function greet(string $name, string $greeting = '你好'): string
{
return "{$greeting}, {$name}!";
}
echo greet('Bobo'); // 你好, Bobo!
echo greet('Bobo', '哈囉'); // 哈囉, Bobo!
Named Arguments、Enums、Match Expression
這三個是從 PHP 8.0/8.1 開始的殺手級特性。
Named Arguments(PHP 8.0+)
不用再數參數順序了:
function createProduct(
string $name,
int $price,
string $category = '其他',
bool $isActive = true,
): array {
return compact('name', 'price', 'category', 'isActive');
}
// 傳統呼叫——你得記住每個位置
createProduct('雞排', 70, '小吃', true);
// Named Arguments——清楚明瞭
createProduct(
name: '雞排',
price: 70,
category: '小吃',
);
// isActive 用預設值 true,不用特別傳
// 還可以跳過中間的參數
createProduct(name: '珍奶', price: 50, isActive: false);
JS/Python 開發者注意: Python 一直有 keyword arguments,JS 則是用解構物件來模擬。PHP 的 Named Arguments 是語言原生支援,IDE 自動補全很方便。
Enums(PHP 8.1+)
終於有了原生 Enum,不用再 const STATUS_ACTIVE = 1 這樣土法煉鋼:
// 基本 Enum
enum OrderStatus
{
case Pending;
case Confirmed;
case Shipped;
case Delivered;
case Cancelled;
}
function updateOrder(int $orderId, OrderStatus $status): void
{
// $status 只能是上面五個值之一,型別安全
}
updateOrder(1, OrderStatus::Confirmed); // ✅
updateOrder(1, 'confirmed'); // ❌ TypeError
// Backed Enum(對應資料庫值)
enum OrderStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
}
// 從資料庫值反查
$status = OrderStatus::from('confirmed'); // OrderStatus::Confirmed
$status = OrderStatus::tryFrom('invalid'); // null(不會拋例外)
// Enum 還能有方法
enum OrderStatus: string
{
case Pending = 'pending';
case Confirmed = 'confirmed';
case Shipped = 'shipped';
case Delivered = 'delivered';
case Cancelled = 'cancelled';
public function label(): string
{
return match ($this) {
self::Pending => '待確認',
self::Confirmed => '已成團',
self::Shipped => '已出貨',
self::Delivered => '已送達',
self::Cancelled => '已取消',
};
}
}
echo OrderStatus::Confirmed->label(); // 已成團
在我們後面打造「揪好買」團購平台時,Enum 會大量用在訂單狀態、團購狀態等地方。
Match Expression(PHP 8.0+)
match 是 switch 的現代替代品——更簡潔、型別安全、回傳值:
// 傳統 switch(容易忘記 break)
switch ($status) {
case 'pending':
$label = '待確認';
break;
case 'confirmed':
$label = '已成團';
break;
default:
$label = '未知';
break;
}
// match(更優雅)
$label = match ($status) {
'pending' => '待確認',
'confirmed' => '已成團',
'shipped', 'delivered' => '已出貨/到貨', // 多值對應
default => '未知',
};
match vs switch 的關鍵差異:
match用嚴格比較(===),不會有0 == 'foo'這種坑match是表達式,可以直接賦值- 沒有
break,不會 fall-through - 沒匹配到且沒
default會拋UnhandledMatchError
現代 PHP 的類別語法:Constructor Promotion 到 Property Hooks(8.0–8.4)
Constructor Promotion(PHP 8.0+)
這大概是讓 PHP class 寫法最精簡的一個特性:
// 傳統寫法(囉嗦)
class Product
{
public string $name;
public int $price;
public string $category;
public function __construct(string $name, int $price, string $category)
{
$this->name = $name;
$this->price = $price;
$this->category = $category;
}
}
// Constructor Promotion(一行搞定)
class Product
{
public function __construct(
public string $name,
public int $price,
public string $category,
) {}
}
$product = new Product('雞排', 70, '小吃');
echo $product->name; // 雞排
TypeScript 開發者會覺得很像:
constructor(public name: string)的概念是一樣的。
Readonly Properties(PHP 8.1+)
設定一次就不能改,適合用在值物件和 DTO:
class GroupBuy
{
public function __construct(
public readonly string $title,
public readonly int $minParticipants,
public readonly \DateTimeImmutable $deadline,
) {}
}
$group = new GroupBuy('辦公室零食團', 5, new \DateTimeImmutable('2026-07-01'));
echo $group->title; // 辦公室零食團
$group->title = '改名'; // ❌ Error: Cannot modify readonly property
Property Hooks(PHP 8.4+)
這是 PHP 8.4 最重磅的特性——類似 C# 的 getter/setter 或 Kotlin 的 property delegation:
class Temperature
{
public float $celsius {
set(float $value) {
if ($value < -273.15) {
throw new \ValueError('低於絕對零度');
}
$this->celsius = $value;
}
}
public float $fahrenheit {
get => $this->celsius * 9 / 5 + 32;
set(float $value) => $this->celsius = ($value - 32) * 5 / 9;
}
}
$temp = new Temperature();
$temp->celsius = 100;
echo $temp->fahrenheit; // 212
$temp->fahrenheit = 32;
echo $temp->celsius; // 0
Asymmetric Visibility(PHP 8.4+)
讀取和寫入可以有不同的存取權限:
class User
{
public function __construct(
public private(set) string $name, // 外部可讀,只有內部可寫
public protected(set) string $email, // 外部可讀,子類別可寫
) {}
}
$user = new User('Bobo', 'bobo@example.com');
echo $user->name; // ✅ 可以讀
$user->name = '改名'; // ❌ Error: Cannot modify private(set) property
Arrow Functions 與 Closure
Closure(匿名函式)
PHP 的 Closure 跟 JS 的匿名函式類似,但有一個關鍵差異——不會自動捕獲外部變數,必須用 use 明確宣告:
$taxRate = 0.05;
// PHP Closure——必須用 use 捕獲外部變數
$calculateTax = function (int $price) use ($taxRate): float {
return $price * $taxRate;
};
echo $calculateTax(1000); // 50.0
// JavaScript——自動捕獲(closure)
const taxRate = 0.05;
const calculateTax = (price) => price * taxRate;
這是很多 JS 開發者初學 PHP 最困惑的地方。PHP 的設計哲學是「顯式優於隱式」,
use讓你明確知道 closure 依賴了哪些外部變數。
Arrow Functions(PHP 7.4+)
短語法,自動捕獲外部變數,只能有單一表達式:
$taxRate = 0.05;
// Arrow Function —— 自動捕獲,不用 use
$calculateTax = fn(int $price): float => $price * $taxRate;
echo $calculateTax(1000); // 50.0
// 常見用途:陣列操作
$prices = [100, 200, 300];
$withTax = array_map(fn($p) => $p * (1 + $taxRate), $prices);
// [105.0, 210.0, 315.0]
陣列高階函式
PHP 8.4 新增了更多實用的陣列函式,讓函式風格程式設計更方便:
$products = [
['name' => '雞排', 'price' => 70, 'active' => true],
['name' => '珍奶', 'price' => 50, 'active' => true],
['name' => '臭豆腐', 'price' => 60, 'active' => false],
];
// array_find(PHP 8.4)—— 找到第一個符合條件的
$cheap = array_find($products, fn($p) => $p['price'] < 55);
// ['name' => '珍奶', 'price' => 50, 'active' => true]
// array_any / array_all(PHP 8.4)
$hasInactive = array_any($products, fn($p) => !$p['active']); // true
$allActive = array_all($products, fn($p) => $p['active']); // false
// 經典的 array_map / array_filter
$activeNames = array_map(
fn($p) => $p['name'],
array_filter($products, fn($p) => $p['active']),
);
// ['雞排', '珍奶']
JS 開發者注意: PHP 的
array_filter和array_map是全域函式而非陣列方法,參數順序跟 JS 不同(array_map(callback, array)vsarray.map(callback))。這是 PHP 最讓人不適應的地方之一,但習慣了就好。Laravel 的 Collection 類別會幫你解決這個問題——到第四章 Eloquent ORM你就會看到。
Composer:PHP 的 npm / pip
Composer 是 PHP 生態系的基石。截至 2026 年,Packagist 上有超過 45 萬個套件,累計安裝超過 1,800 億次。Laravel 本身就是透過 Composer 安裝的。
基本操作
# 建立新專案(從現有套件)
composer create-project laravel/laravel my-project
# 安裝相依套件(等同 npm install)
composer install
# 新增套件(等同 npm install package-name)
composer require guzzlehttp/guzzle
# 新增開發用套件(等同 npm install --save-dev)
composer require --dev pestphp/pest
# 更新所有套件
composer update
# 移除套件
composer remove guzzlehttp/guzzle
composer.json vs package.json
| 概念 | Composer (PHP) | npm (Node.js) | pip (Python) |
|---|---|---|---|
| 設定檔 | composer.json | package.json | pyproject.toml |
| Lock 檔 | composer.lock | package-lock.json | requirements.txt |
| 套件目錄 | vendor/ | node_modules/ | site-packages/ |
| 執行指令 | composer run | npm run | — |
| 全域安裝 | composer global require | npm install -g | pip install |
一個典型的 composer.json:
版本說明:本系列以 Laravel 12 為基準。Laravel 13 已於 2026 年 3 月 17 日正式發布,若你跟著做時裝到 13,核心概念完全相同,只需將
^12.0改為^13.0,其餘無需調整。
{
"name": "bobo/jiu-hao-mai",
"description": "揪好買——台灣團購平台",
"type": "project",
"require": {
"php": "^8.4",
"laravel/framework": "^12.0"
},
"require-dev": {
"pestphp/pest": "^3.0"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
}
}
PSR-4 自動載入
PHP 不像 JS 有 import/require,也不像 Python 有 import。PHP 的模組系統是透過 Composer 的 PSR-4 autoloading 來實現的:
app/Models/Product.php:
// 不用手動 require 每一個檔案
// Composer 會根據 namespace 自動找到對應的檔案
namespace App\Models;
class Product
{
// ...
}
app/Http/Controllers/ProductController.php:
namespace App\Http\Controllers;
use App\Models\Product; // 自動載入 app/Models/Product.php
class ProductController
{
public function index()
{
$products = Product::all();
}
}
規則很簡單:Namespace 對應目錄路徑。App\Models\Product 對應 app/Models/Product.php。Composer 的 autoloader 幫你處理所有的檔案載入,你只需要寫 use。
這跟 Python 的模組系統很像——
import app.models.product對應app/models/product.py。差別是 PHP 用\而不是.,且 namespace 宣告是手動的。
PSR 標準:PHP 社群的共識
PSR(PHP Standards Recommendations)是 PHP-FIG(Framework Interoperability Group)制定的標準,確保不同框架和套件之間能互通。你不需要全部背下來,但知道這幾個最常見的就夠:
| 標準 | 名稱 | 一句話說明 | 對應其他生態系 |
|---|---|---|---|
| PSR-1 | Basic Coding Standard | 基本的命名規範(類別用 PascalCase、方法用 camelCase) | ESLint 基本規則 |
| PSR-4 | Autoloading Standard | Namespace 對應目錄結構的自動載入規則 | Node.js module resolution |
| PSR-12 | Extended Coding Style | 程式碼風格規範(縮排、大括號位置等) | Prettier / Black |
| PSR-7 | HTTP Message Interface | HTTP Request/Response 的標準介面 | Express 的 req/res |
| PSR-11 | Container Interface | DI Container 的標準介面 | — |
實務上,Laravel 完全遵循 PSR-4 和 PSR-12。你安裝一個叫 Laravel Pint 的工具,就能一鍵格式化程式碼:
# 安裝(Laravel 12 內建)
# 格式化所有 PHP 檔案
./vendor/bin/pint
就像 JavaScript 有 Prettier、Python 有 Black——PHP 有 Pint。風格爭論到此結束。
小結:準備好進入 Laravel 了
這一章我們用最快的速度帶你走過了現代 PHP 的核心:
- 型別系統——
int、string、Union Types、Intersection Types,執行期檢查 - 現代語法——Named Arguments、Enums、Match Expression、Constructor Promotion
- PHP 8.4 新特性——Property Hooks、Asymmetric Visibility、
array_find()/array_any()/array_all() - Closure 與 Arrow Functions——注意
use關鍵字的差異 - Composer——套件管理、PSR-4 自動載入
- PSR 標準——社群共識、Pint 格式化工具
如果你是 JavaScript 開發者,最需要適應的是:
- 變數要加
$ - Closure 要用
use捕獲外部變數 - 陣列操作是全域函式而非方法(但 Laravel Collection 會解決這個問題)
- Namespace 用
\不是.或/
如果你是 Python 開發者,最需要適應的是:
- 每行結尾要加
; - 大括號
{}而不是縮排 $開頭的變數->而不是.存取屬性和方法
這些都是語法糖衣的差異,核心概念(型別、函式、類別、模組化)是通用的。你已經會程式設計了,只是在學一門新的方言。
下一章,我們正式進入 Laravel 12——從 composer create-project 開始,十分鐘內讓「揪好買」的專案骨架跑起來。