PHP 原生陣列功能強大,但缺乏型別約束、統一介面與物件導向封裝。參考 .NET Collection 設計,本文逐步建構一個功能完整、支援惰性載入的 PHP 集合類別。
核心需求:封裝陣列、提供型別安全介面、支援鍵值存取。
<?php
class Collection {
private array $items = [];
// 新增項目
public function addItem(mixed $item, mixed $key = null): void {
if ($key !== null) {
if (isset($this->items[$key])) {
throw new InvalidArgumentException("Key \"$key\" already exists.");
}
$this->items[$key] = $item;
} else {
$this->items[] = $item;
}
}
// 移除項目
public function removeItem(mixed $key): void {
if (!isset($this->items[$key])) {
throw new OutOfBoundsException("Invalid key \"$key\".");
}
unset($this->items[$key]);
}
// 取得項目
public function getItem(mixed $key): mixed {
if (!isset($this->items[$key])) {
throw new OutOfBoundsException("Invalid key \"$key\".");
}
return $this->items[$key];
}
// 取得所有鍵
public function keys(): array {
return array_keys($this->items);
}
// 取得長度
public function length(): int {
return count($this->items);
}
// 檢查鍵是否存在
public function exists(mixed $key): bool {
return isset($this->items[$key]);
}
}
?>
搭配具體領域物件 (如 Course) 使用:
<?php
class Course {
private int $id;
private string $code;
private string $name;
public function __construct(int $id, string $code, string $name) {
$this->id = $id;
$this->code = $code;
$this->name = $name;
}
public function getName(): string { return $this->name; }
public function getId(): int { return $this->id; }
public function getCode(): string { return $this->code; }
public function __toString(): string { return $this->name; }
}
// 型別特化集合 (PHP 8.1+ 可用泛型註解或繼承限制)
class CourseCollection extends Collection {
public function addItem(Course $item, mixed $key = null): void {
parent::addItem($item, $key);
}
public function getItem(mixed $key): Course {
return parent::getItem($key);
}
}
// 使用
$courses = new CourseCollection();
$courses->addItem(new Course(1, '001', '國文'), 1);
$courses->addItem(new Course(2, '002', '數學'), 2);
echo $courses->getItem(1); // 輸出: 國文
?>
解決「關聯資料量大、不想預先載入」的問題。引入回調機制,首次存取時才觸發資料載入。
<?php
class LazyCollection extends Collection {
private ?callable $loadCallback = null;
private bool $isLoaded = false;
// 設定載入回調
public function setLoadCallback(callable $callback): void {
$this->loadCallback = $callback;
}
// 內部:所有公開存取方法前置檢查
private function checkCallback(): void {
if ($this->loadCallback !== null && !$this->isLoaded) {
$this->isLoaded = true;
($this->loadCallback)($this); // 傳入 $this 讓回調填充資料
}
}
// 覆寫所有公開方法,加入 checkCallback()
public function addItem(mixed $item, mixed $key = null): void {
$this->checkCallback();
parent::addItem($item, $key);
}
public function removeItem(mixed $key): void {
$this->checkCallback();
parent::removeItem($key);
}
public function getItem(mixed $key): mixed {
$this->checkCallback();
return parent::getItem($key);
}
public function keys(): array {
$this->checkCallback();
return parent::keys();
}
public function length(): int {
$this->checkCallback();
return parent::length();
}
public function exists(mixed $key): bool {
$this->checkCallback();
return parent::exists($key);
}
}
?>
<?php
class Student {
private int $id;
private string $name;
public LazyCollection $courses;
public function __construct(int $id, string $name) {
$this->id = $id;
$this->name = $name;
$this->courses = new LazyCollection();
// 設定回調:捕獲 $this 以便存取學生 ID 查詢課程
$this->courses->setLoadCallback([$this, 'loadCourses']);
}
public function getName(): string { return $this->name; }
public function getId(): int { return $this->id; }
// 回調方法:實際從資料庫載入課程並填充集合
public function loadCourses(Collection $collection): void {
// 模擬資料庫查詢
$courseData = [
['id' => 1, 'code' => '001', 'name' => '國文'],
['id' => 2, 'code' => '002', 'name' => '數學'],
];
foreach ($courseData as $data) {
$course = new Course($data['id'], $data['code'], $data['name']);
$collection->addItem($course, $data['id']);
}
}
}
// 使用:實例化 Student 時不查詢課程
$student = new Student(1, '馬江');
echo $student->getName(); // 輸出: 馬江
// 首次存取 courses 時觸發 loadCourses()
$course = $student->courses->getItem(1);
echo $course; // 輸出: 國文 (此時才執行 SQL 查詢)
?>
LazyCollectionProxy,符合單一職責原則。從簡單陣列封裝到支援惰性載入的 LazyCollection,我們逐步建構出符合領域驅動設計 (DDD) 聚合根模式的集合類別。關鍵在於 回調注入 與 首次存取攔截,將資料獲取邏輯延遲到真正需要時刻,有效降低記憶體佔用與資料庫查詢壓力。