2024年PHP开发者大会(日本)

对象接口

对象接口允许您创建代码,该代码指定类必须实现哪些方法和属性,而无需定义这些方法或属性的实现方式。接口与类、特性和枚举共享命名空间,因此它们不能使用相同的名称。

接口的定义方式与类相同,但是使用interface关键字替换class关键字,并且没有任何方法定义其内容。

接口中声明的所有方法都必须是公共的;这是接口的特性。

实际上,接口服务于两个互补的目的

  • 允许开发人员创建不同类的对象,这些对象可以互换使用,因为它们实现了相同的接口或多个接口。一个常见的例子是多个数据库访问服务、多个支付网关或不同的缓存策略。不同的实现可以在不需要更改使用它们的代码的情况下进行交换。
  • 允许函数或方法接受并操作符合接口的参数,而不关心对象可能执行的其他操作或其实现方式。这些接口通常命名为IterableCacheableRenderable等等,以描述行为的意义。

接口可以定义魔术方法来要求实现类实现这些方法。

注意:

尽管支持在接口中包含构造函数,但强烈建议不要这样做。这样做会大大降低实现接口的对象的灵活性。此外,构造函数不受继承规则的约束,这可能会导致不一致和意外的行为。

implements

要实现接口,使用implements运算符。接口中的所有方法都必须在类中实现;否则将导致致命错误。如果需要,类可以通过逗号分隔来实现多个接口。

警告

实现接口的类可以使用与其参数不同的名称。但是,从PHP 8.0开始,该语言支持命名参数,这意味着调用者可以依赖接口中的参数名称。因此,强烈建议开发人员使用与正在实现的接口相同的参数名称。

注意:

可以使用extends运算符像类一样扩展接口。

注意:

实现接口的类必须使用兼容的签名声明接口中的所有方法。一个类可以实现多个声明同名方法的接口。在这种情况下,实现必须遵循所有接口的签名兼容性规则协变和逆变可以应用。

常量

接口可以有常量。接口常量的作用与类常量完全相同。在PHP 8.1.0之前,它们不能被继承它们的类/接口覆盖。

属性

从PHP 8.4.0开始,接口也可以声明属性。如果这样做了,声明必须指定属性是可读、可写还是两者兼有。接口声明仅适用于公共读写访问。

类可以通过多种方式满足接口属性。它可以定义一个公共属性。它可以定义一个公共的虚拟属性,该属性仅实现相应的钩子。或者,只读属性可以由readonly属性满足。但是,可设置的接口属性不能是readonly

示例 #1 接口属性示例

<?php
interface I
{
// 实现类必须具有一个公共可读属性,
// 但它是否可公共设置不受限制。
public string $readable { get; }

// 实现类必须具有一个公共可写属性,
// 但它是否可公共读取不受限制。
public string $writeable { set; }

// 实现类必须具有一个既可公共读取也可公共写入的属性。
public string $both { get; set; }
}

// 此类将所有三个属性实现为传统的、未挂钩的
// 属性。这完全有效。
class C1 implements I
{
public
string $readable;

public
string $writeable;

public
string $both;
}

// 此类仅使用请求的钩子实现所有三个属性
// 。这也完全有效。
class C2 implements I
{
private
string $written = '';
private
string $all = '';

// 仅使用 get 钩子创建虚拟属性。
// 这满足“public get”要求。
// 它不可写,但这并非接口要求。
public string $readable { get => strtoupper($this->writeable); }

// 接口只要求属性可设置,
// 但包含 get 操作也完全有效。
// 此示例创建了一个虚拟属性,这很好。
public string $writeable {
get => $this->written;
set {
$this->written = $value;
}
}

// 此属性要求可读写,
// 因此我们需要同时实现两者,或者允许它具有
// 默认行为。
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;
}
}

// 这将无效
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
class BadTemplate implements Template
{
private
$vars = [];

public function
setVariable($name, $var)
{
$this->vars[$name] = $var;
}
}
?>

示例 #3 可扩展接口

<?php
interface A
{
public function
foo();
}

interface
B extends A
{
public function
baz(Baz $baz);
}

// 这将有效
class C implements B
{
public function
foo()
{
}

public function
baz(Baz $baz)
{
}
}

// 这将无效并导致致命错误
class D implements B
{
public function
foo()
{
}

public function
baz(Foo $foo)
{
}
}
?>

示例 #4 多接口的方差兼容性

<?php
class Foo {}
class
Bar extends Foo {}

interface
A {
public function
myfunc(Foo $arg): Foo;
}

interface
B {
public function
myfunc(Bar $arg): Bar;
}

class
MyClass implements A, B
{
public function
myfunc(Foo $arg): Bar
{
return new
Bar();
}
}
?>

示例 #5 多接口继承

<?php
interface A
{
public function
foo();
}

interface
B
{
public function
bar();
}

interface
C extends A, B
{
public function
baz();
}

class
D implements 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 运算符和 类型声明

添加注释

用户贡献注释 4 条注释

thanhn2001 at gmail dot com
13 年前
PHP 阻止接口常量被直接继承它的类/接口覆盖。但是,进一步的继承允许这样做。这意味着接口常量并非如先前评论中提到的那样是最终的。这是一个错误还是一个特性?

<?php

interface a
{
const
b = 'Interface constant';
}

// 输出:Interface constant
echo a::b;

class
b implements a
{
}

// 这有效!
class c extends b
{
const
b = 'Class constant';
}

echo
c::b;
?>
vcnbianchi
2年前
所有接口方法都是公共的,同时也是抽象的。
williebegoode at att dot net
10年前
在他们关于设计模式的书中,Erich Gamma 及其同事(又称“四人帮”)可以互换使用“接口”和“抽象类”这两个术语。在使用 PHP 和设计模式时,接口虽然明确规定了实现中包含的内容,但也为代码重用和修改提供了有益的指导。只要实现的更改遵循接口(无论是接口还是带有抽象方法的抽象类),就可以安全地更新大型复杂程序,而无需重新编写整个程序或模块。

在使用 PHP 进行面向对象编程时,既有作为关键字的“接口”(object interfaces),也有更广泛意义上的“接口”(interfaces),后者包括对象接口和抽象类。从“松散绑定”(loosely bound objects)的角度来看待“接口”的两种用法,有助于理解其易于修改和重用的目的。重点从“契约式”转向“松散绑定”,以便于协同开发和代码重用。
xedin dot unknown at gmail dot com
3年前
此页面指出,如果扩展多个具有相同方法的接口,则签名必须兼容。但这并非全部:`extends` 的顺序很重要。这是一个已知问题,虽然它是否为 bug 存在争议,但应注意这一点,并以此为原则编写接口。

https://bugs.php.net/bug.php?id=67270
https://bugs.php.net/bug.php?id=76361
https://bugs.php.net/bug.php?id=80785
To Top