Перейти к содержимому
Поиск
Php Разработка

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

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

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

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

Зачем ООП: от корзины в массиве к правилам магазина

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

Сначала полезный инструмент: одинаковые ценники для двух магазинов

Нужно печатать ценники двух магазинов: «Чайный дом: Чай» и «Кофейня: Кофе». Способ составления подписи одинаковый; отличается сохранённое название магазина. На этой маленькой задаче видно, какую роль играет объект, без базы данных, оплаты и десятков классов.

Что получится. Один раз настроим два независимых инструмента. Затем получим подпись коротким вызовом с названием товара.

Как проходит конкретный вызов
  1. Вход: товар Чай и настройка Чайный дом
  2. Операция label соединяет сохранённую настройку с товаром
  3. Возвращённая строка попадает в echo
  4. Второй инструмент использует свою настройку Кофейня

Нужны переменные и вызовы функций: сохранение значения, аргументы и return. Здесь мы объясним новый способ держать настройки рядом с действием.

Работающая функция — честная исходная точка

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

Корзина хранит данные, методы сохраняют их согласованность

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

  • Каталог задаёт цену

    TEA стоит 15000 копеек. Цена берётся из доверенного каталога, количество — из проверенного ввода.

  • Внутри Cart

    Данные одной корзины принадлежат конкретному объекту.

    • items

      TEA: цена 15000, количество сначала 2, затем 3.

    • discountPercent

      Скидка 10%. Внешний код не записывает private-поле напрямую.

  • changeQuantity проверяет изменение

    3 принимается. 0 отклоняется до изменения корзины; прежнее допустимое состояние остаётся.

  • orderData заново считает итог

    2 товара: 27000. После изменения на 3: 40500. Итог не берётся из устаревшей отдельной переменной.

Расчёт остаётся обычной функцией calculateTotal. Польза объекта — в месте хранения состояния и проверенных операциях над ним; такую же дисциплину можно построить процедурно.

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

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

function label(string $shop, string $product): string
{
    return $shop . ': ' . $product;
}
echo label('Чайный дом', 'Чай'), "\n";
echo label('Кофейня', 'Кофе'), "\n";
Ожидаемый результат
Чайный дом: Чай
Кофейня: Кофе

string перед параметром означает строку; : string после скобок — строковый результат. Точка соединяет тексты. При первом вызове $shop получает Чайный дом, $product — Чай; return отдаёт готовую строку в echo. Следующий вызов выполняется с другой парой значений.

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

Требование выросло: настройка нужна для нескольких вызовов

Магазин печатает ценники нескольких товаров. В функции мы можем хранить название в переменной и передавать его повторно. Это нормальное решение. Сложность состоит не в копировании функции, а в ответственности за то, какие настройки использует конкретная операция.

Одна настройка, несколько действийСамостоятельный пример
<?php
declare(strict_types=1);

function label(string $shop, string $product): string
{
    return $shop . ': ' . $product;
}
$shop = 'Чайный дом';
echo label($shop, 'Чай'), "\n";
echo label($shop, 'Сахар'), "\n";
Ожидаемый результат
Чайный дом: Чай
Чайный дом: Сахар

Настройка хранится в $shop, функция получает её явно. Так можно строить и большое приложение: общие функции, проверенные структуры данных и явные параметры остаются полноценным подходом.

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

Объект хранит настройку рядом с действием

Два инструмента, одна реализация labelСамостоятельный пример
<?php
declare(strict_types=1);

class LabelPrinter
{
    public string $shop = '';
    public function label(string $product): string
    {
        return $this->shop . ': ' . $product;
    }
}
$tea = new LabelPrinter();
$tea->shop = 'Чайный дом';
$coffee = new LabelPrinter();
$coffee->shop = 'Кофейня';
echo $tea->label('Чай'), "\n";
echo $coffee->label('Кофе'), "\n";
Ожидаемый результат
Чайный дом: Чай
Кофейня: Кофе

Класс LabelPrinter описывает устройство инструмента один раз. new создаёт каждый отдельный экземпляр. $tea и $coffee хранят разные объекты; у каждого своё поле shop. Метод label — функция, объявленная внутри класса. Во время $tea->label(...) переменная $this означает объект $tea.

Запись в примереЧто делает каждый элемент
class LabelPrinter { ... }class объявляет класс; имя выбираем сами; фигурные скобки ограничивают его описание. Само объявление ещё не создаёт ни одного настроенного принтера.
public string $shop = '';Поле, или свойство, хранит настройку объекта. public разрешает читать и менять её снаружи; string — тип значения; пустая строка — начальное значение.
$tea = new LabelPrinter();new создаёт объект по описанию класса; = сохраняет его в переменной. Скобки относятся к созданию экземпляра; настройки конструктора изучим отдельно.
$tea->label('Чай')Стрелка -> выбирает метод у конкретного объекта. Чай попадёт в параметр $product; $this->shop прочитает настройку этого же объекта.
return $this->shop . ': ' . $product;Получаем две строки, соединяем их с разделителем и возвращаем результат именно вызывающей строке. После echo выполнение продолжается со следующей команды.

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

Что выиграли и чего объект пока не гарантирует

ВопросФункция и настройкиОбъект
Где общий код?В одной функции label.В одном методе LabelPrinter::label.
Где настройка магазина?В переменной, передаём в каждый вызов.В поле конкретного объекта; вызов передаёт только товар.
Можно ли использовать два магазина?Да, передавать разные настройки.Да, создать два экземпляра с разным состоянием.
Проверяет ли это права пользователя?Нет, требуется отдельная проверка.Нет, private/public не являются авторизацией.
Живёт ли настройка между HTTP-запросами?Нет, если её не сохранить.Сам объект тоже не переживает независимый запрос без отдельного механизма хранения.

Выигрыш объекта — связать конкретное состояние и действия над ним, когда такая связь помогает пользоваться инструментом и поддерживать правила. Названия «сервис», «сущность», «объект-значение», «адаптер» описывают роли, а не новые команды PHP. К ним перейдём на примерах заказа после освоения class, new, свойств и методов.

Когда достаточно функции

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

function priceLabel(int $rubles): string
{
    return $rubles . ' руб.';
}
echo priceLabel(1290), "\n";
echo priceLabel(350), "\n";
Ожидаемый результат
1290 руб.
350 руб.

Функция зависит только от аргумента и не хранит настроек. Создавать класс только ради имени priceLabel необязательно. Здесь суммы целые рубли; расчёты дробных сумм и валют — другая задача.

Перенос идеи на клиент CRM

Клиент CRM хранит адрес конкретного портала и транспорт — инструмент выполнения HTTP-запросов. Тогда вызов $b24->listMethod($filters, "crm.deal.list") получает данные конкретного портала, а не просит передавать настройки заново при каждом методе. Такой удобный вызов мы соберём после конструкторов и композиции. Названия Bitrix24 и Telegram сами по себе не требуют ООП; осмысленна отдельная конфигурация клиента и явная замена транспорта в проверке.

Загрузка класса из Composer не авторизует клиента и не загружает все классы приложения заранее. Автозагрузчик подключает нужное определение по запросу; new создаёт объект; сетевой вызов выполняет только написанный для него метод. Не прячьте вход в API в конструкторе без конкретной причины.

Упражнение и следующий шаг

Попробуйте. Добавьте третий принтер для Пекарни и выведите Пекарня: Хлеб, не изменяя класс.

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

Создайте $bakery = new LabelPrinter(); задайте $bakery->shop = "Пекарня"; затем echo $bakery->label("Хлеб"). Третья настройка существует отдельно, а код метода всё ещё один.

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

Пример: не перепутать сумму с произвольным числом

Применение после конструктора и readonly: сложим цену 129900 копеек и доставку 30000 копеек. Сумма должна сохранить валюту и отклонить сложение рублей с евро. Как конструктор проверяет исходные данные; что защищает readonly.

Одно сложение создаёт новую сумму
  1. $price содержит 129900 RUB; $delivery — 30000 RUB
  2. plus получает второй Money в $other
  3. Проверяются валюта и вместимость int
  4. new self возвращает 159900 RUB; исходные суммы сохраняются
Сумма в копейках проверяет валюту и создаёт новое значениеСамостоятельный пример
<?php
declare(strict_types=1);

final readonly class Money
{
    public function __construct(public int $minor, public string $currency)
    {
        if ($minor < 0 || !in_array($currency, ['RUB', 'EUR'], true)) {
            throw new InvalidArgumentException('Некорректная сумма');
        }
    }
    public function plus(self $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new DomainException('Разные валюты');
        }
        if ($other->minor > PHP_INT_MAX - $this->minor) {
            throw new OverflowException('Слишком большая сумма');
        }
        return new self($this->minor + $other->minor, $this->currency);
    }
}
$price = new Money(129900, 'RUB');
$delivery = new Money(30000, 'RUB');
echo $price->plus($delivery)->minor, "\n";
try { $price->plus(new Money(100, 'EUR')); }
catch (DomainException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
159900
Разные валюты

Деньги здесь — не float: итог хранится в минимальных денежных единицах. Валюты не смешиваются, переполнение проверено. Это упрощённая модель для положительных сумм с двумя знаками; у валют бывают другие масштабы, а возврат и бухгалтерская проводка требуют отдельной модели.

Пример: метод вместо свободной записи статуса

Ещё одно применение: после отметки оплаты заказ должен показать paid, а повторная отметка должна быть отклонена. Это локальное правило объекта; реальный банк здесь не вызывается. Закрытое состояние и разрешённая операция объясняют private и метод markPaid, а проект магазина связывает это с результатом платёжного исполнителя.

Оплаченный заказ нельзя незаметно оплатить второй разСамостоятельный пример
<?php
declare(strict_types=1);

final class Order
{
    private string $status = 'new';
    public function __construct(public readonly string $id) {}
    public function markPaid(): void
    {
        if ($this->status !== 'new') {
            throw new DomainException('Переход запрещён');
        }
        $this->status = 'paid';
    }
    public function status(): string { return $this->status; }
}
$order = new Order('order-1042');
$order->markPaid();
echo $order->id, ':', $order->status(), "\n";
try { $order->markPaid(); }
catch (DomainException $error) { echo $error->getMessage(), "\n"; }
Ожидаемый результат
order-1042:paid
Переход запрещён

Метод защищает состояние одного объекта. Два параллельных запроса всё ещё могут прочитать один заказ как new: от двойной записи защищает транзакция, версия строки или уникальный ключ в БД. ООП само по себе не обеспечивает конкурентную безопасность.

От запроса до изменения состояния
  1. Контроллер проверяет вход и права
  2. Сервис загружает заказ
  3. Order проверяет бизнес-переход
  4. Репозиторий сохраняет с защитой от гонки
  5. Контроллер возвращает понятный HTTP-ответ

Почему старые примеры PHP ведут себя иначе: карта изменений ООП

Вы открыли старую статью и видите метод с именем класса вместо __construct или функцию __autoload. Не копируйте строку до выяснения версии: в современных PHP исторический приём может стать обычным методом, ошибкой или удалённой возможностью. Таблица ниже следует пунктам официального OOP Changelog; это история конкретного раздела, а не полный список всех изменений PHP.

Читайте её как маршрут диагностики: заметили конструкцию → нашли период изменения → перешли к современному объяснению → проверили минимальный пример на своей минимально поддерживаемой версии. Наши новые исполняемые опыты проверены на PHP 8.5.11; PHP 5 и 7 здесь не запускались.

Дата примера — часть технического контекста
  1. Найти необычную конструкцию в старом коде
  2. Проверить её историческую границу
  3. Выбрать действующий механизм из связанного урока
  4. Прогнать совместимость проекта на целевой версии
ВерсияИзменения модели ООП
8.5static → self в final; порядок связывания трейтов
8.4Property hooks; lazy objects
8.1final константы; переопределяемые interface-константы
8.0Nullsafe ?->
7.4Исключения в __toString; вариативность типов; типизированные свойства
7.3Историческое ограничение unpack Traversable; статические ссылки; literal instanceof
7.2__autoload устаревает; object зарезервирован; group-use запятая; расширение параметров; повторный abstract
7.1void/iterable зарезервированы; видимость констант
7.0Устаревают static-вызов обычного метода и именной конструктор; group-use; anonymous class; перебор объектов; совместимые trait-свойства
5.6__debugInfo
5.5::class; finally
5.4Трейты; контроль абстрактного конструктора
5.3.3Именной конструктор в namespace больше не конструктор
5.3Defaults интерфейса; переменная::константа; диагностика magic; перехват autoload-исключений; __callStatic; heredoc/nowdoc; late static binding; __invoke
5.2__toString в строковых контекстах; диагностика преобразования
5.1.3var перестал выдавать прежний E_STRICT
5.1__set_state; __isset; __unset

Для современного проекта из этой таблицы не следует разрешение использовать удалённое __autoload: с PHP 8.0 используйте spl_autoload_register или Composer. Метод с именем класса в PHP 8 не служит конструктором; нужен __construct. Обычный метод вызывается через экземпляр, а не статически. Var всё ещё историческая форма публичного свойства, но явное public понятнее читателю.

Строка 7.3 про ключи распаковки историческая: современные PHP 8 поддерживают именованные аргументы, поэтому нельзя превратить старый запрет нечисловых ключей в универсальное правило для сегодняшнего кода. Также не используйте старые уровни диагностики из таблицы как обещание текущего Error или warning.

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

Упражнение: в библиотеке найдены __autoload и класс с методом, совпадающим с его именем. Решение: отдельно заменить загрузчик и проверить создание объекта после перехода на __construct. Простого исправления parse error недостаточно: старый код мог молча перестать инициализировать обязательные поля.

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

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

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

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

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