В строковом поле статуса можно случайно сохранить readed вместо read. Программа не поймёт, что это опечатка, пока не встретит её в неожиданной ветке. Перечисление enum делает допустимые варианты частью кода и помогает PHP проверить их раньше.

Объявляем статус как enum

Создайте src/ReadingStatus.php:

<?php
declare(strict_types=1);

namespace App;

enum ReadingStatus: string
{
    case Planned = 'planned';
    case Reading = 'reading';
    case Read = 'read';
}

В строковом перечислении у каждого варианта есть строковое значение для хранения или обмена данными. Внутри PHP мы передаём не произвольный текст, а ReadingStatus::Read. Попытка создать несуществующий вариант будет заметна.

Используем enum в модели

Обновите модель книги: статус получит контролируемый тип, а объект перестанет меняться после создания:

<?php
declare(strict_types=1);

namespace App;

final readonly class Book
{
    public function __construct(
        private string $title,
        private string $author,
        private int $year,
        private ReadingStatus $status = ReadingStatus::Planned,
    ) {
        if (trim($title) === '') {
            throw new \InvalidArgumentException('У книги должно быть название.');
        }
    }

    public function title(): string { return $this->title; }
    public function author(): string { return $this->author; }
    public function year(): int { return $this->year; }
    public function status(): ReadingStatus { return $this->status; }
}

Readonly-классы появились в PHP 8.2. Их объявленные свойства должны иметь тип и не могут быть изменены после инициализации. Для обычного каталога это удобно: запись создаётся с согласованными значениями и дальше передаётся между функциями без риска, что её статус незаметно поменяют в середине операции.

$book = new Book(
    title: 'Дюна',
    author: 'Фрэнк Герберт',
    year: 1965,
    status: ReadingStatus::Read,
);

if ($book->status() === ReadingStatus::Read) {
    echo 'Книга прочитана';
}

Readonly — не глубокая заморозка всего объекта

Readonly запрещает повторно присвоить само свойство. Если свойство содержит изменяемый объект, его внутреннее состояние может измениться отдельно. В нашей модели свойства — строки, число и enum, поэтому этого нюанса пока не возникает. Для вложенных объектов правила проектируют отдельно.

Поддержка нескольких версий

Enums появились в PHP 8.1, readonly-классы — в PHP 8.2. Если проект должен работать на PHP 8.1, используйте readonly-свойства вместо readonly-класса. Для нового курса ориентируемся на поддерживаемую ветку PHP 8.5, но важно понимать минимальную версию синтаксиса каждого элемента.

В PHP 8.5 также появились функции array_first() и array_last() для получения первого и последнего значения массива; если в проекте поддерживается более ранняя ветка, оставляйте совместимый вариант с проверкой массива. В основной модели каталога это не нужно, поэтому не будем добавлять новую функцию только ради короткой записи.

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

Часть 8 из 16.

← Часть 7: Классы и объекты PHP: создаём модель книги · Часть 9: Валидация и исключения PHP: отклоняем неверные данные →

Документация