Усе створення обʼєктів у Yii2 проходить через одну точку: Yii::createObject(). Це тонка обгортка над Yii::$container->get(), а сам Yii::$container це екземпляр yii\di\Container, який створюється в Yii.php ще до застосунку. Приймає він три форми: імʼя класу, масив із ключем class і рештою публічних властивостей, або callable. Коли визначення для класу немає, контейнер просто резолвить конструктор: getDependencies() читає типи параметрів через рефлексію, підставляє замість класових типів Instance::of() і рекурсивно їх розгортає, а метадані рефлексії кешує, щоб не читати їх удруге. Скалярні параметри й ті, що мають default, контейнер не чіпає. Якщо клас реалізує yii\base\Configurable (тобто будь-який спадкоємець BaseObject), масив конфігу передається останнім аргументом конструктора і застосовується до init(); для звичайних класів властивості присвоюються після створення.
Другий реєстр, який плутають із контейнером, це сам застосунок. yii\web\Application наслідує Module, а той yii\di\ServiceLocator, тому секція components у конфігу це список визначень локатора. Звернення Yii::$app->db іде через __get() у ServiceLocator::get('db'), і ось там обʼєкт створюється при першому запиті (тим самим Yii::createObject()), кладеться в _components і далі повертається без перестворення. Компонент, отже, синглтон у межах запиту, до того ж ледачий: описані в конфігу, але жодного разу не викликані компоненти не займають ні памʼяті, ні звʼязків із БД. Винятки перелічені в bootstrap: ці id ініціалізуються одразу на старті. Ваші components мерджаться з coreComponents(), тому змінити одну властивість urlManager можна, не переписуючи його цілком. Зворотний бік розділення реєстрів: Yii::$container нічого не знає про db, і щоб класу дістався саме компонент застосунку, у конфігу ставлять Instance::of('db') або приймають властивість типу public $db = 'db' і розгортають її в init() через Instance::ensure($this->db, Connection::class), як роблять yii\rbac\DbManager і yii\caching\DbCache.
Різниця між definitions і singletons обходиться дорого саме тому, що на вигляд секції однакові. Yii::$container->set() (і секція definitions у конфігу, доступна з 2.0.11) означає «ось рецепт», і кожен get() виконує рецепт заново. Сервіс із внутрішнім кешем, пул зʼєднань або будь-що, що має бути одне на процес, реєструють через setSingleton() чи секцію singletons. Орієнтир при виборі: якщо два різні обʼєкти одного класу в одному запиті ламають логіку, потрібен синглтон; якщо обʼєкт має стан на одну операцію (білдер звіту, рендерер конкретного документа), новий екземпляр безпечніший. Найкорисніший побічний ефект того, що фреймворк усюди кличе createObject(), це глобальна підміна: Yii::$container->set(LinkPager::class, ['maxButtonCount' => 5]) змінює всі пейджери проєкту, set(ActiveForm::class, [...]) усі форми, а set(SomeVendorClass::class, MyClass::class) дозволяє замінити клас із vendor без копіювання коду. Працює це рівно доти, доки в коді не зʼявляється new.
Конфігурація в Yii2 це звичайні PHP-масиви. Basic-шаблон тримає config/web.php, console.php, db.php, params.php, і вхідний скрипт робить new yii\web\Application($config). Advanced-шаблон додає шари common/frontend/backend і зливає їх ArrayHelper::merge(), а файли *-local.php лежать у .gitignore і генеруються php init із теки environments. Команди config:cache тут немає й вона не потрібна, бо масиви й так кешує opcache, але звідси правило: у конфігу не місце обчисленням, які виконаються на кожен запит. .env теж не з коробки, тому секрети або в *-local.php, або через getenv(). Плюс специфіка Yii: аліаси (@app, @runtime, @webroot, свої) задаються в тому ж конфігу, а Yii::getAlias() використовує половина фреймворку.
З Laravel Yii2 розходиться в трьох місцях. У Laravel один контейнер на все, включно з тим, що в Yii2 живе в components; тут реєстрів два, і Yii::$app->set() та Yii::$container->set() не взаємозамінні. Різний і спосіб опису: Laravel описує створення closure у сервіс-провайдері з розділенням register/boot, Yii2 описує результат масивом публічних властивостей, а роль boot грає bootstrap і init() компонента. Найсильніше відрізняється обсяг автоматики. Laravel резолвить інтерфейси контекстно (when()->needs()->give()), тегує привʼязки, впорскує залежності в методи контролерів, jobs і closure; Yii2 має одне визначення на клас глобально, без контекстної привʼязки, зате з 2.0.36 вміє впорскувати в параметри екшена через bindInjectedParams() і в довільний callable через Container::invoke(). Статичний доступ є в обох (Yii::$app->mailer проти фасадів), і в обох він однаково заважає тестам, поки залежність не приходить через конструктор.
// config/web.php: components і container це два різні реєстри
use yii\db\Connection;
use app\service\{Geocoder, MailerInterface, PdfRenderer, SmtpMailer};
return [
'id' => 'app',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'], // створюється одразу, не ліниво
'components' => [ // ServiceLocator: один обʼєкт на запит
'db' => [
'class' => Connection::class, // 'class' + публічні властивості
'dsn' => 'pgsql:host=127.0.0.1;dbname=app',
'username' => 'app',
'password' => getenv('DB_PASSWORD'), // секрет не в git
'enableSchemaCache' => true,
],
],
'container' => [ // з 2.0.11 іде в Yii::$container
'definitions' => [
// новий обʼєкт на кожен createObject()
PdfRenderer::class => ['class' => PdfRenderer::class, 'dpi' => 150],
// глобальна підміна налаштувань класу фреймворку
yii\widgets\LinkPager::class => ['maxButtonCount' => 5],
],
'singletons' => [
MailerInterface::class => SmtpMailer::class,
// callable отримує ($container, $params, $config)
Geocoder::class => fn ($c) => new Geocoder(getenv('GEO_KEY')),
],
],
];
// --- будь-де в коді ---
$a = Yii::createObject(PdfRenderer::class); // те саме, що Yii::$container->get()
$b = Yii::createObject(PdfRenderer::class); // $a !== $b: definitions не синглтон
$m = Yii::$container->get(MailerInterface::class); // той самий до кінця запиту
Yii::$app->db === Yii::$app->get('db'); // компонент: локатор, не контейнер
Скажіть одним реченням: «у Yii2 два реєстри: контейнер за замовчуванням віддає новий обʼєкт, а компоненти застосунку це ліниві синглтони, і склеює їх `Yii::createObject()`». Далі одразу дайте практичний приклад глобальної підміни: `Yii::$container->set(LinkPager::class, ['maxButtonCount' => 5])` змінює всі пейджери, бо віджети створюються через `createObject()`.