PHP 會阻止介面中的常數被直接繼承它的類別/介面覆蓋。然而,進一步的繼承允許這樣做。這意味著介面常數並不像先前評論中提到的那樣是 final。這是一個錯誤還是一個功能?
<?php
interface a
{
const b = '介面常數';
}
// 顯示:介面常數
echo a::b;
class b implements a
{
}
// 這個可以運作!!!
class c extends b
{
const b = '類別常數';
}
echo c::b;
?>
物件介面允許您建立程式碼來指定類別必須實作的方法和屬性,而不必定義這些方法或屬性是如何實作的。介面與類別、特性和列舉共用一個命名空間,因此它們不能使用相同的名稱。
介面的定義方式與類別相同,但使用關鍵字 interface
取代 class
關鍵字,且任何方法都沒有定義其內容。
所有在介面中宣告的方法都必須是公開的;這是介面的本質。
實際應用上,介面有兩個相輔相成的目的:
Iterable
、Cacheable
、Renderable
等等,用以描述其行為的意義。介面可以定義魔術方法,要求實作的類別也必須實作這些方法。
注意:
雖然允許在介面中包含建構子,但強烈建議不要這樣做。這樣做會顯著降低實作介面的物件的彈性。此外,建構子不受繼承規則的約束,這可能會導致不一致且難以預料的行為。
implements(實作)
要實作介面,可以使用 implements
運算子。介面中的所有方法都必須在類別中實作;若未實作,將會導致致命錯誤。如果需要,類別可以透過逗號分隔來實作多個介面。
實作介面的類別可以使用與介面不同的參數名稱。然而,從 PHP 8.0 開始,該語言支援具名引數,這意味著呼叫者可能會依賴介面中的參數名稱。因此,強烈建議開發者使用與所實作介面相同的參數名稱。
注意:
介面可以像類別一樣使用
extends
運算子來繼承。
注意:
實作介面的類別必須宣告介面中所有方法,並具有相容的簽章。一個類別可以實作多個宣告相同名稱方法的介面。在這種情況下,實作必須遵循所有介面的簽章相容性規則。因此可以應用共變數與反變數。
介面可以擁有常數。介面常數的運作方式與類別常數完全相同。在 PHP 8.1.0 之前,繼承它們的類別/介面無法覆寫它們。
從 PHP 8.4.0 開始,介面也可以宣告屬性。如果要宣告屬性,則必須指定該屬性是可讀的、可寫的,或是兩者皆可。介面宣告僅適用於公開讀取和寫入的權限。
一個類別可以透過多種方式滿足介面屬性。它可以定義一個公開屬性。它可以定義一個公開的虛擬屬性,只實作對應的鉤子(hook)。或者,唯讀屬性可以滿足唯讀屬性。然而,可設定的介面屬性不能是 readonly
。
範例 #1 介面屬性範例
<?php
interface I
{
// An implementing class MUST have a publicly-readable property,
// but whether or not it's publicly settable is unrestricted.
public string $readable { get; }
// An implementing class MUST have a publicly-writeable property,
// but whether or not it's publicly readable is unrestricted.
public string $writeable { set; }
// An implementing class MUST have a property that is both publicly
// readable and publicly writeable.
public string $both { get; set; }
}
// This class implements all three properties as traditional, un-hooked
// properties. That's entirely valid.
class C1 implements I
{
public string $readable;
public string $writeable;
public string $both;
}
// This class implements all three properties using just the hooks
// that are requested. This is also entirely valid.
class C2 implements I
{
private string $written = '';
private string $all = '';
// Uses only a get hook to create a virtual property.
// This satisfies the "public get" requirement.
// It is not writeable, but that is not required by the interface.
public string $readable { get => strtoupper($this->writeable); }
// The interface only requires the property be settable,
// but also including get operations is entirely valid.
// This example creates a virtual property, which is fine.
public string $writeable {
get => $this->written;
set {
$this->written = $value;
}
}
// This property requires both read and write be possible,
// so we need to either implement both, or allow it to have
// the default behavior.
public string $both {
get => $this->all;
set {
$this->all = strtoupper($value);
}
}
}
?>
範例 #2 介面範例
<?php
// 宣告介面 'Template'
interface Template
{
public function setVariable($name, $var);
public function getHtml($template);
}
// 實作介面
// 這個會正常運作
class WorkingTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
public function getHtml($template)
{
foreach($this->vars as $name => $value) {
$template = str_replace('{' . $name . '}', $value, $template);
}
return $template;
}
}
// 這個會失敗
// 致命錯誤:類別 BadTemplate 包含 1 個抽象方法
// 因此必須宣告為抽象類別 (Template::getHtml)
class BadTemplate implements Template
{
private $vars = [];
public function setVariable($name, $var)
{
$this->vars[$name] = $var;
}
}
?>
範例 #3 可擴展的介面
<?php
介面 A
{
public function foo();
}
介面 B 延伸 A
{
public function baz(Baz $baz);
}
// 這個會正常運作
類別 C 實作 B
{
public function foo()
{
}
public function baz(Baz $baz)
{
}
}
// 這個不會正常運作,會導致致命錯誤
類別 D 實作 B
{
public function foo()
{
}
public function baz(Foo $foo)
{
}
}
?>
範例 #4 變異數與多個介面的相容性
<?php
類別 Foo {}
類別 Bar 延伸 Foo {}
介面 A {
public function myfunc(Foo $arg): Foo;
}
介面 B {
public function myfunc(Bar $arg): Bar;
}
類別 MyClass 實作 A, B
{
public function myfunc(Foo $arg): Bar
{
return new Bar();
}
}
?>
範例 #5 多重介面繼承
<?php
介面 A
{
public function foo();
}
介面 B
{
public function bar();
}
介面 C 延伸 A, B
{
public function baz();
}
類別 D 實作 C
{
public function foo()
{
}
public function bar()
{
}
public function baz()
{
}
}
?>
範例 #6 具有常數的介面
<?php
interface A
{
const B = 'Interface constant';
}
// 顯示:Interface constant
echo A::B;
class B implements A
{
const B = 'Class constant';
}
// 顯示:Class constant
// 在 PHP 8.1.0 之前,這是不允許的,因為不允許覆寫常數。
echo B::B;
?>
範例 #7 具有抽象類別的介面
<?php
interface A
{
public function foo(string $s): string;
public function bar(int $i): int;
}
// 抽象類別可以只實作介面的一部分。
// 繼承抽象類別的類別必須實作其餘部分。
abstract class B implements A
{
public function foo(string $s): string
{
return $s . PHP_EOL;
}
}
class C extends B
{
public function bar(int $i): int
{
return $i * 2;
}
}
?>
範例 #8 同時繼承和實作
<?php
class One
{
/* ... */
}
interface Usable
{
/* ... */
}
interface Updatable
{
/* ... */
}
// 這裡的關鍵字順序很重要。「extends」必須放在前面。
class Two extends One implements Usable, Updatable
{
/* ... */
}
?>
介面與類型聲明一起,提供了一個很好的方法來確保特定物件包含特定的方法。請參閱 instanceof 運算子和 類型聲明。
PHP 會阻止介面中的常數被直接繼承它的類別/介面覆蓋。然而,進一步的繼承允許這樣做。這意味著介面常數並不像先前評論中提到的那樣是 final。這是一個錯誤還是一個功能?
<?php
interface a
{
const b = '介面常數';
}
// 顯示:介面常數
echo a::b;
class b implements a
{
}
// 這個可以運作!!!
class c extends b
{
const b = '類別常數';
}
echo c::b;
?>
在 Erich Gamma 和他的夥伴(又稱「四人幫」)關於設計模式的書中,他們交替使用「介面」和「抽象類別」這兩個術語。在使用 PHP 和設計模式時,介面雖然清楚地是一份關於實作中應包含內容的「合約」,但它也是一個有助於重複使用和進行更改的指南。只要實作的更改遵循介面(無論它是介面還是具有抽象方法的抽象類別),就可以安全地更新大型複雜程式,而無需重新編寫整個程式或模組。
在使用物件介面(作為關鍵字)的 PHP 編碼中,以及在更廣泛的上下文中包含物件介面和抽象類別的「介面」中,為了易於更改和重複使用而「鬆散綁定」(鬆散綁定的物件)的目的,是思考「介面」這兩個用法的有用方式。重點從「合約」轉移到「鬆散綁定」,以促進合作開發和重複使用。
這個頁面提到,如果繼承多個具有相同方法的介面,則簽章必須相容。但这並非全部:`extends` 的順序很重要。這是一個已知的問題,雖然它是否是一個錯誤是有爭議的,但開發者應該意識到它,並在編寫介面程式碼時考慮到這一點。
https://bugs.php.net/bug.php?id=67270
https://bugs.php.net/bug.php?id=76361
https://bugs.php.net/bug.php?id=80785