php类实现接口必须用implements关键字,不能用extends;类可继承一个父类再实现多个接口,方法签名须严格匹配,接口无属性但可定义public Static常量。

PHP类实现接口必须用implements关键字
PHP里接口不是继承关系,不能用extends,必须用implements。这是最常写错的地方——尤其从Java或C#转过来的人,一顺手就写成class A extends B implements C,结果发现B是类、C是接口,语法直接报错:Cannot use 'C' as class name as it is reserved(如果接口名被误当类名解析)或者更常见的Parse Error: syntax error, unexpected 'implements'。
正确写法只有一种:类可以extends一个父类,但所有接口都得跟在implements后面,多个接口用逗号分隔。
-
class User implements JsonSerializable, Loggable✅ -
class Admin extends User implements PermissionCheckable✅(先继承,再实现) -
class Guest extends User, Loggable❌(,不能替代implements) -
class ApiClient implements ArrayAccess extends HttpClient❌(extends不能放在implements之后)
接口方法必须全部实现,且签名严格匹配
PHP不会帮你补方法,也不会宽容参数类型或返回值的微小差异。只要接口里声明了public function save(String $data): bool,你实现时漏掉string类型声明、改成function save($data): int,或者把bool换成void,运行时就会报致命错误:Declaration of MyClass::save() must be compatible with MyInterface::save()。
注意几个易忽略点:
立即学习“PHP免费学习笔记(深入)”;
- 接口中方法默认是
public,实现类里不能降级为protected或private - PHP 8+ 开启严格模式后,
return类型不匹配会直接报Fatal error,不是警告 - 可选参数在接口中不允许出现——接口方法签名必须是“最小契约”,所有参数都算必填;实现类可以加默认值,但不能删参数
- 如果接口方法带
&$ref引用参数,实现类也必须带&,否则签名不一致
一个类可以实现多个接口,但不能重复实现同一接口
PHP允许class OrderProcessor implements Payable, Shippable, Notifiable,这很常见。但如果你通过trait引入了某个接口的实现,又在类声明里再次implements同一个接口,PHP会报错:Class OrderProcessor cannot implement previously implemented interface Payable。
典型踩坑场景:
- 父类已经
implements Cacheable,子类又写class RedisCache extends BaseCache implements Cacheable→ 报错 - 用了laravel的
InteractsWithQueuetrait,它本身不实现接口,但你误以为它替你实现了ShouldQueue,结果忘了在类上显式implements ShouldQueue→ 队列不生效,无报错但逻辑失效 - 两个trait各自提供同名方法,且都声称“满足某接口”,但没真正实现接口契约 → 运行时报方法未实现错误,而不是trait冲突
接口不能有属性,但可以用const定义常量
接口里不能声明public $host = 'localhost'这类属性,但可以定义const。这些常量会被自动视为public、static,且不可覆盖。常量名在实现类中可通过self::STATUS_PENDING或MyInterface::STATUS_PENDING访问。
注意兼容性细节:
- PHP 7.1+ 才支持接口中使用
const,老版本会直接解析失败 - 接口常量不能是数组(PHP 8.1+才支持
array常量,但接口里仍不支持) - 别在接口里定义
const VERSION = '1.0'然后指望子类能重写——它就是死的,改不了 - 如果多个接口定义了同名常量(比如都叫
MAX_RETRY),而一个类同时实现它们,不会冲突,但调用时必须用完整接口名限定,否则报ambiguous constant
接口本身不包含逻辑,也不参与实例化,它的作用就是划清“能做什么”的边界。很多人卡在“为什么写了implements却还报错”,问题往往不在语法,而在方法签名细节、继承链干扰或常量引用方式——这些地方没有运行时提示,只有报错那一刻才暴露。