MidТеория3 min

Интерфейсы и трейты

interface, implements, trait, conflict resolution

Интерфейсы

<?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 = [],
    ) {
    }
}

Вопросы с экзамена ZCE

Проверь себя

5 из 13

Какой XML-протокол обеспечивает коммуникацию между машинами, включая публикацию, поиск, привязку и вызов веб-сервиса?

Как разрешить конфликт одноимённых методов из двух трейтов?

Какие типы данных может возвращать функция с типом `iterable`? ```php function a(): iterable { // } ```

Выберите все правильные варианты

Может ли трейт содержать абстрактные методы?

Могут ли интерфейсы содержать свойства?

Code Challenges

LRU-кэш

Реализуйте LRU Cache (Least Recently Used) с методами get() и put(). При превышении ёмкости удаляется наименее используемый элемент.

Test Cases

1. Input: capacity=2, put(a,1), put(b,2), get(a)→ Expected: 1
2. Input: capacity=2, put(a,1), put(b,2), put(c,3), get(a)→ Expected: null
3. Input: capacity=2, put(a,1), put(b,2), get(a), put(c,3), get(b)→ Expected: null