Как проинициализировать класс B при инициализации класса A?

«Как проинициализировать класс B при инициализации класса A?» — вопрос из категории ООП, который задают на 24% собеседований PHP Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В PHP есть несколько способов создать экземпляр одного класса внутри другого. Выбор зависит от степени связанности классов и требований к тестируемости.

1. Создание в конструкторе (Жёсткая зависимость) Самый простой, но наименее гибкий способ. Класс A жёстко зависит от конкретной реализации B.

class B {
    public function doSomething(): string {
        return "Work done by B";
    }
}

class A {
    private B $b;

    public function __construct() {
        // Класс A сам создаёт экземпляр B
        $this->b = new B();
    }

    public function run(): void {
        echo $this->b->doSomething();
    }
}

// Использование
$a = new A(); // Внутри автоматически создастся B
$a->run(); // Output: Work done by B

2. Внедрение зависимости через конструктор (Dependency Injection - DI) Предпочтительный способ. Класс A получает уже готовый объект B извне. Это делает код гибким и легко тестируемым (можно подменить B на mock-объект в тестах).

class A {
    private B $b;

    // B "внедряется" в A извне
    public function __construct(B $b) {
        $this->b = $b;
    }

    public function run(): void {
        echo $this->b->doSomething();
    }
}

// Использование
$b = new B();
$a = new A($b); // Передаём экземпляр B в A
$a->run();

// В Laravel или другом DI-контейнере это происходит автоматически:
// $a = app(A::class); // Контейнер сам создаст B и передаст его в A

3. Ленивая инициализация (Lazy Initialization) Объект B создаётся только в момент первого обращения к нему, а не при создании A. Полезно, если создание B — ресурсоёмкая операция, которая может не понадобиться.

class A {
    private ?B $b = null; // Инициализируем как null

    public function getB(): B {
        if ($this->b === null) {
            $this->b = new B(); // Создаём только здесь
        }
        return $this->b;
    }
}

// Использование
$a = new A(); // Класс B ещё не создан
// ... какая-то другая работа ...
$a->getB()->doSomething(); // Класс B создаётся в этот момент

Итог: Для чистого, тестируемого кода я почти всегда выбираю внедрение зависимости (вариант 2). Фреймворки вроде Laravel или Symfony имеют мощные DI-контейнеры, которые полностью берут на себя управление созданием и связыванием таких объектов.