2024 年 PHP 日本研討會

物件繼承

繼承是一個完善的程式設計原則,PHP 在其物件模型中運用了這個原則。這個原則會影響許多類別和物件之間的關係。

例如,當擴展一個類別時,子類別會繼承父類別的所有公開和受保護的方法、屬性和常數。除非子類別覆寫這些方法,否則它們將保留其原始功能。

這對於定義和抽象化功能很有用,並且允許在類似的物件中實現額外的功能,而無需重新實現所有共享的功能。

父類別的私有方法無法被子類別存取。因此,子類別可以自行重新實現私有方法,而不需考慮一般的繼承規則。然而,在 PHP 8.0.0 之前,`final` 和 `static` 限制會應用於私有方法。從 PHP 8.0.0 開始,唯一強制執行的私有方法限制是 `private final` 建構函式,因為這是使用靜態工廠方法時「禁用」建構函式的常見方法。

方法、屬性和常數的可見性可以放寬,例如,`protected` 方法可以標記為 `public`,但它們不能被限制,例如,將 `public` 屬性標記為 `private`。建構函式是一個例外,其可見性可以被限制,例如,`public` 建構函式可以在子類別中標記為 `private`。

注意事項:

除非使用自動載入,否則必須先定義類別才能使用它們。如果一個類別繼承另一個類別,則父類別必須在子類別結構之前宣告。此規則適用於繼承其他類別和介面的類別。

注意事項:

不允許使用唯讀屬性覆寫讀寫屬性,反之亦然。

<?php

class A {
public
int $prop;
}
class
B extends A {
// 不合法:讀寫 -> 唯讀
public readonly int $prop;
}
?>

範例 #1 繼承範例

<?php

class Foo
{
public function
printItem($string)
{
echo
'Foo: ' . $string . PHP_EOL;
}

public function
printPHP()
{
echo
'PHP is great.' . PHP_EOL;
}
}

class
Bar extends Foo
{
public function
printItem($string)
{
echo
'Bar: ' . $string . PHP_EOL;
}
}

$foo = new Foo();
$bar = new Bar();
$foo->printItem('baz'); // 輸出:'Foo: baz'
$foo->printPHP(); // 輸出:'PHP is great'
$bar->printItem('baz'); // 輸出:'Bar: baz'
$bar->printPHP(); // 輸出:'PHP is great'

?>

與內建類別的回傳類型相容性

在 PHP 8.1 之前,大多數內建類別或方法並未宣告其回傳類型,並且在繼承它們時允許任何回傳類型。

從 PHP 8.1.0 開始,大多數內建方法開始「暫時性地」宣告其回傳類型,在這種情況下,方法的回傳類型應該與被繼承的父類別相容;否則,會發出棄用通知。請注意,缺少明確的回傳宣告也被視為簽章不匹配,因此也會導致棄用通知。

如果由於 PHP 跨版本相容性問題而無法為覆寫方法宣告回傳類型,則可以添加 ReturnTypeWillChange 屬性來抑制棄用通知。

範例 #2 覆寫方法未宣告任何回傳類型

<?php
class MyDateTime extends DateTime
{
public function
modify(string $modifier) { return false; }
}

// 從 PHP 8.1.0 開始出現「已棄用:MyDateTime::modify(string $modifier) 的回傳類型應與 DateTime::modify(string $modifier): DateTime|false 相容,或者應使用 #[\ReturnTypeWillChange] 屬性來暫時抑制此通知」
?>

範例 #3 覆寫方法宣告了錯誤的回傳類型

<?php
class MyDateTime extends DateTime
{
public function
modify(string $modifier): ?DateTime { return null; }
}

// 從 PHP 8.1.0 開始出現「Deprecated: Return type of MyDateTime::modify(string $modifier): ?DateTime should either be compatible with DateTime::modify(string $modifier): DateTime|false, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice」的警告
?>

範例 #4:覆寫的方法宣告了錯誤的返回類型,但沒有棄用通知

<?php
class MyDateTime extends DateTime
{
/**
* @return DateTime|false
*/
#[\ReturnTypeWillChange]
public function
modify(string $modifier) { return false; }
}

// 不會觸發警告
?>
新增註解

使用者貢獻的註解 6 則註解

jackdracona at msn dot com
14 年前
以下是一些關於 PHP 繼承的說明 - 網路上有很多錯誤的資訊。PHP 的確支援多層繼承。(我使用 5.2.9 版本測試過)。它不支援多重繼承。

這表示您不能讓一個類別繼承兩個其他的類別(請參閱 extends 關鍵字)。然而,您可以讓一個類別繼承另一個類別,而該類別又繼承另一個類別,依此類推。

範例

<?php
class A {
// 更多程式碼在此
}

class
B extends A {
// 更多程式碼在此
}

class
C extends B {
// 更多程式碼在此
}


$someObj = new A(); // 沒有問題
$someOtherObj = new B(); // 沒有問題
$lastObj = new C(); // 仍然沒有問題

?>
Mohammad Istanbouly
7 年前
我認為對於初學者來說,理解繼承的最佳方式是透過實際的例子,所以我這裡提供一個簡單的例子。

<?php

class Person
{
public
$name;
protected
$age;
private
$phone;

public function
talk(){
//在此處執行操作
}

protected function
walk(){
//在此處執行操作
}

private function
swim(){
//在此處執行操作
}
}

class
Tom extends Person
{
/*因為 Tom 類別繼承自 Person 類別,這表示
Tom 類別是子類別,而 Person 類別是
父類別,子類別將繼承父類別的所有 public
和 protected 成員(屬性和方法)*/

/*所以 Tom 類別將擁有以下屬性和方法*/

//public $name;
//protected $age;
//public function talk(){}
//protected function walk(){}

//但它不會繼承 private 成員
//這就是物件繼承的意義
}
akashwebdev at gmail dot com
9 年前
多重繼承不支援的想法是正確的,但透過 trait 可以重新審視。

例如:

<?php
trait custom
{
public function
hello()
{
echo
"hello";
}
}

trait
custom2
{
public function
hello()
{
echo
"hello2";
}
}

class
inheritsCustom
{
use
custom, custom2
{
custom2::hello insteadof custom;
}
}

$obj = new inheritsCustom();
$obj->hello();
?>
strata_ranger at hotmail dot com
14 年前
我最近在擴展一個 PEAR 類別時,遇到一種情況,我想要呼叫類別階層中上兩層的建構函式,而忽略直接父類別。在這種情況下,您需要使用 :: 運算子明確地參考類別名稱。

幸運的是,就像使用 'parent' 關鍵字一樣,PHP 能夠正確地識別您正在從物件類別階層內部的 protected 環境中呼叫該函式。

例如:

<?php
類別 foo
{
公開 函式
something()
{
回應
__CLASS__; // foo
var_dump($this);
}
}

類別
foo_bar 繼承 foo
{
公開 函式
something()
{
回應
__CLASS__; // foo_bar
var_dump($this);
}
}

類別
foo_bar_baz 繼承 foo_bar
{
公開 函式
something()
{
回應
__CLASS__; // foo_bar_baz
var_dump($this);
}

公開 函式
call()
{
回應
self::something(); // self
回應 parent::something(); // parent
回應 foo::something(); // grandparent
}
}

error_reporting(-1);

$obj = new foo_bar_baz();
$obj->call();

// 輸出類似以下:
// foo_bar_baz
// object(foo_bar_baz)[1]
// foo_bar
// object(foo_bar_baz)[1]
// foo
// object(foo_bar_baz)[1]

?>
jarrod at squarecrow dot com
15 年前
您可以使用「abstract」關鍵字強制類別只能被繼承。當您使用 abstract 定義類別時,任何嘗試直接實例化它的行為都會導致致命錯誤。這在基底類別的情況下非常有用,基底類別會被多個子類別繼承,但您希望限制直接實例化它的能力。

範例........

<?php

抽象類別 Cheese
{
// 只能被其他類別繼承
}

類別
Cheddar 繼承 Cheese
{
}

$dinner = new Cheese; // 致命錯誤
$lunch = new Cheddar; // 可以正常運作!

?>
匿名
5 年前
如果您在子類別中重新宣告一個函式,但使用了不同的參數,PHP7 會發出警告。例如:

類別 foo {
函式 print($text='') {
印出 $text;
}
}

類別 bar 繼承 foo {
函式 print($text1='',$text2='') {
印出 $text1.$text2;
}
}

會產生 PHP 警告:bar::print($text1 = '', $text2 = '') 的宣告應與 foo::print($text= '') 相容。
To Top