Menu
Coddy logo textTech

Абстрактные методы в трейтах

Часть раздела Объектно-ориентированное программирование путешествия по PHP на Coddy — урок 54 из 91.

Трейты могут предоставлять готовые к использованию методы, но иногда вам нужно, чтобы класс, использующий трейт, реализовал определенную функциональность. Именно здесь становятся полезными абстрактные методы в трейтах — они определяют контракт, который должен выполнить использующий их класс.

Когда трейт объявляет абстрактный метод, любой класс, использующий этот трейт, должен его реализовать:

<?php
trait Notifiable {
    abstract public function getEmail(): string;
    
    public function sendNotification(string $message): void {
        echo "Sending to " . $this->getEmail() . ": $message";
    }
}

class User {
    use Notifiable;
    
    public function __construct(private string $email) {}
    
    public function getEmail(): string {
        return $this->email;
    }
}

$user = new User("john@example.com");
$user->sendNotification("Welcome!");

Вывод:

Sending to john@example.com: Welcome!

Трейт предоставляет метод sendNotification(), но полагается на то, что getEmail() будет реализован классом. Это создает мощный паттерн, в котором трейт обрабатывает общую логику, делегируя специфические детали реализующему классу.

Если класс использует трейт без реализации абстрактного метода, PHP выдает фатальную ошибку. Это гарантирует, что трейты могут безопасно полагаться на существование определенных методов, делая ваш код более предсказуемым и удобным в сопровождении.

challenge icon

Задание

Легко

Давайте создадим систему ценообразования, в которой различные типы продуктов рассчитывают свои окончательные цены по-разному, но все они используют общую логику скидок через трейт с абстрактным методом.

Вы организуете свой код в трех файлах:

  • Discountable.php — Создайте трейт с именем Discountable, который обеспечивает функциональность скидок. Трейт должен содержать:
    • Абстрактный метод getBasePrice(): float, который должны реализовать классы, использующие этот трейт
    • Метод applyDiscount(int $percent), который рассчитывает и возвращает цену после применения процента скидки к базовой цене
    • Метод getFinalPrice(int $percent), который возвращает отформатированную строку: "Final price: $[discounted_price]" с 2 знаками после запятой
    Трейт обрабатывает все математические расчеты скидок, но полагается на каждый класс в определении того, что является базовой ценой.
  • Product.php — Создайте класс Product, который использует трейт Discountable. Подключите файл трейта. Используйте продвижение свойств в конструкторе (constructor promotion) для определения приватного свойства $name (string) и приватного свойства $price (float). Реализуйте обязательный метод getBasePrice(), чтобы он возвращал цену продукта.
  • main.php — Подключите файл Product. Вы получите три входных значения: название продукта, цену и процент скидки. Создайте экземпляр Product и выведите две строки:
    • Базовую цену, отформатированную как "Base: $[price]" с 2 знаками после запятой
    • Результат вызова getFinalPrice() с процентом скидки

Этот паттерн эффективен, так как трейт Discountable может использоваться любым классом — продуктами, услугами, подписками — до тех пор, пока они реализуют getBasePrice(). Трейт предоставляет общую логику скидок, в то время как каждый класс сам определяет, откуда берется его базовая цена.

Шпаргалка

Трейты могут объявлять абстрактные методы, которые должны быть реализованы любым классом, использующим этот трейт:

<?php
trait Notifiable {
    abstract public function getEmail(): string;
    
    public function sendNotification(string $message): void {
        echo "Sending to " . $this->getEmail() . ": $message";
    }
}

class User {
    use Notifiable;
    
    public function __construct(private string $email) {}
    
    public function getEmail(): string {
        return $this->email;
    }
}

Трейт предоставляет готовые к использованию методы, требуя при этом от класса реализации конкретной функциональности. Если класс использует трейт без реализации абстрактного метода, PHP выдает фатальную ошибку.

Этот паттерн позволяет трейтам обрабатывать общую логику, делегируя специфические детали реализующему классу.

Попробуйте сами

<?php

require_once 'Product.php';

// Чтение входных данных
$name = trim(fgets(STDIN));
$price = floatval(trim(fgets(STDIN)));
$discountPercent = intval(trim(fgets(STDIN)));

// TODO: Создать экземпляр Product с именем и ценой

// TODO: Вывести базовую цену, отформатированную как "Base: $[price]" с 2 знаками после запятой

// TODO: Вывести результат вызова getFinalPrice() с процентом скидки

?>
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование