PHP: полный мануал с примерами, функциями и ООП
Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Дерево руководства
Глава 45 · От основ к архитектуре
Абстрактные классы: общий алгоритм и точки расширения
Применяем шаблонный метод к экспорту заказов, отделяем обязательные шаги от изменяемых и выбираем между наследованием и композицией.
Одинаковая подготовка отчёта, разные форматы
Заказы выгружаются текстом и JSON. Обе выгрузки должны отклонять пустой список, а кодирование отличается. Абстрактный класс хранит общую часть алгоритма и требует дописать выбранный шаг.
Что получится. Одна последовательность проверки сформирует Чай, Кофе или ["Чай","Кофе"].
- render получает список названий
- Общий код проверяет, что список не пуст
- Вызов encode переходит в реализацию выбранного потомка
- Готовая строка возвращается из 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 уже не выражает реальную потребность.
- $report->render(["Чай", "Кофе"]) входит в OrderReport
- Проверка [] не срабатывает
- encode($titles) переходит в JsonOrderReport
- json_encode возвращает строку ["Чай","Кофе"]
- return в render отдаёт её исходному echo
Передать кодирование отдельно, если оно меняется независимо
Если один и тот же экспортёр должен менять формат во время жизни объекта или иметь другой родительский класс, передайте объект Encoder через конструктор. Это композиция. Можно передать и callable-функцию, если нужен единственный шаг без состояния. Абстрактная основа удобна стабильному семейству отчётов; она не является обязательной ступенью зрелого приложения.
| Изменение требования | Инструмент |
|---|---|
| Несвязанные классы обещают render | Интерфейс |
| Семейство разделяет проверенный порядок шагов | Абстрактная основа |
| Кодировщик заменяется независимо от отчёта | Композиция |
| Небольшое чистое преобразование без состояния | Функция |
Платёжные адаптеры: не прятать особенности протокола
У платёжных исполнителей может совпадать проверка суммы, но отличаться подпись, формат запроса, подтверждение и поведение повторного вызова. Основа полезна лишь для действительно одинакового алгоритма. Нельзя объявить один общий pay, который после любого HTTP 200 помечает заказ оплаченным: успешный транспортный ответ ещё не подтверждение платежа. Общий внешний интерфейс часто полезнее общего наследуемого состояния.
Попробуйте. Добавьте LinesOrderReport: каждое название на отдельной строке.
Отказ общего шага одинаков для обоих форматов
<?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. Этот приём продолжает шаблон общего действия; если достаточно одного класса, обычный метод проще.
- PriceLabel знает текст Стоимость
- BundlePrice хранит цену 150 и количество 3
- get amount вычисляет 450
- Унаследованный label соединяет подпись с суммой
<?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 нельзя.
Источники и обсуждения
- Абстрактные классы
- Final
- Исключения и finally
- Хуки свойств
- Зачем нужны интерфейс и абстрактный класс на реальной задаче — обсуждение проблемы
- Выбор интерфейса или абстрактного класса — обсуждение проблемы
Код и руководство с собой
Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.
Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.
Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.


