Конструкторы и деструкторы
Конструктор
__construct() вызывается автоматически при new.
class User
{
public function __construct(
public string $name,
public int $age,
) {
echo "Создан $name\n";
}
}
$user = new User('Иван', 25); // Создан Иван
Задача: инициализация объекта.
Старый синтаксис
До PHP 8.0:
class User
{
public string $name;
public int $age;
public function __construct(string $name, int $age)
{
$this->name = $name;
$this->age = $age;
}
}
С 8.0 — promotion:
class User
{
public function __construct(
public string $name,
public int $age,
) {}
}
Значения по умолчанию
class Config
{
public function __construct(
public string $env = 'production',
public bool $debug = false,
public array $options = [],
) {}
}
$c1 = new Config(); // все дефолты
$c2 = new Config('local', true); // частично
$c3 = new Config(env: 'dev', debug: true); // named args
Валидация в конструкторе
Хорошая практика — защита инвариантов:
final class Email
{
public function __construct(
public readonly string $value,
) {
if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
throw new InvalidArgumentException("Некорректный email: $value");
}
}
}
new Email('not-an-email'); // InvalidArgumentException
Value Object не может быть создан с некорректными данными.
Приватный конструктор
Запрещает new снаружи:
class Singleton
{
private static ?self $instance = null;
private function __construct() {}
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
}
$s1 = Singleton::getInstance();
$s2 = Singleton::getInstance();
var_dump($s1 === $s2); // true
⚠️ Singleton — антипаттерн. Используй DI.
Фабричные конструкторы
Именованные конструкторы:
final readonly class Duration
{
private function __construct(
public int $seconds,
) {}
public static function fromSeconds(int $seconds): self
{
return new self($seconds);
}
public static function fromMinutes(int $minutes): self
{
return new self($minutes * 60);
}
public static function fromHours(int $hours): self
{
return new self($hours * 3600);
}
public function toMinutes(): int
{
return (int) ($this->seconds / 60);
}
}
$d = Duration::fromMinutes(5);
echo $d->seconds; // 300
Паттерн — несколько понятных способов создания.
self vs static
self — текущий класс:
class Foo
{
public function __construct()
{
// всегда Foo
}
}
static — вызывающий класс:
class Base
{
public static function create(): static
{
return new static();
}
}
class Child extends Base {}
$c = Child::create(); // Child
Наследование конструктора
Если у родителя есть конструктор — вызывать parent::__construct():
class BaseEntity
{
public function __construct(
public readonly int $id,
) {}
}
class User extends BaseEntity
{
public function __construct(
int $id,
public readonly string $email,
) {
parent::__construct($id);
}
}
Если не вызвать — родительский не сработает.
Деструктор
__destruct() вызывается при уничтожении объекта.
class File
{
private $handle;
public function __construct(string $path)
{
$this->handle = fopen($path, 'r');
}
public function __destruct()
{
if ($this->handle) {
fclose($this->handle);
}
}
}
Когда вызывается:
- Объект больше не используется — сборщик мусора.
- Скрипт завершается.
unset($obj).
Порядок вызова
Создание:
- Родитель
__construct. - Ребёнок
__construct.
Уничтожение:
- Ребёнок
__destruct. - Родитель
__destruct.
Проблемы с деструктором
⚠️ Не полагайся на порядок — PHP вызывает деструкторы по-разному.
⚠️ Исключения в деструкторе — плохо, могут сломать скрипт.
Рекомендация: не используй деструкторы для важной логики. Только для освобождения ресурсов.
Практика: соединение с БД
final class DatabaseConnection
{
private \PDO $pdo;
public function __construct(
string $host,
string $database,
string $user,
string $password,
) {
$dsn = "pgsql:host=$host;dbname=$database";
$this->pdo = new \PDO($dsn, $user, $password, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
]);
}
public function query(string $sql, array $params = []): array
{
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
return $stmt->fetchAll();
}
public function close(): void
{
$this->pdo = null;
}
}
Обычно закрывать явно не нужно — PHP сделает это сам.
Промоушен + валидация
final class Product
{
public function __construct(
public readonly string $name,
public readonly int $price,
) {
if ($price < 0) {
throw new InvalidArgumentException('Цена не может быть отрицательной');
}
if (empty($name)) {
throw new InvalidArgumentException('Название обязательно');
}
}
}
Несколько конструкторов
В PHP нет перегрузки конструктора. Используй статические методы:
final class Money
{
private function __construct(
public readonly int $amount,
public readonly string $currency,
) {}
public static function rub(int $amount): self
{
return new self($amount, 'RUB');
}
public static function usd(int $amount): self
{
return new self($amount, 'USD');
}
public static function zero(string $currency = 'RUB'): self
{
return new self(0, $currency);
}
}
$price = Money::rub(100000);
Практика: заказ
<?php
declare(strict_types=1);
enum OrderStatus: string
{
case Pending = 'pending';
case Paid = 'paid';
case Cancelled = 'cancelled';
}
final class Order
{
private array $items = [];
public function __construct(
public readonly string $id,
public readonly string $customerId,
private OrderStatus $status = OrderStatus::Pending,
private int $totalAmount = 0,
) {
if ($totalAmount < 0) {
throw new InvalidArgumentException('Сумма не может быть отрицательной');
}
}
public static function create(string $customerId): self
{
return new self(
id: bin2hex(random_bytes(8)),
customerId: $customerId,
);
}
public function addItem(string $productId, int $price, int $qty = 1): void
{
if ($this->status !== OrderStatus::Pending) {
throw new RuntimeException('Нельзя изменить оплаченный заказ');
}
$this->items[] = compact('productId', 'price', 'qty');
$this->totalAmount += $price * $qty;
}
public function getTotalAmount(): int
{
return $this->totalAmount;
}
public function pay(): void
{
if ($this->status !== OrderStatus::Pending) {
throw new RuntimeException('Заказ нельзя оплатить');
}
if (empty($this->items)) {
throw new RuntimeException('Заказ пустой');
}
$this->status = OrderStatus::Paid;
}
public function getStatus(): OrderStatus
{
return $this->status;
}
}
$order = Order::create('user-1');
$order->addItem('iphone', 100000, 2);
$order->pay();
echo $order->getStatus()->value; // paid
echo $order->getTotalAmount(); // 200000
Итог
__construct— при создании.- Promotion — короче.
- Валидация в конструкторе — защита инвариантов.
- Приватный конструктор — для Singleton/Factory.
- Статические фабрики — несколько способов создания.
parent::__construct()— для наследников.__destruct— редко, только для ресурсов.- Порядок: родитель → ребёнок при создании, наоборот при удалении.
Дальше — видимость: public, protected, private.