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
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
?>