PHP конструктор: полное описание и примеры

Раздел: ООП в PHP -> Конструкторы

Конструктор класса в PHP: основы и варианты использования

Конструктор это специальный метод, который автоматически вызывается при создании объекта с помощью оператора new. Его основная цель инициализация свойств объекта и выполнение начальной настройки. В PHP конструктор обозначается именем __construct.

class Person {
    public string $name;

    public function __construct(string $name) {
        $this->name = $name;
    }
}

$person = new Person('Алексей');
echo $person->name; // Алексей

Php class construct (конструктор классов в php)

Если конструктор не объявлен, PHP использует пустой конструктор, унаследованный от класса stdClass (или родителя). При передаче в конструктор меньшего количества аргументов, чем объявлено (без значений по умолчанию), возникает фатальная ошибка ArgumentCountError.

Типичная ошибка

Попытка передать аргументы в конструктор в неправильном порядке или с неверными типами. Решение: использовать типизированные параметры и именованные аргументы (PHP 8+).

Как сделать конструктор с необязательными параметрами?

Можно задать значения по умолчанию для одного или нескольких параметров. Параметры с умолчанием должны идти после обязательных.

class User {
    public string $name;
    public int $age;

    public function __construct(string $name, int $age = 18) {
        $this->name = $name;
        $this->age = $age;
    }
}

$user1 = new User('Мария'); // возраст 18
$user2 = new User('Иван', 25);

Цель: упростить вызов, когда значение не требуется указывать каждый раз. Проблема: если позже добавляется обязательный параметр после необязательного, порядок нарушится.

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

Если параметров много и некоторые имеют значения по умолчанию, легко запутаться. Рекомендуется использовать именованные аргументы (PHP 8).

Как передавать параметры конструктора по имени, не соблюдая порядок?

В PHP 8 можно передавать аргументы конструктора по имени, игнорируя порядок объявления. Это особенно удобно при большом количестве необязательных параметров.

class Config {
    public function __construct(
        public string $host = 'localhost',
        public int $port = 3306,
        public string $dbname = 'test'
    ) {}
}

$config = new Config(port: 5432, dbname: 'production');
// host остаётся 'localhost'

Цель: повышение читаемости кода, возможность пропустить необязательные параметры. Проблема: при именованных аргументах легко ошибиться в названии параметра (ошибка времени выполнения).

Типичная ошибка: неверное имя параметра

Если изменено имя параметра в классе, все именованные вызовы станут невалидными. Решение: следить за рефакторингом.

Как сократить код, объединив объявление свойств и присваивание в конструкторе?

Промоция свойств (property promotion) появилась в PHP 8. Свойства объявляются прямо в списке параметров конструктора с указанием модификатора доступа.

class Point {
    public function __construct(
        public int $x,
        public int $y = 0
    ) {}
}

$point = new Point(x: 10);
// Свойства $point->x и $point->y создаются автоматически

Цель: уменьшение шаблонного кода, улучшение читаемости. Проблема: нельзя использовать одновременно с явным объявлением свойства с тем же именем; также нельзя изменять модификатор в процессе (например, сделать public readonly).

Ошибка: двойное объявление свойства

Если свойство объявлено в теле класса и одновременно в промоции, возникает фатальная ошибка. Решение: удалить явное объявление.

Как в дочернем классе вызвать конструктор родителя?

Для вызова родительского конструктора используется parent::__construct().

class Animal {
    public string $name;
    public function __construct(string $name) {
        $this->name = $name;
    }
}

class Dog extends Animal {
    public string $breed;

    public function __construct(string $name, string $breed) {
        parent::__construct($name); // вызов конструктора Animal
        $this->breed = $breed;
    }
}

$dog = new Dog('Рекс', 'Овчарка');

Цель: переиспользование кода инициализации родителя. Проблема: если родительский конструктор изменил сигнатуру, нужно обновлять все дочерние классы. Также если parent::__construct не вызван, родительская инициализация не произойдет.

Ошибка: забыли вызвать parent::__construct

Свойства родителя останутся неинициализированными. Рекомендуется всегда вызывать родительский конструктор, если он не пустой.

Как обеспечить правильные типы аргументов конструктора?

Типизация параметров и включение строгой типизации (declare(strict_types=1)) помогают избежать неявных преобразований.

declare(strict_types=1);

class Order {
    public function __construct(
        private int $id,
        private float $amount
    ) {}
}

$order = new Order(1, 99.99); // корректно
// $order = new Order('1', 99.99); // TypeError в строгом режиме

Цель: контроль типов на этапе компиляции (в PHP 8 - на этапе выполнения). Проблема: в нестрогом режиме PHP может преобразовать строку в число, что иногда приводит к скрытым ошибкам.

Типичная ошибка: передача строки вместо числа

Если ожидается int, а передана строка '123', в нестрогом режиме она преобразуется, но может вызвать неожиданное поведение. Включение strict_types решает проблему.

Как передать множество параметров в виде массива?

Иногда удобнее передать конфигурацию или набор опций в виде ассоциативного массива.

class DatabaseConnection {
    private string $host;
    private int $port;

    public function __construct(array $config) {
        $this->host = $config['host'] ?? 'localhost';
        $this->port = (int)($config['port'] ?? 3306);
        // можно добавить валидацию
    }
}

$db = new DatabaseConnection(['host' => 'db.example.com', 'port' => 5432]);

Цель: гибкость, не нужно запоминать порядок параметров. Проблема: отсутствует автозаполнение в IDE, необходимо проверять существование ключей и типы значений.

Ошибка: обращение к несуществующему ключу без проверки

Если ключ отсутствует и не используется ??, возникает Warning и null. Решение: использовать оператор объединения с null или проверять array_key_exists.

Расширенные примеры использования конструктора

Конструктор с только для чтения свойствами (readonly, PHP 8.1)

Свойства, объявленные как readonly, могут быть установлены только один раз, обычно в конструкторе.

Пример
class ImmutablePoint {
    public function __construct(
        public readonly int $x,
        public readonly int $y
    ) {}
}

$point = new ImmutablePoint(10, 20);
// $point->x = 30; // Ошибка: cannot modify readonly property
Fatal error: Uncaught Error: Cannot modify readonly property ImmutablePoint::$x

Цель: создание неизменяемых объектов (Value Objects). Проблема: readonly свойства нельзя использовать с типами, допускающими null, если они не инициализированы.

Конструктор в трейте

Трейты могут содержать конструктор, который будет вызван, если класс не определяет свой собственный.

Пример
trait Timestampable {
    public DateTime $createdAt;

    public function __construct() {
        $this->createdAt = new DateTime();
    }
}

class Article {
    use Timestampable;

    public string $title;

    public function __construct(string $title) {
        $this->title = $title;
    }
}

$article = new Article('Новости');
echo $article->createdAt->format('Y-m-d');
2025-01-29 (текущая дата)

Важно: если класс переопределяет конструктор, трейт-конструктор не вызывается автоматически. Для вызова трейтового конструктора нужно явно использовать parent::__construct() (но трейт не является родителем). Вместо этого рекомендуется использовать паттерн с фабриками или инициализацией через отдельный метод.

Ошибка: трейт-конструктор не срабатывает

Если в классе объявлен свой __construct, трейтовый не вызывается. Решение: не объявлять конструктор в классе, либо переработать архитектуру.

Фабричный статический метод вместо множественных конструкторов

PHP не поддерживает несколько конструкторов с разными сигнатурами. Альтернатива - приватный конструктор и статические фабричные методы.

Пример
class User {
    private string $name;

    private function __construct(string $name) {
        $this->name = $name;
    }

    public static function fromEmail(string $email): self {
        $name = explode('@', $email)[0];
        return new self($name);
    }

    public static function fromId(int $id): self {
        // загрузка из БД
        $name = "User_$id";
        return new self($name);
    }
}

$user1 = User::fromEmail('ivan@example.com');
$user2 = User::fromId(42);

Цель: создать объект разными способами без путаницы в параметрах. Проблема: приватный конструктор скрыт, все создание идет через фабрики.

Валидация в конструкторе и выброс исключений

Конструктор может проверять входные данные и выбрасывать исключения при неверных значениях.

Пример
class EmailAddress {
    private string $email;

    public function __construct(string $email) {
        if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Некорректный email: $email");
        }
        $this->email = $email;
    }
}

try {
    $email = new EmailAddress('invalid');
} catch (InvalidArgumentException $e) {
    echo $e->getMessage();
}
Некорректный email: invalid

Цель: гарантировать, что объект всегда находится в валидном состоянии. Проблема: при наследовании валидация может усложниться.

Использование match в конструкторе для стратегий

Можно выбирать реализацию в зависимости от переданного параметра.

Пример
interface Handler {
    public function handle(): void;
}

class AHandler implements Handler {
    public function handle(): void { echo "Обработчик A"; }
}

class BHandler implements Handler {
    public function handle(): void { echo "Обработчик B"; }
}

class App {
    private Handler $handler;

    public function __construct(string $type) {
        $this->handler = match($type) {
            'a' => new AHandler(),
            'b' => new BHandler(),
            default => throw new InvalidArgumentException("Неизвестный тип: $type")
        };
    }

    public function run(): void {
        $this->handler->handle();
    }
}

$app = new App('a');
$app->run();
Обработчик A

Цель: отделение логики выбора от клиентского кода. Проблема: конструктор становится ответственным за создание зависимостей, что затрудняет тестирование.

Конструктор с внедрением зависимостей (DI)

Класс может принимать готовые объекты (сервисы) через конструктор.

Пример
class Logger {
    public function log(string $msg): void {
        echo "[LOG] $msg";
    }
}

class UserService {
    private Logger $logger;

    public function __construct(Logger $logger) {
        $this->logger = $logger;
    }

    public function createUser(string $name): void {
        // создание пользователя
        $this->logger->log("Создан пользователь $name");
    }
}

$logger = new Logger();
$service = new UserService($logger);
$service->createUser('Анна');
[LOG] Создан пользователь Анна

Цель: слабая связанность и простое тестирование (можно подменить Logger на mock). Проблема: при большом количестве зависимостей конструктор становится громоздким.

Конструктор классов в PHP - comments

En
Php class construct (php)