Объектно-ориентированное программирование в PHP
Если ООП для вас новое или вы учите PHP с нуля, сначала пройдите материалы без привязки к синтаксису: парадигмы и уровни абстракции, затем ООП — о разделе — зачем объекты, введение, абстракция, инкапсуляция, наследование, полиморфизм.
Ниже — как это устроено в PHP.
Теория и синтаксис PHP
| Понятие ООП | Как выражено в PHP | Аналог в Java |
|---|---|---|
| Класс, АДТ (алгебраический тип данных) | class, readonly class (8.2+) | class, record |
| Объект / экземпляр | new ClassName() | new ClassName() |
| Конструктор | __construct(); promotion свойств (8+) | конструктор класса |
| Свойство | $property с типом (7.4+) | поле класса |
| Инкапсуляция | public / protected / private | те же модификаторы |
| Наследование класса | extends (один родитель) | extends |
| Реализация контракта | implements (несколько интерфейсов) | implements |
| Горизонтальное переиспользование | trait | нет (интерфейсы + default) |
| Абстракция | interface, abstract class | то же |
| Полиморфизм подтипов | переопределение, тип параметра | @Override, ссылки на супертип |
| Перегрузка | нет перегрузки методов по сигнатуре | перегрузка разрешена |
| Сообщения | ->; магические __call, __get | вызовы методов; reflection |
| Статика | static | static |
| Копирование | clone, __clone() | clone(), copy constructors |
| Передача объектов | по ссылке (один экземпляр) | по ссылке на объект |
Определения — раздел 4-08-oop. Синтаксис JVM-языка для сопоставления — ООП в Java. Обзор PHP — раздел PHP.
Кратко для новичка:
- Класс —
class User { };new User()вызывает__construct. public/private/protected— кто видит поле или метод.- Наследование —
extends;parent::— вызов метода родителя. - Интерфейс и trait — контракт и переиспользуемый код без второго родителя.
- Полиморфизм — тип-hint
ShapeпринимаетCircleиRectangleв одной функции.
Создание экземпляра и присваивание переменным
new и __construct
При выполнении $obj = new User("Пётр") PHP:
- Выделяет память под объект.
- Вызывает
__construct()(если определён) или конструктор по умолчанию. - Возвращает ссылку на экземпляр.
class User {
public function __construct(
public string $name,
private string $email = '',
) {}
}
$user = new User('Пётр', 'p@example.com');
$alias = $user; // та же ссылка
$alias->name = 'Петр'; // изменится и $user->name
$other = new User('Анна'); // другой объект
Разбор:
- Constructor property promotion (PHP 8+) объявляет свойство и параметр в одной строке — аналог compact-конструктора в Java record, но объект остаётся изменяемым (если не
readonly). $alias = $userне клонирует объект — обе переменные указывают на один экземпляр.- Для независимой копии —
clone $user(см. раздел Клонирование объектов ниже).
Цепочка вызова родительского конструктора
class Employee extends User {
public function __construct(
string $name,
public string $department,
) {
parent::__construct($name); // обязательно явно, если свой __construct
}
}
Разбор:
- В отличие от C#/Kotlin, PHP не вызывает
parent::__construct()автоматически, если в дочернем классе есть свой конструктор. - Пропуск
parent::__construct()оставляет поля родителя неинициализированными.
Фабрики и статические конструкторы
class Money {
private function __construct(
public readonly int $amountCents,
public readonly string $currency,
) {}
public static function rub(int $rubles): self {
return new self($rubles * 100, 'RUB');
}
}
$price = Money::rub(150);
Разбор:
private __construct+staticфабрика — идиома, когда нужны именованные способы создания (rub,usd).- В Java аналог — приватный конструктор +
static Money rub(int).
Модификаторы доступа
| Модификатор | Класс | Наследник | Внешний код | Заметка |
|---|---|---|---|---|
public | да | да | да | API объекта |
protected | да | да | нет | для расширения подклассами |
private | да | да* | нет | *доступ к private другого объекта того же класса разрешён |
class BankAccount {
private float $balance = 0.0;
public function deposit(float $amount): void {
if ($amount > 0) {
$this->balance += $amount;
}
}
public function transferTo(BankAccount $other, float $amount): void {
if ($amount > 0 && $amount <= $this->balance) {
$this->balance -= $amount;
$other->balance += $amount; // OK: тот же класс
}
}
public function getBalance(): float {
return $this->balance;
}
}
Разбор:
privateв PHP проверяется на уровне класса, не экземпляра — метод может трогатьprivateдругогоBankAccount.- С PHP 7.4+ свойства типизируются; с 8.0 —
readonlyдля однократной инициализации. - Публичные свойства допустимы в DTO, но для доменных сущностей предпочтительны методы с инвариантами.
Четыре столпа ООП в PHP
Интерактивные схемы — общие принципы (псевдокод). Подробнее: абстракция, инкапсуляция, наследование, полиморфизм.
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Play ITЗагрузка интерактивного демо…
Абстракция
Скрытие деталей за контрактом. В PHP:
interface Logger { public function log(string $message): void; }— только обязательные методы.abstract class AbstractRepository { abstract public function find(string $id): ?Entity; public function mustFind(string $id): Entity { /* шаблон */ } }— общий код + обязательный шаг в потомке.
interface PaymentGateway {
public function charge(int $amountCents): string;
}
final class StripeGateway implements PaymentGateway {
public function charge(int $amountCents): string {
// детали HTTP к Stripe
return 'pi_123';
}
}
Разбор:
- Сервис заказов зависит от
PaymentGateway, не от Stripe. final classзапрещает наследование — аналогfinalв Java для классов-реализаций.
Инкапсуляция
Состояние меняется через методы с правилами (deposit, withdraw). Подробный разбор — в разделе Инкапсуляция ниже.
Наследование
extends переносит поведение и защищённые члены. Используйте только для отношения является (is-a); для миксинов — trait.
Полиморфизм
Тип параметра Animal $a принимает Dog и Cat; вызывается переопределённый метод. Второй путь — несколько реализаций одного interface (предпочтительнее для слабой связности).
ООП в PHP
PHP отвечает на три прикладных вопроса веб-приложения:
- Где хранится состояние сущности (
User,Order,Invoice) и кто имеет право его менять. - Где живут бизнес-правила и как гарантировать их выполнение в каждом сценарии.
- Как заменить реализацию (логгер, репозиторий, платёжный шлюз) без переписывания всего приложения.
Полноценная поддержка ООП — с PHP 5.0 (2004); версии 7.x и 8.x добавили типы, readonly, enum и promotion свойств в конструкторе. В Laravel и Symfony объектный стиль — фактический стандарт для доменной логики.
КЛАСС Кот
поля: имя, возраст
метод мяукнуть()
КОНЕЦ
объект barsik := новый Кот(имя="Барсик", возраст=3)
barsik.мяукнуть()
Разбор:
- Блок показывает ООП-идею в псевдокоде без привязки к синтаксису конкретного языка.
- Класс задает структуру данных (
имя,возраст) и поведение (мяукнуть). - Объект
barsik— конкретный экземпляр класса с собственным состоянием. - Вызов
barsik.мяукнуть()демонстрирует отправку сообщения объекту через метод. - Такой формат помогает сначала понять модель, а потом переходить к PHP-реализации.
Пример класса
Справочно на PHP
Код ITЗагрузка примера кода…
Разбор:
class Unit— шаблон игровой сущности: свойства и методы в одном типе.$thisв методах ссылается на текущий объект и его состояние.getDamage()— вычисляемое значение урона по характеристикам и уровню.attack($target)— взаимодействие объектов: инициатор меняетhealthцели через метод.new Unit()создаёт экземпляр;->— доступ к полям и методам объекта.<?phpоткрывает блок PHP; в файлах только с кодом закрывающий?>часто опускают.- Запуск:
php filename.php— интерпретатор выполняет инструкции и выводит результат в консоль.
Интерактивная схема — класс и объект (псевдокод, подходит для любого ООП-языка). Полный разбор принципов: ООП в разделе "Код и разработка".
Play ITЗагрузка интерактивного демо…
Объектно-ориентированное программирование
ООП — парадигма, в которой программа строится вокруг объектов (экземпляров классов). Класс задаёт структуру данных и поведение; объект хранит конкретные значения и отвечает на вызовы методов.
В PHP можно писать и процедурно, но в крупных проектах доменная логика обычно оформляется классами с инкапсуляцией, интерфейсами и полиморфизмом.
Практический порядок для новичка:
- Спроектировать одну сущность с правилами изменения состояния.
- Вынести общий контракт в
interface. - Добавлять
extendsилиtraitтолько когда связь — отношение является.
Класс и объект — единица абстракции и её воплощение
В основе объектно-ориентированного подхода лежит понятие класса — это шаблон или чертёж, описывающий структуру и поведение некоторой сущности. Класс определяет, какие данные могут храниться в объекте (их называют свойствами, полями или атрибутами), и какие действия объект может выполнять (методы). Важно подчеркнуть: класс сам по себе не содержит данных и не выполняет код — он лишь задаёт форму.
Объект — это конкретный экземпляр класса. При создании объекта в памяти выделяется пространство под его свойства, инициализируются значения, вызываются конструкторы. Один и тот же класс может порождать неограниченное количество объектов, каждый из которых обладает независимым состоянием.
Рассмотрим базовый пример:
Код ITЗагрузка примера кода…
Разбор:
__construct($name)автоматически вызывается приnew User("Петр").$this->name = $nameсохраняет входное значение в состояние конкретного объекта.sayHello()формирует ответ на основе внутреннего свойства$name.echo $user->sayHello()вызывает метод и выводит его результат.- Код демонстрирует связку "класс -> объект -> метод" в минимальном виде.
Класс User объявляется с помощью ключевого слова class. Внутри него определено одно публичное свойство $name и два метода: __construct() и sayHello(). Метод __construct() — это конструктор: специальный метод, который автоматически вызывается при создании нового объекта с помощью оператора new. Конструктор принимает аргумент $name, который присваивается свойству $this->name. Здесь $this — это псевдопеременная, ссылающаяся на текущий объект, то есть на тот экземпляр, в контексте которого выполняется метод. Без $this обращение к свойству было бы неоднозначным: интерпретатор не смог бы отличить локальную переменную от свойства класса.
Метод sayHello() — обычный метод экземпляра: он не принимает параметров, но использует значение свойства $name текущего объекта для формирования строки. Обращение к методу производится через оператор ->, что характерно для PHP при работе с объектами.
После объявления класса выполняется инструкция $user = new User("Петр");. Эта инструкция создаёт объект — экземпляр класса User, передавая в конструктор строку "Петр". В результате свойство $name этого объекта получает значение "Петр". Далее вызов $user->sayHello() приводит к выполнению метода sayHello() в контексте именно этого объекта, и возвращаемая строка содержит имя "Петр".
Стоит отметить: в PHP объекты передаются и присваиваются по ссылке (начиная с версии 5.0). Это означает, что при выполнении $anotherUser = $user; обе переменные будут ссылаться на один и тот же объект в памяти. Изменение свойств через одну переменную отразится и при обращении через другую. Такое поведение отличается от передачи примитивных типов (строк, чисел), которые копируются по значению. В случаях, когда требуется создать независимую копию объекта, используется клонирование через clone.
Также следует упомянуть, что в PHP до версии 8.0 не существовало строгой типизации параметров конструктора по умолчанию, хотя начиная с PHP 7.0 появилась возможность указывать объявление типов (type declarations) — в частности, скалярные типы (string, int, bool, float) и ? для обозначения допустимости null. В версии 8.0 был введён механизм конструкторного повышения (constructor property promotion), позволяющий объединить объявление свойства, параметра и присваивание в одну строку:
class User {
public function __construct(
public string $name
) {}
}
Разбор:
- Это constructor property promotion из PHP 8+, сокращающий шаблонный код.
public string $nameодновременно объявляет свойство и параметр конструктора.- Переданное значение автоматически присваивается полю без ручного
$this->name = $name. - Подход ускоряет разработку простых DTO и value object.
Этот синтаксис создаёт публичное свойство $name и автоматически присваивает ему значение переданного аргумента. Он не меняет семантики, но сокращает шаблонный код.
Наследование — иерархия и расширение поведения
Интерактивная схема — наследование (псевдокод). Подробнее: Наследование.
Play ITЗагрузка интерактивного демо…
Наследование — один из фундаментальных механизмов ООП, позволяющий создавать новые классы на основе существующих. Класс, от которого наследуют, называется родительским (или базовым, суперклассом), а класс, который наследует — дочерним (или производным, подклассом). Дочерний класс автоматически получает все не приватные свойства и методы родительского класса и может расширять или изменять их поведение.
В PHP наследование реализуется с помощью ключевого слова extends. Оно поддерживает только одиночное наследование — то есть дочерний класс может иметь лишь одного непосредственного родителя. Это ограничение осознанно и направлено на упрощение модели взаимодействия и избежание проблем множественного наследования (например, ромбовидной зависимости). Вместо множественного наследования PHP предлагает использовать трейты и интерфейсы, о чём будет сказано далее.
Рассмотрим пример:
class Admin extends User {
public function role() {
return "Администратор";
}
}
Разбор:
extends Userсоздает отношение наследования "Admin является User".- Класс
Adminполучает доступ к публичным и защищенным членамUser. - Метод
role()добавляет специализированное поведение дочернего класса. - Пример показывает расширение базового контракта без копирования кода.
Здесь класс Admin объявляется как расширение класса User. Это означает, что любой объект класса Admin будет обладать всеми свойствами и методами, определёнными в User, включая $name, __construct() и sayHello(), а также дополнительным методом role(), специфичным для администратора.
С точки зрения памяти и инициализации, при создании объекта new Admin("Анна") порядок выполнения следующий:
- Вызывается конструктор
Admin. Поскольку в приведённом примере явного конструктора вAdminнет, PHP автоматически вызывает конструктор родительского классаUser, если он определён и совместим по сигнатуре. - В конструкторе
Userпроисходит присваивание$this->name = "Анна". - Объект инициализирован и готов к использованию.
Если дочерний класс определяет собственный конструктор, то вызов родительского конструктора не происходит автоматически. Это важное отличие от некоторых других языков, где вызов super() может быть неявным. В PHP при необходимости явно вызывается родительский конструктор через parent::__construct(...). Например:
class Moderator extends User {
private $permissions;
public function __construct($name, array $permissions) {
parent::__construct($name); // явный вызов конструктора родителя
$this->permissions = $permissions;
}
}
Разбор:
- Дочерний класс объявляет собственный конструктор с дополнительным параметром
permissions. parent::__construct($name)явно вызывает инициализацию родительской части объекта.- Приватное свойство
$permissionsхранит расширенное состояние только этого класса. - Такой паттерн важен, когда наследник дополняет, а не заменяет базовый контракт.
Пропуск parent::__construct() в таком случае приведёт к тому, что свойства родительского класса (в данном случае $name) останутся неинициализированными, что может вызвать ошибки или неопределённое поведение.
Наследование тесно связано с принципом подстановки Барбары Лисков: объект дочернего класса должен быть взаимозаменяемым с объектом родительского класса без нарушения корректности программы. Это означает, что дочерний класс не должен ослаблять предусловия, усиливать постусловия или изменять контракт методов (например, возвращать значения иных типов, если это не разрешено ковариантностью или контравариантностью). В PHP начиная с версии 7.4 реализована ковариантность возвращаемых типов и контравариантность типов параметров при переопределении методов, что делает соблюдение этого принципа более строго контролируемым:
Код ITЗагрузка примера кода…
Разбор:
- Базовый класс
Shapeзадает общий методgetArea()с типомfloat. Rectangleпереопределяет реализацию, сохраняя совместимый контракт возврата.- Конструктор принимает размеры и сохраняет их в типизированных свойствах.
- Полиморфный вызов
getArea()работает единообразно через ссылку наShape. - Пример закрепляет идею корректного override без нарушения сигнатуры.
Здесь переопределение getArea() корректно: возвращаемый тип совпадает, а реализация изменена. Если бы дочерний класс возвращал, скажем, строку — это вызвало бы фатальную ошибку на этапе компиляции (в runtime, но до выполнения основного кода).
Следует избегать чрезмерного углубления иерархий наследования. В PHP, как и в большинстве языков, сложные древовидные структуры (A extends B extends C extends D…) затрудняют анализ кода, усложняют тестирование и повышают связанность. Предпочтительнее проектировать "плоские" иерархии, в которых наследование используется только для выражения отношения "является" (is-a), а не для повторного использования кода как такового.
Инкапсуляция — управление доступом и сокрытие реализации
Интерактивная схема — инкапсуляция (псевдокод). Подробнее: Инкапсуляция.
Play ITЗагрузка интерактивного демо…
Инкапсуляция — это принцип, согласно которому внутреннее состояние объекта (его свойства) защищается от прямого внешнего вмешательства, а взаимодействие с объектом осуществляется только через чётко определённый интерфейс — его публичные методы. Цель — обеспечить целостность данных и уменьшить связанность между компонентами системы.
В PHP инкапсуляция реализуется с помощью модификаторов доступа — public, protected, private.
public— элемент доступен из любого контекста — внутри класса, извне, из дочерних классов.protected— элемент доступен только внутри класса и его дочерних классов, но не извне.private— элемент доступен только внутри того класса, в котором он объявлен; даже дочерние классы не могут получить к нему доступ.
Пример:
class BankAccount {
private float $balance = 0.0;
public function deposit(float $amount): void {
if ($amount > 0) {
$this->balance += $amount;
}
}
public function getBalance(): float {
return $this->balance;
}
}
Разбор:
private float $balanceскрывает внутреннее состояние от прямой записи извне.deposit(float $amount)реализует контролируемую точку изменения баланса.- Валидация
if ($amount > 0)защищает объект от некорректного ввода. getBalance()дает только чтение и сохраняет инварианты класса.- Блок показывает практическую инкапсуляцию через публичный API.
Свойство $balance объявлено как private, что исключает прямое присваивание вида $account->balance = -1000. Вместо этого изменение баланса возможно только через метод deposit(), который включает валидацию: отрицательные суммы отклоняются. Метод getBalance() предоставляет только чтение текущего значения — клиентский код не может изменить баланс, минуя бизнес-логику.
Важно: в PHP до версии 7.4 не было поддержки типизации свойств, и их объявление выглядело как private $balance;. Начиная с PHP 7.4, появилась возможность объявлять типы прямым синтаксисом (private float $balance); в PHP 8.0 — инициализировать свойства в месте объявления, включая выражения (private DateTime $createdAt = new DateTime();), что сокращает необходимость в конструкторе для тривиальных инициализаций.
Модификаторы доступа действуют на уровне класса. Это означает, что метод одного объекта класса A может обращаться к private-свойствам другого объекта того же класса A. Такое поведение соответствует спецификации языка и отличается от, например, C++ или Java, где доступ проверяется на уровне экземпляра. Это редко вызывает проблемы на практике, но важно понимать при проектировании систем, чувствительных к инкапсуляции на уровне объектов.
Полиморфизм — единообразие в разнообразии
Интерактивная схема — полиморфизм (псевдокод). Подробнее: Полиморфизм.
Play ITЗагрузка интерактивного демо…
Полиморфизм — способность объектов разных классов обрабатываться единообразно при условии, что они реализуют общий интерфейс или наследуют от общего предка. Это позволяет писать код, не зависящий от конкретных типов, а опирающийся на абстракции.
В PHP полиморфизм проявляется двумя основными способами:
- Через наследование и переопределение методов:
Код ITЗагрузка примера кода…
Разбор:
-
Animalзадает общий интерфейс поведения через методmakeSound(). -
DogиCatпереопределяют метод своей конкретной реализацией. -
Функция
announce(Animal $animal)зависит от абстракции, а не от конкретного класса. -
При вызове PHP выбирает нужную реализацию динамически по реальному типу объекта.
-
Это классический пример runtime-полиморфизма.
Здесь функция
announce()принимает параметр типаAnimal, но работает корректно с любым подклассом, поскольку каждый из них переопределяетmakeSound()в соответствии со своей спецификой. Выбор реализации происходит динамически, во время выполнения, на основе реального типа объекта.
- Через интерфейсы, что является более гибким и предпочтительным подходом (см. ниже).
Полиморфизм лежит в основе таких практик, как внедрение зависимостей (dependency injection), использование фабрик, стратегий и других шаблонов проектирования. Он позволяет разделять интерфейс и реализацию, повышая тестируемость (через моки и заглушки) и расширяемость (через добавление новых реализаций без изменения клиентского кода).
Интерфейсы — контракты без реализации
Интерфейс — это особый вид абстракции, который задаёт контракт — набор методов, которые обязан реализовать любой класс, заявивший о его поддержке. В отличие от класса, интерфейс не содержит реализации методов (до PHP 8.0 — вообще никакой; начиная с PHP 8.0 — может содержать методы по умолчанию, но это исключение из правила и требует осторожного применения).
Интерфейс объявляется ключевым словом interface, а его реализация — implements. Класс может реализовывать несколько интерфейсов, что компенсирует отсутствие множественного наследования.
Рассмотрим пример:
Код ITЗагрузка примера кода…
Разбор:
interface Loggerопределяет контракт методаlog()без реализации.implements Loggerобязывает классы реализовать метод с совместимой сигнатурой.FileLoggerиConsoleLoggerреализуют один и тот же интерфейс разными способами.- Клиентский код может подставлять любую реализацию без изменения вызывающей логики.
- Такой подход уменьшает связность и упрощает тестирование через моки.
Интерфейс Logger определяет единый метод log(), принимающий строку. Ни один класс не знает, как именно будет происходить логирование — важен только факт, что вызов log() возможен и соответствует сигнатуре. Это позволяет писать клиентский код, не привязанный к конкретной реализации:
Код ITЗагрузка примера кода…
Разбор:
private Logger $loggerхранит зависимость на уровне интерфейса.- Конструкторная инъекция передает реализацию извне и поддерживает DI.
createUser()вызываетlog()без знания конкретного способа логирования.- Замена
FileLoggerнаConsoleLoggerвыполняется одной строкой конфигурации. - Блок демонстрирует инверсию зависимостей в прикладном коде.
Интерфейсы особенно ценны в крупных системах, где важна заменяемость компонентов. Они позволяют явно выразить зависимости на уровне типов и поддерживаются инструментами статического анализа (например, PHPStan, Psalm), что повышает надёжность кода.
Начиная с PHP 8.0, интерфейсы могут содержать методы по умолчанию (default interface methods), заимствованные из Java. Это позволяет добавлять новые методы в существующие интерфейсы без нарушения обратной совместимости — классы, уже реализующие интерфейс, могут не переопределять новый метод, если его реализация по умолчанию приемлема. Однако такая практика требует осторожности: чрезмерное использование методов по умолчанию может привести к "размыванию" контракта и снижению явности.
Сравнение с таблицей и примерами — Абстракция в ООП. В PHP:
| Критерий | interface | abstract class |
|---|---|---|
| Роль | Контракт способности (can-do) | Частичная реализация семейства (is-a) |
| Ключевые слова | implements, несколько штук | extends, один родитель |
| Состояние | Константы; с PHP 8.0+ — методы по умолчанию | Свойства и реализованные методы |
| Общий код без иерархии | Часто интерфейс + trait | — |
Абстрактный класс (abstract class) подходит, когда наследники — одно семейство и им нужны общие поля или конструктор.
Интерфейс подходит, когда важен только контракт для полиморфного кода. Если нужна ещё и готовая реализация, но типы не родственники, в PHP обычно подключают trait, а не наследуют абстрактный класс ради одного метода.
Трейты — повторное использование кода без наследования
Трейт (trait) — это механизм горизонтального повторного использования кода, введённый в PHP 5.4. Он позволяет внедрять набор методов (и, начиная с PHP 8.0, даже свойств) в класс без установления иерархической связи "родитель–потомок". Трейты особенно полезны в условиях одиночного наследования, когда требуется совместить поведение из нескольких независимых источников.
Трейт объявляется ключевым словом trait, а подключается в класс с помощью use. В отличие от интерфейсов, трейты содержат реализацию, но, в отличие от классов, не могут быть инстанцированы напрямую.
Пример:
Код ITЗагрузка примера кода…
Разбор:
trait Loggableинкапсулирует переиспользуемый методlog.use Loggable;внедряет этот метод в класс без наследования.- Метод
process()использует трейт как обычный метод экземпляра. - Трейты полезны для кросс-срезной технической логики.
- При этом доменные зависимости обычно лучше передавать через композицию.
После подключения трейта Loggable в класс Service, метод log() становится доступен как обычный метод экземпляра, будто он был объявлен непосредственно в теле класса.
Трейты могут содержать как публичные, так и защищённые методы и свойства. Начиная с PHP 8.2, трейты получили поддержку свойств, включая типизированные и абстрактные свойства — это позволяет выносить в трейты поведение и часть состояния.
Разрешение конфликтов
Если два или более трейта предоставляют метод с одинаковым именем, PHP не может разрешить конфликт автоматически и генерирует фатальную ошибку. Для явного управления используется конструкция insteadof и as:
Код ITЗагрузка примера кода…
Разбор:
- Оба трейта содержат метод с одинаковым именем
greet, что создает конфликт. A::greet insteadof Bвыбирает реализацию из трейтаA.B::greet as greetFromBсохраняет вторую реализацию под алиасом.- Механизм позволяет явно и безопасно разрешать коллизии методов.
- Такой контроль делает смешивание нескольких трейтов предсказуемым.
Здесь greet() вызовет реализацию из трейта A, а greetFromB() — из B. Это позволяет сохранить обе реализации при необходимости.
Влияние на инкапсуляцию и иерархию
Трейты нарушают строгую иерархическую модель, но не нарушают инкапсуляцию — методы трейта выполняются в контексте того класса, в который они подключены, и имеют доступ к $this, включая приватные и защищённые члены хост-класса. Обратное неверно: хост-класс не имеет прямого доступа к приватным членам трейта, но может вызывать его публичные и защищённые методы.
Трейты не следует использовать как замену композиции. Если поведение логически независимо и может быть представлено как отдельный объект (например, Logger, Validator), предпочтительнее внедрять его через зависимость. Трейты оправданы, когда речь идёт о техническом кросс-куттинг-концерне (cross-cutting concern) — например, ведение лога, измерение времени выполнения, кэширование вызовов, — где внедрение отдельного объекта избыточно или неудобно.
Статические члены — поведение и данные на уровне класса
В дополнение к методам и свойствам экземпляра, PHP поддерживает статические члены — они принадлежат самому классу. Они инициализируются один раз при первом обращении к классу и существуют в единственном экземпляре на всё время выполнения скрипта.
Статическое свойство объявляется с модификатором static, доступ к нему осуществляется через self::, static:: или имя класса (ClassName::$property). Аналогично — статические методы.
Код ITЗагрузка примера кода…
Разбор:
private static int $countпринадлежит классу, а не экземпляру.- Конструктор увеличивает общий счетчик при каждом
new Counter(). public static function getCount()читает состояние без создания объекта.Counter::getCount()вызывается через оператор::.- Пример показывает модель общего состояния на уровне класса.
Здесь $count — общий счётчик для всех экземпляров. Каждое создание объекта увеличивает его значение, а getCount() позволяет получить текущее состояние без необходимости создавать объект.
Ключевое различие между self:: и static:: — позднее статическое связывание (late static binding), введённое в PHP 5.3. self:: всегда ссылается на класс, в котором метод объявлен, тогда как static:: ссылается на класс, с которым был вызван метод (возможно, дочерний). Это критично при наследовании статических членов.
Код ITЗагрузка примера кода…
Разбор:
self::привязывается к классу, где метод объявлен (Base).static::использует позднее статическое связывание и учитывает класс вызова.- В
Child::who()возвращаетсяBase, потому что метод используетself. - В
Child::who2()возвращаетсяChild, потому что метод используетstatic. - Разница критична при проектировании расширяемых статических API.
Статические члены удобны для фабрик, реестров, утилитарных классов, но их чрезмерное применение ведёт к глобальному состоянию, что затрудняет тестирование и нарушает принцип единственной ответственности. В современных приложениях статические методы стараются минимизировать в пользу внедрения зависимостей и инверсии управления.
Магические методы — управление поведением объекта на уровне языка
PHP предоставляет набор магических методов — специальных методов с зарезервированными именами, которые вызываются автоматически при выполнении определённых операций с объектом. Они позволяют переопределить стандартное поведение — доступ к несуществующим свойствам, вызов несуществующих методов, сериализацию, строковое представление и др.
Наиболее часто используемые:
__construct()— конструктор (уже рассмотрен).__destruct()— деструктор; вызывается при уничтожении объекта (например, при выходе из области видимости или явномunset). Используется для освобождения ресурсов (файловые дескрипторы, соединения).__get($name)и__set($name, $value)— вызываются при чтении и записи недоступного (например, несуществующего или приватного) свойства. Позволяют реализовать динамические свойства, lazy loading, прокси.__call($name, $arguments)и__callStatic($name, $arguments)— вызываются при вызове несуществующего метода экземпляра или статического метода соответственно. Основа для реализации прокси, фасадов, Fluent Interface.__toString()— вызывается при попытке использовать объект как строку (например, вecho). Должен возвращать строку, иначе — фатальная ошибка.__invoke()— позволяет вызывать объект как функцию:$obj().
Пример динамического доступа:
Код ITЗагрузка примера кода…
Разбор:
__setперехватывает запись в недоступные или несуществующие свойства.__getперехватывает чтение и возвращает значение из внутреннего массива.- Такой шаблон реализует динамическую конфигурацию и lazy-доступ.
- Магические методы повышают гибкость, но скрывают явные контракты полей.
- Для production-кода важно ограничивать такие точки динамики.
Магические методы мощны, но их следует использовать с осторожностью — они делают поведение объекта менее прозрачным, затрудняют статический анализ и могут скрывать ошибки (например, опечатка в имени свойства не вызовет ошибку, а создаст новое динамическое поле). В production-коде рекомендуется ограничивать их применение чётко обозначенными паттернами (например, только __toString() и __invoke() — в DTO и коллбэках).
Анонимные классы — локальная реализация интерфейсов и адаптеров
Начиная с PHP 7.0, доступны анонимные классы — объявление и инстанцирование класса в одном выражении. Они полезны для создания одноразовых реализаций интерфейсов, моков в тестах или адаптеров без загрязнения глобального пространства имён.
$logger = new class implements Logger {
public function log(string $message): void {
syslog(LOG_INFO, $message);
}
};
$manager = new UserManager($logger);
Разбор:
new class ...создает анонимный класс прямо в месте использования.implements Loggerгарантирует совместимость с зависимостьюUserManager.- Метод
log()задает одноразовую реализацию без отдельного именованного класса. - Прием удобен в тестах и локальных адаптерах.
- Код уменьшает шум в проекте, когда реализация нужна в одном месте.
Анонимный класс может наследовать от другого класса, реализовывать интерфейсы, использовать трейты и содержать свойства и методы — как обычный класс. Отличие лишь в том, что он не имеет имени и объявляется inline.
Синтаксически анонимный класс похож на функцию: new class (аргументы) extends … implements … { … }. Аргументы передаются в конструктор, если он определён.
Анонимные классы часто применяются в тестировании (PHPUnit поддерживает их напрямую), при создании замыканий с состоянием или адаптации сторонних библиотек под собственный интерфейс без создания именованного класса.
Клонирование объектов
Как уже отмечалось, присваивание объекта в PHP создаёт новую ссылку на тот же экземпляр. Для получения независимой копии используется оператор clone.
По умолчанию clone выполняет поверхностное копирование — все свойства копируются по значению, но если свойство содержит ссылку на другой объект, то копируется лишь сама ссылка — оба объекта (оригинал и клон) будут указывать на один и тот же вложенный объект.
Для управления процессом клонирования класс может определить магический метод __clone(). Он вызывается автоматически после создания копии и может содержать логику глубокого копирования или сброса состояния:
class User {
public string $name;
public DateTime $createdAt;
public function __construct(string $name) {
$this->name = $name;
$this->createdAt = new DateTime();
}
public function __clone() {
$this->createdAt = clone $this->createdAt; // глубокое копирование DateTime
}
}
Разбор:
- Оператор
cloneпо умолчанию делает поверхностное копирование объекта. __clone()позволяет вручную скорректировать поведение после клонирования.- Повторный
clone $this->createdAtсоздает независимый объект времени. - Это устраняет общий mutable-ссылочный объект между оригиналом и копией.
- Прием важен для корректной изоляции состояния.
Без __clone() оба объекта ($user и clone $user) имели бы общую ссылку на один объект DateTime, и изменение времени у одного повлияло бы на другого.
Сериализация и десериализация
Сериализация — преобразование объекта в строку (обычно для хранения или передачи), десериализация — обратный процесс. В PHP это делается функциями serialize() и unserialize().
По умолчанию сериализуются все нестатические свойства, включая приватные (но с модификатором класса в имени для избежания конфликтов). Однако такое поведение не всегда приемлемо: например, ресурсы (соединения с БД) нельзя сериализовать.
Для контроля процесса используются магические методы:
__sleep()— вызывается перед сериализацией; должен вернуть массив имён свойств, подлежащих сохранению. Может использоваться для закрытия ресурсов.__wakeup()— вызывается после десериализации; восстанавливает состояние (например, переоткрывает соединения).
Код ITЗагрузка примера кода…
Разбор:
__sleep()определяет, какие свойства попадут в сериализованное представление.- Здесь сохраняется только
dsn, а несериализуемый ресурсPDOисключается. __wakeup()пересоздает соединение послеunserialize.- Шаблон помогает безопасно переносить объект между процессами или кешами.
- Важно явно восстанавливать все ресурсы, которые не живут в строковом дампе.
Начиная с PHP 7.4, рекомендуется использовать механизм магических методов сериализации (__serialize() и __unserialize()), который безопаснее и явнее:
public function __serialize(): array {
return ['dsn' => $this->dsn];
}
public function __unserialize(array $data): void {
$this->dsn = $data['dsn'];
$this->pdo = new PDO($this->dsn);
}
Разбор:
__serialize()возвращает точный массив данных для сохранения состояния.__unserialize()получает этот массив и восстанавливает объект вручную.- Механизм современнее и надежнее пары
__sleep/__wakeup. - Такой подход делает сериализацию явной и проще контролируется при рефакторинге.
- Пример показывает безопасный цикл восстановления
PDOпо сохраненномуdsn.
Эти методы не подвержены проблемам с именованием приватных свойств и более совместимы с будущими версиями.
Сравнение PHP с Java
| Тема | PHP | Java |
|---|---|---|
| Модель типов | динамическая + опциональные типы (7+) | статическая |
| Конструктор | __construct, promotion (8+) | имя класса / compact record |
| Перегрузка методов | нет (последнее объявление побеждает) | да |
| Множественное наследование классов | нет | нет |
| Примеси | trait с insteadof / as | интерфейсы с default methods |
| Магические методы | __get, __call, __toString | ограниченный набор (toString, equals) |
| Передача объектов | по ссылке | по ссылке на объект |
null | допустим везде без ? (до strict) | Optional, nullable types |
| Namespace | namespace, PSR-4 autoload | package, module system |
| Анонимные классы | new class { } | локальные классы (ограниченно) |
| Сериализация | serialize / __sleep | Serializable, JSON APIs |
PHP в веб-стеке (Laravel, Symfony) использует те же идеи DI и интерфейсов, что Java Spring — но рантайм остаётся интерпретируемым и динамическим.
Типичные ошибки
| Ошибка | Последствие | Что делать |
|---|---|---|
| Публичные свойства без инвариантов | некорректное состояние ($user->balance = -1) | private + методы; readonly для DTO |
Забыли parent::__construct() | неинициализированные поля родителя | явный вызов в каждом конструкторе наследника |
| Трейты вместо композиции | класс с десятком use и размытой ответственностью | внедрять зависимости через конструктор |
Злоупотребление __get/__set | опечатки не ловятся, IDE не подсказывает | явные свойства и методы |
| Ожидание перегрузки как в Java | второй метод с тем же именем перезапишет первый | разные имена или optional parameters |
$a = $b для копии | оба изменяют один объект | clone + __clone для глубокого копирования |
| Интерфейс с логикой в default methods без нужды | размытый контракт | default methods — только для обратной совместимости |
| Статические синглтоны везде | глобальное состояние, сложные тесты | DI-контейнер фреймворка |
Чеклист проектирования класса в PHP
- Инварианты — все обязательные поля заданы в
__construct; недопустимые значения отклоняются. - Видимость — минимум
public; состояние —privateилиprotected. - Контракт — общие зависимости через
interface, не через конкретный класс. - Наследование — только отношение является; иначе композиция или
traitдля технического кросс-ката. - Магия —
__call/__getтолько в инфраструктуре (ORM, прокси), не в домене. - Типы — аргументы и возврат с
declare(strict_types=1);в новых файлах. - Тест — PHPUnit с моками интерфейсов, не конкретных HTTP-клиентов.
См. также
- Пространства имён и автозагрузка
- Современный PHP 8 — enum, readonly, атрибуты
- Функции и замыкания в PHP
- Обработка исключений в прикладном коде PHP
- ООП в Java — номинальная модель для сравнения
Учебные примеры ООП
Небольшие самодостаточные программы, которые показывают классы, объекты, инкапсуляцию, наследование и взаимодействие нескольких типов на одной предметной области.
Класс и объект
Чертёж класса Figure и конкретные объекты — круг и квадрат.
Код ITЗагрузка примера кода…
Банковский счёт
Инкапсуляция: скрытое поле баланса и методы deposit/withdraw.
Код ITЗагрузка примера кода…
Наследование
Родитель Animal и дочерние Cat и Dog с общим eat() и своим speak().
Код ITЗагрузка примера кода…
Смартфон
Состояние объекта: заряд батареи, звонки и подзарядка.
Код ITЗагрузка примера кода…
Студент
Список оценок, средний балл и проходной порог.
Код ITЗагрузка примера кода…
Корзина покупок
Взаимодействие Product, Cart и Order при оформлении заказа.
Код ITЗагрузка примера кода…
Автомобиль
Пробег, расход топлива и напоминание о техобслуживании.
Код ITЗагрузка примера кода…
Пользователь
Скрытый пароль, вход в систему и публикация сообщений.
Код ITЗагрузка примера кода…