对象接口

使用接口(interface),可以指定某个类必须实现哪些方法,但不需要定义这些方法的具体内容。 由于接口(interface)和类(class)、trait 共享了命名空间,所以它们不能重名。

接口就像定义一个标准的类一样,通过 interface 关键字替换掉 class 关键字来定义,但其中所有的方法都是空的。

接口中定义的所有方法都必须是公有,这是接口的特性。

在实践中,往往出于两个辅助目的使用接口:

  • 因为实现了同一个接口,所以开发者创建的对象虽然源自不同的类,但可能可以交换使用。 常用于多个数据库的服务访问、多个支付网关、不同的缓存策略等。 可能不需要任何代码修改,就能切换不同的实现方式。
  • 能够让函数与方法接受一个符合接口的参数,而不需要关心对象如何做、如何实现。 这些接口常常命名成类似 IterableCacheableRenderable, 以便于体现出功能的含义。

接口可以定义魔术方法,以便要求类(class)实现这些方法。

Note:

虽然没有禁止,但是强烈建议不要在接口中使用 构造器。 因为这样在对象实现接口时,会大幅降低灵活性。 此外,也不能强制确保构造器遵守继承规则,将导致不可预料的行为结果。

实现(implements

要实现一个接口,使用 implements 操作符。类中必须实现接口中定义的所有方法,否则会报一个致命错误。 类可以实现多个接口,用逗号来分隔多个接口的名称。

Warning

类实现(implement)两个接口时,如果它们定义了相同名称的方法,只有签名相同的时候才是允许的。

Warning

实现接口的时候,class 中的参数名称不必和接口完全一致。 然而, PHP 8.0 起语法开始支持命名参数, 也就是说调用方会依赖接口中参数的名称。 因此,强烈建议开发者的参数的命名,在类和接口中保持一致。

Note:

接口也可以通过 extends 操作符扩展。

Note:

类实现接口时,必须以兼容的签名定义接口中所有方法。

常量

接口中也可以定义常量。接口常量和类常量的使用完全相同,但是不能被子类或子接口所覆盖。

范例

Example #1 接口示例

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

// 下面的写法是错误的,会报错,因为没有实现 getHtml():
// 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;
    }
}
?>

Example #2 可扩充的接口

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

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

// 正确写法
class implements B
{
    public function 
foo()
    {
    }

    public function 
baz(Baz $baz)
    {
    }
}

// 错误写法会导致一个致命错误
class implements B
{
    public function 
foo()
    {
    }

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

Example #3 扩展多个接口

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

interface 
B
{
    public function 
bar();
}

interface 
extends AB
{
    public function 
baz();
}

class 
implements C
{
    public function 
foo()
    {
    }

    public function 
bar()
    {
    }

    public function 
baz()
    {
    }
}
?>

Example #4 使用接口常量

<?php
interface A
{
    const 
'Interface constant';
}

// 输出接口常量
echo A::B;

// 错误写法,因为常量不能被覆盖。接口常量的概念和类常量是一样的。
class implements A
{
    const 
'Class constant';
}
?>

Example #5 抽象(abstract)类的接口使用

<?php
interface A
{
    public function 
foo(string $s): string;

    public function 
bar(int $i): int;
}

// 抽象类可能仅实现了接口的一部分。
// 扩展该抽象类时必须实现剩余部分。
abstract class implements A
{
    
pubic function foo(string $s): string
    
{
        return 
$s PHP_EOL;
    }
}

class 
extends B
{
    public function 
bar(int $i): int
    
{
        return 
$i 2;
    }
}
?>

Example #6 同时使用扩展和实现

<?php

class One
{
    
/* ... */
}

interface 
Usable
{
    
/* ... */
}

interface 
Updatable
{
    
/* ... */
}

// 关键词顺序至关重要: 'extends' 必须在前面
class Two extends One implements UsableUpdatable
{
    
/* ... */
}
?>

接口加上类型约束,提供了一种很好的方式来确保某个对象包含有某些方法。参见 instanceof 操作符和类型声明

User Contributed Notes

xedin dot unknown at gmail dot com 09-Mar-2021 09:44
This page says that if extending multiple interfaces with the same methods, the signature must be compatible. But this is not all there is to it: the order of `extends` matters. This is a known issue, and while it is disputable whether or not it is a bug, one should be aware of it, and code interfaces with this in mind.

https://bugs.php.net/bug.php?id=67270
https://bugs.php.net/bug.php?id=76361
https://bugs.php.net/bug.php?id=80785
williebegoode at att dot net 10-Aug-2014 10:42
In their book on Design Patterns, Erich Gamma and his associates (AKA: "The Gang of Four") use the term "interface" and "abstract class" interchangeably. In working with PHP and design patterns, the interface, while clearly a "contract" of what to include in an implementation is also a helpful guide for both re-use and making changes. As long as the implemented changes follow the interface (whether it is an interface or abstract class with abstract methods), large complex programs can be safely updated without having to re-code an entire program or module.

In PHP coding with object interfaces (as a keyword) and "interfaces" in the more general context of use that includes both object interfaces and abstract classes, the purpose of "loose binding" (loosely bound objects) for ease of change and re-use is a helpful way to think about both uses of the  term "interface." The focus shifts from "contractual" to "loose binding" for the purpose of cooperative development and re-use.
thanhn2001 at gmail dot com 03-Mar-2011 01:37
PHP prevents interface a contant to be overridden by a class/interface that DIRECTLY inherits it.  However, further inheritance allows it.  That means that interface constants are not final as mentioned in a previous comment.  Is this a bug or a feature?

<?php

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

// Prints: Interface constant
echo a::b;

class
b implements a
{
}

// This works!!!
class c extends b
{
    const
b = 'Class constant';
}

echo
c::b;
?>