createAndCopyFrom(OperatingSystem $os): OperatingSystem; } class Linux extends OperatingSystem { public function createAndCopyFrom(OperatingSystem $os): OperatingSystem { // ... } } class MacOS extends OperatingSystem { public function createAndCopyFrom(OperatingSystem $os): OperatingSystem { // ... } }
{ public function __construct(private CurlHttpClient $client) { } public function download(string $url) { return $this->client->request(/* ... */); } }
type T. Then f(y) should be true for objects y of type S where S is a subtype of T. Barbara Liskov A behavioral notion of subtyping - 1994 Sales Deck Name Wikimedia commons
User { public function getReferral(): UserInterface {} } class Admin extends User { public function getReferral(): AdminInterface {} } $user = new User(); $admin = new Admin(); assert($user->getReferral() instanceof UserInterface); assert($admin->getReferral() instanceof UserInterface); // Boom, Liskov again!
User { public function getReferral(): UserInterface {} } class Admin extends User { public function getReferral(): AdminInterface|string {} } $user = new User(); $admin = new Admin(); assert($user->getReferral() instanceof UserInterface); assert($admin->getReferral() instanceof UserInterface); // PHP error
User { public function getReferral(): AdminInterface {} } class Admin extends User { public function getReferral(): UserInterface {} } $user = new User(); $admin = new Admin(); assert($user->getReferral() instanceof UserInterface); assert($admin->getReferral() instanceof UserInterface); // PHP error
public function setIdentifier(int $i) { /* ... */ } } class Admin extends User { public function setIdentifier(int|string $i) { /* ... */ } } act(new User()); act(new Admin());
implements Traversable {} // Back to our code function act(User $user) { $user->doSomething(new \IteratorIterator(/* ... */)); } class User { public function attachReferrals(\Traversable $refs) { /* ... */ } } class Admin extends User { public function attachReferrals(\ArrayObject $refs) { /* ... */ } } act(new User()); act(new Admin()); // Boom, PHP error!
* @template T */ class TypedArray { /** * @param array<T> $elements */ public function __construct(private array $elements) {} /** * @return T */ public function at(int $index): mixed { return $this->elements[$index]; } } /** @var TypedArray<UserInterface> $array */ $array = new TypedArray([new User(), new User()]); $user = $array->at(0); // IDEs and static analyzers should know that $user is of type UserInterface
* @template T */ class TypedArray { /** * @param array<T> $elements */ public function __construct(private array $elements) {} /** * @return T */ public function at(int $index): mixed { return $this->elements[$index]; } } /** @var TypedArray<UserInterface> $array */ $array = new TypedArray([new User(), new User()]); $user = $array->at(0); // IDEs and static analyzers should know that $user is of type UserInterface
* @template T */ class TypedArray { /** * @param array<T> $elements */ public function __construct(private array $elements) {} /** * @return T */ public function at(int $index): mixed { return $this->elements[$index]; } } /** @var TypedArray<UserInterface> $array */ $array = new TypedArray([new User(), new User()]); $user = $array->at(0); // IDEs and static analyzers should know that $user is of type UserInterface
of \stdClass */ class TypedArrayOfDynamicObjects { // ... /** * @param T value */ public function push(mixed $value): void { // ... } } /** @var TypedArrayOfDynamicObjects<MyDynamicClass> $array */ $array = new TypedArrayOfDynamicObjects([ new MyDynamicClass(), new MyDynamicClass(), ]); $array->push(new \stdClass()); // IDEs and static analyzers should warn here
of \stdClass */ class TypedArrayOfDynamicObjects { // ... /** * @param T value */ public function push(mixed $value): void { // ... } } /** @var TypedArrayOfDynamicObjects<MyDynamicClass> $array */ $array = new TypedArrayOfDynamicObjects([ new MyDynamicClass(), new MyDynamicClass(), ]); $array->push(new \stdClass()); // IDEs and static analyzers should warn here
of \stdClass */ class TypedArrayOfDynamicObjects { // ... /** * @param T value */ public function push(mixed $value): void { // ... } } /** @var TypedArrayOfDynamicObjects<MyDynamicClass> $array */ $array = new TypedArrayOfDynamicObjects([ new MyDynamicClass(), new MyDynamicClass(), ]); $array->push(new \stdClass()); // IDEs and static analyzers should warn here
Link { public function render(): void; } class ButtonLink implements Button, Link { public function render(): void { echo '<button>Click me</button>'; } }
implements UIElement { public function render(): void { echo '<button>Click me</button>'; } } class Link implements UIElement { public function render(): void { echo '<a href="#">Click me</a>'; } } class ButtonLink extends Button, Link {}
function render(); } class Button implements UIElement { public function render(): void { echo '<button>Click me</button>'; } } class Link implements UIElement { public function render(): void { echo '<a href="#">Click me</a>'; } } class ButtonLink extends Button, Link {}
'<button>Click me</button>'; } } trait Link { public function render(): void { echo '<a href="#">Click me</a>'; } } class ButtonLink { use Button, Link; // But fatal error! }
... */ } } trait Link { public function render(): void { /* ... */ } } class ButtonLink { use Button, Link { Button::render insteadof Link; Button::render as renderButton; Link::render as renderLink; } public function renderElement(): void { $this->renderButton(); // calls Button::render $this->renderLink(); // calls Link::render } }
... */ } } trait Link { public function render(): void { /* ... */ } } class ButtonLink { use Button, Link { Button::render insteadof Link; Button::render as renderButton; Link::render as renderLink; } public function renderElement(): void { $this->renderButton(); // calls Button::render $this->renderLink(); // calls Link::render } }
... */ } } trait Link { public function render(): void { /* ... */ } } class ButtonLink { use Button, Link { Button::render insteadof Link; Button::render as renderButton; Link::render as renderLink; } public function renderElement(): void { $this->renderButton(); // calls Button::render $this->renderLink(); // calls Link::render } }