Александр Некрасов · Автономное руководство

PHP: полный мануал с примерами, функциями и ООП

Разделы этой страницы
  1. Абстрактные классы: общий алгоритм и точки расширения
  2. Одинаковая подготовка отчёта, разные форматы
  3. Абстрактный класс нужен общему алгоритму
  4. Полезный общий алгоритм: проверка плюс кодирование
  5. Не путать шаг алгоритма со свойством hook
  6. Передать кодирование отдельно, если оно меняется независимо
  7. Платёжные адаптеры: не прятать особенности протокола
  8. Отказ общего шага одинаков для обоих форматов
  9. Дальше: полный договор языка и специальные случаи
  10. Абстрактное свойство: общий метод, разные вычисления — PHP 8.4+
  11. Код и руководство с собой
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 45 · От основ к архитектуре

Абстрактные классы: общий алгоритм и точки расширения

Применяем шаблонный метод к экспорту заказов, отделяем обязательные шаги от изменяемых и выбираем между наследованием и композицией.

Одинаковая подготовка отчёта, разные форматы

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

Что получится. Одна последовательность проверки сформирует Чай, Кофе или ["Чай","Кофе"].

Как проходит конкретный вызов
  1. render получает список названий
  2. Общий код проверяет, что список не пуст
  3. Вызов encode переходит в реализацию выбранного потомка
  4. Готовая строка возвращается из render

Наследование; договор операции.

Небольшой обязательный шаг внутри готового методаСамостоятельный пример
<?php
declare(strict_types=1);

abstract class Report
{
    public function title(): string { return 'Отчёт: ' . $this->format(); }
    abstract protected function format(): string;
}
class JsonReport extends Report
{
    protected function format(): string { return 'JSON'; }
}
echo (new JsonReport())->title(), "\n";
Ожидаемый результат
Отчёт: JSON

abstract class нельзя создать напрямую. Его обычный метод title уже имеет тело. abstract protected format объявляет обязательный внутренний шаг без тела; JsonReport предоставляет его. Во время title переменная $this остаётся тем же JsonReport.

Абстрактный класс нужен общему алгоритму

Наглядная модель

Экспорт проверяет данные один раз, формат меняется

Магазину нужны JSON и простой текст с теми же ID заказов. Общую проверку не копируем в каждый формат.

  • OrderExport.export([7, 8])

    Унаследованный final-метод проверяет положительные целые ID, затем вызывает render.

    • JsonOrderExport.render

      Получает уже проверенные [7, 8] и возвращает строку {"orders":[7,8]}.

    • TextOrderExport.render

      Получает те же [7, 8], но возвращает текст 7;8.

Нельзя создать абстрактный OrderExport напрямую. Создаётся конкретный экспортёр, который наследует проверку и реализует форматирование. При ID = 0 проверка останавливает оба формата до render.

Запись в примереЧто делает каждый элемент
abstract class ReportНезавершённая основа; new Report запрещён.
abstract protected function format(): string;Потомок обязан дать совместимое тело, если сам не остаётся abstract. Метод нужен основе, поэтому protected.
$this->format()Общий метод вызывает реализацию у текущего конкретного объекта.

Интерфейс описывает обещание потребителю и позволяет несвязанным классам его выполнить. Абстрактный класс дополнительно предоставляет реализацию и может хранить состояние. Если общей реализации нет, пустая основа часто только занимает единственное место родителя. В классе могут быть обычные и абстрактные члены; private abstract-метод не даёт потомку доступного места реализации.

Полезный общий алгоритм: проверка плюс кодирование

Два формата проходят одну обязательную проверкуСамостоятельный пример
<?php
declare(strict_types=1);

abstract class OrderReport
{
    final public function render(array $titles): string
    {
        if ($titles === []) {
            throw new InvalidArgumentException('Нет товаров для отчёта');
        }
        return $this->encode($titles);
    }
    abstract protected function encode(array $titles): string;
}
class JsonOrderReport extends OrderReport
{
    protected function encode(array $titles): string
    {
        return json_encode($titles, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    }
}
class TextOrderReport extends OrderReport
{
    protected function encode(array $titles): string
    {
        return implode(', ', $titles);
    }
}

$titles = ['Чай', 'Кофе'];
echo (new TextOrderReport())->render($titles), "\n";
echo (new JsonOrderReport())->render($titles), "\n";
Ожидаемый результат
Чай, Кофе
["Чай","Кофе"]

final у render не даёт потомку убрать общую проверку. encode — точка изменения; protected скрывает её от внешнего кода. implode соединяет элементы списка, json_encode формирует JSON. Вход этого маленького договора — уже проверенный список строк; произвольный импорт надо разобрать до render.

Не путать шаг алгоритма со свойством hook

encode иногда называют hook шаблонного алгоритма: место, которое переопределяет потомок. Это обычный метод, не синтаксис property hooks PHP 8.4. Держите таких точек мало и называйте их по действию. Если порядок шагов можно свободно менять, final render уже не выражает реальную потребность.

Точный вызов JSON-отчёта
  1. $report->render(["Чай", "Кофе"]) входит в OrderReport
  2. Проверка [] не срабатывает
  3. encode($titles) переходит в JsonOrderReport
  4. json_encode возвращает строку ["Чай","Кофе"]
  5. return в render отдаёт её исходному echo

Передать кодирование отдельно, если оно меняется независимо

Если один и тот же экспортёр должен менять формат во время жизни объекта или иметь другой родительский класс, передайте объект Encoder через конструктор. Это композиция. Можно передать и callable-функцию, если нужен единственный шаг без состояния. Абстрактная основа удобна стабильному семейству отчётов; она не является обязательной ступенью зрелого приложения.

Изменение требованияИнструмент
Несвязанные классы обещают renderИнтерфейс
Семейство разделяет проверенный порядок шаговАбстрактная основа
Кодировщик заменяется независимо от отчётаКомпозиция
Небольшое чистое преобразование без состоянияФункция

Платёжные адаптеры: не прятать особенности протокола

У платёжных исполнителей может совпадать проверка суммы, но отличаться подпись, формат запроса, подтверждение и поведение повторного вызова. Основа полезна лишь для действительно одинакового алгоритма. Нельзя объявить один общий pay, который после любого HTTP 200 помечает заказ оплаченным: успешный транспортный ответ ещё не подтверждение платежа. Общий внешний интерфейс часто полезнее общего наследуемого состояния.

Попробуйте. Добавьте LinesOrderReport: каждое название на отдельной строке.

Разобрать решение

Унаследуйте OrderReport и реализуйте protected encode через implode("\n", $titles). Общий render и его отказ для [] останутся прежними.

Отказ общего шага одинаков для обоих форматов

Пустой отчёт не создаёт вводящий в заблуждение файлСамостоятельный пример
<?php
declare(strict_types=1);

abstract class OrderReport
{
    final public function render(array $titles): string
    {
        if ($titles === []) {
            throw new InvalidArgumentException('Нет товаров для отчёта');
        }
        return $this->encode($titles);
    }
    abstract protected function encode(array $titles): string;
}
class JsonOrderReport extends OrderReport
{
    protected function encode(array $titles): string
    {
        return json_encode($titles, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR);
    }
}
class TextOrderReport extends OrderReport
{
    protected function encode(array $titles): string
    {
        return implode(', ', $titles);
    }
}

foreach ([new TextOrderReport(), new JsonOrderReport()] as $report) {
    try {
        echo $report->render([]), "\n";
    } catch (InvalidArgumentException $error) {
        echo $error->getMessage(), "\n";
    }
}
Ожидаемый результат
Нет товаров для отчёта
Нет товаров для отчёта

foreach последовательно берёт два готовых исполнителя. Пустой массив отклоняется до вызова encode, поэтому политика не расходится между форматами. В другом сервисе пустой отчёт может быть допустим — тогда меняют общее правило осознанно.

Дальше: полный договор языка и специальные случаи

PHP 8.4 добавляет абстрактные свойства с требованиями get/set. Их договор и допустимые реализации ниже; для обычного общего алгоритма достаточно показанных методов.

Разделы ниже сохраняют специальные правила официального PHP Manual: Абстрактное свойство: общий метод, разные вычисления — PHP 8.4+.

Абстрактное свойство: общий метод, разные вычисления — PHP 8.4+

Для ценника есть общий формат строки, но сумма считается по-разному. Абстрактный базовый класс может оставить конкретный get потомку, сохранив общий метод label. Этот приём продолжает шаблон общего действия; если достаточно одного класса, обычный метод проще.

Общий ценник использует конкретную формулу
  1. PriceLabel знает текст Стоимость
  2. BundlePrice хранит цену 150 и количество 3
  3. get amount вычисляет 450
  4. Унаследованный label соединяет подпись с суммой
Базовый ценник не знает формулу стоимости — PHP 8.4+Самостоятельный пример
<?php
declare(strict_types=1);

abstract class PriceLabel
{
    abstract public int $amount { get; }

    public function label(): string
    {
        return 'Стоимость: ' . $this->amount;
    }
}

class BundlePrice extends PriceLabel
{
    public int $amount {
        get => $this->unitPrice * $this->count;
    }

    public function __construct(private int $unitPrice, private int $count) {}
}

echo (new BundlePrice(150, 3))->label(), "\n";
Ожидаемый результат
Стоимость: 450

Abstract перед свойством оставляет требование без реализации. Наследник завершает его конкретным get. Вызов label выполняет обычный метод родителя; чтение amount направляется в реализацию BundlePrice. Абстрактный класс сам создать через new нельзя.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.