Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Трейты

У класса может быть только один родитель. Но бывает, что одинаковый код нужен классам, которые ничем не связаны: и статье, и товару пригодится метод для формирования адреса страницы. Для этого есть трейты — наборы методов, которые можно подключить к любому классу.

Объявление и подключение

Трейт объявляют словом trait, а подключают внутри класса словом use:

<?php
 
trait Greets
{
    public function greet(): string
    {
        return 'Привет!';
    }
}
 
class User
{
    use Greets;
}
 
echo (new User())->greet(); // Привет!

Метод трейта работает так, будто написан в самом классе.

Один трейт — много классов

<?php
 
trait HasSlug
{
    public function slug(): string
    {
        return strtolower(str_replace(' ', '-', $this->title));
    }
}
 
class Post
{
    use HasSlug;
 
    public function __construct(public string $title) {}
}
 
class Product
{
    use HasSlug;
 
    public function __construct(public string $title) {}
}
 
echo (new Post('Hello World'))->slug(), "\n";  // hello-world
echo (new Product('Green Tea'))->slug();       // green-tea

Трейт обращается к $this->title — свойству класса, в который он подключён.

Свойства в трейте

В трейте можно объявлять и свойства:

<?php
 
trait CountsViews
{
    private int $views = 0;
 
    public function view(): int
    {
        return ++$this->views;
    }
}
 
class Page
{
    use CountsViews;
}
 
$page = new Page();
$page->view();
 
echo $page->view(); // 2

Требование к классу

Абстрактный метод в трейте обязывает класс реализовать его. Так трейт заявляет, что ему нужно для работы:

<?php
 
trait Describes
{
    abstract public function name(): string;
 
    public function describe(): string
    {
        return 'Это ' . $this->name();
    }
}
 
class City
{
    use Describes;
 
    public function name(): string
    {
        return 'Казань';
    }
}
 
echo (new City())->describe(); // Это Казань

Несколько трейтов

Трейты перечисляют через запятую. Если в них есть методы с одинаковым именем, конфликт разрешают словом insteadof:

<?php
 
trait Hello
{
    public function say(): string
    {
        return 'Привет';
    }
}
 
trait Bye
{
    public function say(): string
    {
        return 'Пока';
    }
}
 
class Speaker
{
    use Hello, Bye {
        Hello::say insteadof Bye;
        Bye::say as sayBye;
    }
}
 
$speaker = new Speaker();
 
echo $speaker->say(), ' ', $speaker->sayBye(); // Привет Пока

Трейт или наследование

СитуацияЧто выбрать
Класс — разновидность другого классаНаследование
Одинаковое поведение нужно несвязанным классамТрейт
Нужно гарантировать наличие методовИнтерфейс

Частая ошибка

Трейт — не тип. Проверить через instanceof, подключён ли он, нельзя. Если по этому признаку нужно отличать объекты, добавьте интерфейс:

<?php
 
interface Sluggable
{
    public function slug(): string;
}
 
trait HasSlug
{
    public function slug(): string
    {
        return 'slug';
    }
}
 
class Post implements Sluggable
{
    use HasSlug;
}
 
var_dump(new Post() instanceof Sluggable); // bool(true)

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.