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

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

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

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

Глава 195 · От основ к практике

ООП в PHP: от объекта с правилом до сервисного клиента

Зачем нужны объекты, где уместны наследование и композиция и как читать ::.

Класс и объект: зачем объединять состояние с правилами

Для одного вычисления класс часто избыточен. Польза появляется, когда одно состояние живёт дольше одной строки и к нему применяются правила: заказ меняет статус, остаток резервируется, API-клиент хранит адрес и токен. Класс даёт этим данным имя и не позволяет обходить согласованные действия.

Как читать выражение new Product('Чай', 34990)
  1. PHP находит объявление класса Product — это описание, а не готовый товар.
  2. new выделяет новый объект и передаёт два аргумента в __construct().
  3. Параметры с public одновременно объявляют и заполняют свойства.
  4. Переменная $tea хранит ссылку на созданный объект; стрелка вызывает его поведение.
Товар хранит данные и отвечает на понятный вопросСамостоятельный пример
<?php
final class Product
{
    public function __construct(
        public string $name,
        public int $priceKopecks,
    ) {}

    public function priceLabel(): string
    {
        return number_format($this->priceKopecks / 100, 2, '.', ' ') . ' ₽';
    }
}

$tea = new Product('Чай', 34990);
echo $tea->name, ': ', $tea->priceLabel(), "\n";
Ожидаемый результат
Чай: 349.90 ₽

class описывает форму и поведение, new создаёт конкретный объект, __construct() принимает обязательное начальное состояние. Стрелка читает свойство или вызывает метод объекта; $this внутри метода означает именно текущий товар.

Не позволить остатку уйти ниже нуляСамостоятельный пример
<?php
final class Stock
{
    public function __construct(
        private int $quantity,
    ) {
        if ($quantity < 0) {
            throw new InvalidArgumentException('Остаток не может быть отрицательным');
        }
    }

    public function reserve(int $amount): void
    {
        if ($amount < 1 || $amount > $this->quantity) {
            throw new DomainException('Недостаточно товара');
        }
        $this->quantity -= $amount;
    }

    public function quantity(): int
    {
        return $this->quantity;
    }
}

$stock = new Stock(5);
$stock->reserve(2);
echo 'Осталось: ', $stock->quantity(), "\n";
Ожидаемый результат
Осталось: 3

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

ЭлементЧто делаетЧастая ошибка
class ProductОбъявляет класс; имя регистрируется PHPСчитать объявление уже созданным объектом
new Product(...) Создаёт экземпляр и вызывает конструкторСоздавать неполный объект и дописывать обязательные поля позже
$thisТекущий объект внутри нестатического методаИспользовать вне объектного контекста
->Свойство или нестатический метод экземпляраПутать со статическим ::
public/private/protectedГраница доступа к членуДелать все свойства public и обходить правила
finalЗапрещает наследование класса или переопределение методаСчитать, что final делает объект неизменяемым

Имя класса подчиняется правилам идентификаторов и пространств имён. Объект можно создать по переменному имени класса, а строку полного имени безопасно получить через Product::class. Типизированное свойство без начального значения нельзя читать до инициализации. Readonly-класс делает объявленные свойства readonly и запрещает динамические свойства, но не делает вложенные объекты неизменяемыми.

Синтаксис свойств, методов, конструкторов и областей видимости подробно раскрыт в главе о классах и объектах.

Каждый самостоятельный пример запускается отдельно: скачайте его архив, распакуйте целиком и выполните php example.php. Сравните вывод с открытым блоком результата. Контекстные примеры честно отмечены и требуют указанного SAPI, расширения или сервиса.

Наследование, интерфейс и композиция: разные задачи

Наследование выражает отношение «является разновидностью» и позволяет дочернему классу получить доступные члены родителя. PHP поддерживает одного родителя через extends и несколько интерфейсов через implements. Переопределённый метод должен оставаться совместимым: код, ожидающий родителя, не должен ломаться на наследнике.

Подменить канал уведомления без изменения оформления заказаСамостоятельный пример
<?php
interface Notifier
{
    public function send(string $message): string;
}

abstract class NamedNotifier implements Notifier
{
    public function __construct(protected string $recipient) {}

    protected function prefix(): string
    {
        return '[' . $this->recipient . '] ';
    }
}

final class TelegramNotifier extends NamedNotifier
{
    public function send(string $message): string
    {
        return $this->prefix() . $message;
    }
}

function notifyPaidOrder(Notifier $notifier, int $orderId): void
{
    echo $notifier->send("Заказ #$orderId оплачен"), "\n";
}

notifyPaidOrder(new TelegramNotifier('@shop'), 42);
Ожидаемый результат
[@shop] Заказ #42 оплачен

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

Композиция выражает отношение «использует». Для клиента API это обычно точнее: клиент Bitrix24 использует HTTP-транспорт, но не является разновидностью Guzzle. Зависимость через конструктор легко заменить тестовой фикстурой и не тащит весь публичный API библиотеки в ваш класс.

Спрятать детали HTTP и оставить в бизнес-коде один понятный вызовСамостоятельный пример
<?php
interface Transport
{
    public function post(string $method, array $parameters): array;
}

final class FixtureTransport implements Transport
{
    public function post(string $method, array $parameters): array
    {
        return ['method' => $method, 'items' => [['ID' => 7, 'TITLE' => 'Заявка']]];
    }
}

final class B24Client
{
    public function __construct(private Transport $transport) {}

    public function listMethod(array $parameters, string $method): array
    {
        $response = $this->transport->post($method, $parameters);
        return $response['items'];
    }
}

$client = new B24Client(new FixtureTransport());
$result = $client->listMethod(['select' => ['ID', 'TITLE']], 'crm.deal.list');
echo $result[0]['ID'], ': ', $result[0]['TITLE'], "\n";
Ожидаемый результат
7: Заявка

Транспорт подставлен через конструктор, поэтому клиент не наследуется от HTTP-библиотеки и не привязан к её внутренним деталям. В рабочем адаптере вместо фикстуры будет Guzzle или cURL, а бизнес-код сохранит короткий вызов listMethod(). Это композиция: объект получает нужный инструмент как зависимость.

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

Оператор разрешения области видимости ::

Название Paamayim Nekudotayim — историческое имя двух двоеточий в сообщениях парсера. Практический смысл проще: оператор выбирает член на уровне класса или специальной области self, parent, static.

Прочитать константу класса и вызвать статический фабричный методСамостоятельный пример
<?php
final class Money
{
    public const CURRENCY = 'RUB';

    private function __construct(public int $kopecks) {}

    public static function fromRubles(int $rubles): self
    {
        return new self($rubles * 100);
    }
}

$price = Money::fromRubles(350);
echo Money::CURRENCY, ' ', $price->kopecks, "\n";
echo Money::class, "\n";
Ожидаемый результат
RUB 35000
Money

:: обращается к члену самого класса: константе, статическому свойству или статическому методу. Money::class возвращает полное имя класса как строку и не создаёт объект. Для обычного метода конкретного экземпляра используется ->.

ЗаписьОткуда берётся членКогда применять
Order::STATUS_PAIDКонкретный названный классКонстанта класса
Order::create()Конкретный названный классОсмысленная статическая фабрика
self::method()Класс, где записан кодРеализация не должна меняться из-за наследника
parent::method()Непосредственный родительДополнить реализацию родителя
static::method()Класс позднего вызоваПолиморфное статическое поведение
Order::classПолное имя класса как строкаКонфигурация, контейнер, логирование
Увидеть разницу между self и static при наследованииСамостоятельный пример
<?php
class Document
{
    public static function selfName(): string
    {
        return self::class;
    }

    public static function calledName(): string
    {
        return static::class;
    }
}

final class Invoice extends Document {}

echo Invoice::selfName(), "\n";
echo Invoice::calledName(), "\n";
Ожидаемый результат
Document
Invoice

self закреплено за классом, где записан метод. static использует класс фактического позднего вызова. parent:: явно обращается к реализации родителя. Позднее статическое связывание полезно фабрикам и наследуемым конфигурациям, но не должно скрывать зависимости приложения в глобальном статическом состоянии.

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

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

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

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

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