PHP 類別與物件 (Class & Object)

PHP 支援物件導向程式設計(Object-Oriented Programming, OOP)。類別是物件的藍圖,定義了物件的屬性和行為。

基本概念

  • 類別 (Class):物件的模板或藍圖
  • 物件 (Object):類別的實例
  • 屬性 (Property):物件的資料
  • 方法 (Method):物件的行為(函數)

定義類別

<?php
class User {
    // 屬性
    public string $name;
    public int $age;

    // 方法
    public function greet(): string {
        return "Hello, I'm {$this->name}";
    }
}

// 建立物件(實例化)
$user = new User();
$user->name = "Alice";
$user->age = 25;

echo $user->greet();  // Hello, I'm Alice
?>

建構子 __construct()

建構子在建立物件時自動執行:

<?php
class User {
    public string $name;
    public int $age;

    public function __construct(string $name, int $age) {
        $this->name = $name;
        $this->age = $age;
    }
}

$user = new User("Alice", 25);
echo $user->name;  // Alice
?>

建構子屬性提升 (PHP 8+)

<?php
class User {
    public function __construct(
        public string $name,
        public int $age,
        public string $email = ''
    ) {}
}

$user = new User("Alice", 25);
echo $user->name;  // Alice
?>

存取修飾符

修飾符類別內子類別外部
public
protected
private
<?php
class User {
    public string $name;       // 公開
    protected string $email;   // 受保護
    private string $password;  // 私有

    public function __construct(string $name, string $email, string $password) {
        $this->name = $name;
        $this->email = $email;
        $this->password = $password;
    }

    public function getEmail(): string {
        return $this->email;
    }
}

$user = new User("Alice", "alice@example.com", "secret");
echo $user->name;          // Alice
// echo $user->email;      // 錯誤:protected
// echo $user->password;   // 錯誤:private
echo $user->getEmail();    // alice@example.com
?>

$this 關鍵字

$this 指向目前的物件實例:

<?php
class Counter {
    private int $count = 0;

    public function increment(): self {
        $this->count++;
        return $this;  // 回傳自己,支援鏈式呼叫
    }

    public function getCount(): int {
        return $this->count;
    }
}

$counter = new Counter();
$counter->increment()->increment()->increment();
echo $counter->getCount();  // 3
?>

靜態成員

使用 static 關鍵字定義靜態屬性和方法:

<?php
class Counter {
    private static int $count = 0;

    public static function increment(): void {
        self::$count++;
    }

    public static function getCount(): int {
        return self::$count;
    }
}

Counter::increment();
Counter::increment();
echo Counter::getCount();  // 2
?>

self vs $this

<?php
class Example {
    private static string $staticProp = 'static';
    private string $instanceProp = 'instance';

    public function show(): void {
        echo self::$staticProp;   // 存取靜態屬性
        echo $this->instanceProp; // 存取實例屬性
    }

    public static function staticShow(): void {
        echo self::$staticProp;
        // echo $this->instanceProp;  // 錯誤:靜態方法中沒有 $this
    }
}
?>

常數

<?php
class Status {
    public const PENDING = 'pending';
    public const ACTIVE = 'active';
    public const COMPLETED = 'completed';
}

echo Status::PENDING;  // pending

// 在物件中使用
$status = Status::ACTIVE;
?>

Getter 和 Setter

<?php
class User {
    private string $name;

    public function getName(): string {
        return $this->name;
    }

    public function setName(string $name): void {
        if (strlen($name) < 2) {
            throw new InvalidArgumentException("名稱至少 2 個字元");
        }
        $this->name = $name;
    }
}

$user = new User();
$user->setName("Alice");
echo $user->getName();  // Alice
?>

解構子 __destruct()

解構子在物件被銷毀時執行:

<?php
class FileHandler {
    private $handle;

    public function __construct(string $filename) {
        $this->handle = fopen($filename, 'r');
    }

    public function __destruct() {
        if ($this->handle) {
            fclose($this->handle);
        }
    }
}
?>

唯讀屬性 (PHP 8.1+)

<?php
class User {
    public function __construct(
        public readonly string $id,
        public string $name
    ) {}
}

$user = new User("123", "Alice");
echo $user->id;      // 123
// $user->id = "456"; // 錯誤:唯讀屬性不能修改
$user->name = "Bob"; // OK
?>

唯讀類別 (PHP 8.2+)

你可以將整個類別宣告為 readonly,這會讓該類別內的所有屬性自動變成 readonly,且禁止動態增加屬性。

<?php
readonly class User {
    public function __construct(
        public string $id,
        public string $name
    ) {}
}

$user = new User("1", "Alice");
// $user->name = "Bob"; // 錯誤:所有屬性都是唯讀的
?>

屬性鉤子 (Property Hooks) (PHP 8.4+)

PHP 8.4 引入了屬性鉤子,讓你可以在讀取或寫入屬性能直接定義自訂邏輯,減少撰寫 Getter 和 Setter 的代碼。

<?php
class User {
    public string $name {
        // get 鉤子:讀取屬性時執行
        get => strtoupper($this->name);

        // set 鉤子:寫入屬性時執行
        set(string $value) {
            if (strlen($value) < 2) {
                throw new InvalidArgumentException("名稱太短");
            }
            $this->name = $value;
        }
    }
}

$user = new User();
$user->name = "alice"; // 觸發 set 鉤子
echo $user->name;      // 觸發 get 鉤子,輸出:ALICE
?>

非對稱可見性 (Asymmetric Visibility) (PHP 8.4+)

你可以為屬性的「讀取」與「寫入」設定不同的權限層級。例如,允許公開讀取,但僅限類別內部修改。

<?php
class Book {
    public function __construct(
        public private(set) string $title, // 公共讀取,私有寫入
        public protected(set) string $author // 公共讀取,子類可寫入
    ) {}

    public function updateTitle(string $newTitle) {
        $this->title = $newTitle; // 內部可以寫入
    }
}

$book = new Book("PHP 入門", "Mike");
echo $book->title; // OK
// $book->title = "新書名"; // 錯誤:外部不可寫入
?>

簡化類別實例化 (PHP 8.4+)

在 PHP 8.4 之後,你可以直接在 new 之後呼叫方法,不再需要額外的括號。

<?php
// PHP 8.4 之前
$result = (new User())->greet();

// PHP 8.4
$result = new User()->greet();
?>

型別宣告

<?php
class User {
    public string $name;
    public ?int $age = null;
    public array $roles = [];

    public function setAge(int $age): self {
        $this->age = $age;
        return $this;
    }

    public function getInfo(): array {
        return [
            'name' => $this->name,
            'age' => $this->age
        ];
    }
}
?>

魔術方法

<?php
class User {
    private array $data = [];

    // 存取不存在的屬性
    public function __get(string $name): mixed {
        return $this->data[$name] ?? null;
    }

    public function __set(string $name, mixed $value): void {
        $this->data[$name] = $value;
    }

    public function __isset(string $name): bool {
        return isset($this->data[$name]);
    }

    // 呼叫不存在的方法
    public function __call(string $name, array $arguments): mixed {
        return "呼叫了 $name 方法";
    }

    // 轉換為字串
    public function __toString(): string {
        return json_encode($this->data);
    }
}

$user = new User();
$user->name = "Alice";  // 觸發 __set
echo $user->name;       // 觸發 __get
echo $user->sayHello(); // 觸發 __call
echo $user;             // 觸發 __toString
?>

物件複製

<?php
class User {
    public string $name;
    public array $settings;

    public function __clone() {
        // 深層複製
        $this->settings = $this->settings;
    }
}

$user1 = new User();
$user1->name = "Alice";
$user1->settings = ['theme' => 'dark'];

// 淺複製
$user2 = $user1;  // 同一個物件
$user2->name = "Bob";
echo $user1->name;  // Bob

// 深複製
$user3 = clone $user1;
$user3->name = "Charlie";
echo $user1->name;  // Bob(不受影響)
?>

完整範例

<?php
class BankAccount {
    private static int $totalAccounts = 0;

    public function __construct(
        private readonly string $accountNumber,
        private string $owner,
        private float $balance = 0
    ) {
        self::$totalAccounts++;
    }

    public function deposit(float $amount): self {
        if ($amount <= 0) {
            throw new InvalidArgumentException("金額必須大於 0");
        }
        $this->balance += $amount;
        return $this;
    }

    public function withdraw(float $amount): self {
        if ($amount > $this->balance) {
            throw new RuntimeException("餘額不足");
        }
        $this->balance -= $amount;
        return $this;
    }

    public function getBalance(): float {
        return $this->balance;
    }

    public static function getTotalAccounts(): int {
        return self::$totalAccounts;
    }
}

$account = new BankAccount("001", "Alice", 1000);
$account->deposit(500)->withdraw(200);
echo $account->getBalance();  // 1300
echo BankAccount::getTotalAccounts();  // 1
?>