Класс как чертёж, объект как конкретный экземпляр

В 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`]
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`]
Класс задаёт общую форму, объекты хранят собственное состояние, а `static` и `const` относятся к уровню класса.
Быстрое повторение
Почему объект `Money` с `private readonly int $amount` лучше передаёт модель цены, чем массив `['amount' => 1200, 'currency' => 'RUB']`?

Свойства, методы и $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 и статический анализ.

Быстрое повторение
В нестатическом методе PHP что означает `$this`?

Видимость: 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(). Так класс остаётся местом, где можно проверить правила: например, запретить деактивацию владельца аккаунта или записать событие.

Быстрое повторение
Почему состояние объекта обычно делают `private`, а наружу дают методы вроде `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-коде хорошая объектная модель обычно начинается не с иерархии, а с инкапсуляции: спрятать состояние, назвать операции и сделать невозможные состояния труднее создать. Наследование, интерфейсы и трейты расширяют эту базу, но не заменяют её.

См. также

Источники

  1. PHP Manual: The Basics
  2. PHP Manual: Visibility
  3. PHP Manual: Properties
  4. PHP Manual: Constructors and Destructors
  5. PHP Manual: Comparing Objects
  6. PHP Manual: Static Keyword
  7. PHP Manual: Class Constants
  8. PHP Manual: Object Cloning