Класс как чертёж, объект как конкретный экземпляр
В PHP класс описывает состояние и поведение: свойства хранят данные, методы выполняют операции, константы задают неизменяемые значения уровня класса. Объект появляется через new и живёт как конкретный экземпляр этого класса. Если в Массивы как ordered map структура данных часто была «мешком» значений, то класс добавляет границы: какие данные допустимы, кто может их менять и какие операции вообще имеют смысл.
<?php
final class Money
{
public function __construct(
private readonly int $amount,
private readonly string $currency,
) {}
public function format(): string
{
return $this->amount . ' ' . $this->currency;
}
}
$price = new Money(1200, 'RUB');
echo $price->format(); // 1200 RUBЗдесь объект Money не просто заменяет массив ['amount' => 1200, 'currency' => 'RUB']. Он фиксирует инвариант: снаружи нельзя случайно переписать amount, а форматирование находится рядом с данными, к которым относится. Это базовая причина, почему объектная модель важна до разговора про Наследование, интерфейсы и трейты.
flowchart TD
Class[Класс: свойства, методы, константы] --> New[`new Class(...)`]
New --> ObjectA[Объект A: своё нестатическое состояние]
New --> ObjectB[Объект B: своё нестатическое состояние]
Class --> Static[static и const: уровень класса]
ObjectA --> Methods[Методы используют `$this`]
ObjectB --> Methods
Static --> NoThis[В static-методе нет `$this`]Свойства, методы и $this
Свойство — переменная, принадлежащая объекту или классу. Метод — функция, объявленная внутри класса. В нестатическом методе доступна псевдопеременная $this: она указывает на объект, для которого вызван метод.
<?php
final class Counter
{
private int $value = 0;
public function increment(): void
{
$this->value++;
}
public function value(): int
{
return $this->value;
}
}
$counter = new Counter();
$counter->increment();
echo $counter->value(); // 1В современном PHP свойства лучше объявлять явно и типизировать. Динамические свойства, когда код просто пишет $object->unknown = 123, в новых версиях PHP считаются плохим сигналом: они легко прячут опечатки и плохо дружат со статическим анализом из PHPDoc, generics и статический анализ.
Видимость: public, protected, private
Видимость отвечает на вопрос «кто может обращаться к члену класса». public доступен отовсюду. private доступен только внутри самого класса. protected доступен внутри класса и его потомков; он относится к теме наследования, но знать его стоит уже здесь.
<?php
final class User
{
public function __construct(
private string $email,
private bool $active = true,
) {}
public function email(): string
{
return strtolower($this->email);
}
public function deactivate(): void
{
$this->active = false;
}
public function isActive(): bool
{
return $this->active;
}
}
$user = new User('Admin@Example.test');
echo $user->email();
// $user->email = 'broken'; // Ошибка: свойство private.Практическое правило: поля состояния чаще делают private, а наружу открывают методы, которые выражают намерение. Не «поставить active = false», а deactivate(). Так класс остаётся местом, где можно проверить правила: например, запретить деактивацию владельца аккаунта или записать событие.
Конструктор, promoted properties и деструктор
Конструктор __construct() вызывается при создании объекта. С PHP 8 удобно использовать constructor property promotion: параметры конструктора одновременно объявляют свойства класса.
<?php
final class ApiToken
{
public function __construct(
public readonly string $value,
public readonly DateTimeImmutable $expiresAt,
) {}
public function isExpired(DateTimeImmutable $now): bool
{
return $now >= $this->expiresAt;
}
}readonly означает, что свойство можно инициализировать один раз, обычно в конструкторе, а потом нельзя переназначить. Это не делает весь объект «глубоко immutable»: если readonly-свойство хранит другой изменяемый объект, сам вложенный объект всё ещё может меняться. Для дат обычно берут DateTimeImmutable, как в Дата и время, чтобы не получить скрытую мутацию.
Деструктор __destruct() вызывается, когда объект уничтожается, но в прикладном веб-коде на него редко стоит опирать важную бизнес-логику. Он годится для освобождения ресурсов или логирования, но не для действий, которые обязаны выполниться в точный момент. Для файлов, сетевых соединений и транзакций лучше иметь явные методы закрытия/commit/rollback; это особенно важно рядом с темами Файловая система и stream wrappers и Транзакции и режимы ошибок PDO.
static, константы и состояние уровня класса
Нестатические свойства принадлежат объекту. Статические свойства и методы принадлежат классу. Их вызывают через ::, а внутри статического метода нет $this, потому что нет конкретного объекта.
<?php
final class Slug
{
public const SEPARATOR = '-';
public static function fromTitle(string $title): string
{
return strtolower(str_replace(' ', self::SEPARATOR, trim($title)));
}
}
echo Slug::fromTitle('PHP Objects'); // php-objectsСтатические методы хорошо подходят для чистых фабрик и небольших утилит без состояния. Статические свойства опаснее: они создают общее изменяемое состояние на весь процесс. В классическом PHP-FPM запросы обычно короткоживущие, но в долгоживущих воркерах из PHP-FPM, RoadRunner и долгоживущие воркеры такое состояние может неожиданно переживать один запрос и влиять на следующий.
Константы класса похожи на public const, private const или protected const значения, которые не меняются во время выполнения. Их удобно использовать для локальных правил класса, но если набор значений является частью доменной модели, стоит сравнить подход с Enum в PHP.
Объекты присваиваются не как массивы
В PHP переменная с объектом хранит доступ к экземпляру. При обычном присваивании объект не копируется: две переменные указывают на один и тот же экземпляр.
<?php
final class Box
{
public function __construct(public string $label) {}
}
$a = new Box('first');
$b = $a;
$b->label = 'second';
echo $a->label; // secondЕсли нужна отдельная копия, используют clone. Но clone по умолчанию делает неглубокую копию: вложенные объекты останутся теми же самыми, если не переопределить __clone().
Сравнение тоже важно. === проверяет, что это один и тот же экземпляр. == сравнивает объекты по классу и значениям свойств. В прикладном коде для identity обычно нужен ===, а для доменного равенства лучше написать явный метод вроде equals().
<?php
final class Email
{
public function __construct(private string $value) {}
public function equals(self $other): bool
{
return strtolower($this->value) === strtolower($other->value);
}
}
$a = new Email('Admin@Example.test');
$b = new Email('admin@example.test');
var_dump($a === $b); // false: разные экземпляры
var_dump($a->equals($b)); // true: одинаковый смыслКогда класс действительно нужен
Класс полезен, когда вокруг данных появляются правила: нормализация email, запрет отрицательной цены, форматирование, lifecycle, права доступа, lazy-загрузка, сравнение по смыслу. Если данных мало и поведения нет, массив или простой DTO может быть честнее. Если же в коде появляются одинаковые проверки рядом с каждым массивом, это знак, что модель пора перенести в объект.
В PHP-коде хорошая объектная модель обычно начинается не с иерархии, а с инкапсуляции: спрятать состояние, назвать операции и сделать невозможные состояния труднее создать. Наследование, интерфейсы и трейты расширяют эту базу, но не заменяют её.
См. также
- Наследование, интерфейсы и трейты — как классы связываются контрактами, потомками и переиспользованием методов.
- Неймспейсы и use — как организовать имена классов в проекте.
- Autoloading и PSR-4 — как PHP находит файлы с классами без ручных
require. - Enum в PHP — когда набор констант лучше оформить как enum.
- Атрибуты и Reflection — как читать метаданные классов, свойств и методов во время выполнения.
- PHPDoc, generics и статический анализ — как анализаторы помогают проверять объектные модели до запуска кода.