2025 年 PHP 常見面試題整理以及對應答案和代碼示例
PHP 面試通常會考察基礎知識(數組、OOP、錯誤處理)和現代特性(類型、屬性、枚舉)。關鍵是要展示你能寫出簡潔、可預測的代碼,同時了解 PHP 8+ 的新變化。
我整理了以下一些常見 PHP 可能面試的。每個問題都有簡潔的答案和可運行的代碼示例,你可以直接復制去試試。
原文鏈接- 2025 年 PHP 常見面試題整理以及對應答案和代碼示例
PHP 8+ 中有哪些面試官關心的變化?
類型系統:聯合類型(int|float)、mixed、never、true|false|null、交集類型,以及可空類型 ?T。
OOP 改進:構造器屬性提升、readonly 屬性/類、枚舉、#[\Override] 屬性。
控制流:match(表達式,無穿透)、nullsafe 操作符 ?->。
開發體驗優化:命名參數、屬性/注解、JIT(性能提升)、array_is_list()。
錯誤處理更合理:字符串和數字比較的方式改進了(比如 0 == “foo” 現在返回 false)。
如果你能清楚地解釋這些特性,面試就會順利很多。
PHP 中 == 和 === 的區別是什么?
== 執行類型轉換;=== 要求相同類型和相同值。
var_dump(42 == "42"); // true (寬松比較)
var_dump(42 === "42"); // false (嚴格比較)// 從 PHP 8 開始:
var_dump(0 == "foo"); // false (舊版本中曾經是 true)
要點:默認使用 ===,除非有非常具體的理由不這樣做。
聯合類型、可空類型和交集類型如何工作?
function area(int|float $w, int|float $h): float {return $w * $h;
}function greet(?string $name): string { // 可空類型return "Hello, " . ($name ?? "stranger");
}interface A { public function foo(): void; }
interface B { public function bar(): void; }class C implements A, B {public function foo(): void {}public function bar(): void {}
}function needsAandB(A&B $x): void { /* ... */ } // 交集類型
int|float
表示兩者之一?string
表示string|null
A&B
表示必須實現 A 和 B 兩個接口
nullsafe 操作符是什么,何時應該使用?
當左側為 null 時,它會自動停止后續的方法/屬性訪問。
$user = null;
echo $user?->profile?->company?->name ?? 'No company'; // "No company"
無需嵌套 if——簡潔且安全。
什么時候應該使用 match 而不是 switch?
match 是表達式(可以返回值),必須處理所有情況,不會像 switch 那樣穿透執行。
$status = 404;
$message = match ($status) {200, 201 => 'OK',404 => 'Not Found',500 => 'Server Error',default => 'Unknown',
};
這減少了因遺忘 break;
引起的錯誤。
用 60 秒解釋 Composer 自動加載和 PSR-4
- 在 composer.json 中添加命名空間
- 將文件放在正確的文件夾中
- 讓 Composer 生成自動加載器
{"autoload": {"psr-4": {"App\\": "src/"}}
}
// src/Service/Pinger.php
namespace App\Service;class Pinger {public function ping(): string { return 'pong'; }
}
// index.php
require __DIR__ . '/vendor/autoload.php';use App\Service\Pinger;
echo (new Pinger())->ping(); // "pong"
修改后運行 composer dump-autoload
。
include 和 require 的區別(及 _once 變體)?
- require → 失敗時產生致命錯誤(停止腳本)
- include → 失敗時產生警告(繼續執行)
- *_once → 只加載文件一次
應用必須的文件使用 require(如 bootstrap),可選部分使用 include。
PHP 引用實際如何工作?
賦值是寫時復制。大部分時候不需要用引用,只有在 API 必須修改參數值時才用。
function bump(int &$n): void { $n++; }
$x = 5;
bump($x);
echo $x; // 6
避免為"微優化"使用引用。它們往往損害清晰度。
解釋異常與錯誤(Throwable)
所有可拋出的都實現 Throwable。Exception 和 Error 是兄弟類。
try {throw new RuntimeException('Oops');
} catch (\Throwable $e) { // 捕獲 Exception 和 Errorerror_log($e->getMessage());
} finally {// 清理工作
}
在應用的邊界層(比如控制器)捕獲 Throwable 很方便,但在庫的內部應該捕獲更具體的異常類型。
PDO 預處理語句防止 SQL 注入
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = :email');
$stmt->execute(['email' => $email]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
經驗法則:始終使用預處理語句;永不插入不可信輸入。
如何正確存儲密碼?
使用 password_hash()
和 password_verify()
。不要自己選擇算法——讓 PHP 選擇默認的。
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);
if (password_verify($loginPassword, $hash)) {// 通過驗證
}
PASSWORD_DEFAULT 會隨著 PHP 版本更新而演進,當 password_needs_rehash()
返回 true 時需要重新哈希。
屬性(Attributes)是什么,如何讀取?
屬性是原生注解。你可以裝飾類、方法等,并通過反射讀取。
#[Attribute(Attribute::TARGET_METHOD)]
class Route {public function __construct(public string $path) {}
}class BlogController {#[Route('/posts')]public function index() {}
}// 讀取:
$ref = new ReflectionMethod(BlogController::class, 'index');
$attrs = $ref->getAttributes(Route::class);
$path = $attrs[0]->newInstance()->path; // '/posts'
框架會用屬性來處理路由、驗證、依賴注入等功能。
枚舉是什么,為什么有用?
枚舉用真正的類型替換"字符串化"的常量。
enum Status: string {case Draft = 'draft';case Published = 'pub';
}function publish(Status $s): bool {return $s === Status::Published;
}publish(Status::Draft); // false
帶值的枚舉保證了比較的安全性,也能讓 IDE 的自動完成更好用。
readonly 屬性和不可變對象
用來創建值對象或 DTO 非常合適。
final class Money {public function __construct(public readonly int $cents,public readonly string $currency,) {}
}$m = new Money(100, 'USD');
// $m->cents = 200; // 錯誤
也可以用 readonly 類來讓全部屬性都變成只讀。
Trait vs 抽象類 vs 接口
- 接口:僅契約(無實現)
- 抽象類:共享基類 + 部分實現
- Trait:跨不相關類的水平復用(混入方法)
trait LoggerTrait {public function log($m){ echo $m; }
}interface Reportable {public function report(): string;
}abstract class BaseReport {abstract public function data(): array;
}class SalesReport extends BaseReport implements Reportable {use LoggerTrait;public function data(): array { return [1,2,3]; }public function report(): string { return json_encode($this->data()); }
}
延遲靜態綁定(static:: vs self::)
self::
綁定到寫代碼的類;static::
延遲到運行時類。
class A {public static function who(): string { return __CLASS__; }public static function call(): string { return static::who(); }
}class B extends A {public static function who(): string { return __CLASS__; }
}echo B::call(); // "B" (延遲靜態綁定)
基類如果要被繼承的話,應該用 static::
。
生成器(yield)處理大數據集
生成器采用懶加載——特別適合處理大量數據流。
function lines(string $file): iterable {$fh = fopen($file, 'r');try {while (($line = fgets($fh)) !== false) {yield rtrim($line, "\n");}} finally {fclose($fh);}
}foreach (lines('huge.txt') as $line) {// 處理而不加載所有內容到內存
}
閉包、“use” 和箭頭函數
$total = 0;
$add = function(int $n) use (&$total) { $total += $n; };array_map($add, [1,2,3]);
echo $total; // 6$double = fn($x) => $x * 2; // 箭頭函數自動按值捕獲
箭頭函數寫起來簡潔,普通閉包能讓你精確控制變量捕獲(use)。
會話和 Cookie:安全默認檢查清單
session_set_cookie_params(['httponly' => true,'samesite' => 'Lax','secure' => isset($_SERVER['HTTPS']),
]);
session_start();
$_SESSION['user_id'] = 123;
- 登錄時重新生成 ID(
session_regenerate_id(true)
) - 設置 secure 和 httponly
- 優先使用 SameSite=Lax 或 Strict,除非第三方流程需要 None
處理日期:優先使用 DateTimeImmutable
$start = new DateTimeImmutable('2025-03-01 09:00', new DateTimeZone('UTC'));
$meeting = $start->modify('+1 hour');echo $start->format('c'); // 未改變
echo $meeting->format('c'); // 新實例
不可變日期可以防止意外修改。記得要明確設置時區,或者早期用 default_timezone_set 設置。
OPcache 和性能基礎
- 生產環境啟用 OPcache——它緩存編譯的字節碼
- 避免過早的微優化;用專門的工具來測量性能(比如 Blackfire、Xdebug profiler)
- 使用正確的數據結構;對大數字列表,SplFixedArray 比常規數組更節省內存
PHP 的垃圾收集器如何工作
PHP 使用引用計數 + 循環收集器的方式管理內存。大部分情況下不用操心這個問題。但如果是長時間運行的進程(比如 worker 或 daemon),又有大量數據結構或循環引用(父子對象互相引用),就需要手動 unset 引用并打破循環,這樣能更早釋放內存。
常見數組陷阱和高級技巧
$a = ['x' => 1, 'y' => 2];
$b = ['y' => 3, 'z' => 4];
$merge = $a + $b; // 按鍵聯合:['x'=>1,'y'=>2,'z'=>4]
$replace = array_merge($a, $b); // ['x'=>1,'y'=>3,'z'=>4]$list = ['a','b','c'];
array_splice($list, 1, 1); // 在索引1處移除:['a','c']
array_is_list(['a','b']); // true(連續數字鍵)
array_is_list(['1'=>'a']); // false
知道何時需要聯合(+)vs 合并(array_merge)。
如何快速測試代碼?
- 將邏輯放入小函數或方法
- 使用 PHPUnit 或 Pest;模擬外部服務
- 快速檢查,添加簡單的 CLI 腳本:
if (PHP_SAPI === 'cli') {assert(area(3, 4) === 12.0);echo "All good\n";
}
CLI 斷言能讓你快速驗證代碼的正確性,即使只是小示例也很有用。
關于 PHP 中的異步
PHP 本身是同步的請求-響應模式,但 Fiber(PHP 8.1)讓 AMPHP 或 ReactPHP 等庫能實現用戶空間的并發處理。面試中不會要求你手寫異步代碼,只要能說明白什么時候需要用(比如 worker 中有大量 I/O 操作)就行。
快速問答
$_POST vs php://input? $_POST 解析表單編碼數據;php://input 讀取原始請求體(對 JSON 有用)。
****get/**set?** 重載屬性訪問的魔術方法——謹慎使用。
__toString()? 對象的字符串表示(必須返回字符串)。
require_once 慢? 有 OPcache 時可忽略;為了正確性使用,不是"速度"。
到處都是靜態方法? 對純函數很方便;為了可測試性/可配置性優先使用 DI。
常量 vs 枚舉? 枚舉給你一個類型;常量只是值。
一個現代的 PHP 例子
<?php
declare(strict_types=1);enum Role: string {case User = 'user';case Admin = 'admin';
}final class User {public function __construct(public readonly int $id,public readonly string $email,public readonly Role $role,) {}
}#[Attribute(Attribute::TARGET_METHOD)]
class RequiresRole {public function __construct(public Role $role) {}
}final class UserController {#[RequiresRole(Role::Admin)]public function delete(User $actor, int $userId): string {if ($actor->role !== Role::Admin) {throw new RuntimeException('Forbidden');}// 假設我們在這里刪除return "Deleted user #{$userId}";}
}// 小型反射驅動的守衛:
$ref = new ReflectionMethod(UserController::class, 'delete');
$attr = $ref->getAttributes(RequiresRole::class)[0] ?? null;
$required = $attr?->newInstance()->role ?? null;$controller = new UserController();
$admin = new User(1, 'a@ex.com', Role::Admin);
$guest = new User(2, 'g@ex.com', Role::User);echo $controller->delete($admin, 99), PHP_EOL; // "Deleted user #99"try {echo $controller->delete($guest, 99);
} catch (RuntimeException $e) {echo $e->getMessage(), PHP_EOL; // "Forbidden"
}
這個小片段展示了:嚴格類型、枚舉、readonly 屬性、屬性和普通異常——所有"8.x 時代"的優點。
總結
如果你只記住一點:
- 優先使用嚴格類型、預處理語句和 password_hash
- 使用 match、nullsafe、枚舉和 readonly 讓意圖明顯
- 保持代碼可測試、小巧和樸實(以最好的方式)
面試看重的是思路清晰:要說明某個特性為什么存在,而不只是怎么用。如果你能為這些代碼例子配上簡潔的解釋,答案就會很有說服力。