Интерфейсы
<?php
declare(strict_types=1);
interface Printable
{
public function print(): void;
}
interface Saveable
{
public function save(string $path): bool;
public function load(string $path): static;
}
// Class implements multiple interfaces
class Document implements Printable, Saveable
{
public function __construct(
private string $content = '',
) {
}
public function print(): void
{
echo $this->content;
}
public function save(string $path): bool
{
return file_put_contents($path, $this->content) !== false;
}
public function load(string $path): static
{
$this->content = file_get_contents($path) ?: '';
return $this;
}
}
// Type hint with interface
function output(Printable $item): void
{
$item->print();
}
Правила интерфейсов
<?php
declare(strict_types=1);
interface CacheInterface
{
// Only public methods allowed
public function get(string $key): mixed;
public function set(string $key, mixed $value, int $ttl = 3600): bool;
public function delete(string $key): bool;
// Constants in interfaces (always public)
public const DEFAULT_TTL = 3600;
// No properties allowed!
// public string $name; // ERROR!
// No method bodies (except PHP 8.0+ default methods don't exist in PHP)
// Methods MUST NOT have a body
}
// Interface extending other interfaces
interface TaggableCacheInterface extends CacheInterface
{
public function invalidateTag(string $tag): void;
}
// Interface can extend MULTIPLE interfaces
interface FullCacheInterface extends CacheInterface, TaggableCacheInterface
{
public function flush(): bool;
}
Запомни: Интерфейсы могут содержать: методы (только сигнатуры, без тела) и константы. НЕ могут содержать: свойства, конструкторы, не-public методы. Класс может реализовать множество интерфейсов. Интерфейс может наследовать множество интерфейсов.
Трейты
<?php
declare(strict_types=1);
trait Timestampable
{
private ?DateTimeImmutable $createdAt = null;
private ?DateTimeImmutable $updatedAt = null;
public function getCreatedAt(): ?DateTimeImmutable
{
return $this->createdAt;
}
public function setCreatedAt(DateTimeImmutable $date): void
{
$this->createdAt = $date;
}
public function touch(): void
{
$this->updatedAt = new DateTimeImmutable();
}
}
trait SoftDeletable
{
private ?DateTimeImmutable $deletedAt = null;
public function softDelete(): void
{
$this->deletedAt = new DateTimeImmutable();
}
public function isDeleted(): bool
{
return $this->deletedAt !== null;
}
public function restore(): void
{
$this->deletedAt = null;
}
}
class Article
{
use Timestampable, SoftDeletable;
public function __construct(
private string $title,
private string $content,
) {
$this->setCreatedAt(new DateTimeImmutable());
}
}
$article = new Article('Test', 'Content');
$article->touch();
$article->softDelete();
echo $article->isDeleted() ? 'Deleted' : 'Active'; // Deleted
Разрешение конфликтов трейтов
<?php
declare(strict_types=1);
trait A
{
public function hello(): string
{
return 'Hello from A';
}
public function shared(): string
{
return 'A::shared';
}
}
trait B
{
public function hello(): string
{
return 'Hello from B';
}
public function shared(): string
{
return 'B::shared';
}
}
class MyClass
{
use A, B {
// Resolve conflict: use A's hello, B's shared
A::hello insteadof B;
B::shared insteadof A;
// Create alias for the excluded method
B::hello as helloFromB;
// Change visibility
A::shared as protected sharedFromA;
}
}
$obj = new MyClass();
echo $obj->hello(); // Hello from A
echo $obj->helloFromB(); // Hello from B
echo $obj->shared(); // B::shared
Ловушка экзамена: Если два трейта имеют метод с одинаковым именем, вы ОБЯЗАНЫ разрешить конфликт с помощью
insteadof. Без этого — Fatal Error.asсоздаёт псевдоним, но НЕ разрешает конфликт.
Приоритет методов
<?php
declare(strict_types=1);
trait Greetable
{
public function greet(): string
{
return 'Trait greet';
}
}
class Base
{
public function greet(): string
{
return 'Base greet';
}
}
class Child extends Base
{
use Greetable;
// Priority order:
// 1. Class own methods (highest)
// 2. Trait methods
// 3. Inherited (parent) methods (lowest)
}
$child = new Child();
echo $child->greet(); // 'Trait greet' (trait overrides parent)
class ChildWithOwn extends Base
{
use Greetable;
public function greet(): string
{
return 'Own greet';
}
}
$child2 = new ChildWithOwn();
echo $child2->greet(); // 'Own greet' (own method wins)
Запомни: Приоритет: собственный метод класса > метод трейта > унаследованный метод родителя. Трейт перекрывает методы родительского класса, но собственный метод класса перекрывает трейт.
Абстрактные методы в трейтах
<?php
declare(strict_types=1);
trait Validatable
{
// Trait can require implementing class to define methods
abstract protected function validate(): bool;
public function saveIfValid(): bool
{
if (!$this->validate()) {
throw new InvalidArgumentException('Validation failed');
}
return true;
}
}
class Order
{
use Validatable;
// MUST implement abstract method from trait
protected function validate(): bool
{
return !empty($this->items);
}
public function __construct(
private array $items = [],
) {
}
}