ZetCode

PHP 接口

上次修改时间:2025 年 5 月 18 日

PHP 中的接口定义了类必须遵循的契约。它们指定了类必须实现哪些方法,而没有定义这些方法应该如何工作。接口实现多态性,允许不同的类可以互换使用,如果它们实现了相同的接口。

基本接口

一个简单的接口定义了实现类必须提供的方法签名。接口本身不包含方法实现。

basic_interface.php
<?php

declare(strict_types=1);

interface Logger {
    public function log(string $message): void;
}

class FileLogger implements Logger {
    public function log(string $message): void {
        echo "Logging to file: $message\n";
    }
}

class DatabaseLogger implements Logger {
    public function log(string $message): void {
        echo "Logging to database: $message\n";
    }
}

$fileLogger = new FileLogger();
$fileLogger->log("Error occurred!");

$dbLogger = new DatabaseLogger();
$dbLogger->log("Error occurred!");

此示例显示了一个基本的 Logger 接口,该接口具有 FileLogger 和 DatabaseLogger 都实现的一个方法。虽然它们执行不同的操作,但这两个类都遵守相同的接口契约。

λ php basic_interface.php
Logging to file: Error occurred!
Logging to database: Error occurred!

多接口

一个类可以实现多个接口,从而允许它履行多个契约。这提供了比继承更大的灵活性,因为 PHP 不支持类的多重继承。

multiple_interfaces.php
<?php

declare(strict_types=1);

interface Loggable {
    public function log(string $message): void;
}

interface Cacheable {
    public function cache(string $key, string $value): void;
    public function getFromCache(string $key): ?string;
}

class Application implements Loggable, Cacheable {
    private array $cache = [];
    
    public function log(string $message): void {
        echo "Application log: $message\n";
    }
    
    public function cache(string $key, string $value): void {
        $this->cache[$key] = $value;
    }
    
    public function getFromCache(string $key): ?string {
        return $this->cache[$key] ?? null;
    }
}

$app = new Application();
$app->log("Starting up");
$app->cache("config", "debug=true");
echo $app->getFromCache("config") . "\n";

Application 类实现了 Loggable 和 Cacheable 接口,为所有必需的方法提供了实现。这允许该类用于期望任何一个接口的上下文中。

λ php multiple_interfaces.php
Application log: Starting up
debug=true

接口继承

接口可以扩展其他接口,继承它们的方法签名。这允许创建契约层次结构。

interface_inheritance.php
<?php

declare(strict_types=1);

interface Readable {
    public function read(): string;
}

interface Writable {
    public function write(string $data): void;
}

interface ReadWritable extends Readable, Writable {
    public function clear(): void;
}

class FileStorage implements ReadWritable {
    public function read(): string {
        return "Reading data from file";
    }
    
    public function write(string $data): void {
        echo "Writing data to file: $data\n";
    }
    
    public function clear(): void {
        echo "Clearing file storage\n";
    }
}

$storage = new FileStorage();
echo $storage->read() . "\n";
$storage->write("Test data");
$storage->clear();

ReadWritable 接口扩展了 Readable 和 Writable 接口,并添加了一个额外的方法。FileStorage 类必须实现所有三个接口中的所有方法。

λ php interface_inheritance.php
Reading data from file
Writing data to file: Test data
Clearing file storage

使用接口实现多态性

接口实现多态性——如果不同的类实现了相同的接口,就可以互换使用它们。这对于创建灵活的系统非常强大。

polymorphism.php
<?php

declare(strict_types=1);

interface PaymentGateway {
    public function processPayment(float $amount): bool;
}

class StripeGateway implements PaymentGateway {
    public function processPayment(float $amount): bool {
        echo "Processing Stripe payment for \$$amount\n";
        return true;
    }
}

class PayPalGateway implements PaymentGateway {
    public function processPayment(float $amount): bool {
        echo "Processing PayPal payment for \$$amount\n";
        return true;
    }
}

class OrderProcessor {
    public function __construct(private PaymentGateway $gateway) {}
    
    public function processOrder(float $amount): bool {
        return $this->gateway->processPayment($amount);
    }
}

// Can use either payment gateway interchangeably
$stripeProcessor = new OrderProcessor(new StripeGateway());
$stripeProcessor->processOrder(100.50);

$paypalProcessor = new OrderProcessor(new PayPalGateway());
$paypalProcessor->processOrder(75.25);

OrderProcessor 不需要知道它正在使用哪个特定的支付网关——它只关心它是否实现了 PaymentGateway 接口。这使得代码更灵活,更易于维护。

λ php polymorphism.php
Processing Stripe payment for $100.5
Processing PayPal payment for $75.25

接口与抽象类

虽然相似,但接口和抽象类的用途不同。接口定义了没有实现的契约,而抽象类可以提供部分实现。

comparison.php
<?php

declare(strict_types=1);

// Interface - pure contract
interface Logger {
    public function log(string $message): void;
}

// Abstract class - can provide some implementation
abstract class AbstractLogger {
    protected string $logPrefix = '';
    
    abstract public function log(string $message): void;
    
    public function setPrefix(string $prefix): void {
        $this->logPrefix = $prefix;
    }
}

class FileLogger extends AbstractLogger implements Logger {
    public function log(string $message): void {
        echo "[{$this->logPrefix}] Log to file: $message\n";
    }
}

$logger = new FileLogger();
$logger->setPrefix("FILE");
$logger->log("Test message");

此示例显示了一个类,该类既扩展了一个抽象类,又实现了一个接口。抽象类提供了一些共享功能 (setPrefix),而接口保证 log 方法存在。

λ php comparison.php
[FILE] Log to file: Test message

接口与 Traits

虽然接口定义了类必须遵循的契约,但 traits 是 PHP 中代码重用的机制。Traits 允许你在多个类中包含方法,帮助避免代码重复。与接口不同,Traits 可以提供实际的方法实现,但它们不能定义契约或独立实例化。

interfaces_vs_traits.php
<?php

declare(strict_types=1);

interface Logger {
    public function log(string $message): void;
}

trait FileLogTrait {
    public function logToFile(string $message): void {
        echo "File log: $message\n";
    }
}

class MyService implements Logger {
    use FileLogTrait;
    public function log(string $message): void {
        // Use trait method for actual logging
        $this->logToFile($message);
    }
}

$service = new MyService();
$service->log("Hello from trait!");

在此示例中,Logger 接口定义了一个契约,而 FileLogTrait trait 提供了一个可重用的方法。MyService 类实现了该接口并使用 trait 来履行该契约。

Traits 和接口可以结合使用,以在 PHP 应用程序中实现最大的灵活性和代码重用。

使用接口进行类型提示

接口对于类型提示参数、返回类型和属性特别有用。这确保代码适用于接口的任何实现。

type_hinting.php
<?php

declare(strict_types=1);

interface Notifier {
    public function sendNotification(string $message): void;
}

class EmailNotifier implements Notifier {
    public function sendNotification(string $message): void {
        echo "Sending email: $message\n";
    }
}

class SMSNotifier implements Notifier {
    public function sendNotification(string $message): void {
        echo "Sending SMS: $message\n";
    }
}

class NotificationService {
    public function __construct(private Notifier $notifier) {}
    
    public function notify(string $message): void {
        $this->notifier->sendNotification($message);
    }
}

$emailService = new NotificationService(new EmailNotifier());
$emailService->notify("Hello via email");

$smsService = new NotificationService(new SMSNotifier());
$smsService->notify("Hello via SMS");

NotificationService 不关心它收到哪个特定的通知程序——它只需要它实现 Notifier 接口。这使得该服务更加灵活且可测试。

λ php type_hinting.php
Sending email: Hello via email
Sending SMS: Hello via SMS

最佳实践

遵循使用接口时的最佳实践可以产生更简洁、更易于维护的代码。以下是一些关键指南。

best_practices.php
<?php

declare(strict_types=1);

// 1. Keep interfaces small and focused (Single Responsibility Principle)
interface UserAuthenticator {
    public function authenticate(string $username, string $password): bool;
}

// 2. Name interfaces clearly (often with adjective or -able suffix)
interface Cacheable {
    public function cache(string $key, mixed $value): void;
    public function getCached(string $key): mixed;
}

// 3. Use interfaces for dependency injection
class OrderService {
    public function __construct(private Logger $logger) {}
    
    public function processOrder(Order $order): void {
        $this->logger->log("Processing order #{$order->getId()}");
        // Process order...
    }
}

// 4. Document interface methods with PHPDoc
interface DataTransformer {
    /**
     * Transforms data from one format to another
     * @param mixed $input The input data to transform
     * @return mixed The transformed data
     * @throws InvalidArgumentException If input is invalid
     */
    public function transform($input);
}

// 5. Prefer interface type hints over concrete classes
function sendAlert(Notifier $notifier, string $message): void {
    $notifier->sendNotification($message);
}

// 6. Consider using interfaces for testing/mocking
interface DatabaseConnection {
    public function query(string $sql): array;
}

// In tests:
class MockDatabaseConnection implements DatabaseConnection {
    public function query(string $sql): array {
        return ['test' => 'data']; // Return test data without real DB
    }
}

这些实践有助于创建易于理解、实现和维护的接口。小型、命名良好的接口以及良好的文档使代码更灵活且更具可测试性。

在本教程中,我们探讨了 PHP 中接口的概念。我们讨论了如何定义和实现接口,它们对代码组织的好处,以及它们如何实现多态性。我们还将接口与抽象类进行了比较,重点介绍了它们之间的区别以及何时使用它们。最后,我们介绍了使用接口的最佳实践,以确保代码的整洁和可维护性。

作者

我的名字是 Jan Bodnar,我是一位充满激情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出 所有 PHP 教程