Interfaces and Polymorphism

An interface lists public methods without bodies. It cannot be instantiated; a class that implements it must supply every method with a compatible signature. Code typed against the interface then works with any class that keeps the promise, which is polymorphism: one call, $rule->apply(), runs different code depending on the object behind it.

Pricing rules behind one interfacePHP
<?php
declare(strict_types=1);
interface PricingRule {
  public function apply(int $cents): int;
  public function label(): string;
}
final class PercentOff implements PricingRule {
  public function __construct(private int $pct) {}
  public function apply(int $cents): int { return intdiv($cents * (100 - $this->pct), 100); }
  public function label(): string { return "{$this->pct}% off"; }
}
function checkout(int $cents, PricingRule ...$rules): int {
  foreach ($rules as $r) echo str_pad($r->label(), 9), $cents = $r->apply($cents), "\n";
  return $cents;
}
$staff = new class implements PricingRule {               // an anonymous implementation
  public function apply(int $cents): int { return $cents - 100; }
  public function label(): string { return 'staff'; }
};
checkout(3750, new PercentOff(10), $staff, new PercentOff(5));
Output
10% off  3375
staff    3275
5% off   3111

checkout() never asks which class it received. If a class leaves a method out, PHP stops with a fatal error when it reaches the declaration: Class PercentOff contains 1 abstract method and must therefore be declared abstract or implement the remaining method (PricingRule::apply). Unlike an abstract class (Abstract and final), an interface holds no code or state, and a class may implement any number of them; since PHP 8.4 it may also require properties (Hooks on Interfaces). Keep interfaces small and type parameters with them, so a test can pass a fake.