У Yii2 немає прихованого фронт-контролера, який щось робить за вас: web/index.php видно повністю, і в ньому чотири дії. Визначаються константи YII_DEBUG і YII_ENV, підключається автозавантажувач Composer, підключається vendor/yiisoft/yii2/Yii.php (він реєструє власний автолоадер за аліасами класів і створює Yii::$container), читається масив конфігурації, і (new yii\web\Application($config))->run() запускає обробку. У конструкторі спершу відпрацьовує preInit(): з basePath виводиться аліас @app, ставляться @vendor, @runtime, @webroot, до ваших components домішуються дефолти з coreComponents(). Потім реєструється errorHandler, решта конфігу розкладається по властивостях, і init() викликає bootstrap() зі списком компонентів та класів, які мають піднятися ще до маршрутизації.
Далі run() кидає подію EVENT_BEFORE_REQUEST і передає Request у handleRequest(). Там Request::resolve() просить UrlManager::parseRequest() перетворити URL на пару «маршрут і параметри». З enablePrettyUrl => false маршрут просто читається з $_GET['r']; з увімкненим pretty URL перебираються rules, а якщо жодне не підійшло і enableStrictParsing вимкнено (це дефолт), шлях береться як маршрут напряму. Коли розібрати не вдалося взагалі, летить NotFoundHttpException. Отриманий маршрут іде в Module::runAction(), звідки народжується контролер, а з нього дія. Рядок, який повернула дія, стає $response->data; якщо дія повернула готовий обʼєкт Response, він піде користувачу як є. Завершує все $response->send(): prepare(), заголовки, тіло.
Компоненти влаштовані ліниво. Yii::$app успадковує ServiceLocator, тож записи в components це лише визначення: обʼєкт створюється при першому Yii::$app->db чи Yii::$app->mailer і далі кешується. Через це десять описаних компонентів не коштують нічого, поки до них не звернулися, а db не відкриває зʼєднання без першого запиту. Виняток тут bootstrap: усе, що в цьому масиві, створюється завжди, тому логер, yii2-debug чи свій BootstrapInterface з реєстрацією подій потрапляють саме туди, а не в components.
Модуль у Yii2 має ту саму природу, що й сам застосунок: обидва наслідують yii\base\Module, тому в модуля є id, свій controllerNamespace, своя тека views, власні components, modules, params, defaultRoute і метод init(). Layout шукається вгору по ієрархії, поки не знайдеться непорожній $layout, тому адмінці достатньо задати свій. Коштує це небагато, але не безкоштовно: модуль потрібно оголосити в modules, інакше маршрут admin/user/view Yii розбере як контролер AdminController з дією user/view і віддасть 404. Заводити модуль варто під частину системи з окремим набором контролерів і окремими правилами доступу; для трьох контролерів вистачить спільного базового класу.
yii2-app-basic тримає один застосунок з web/index.php і консольним скриптом yii. yii2-app-advanced розводить це на frontend, backend, console і спільний common, де живуть моделі, міграції й базова конфігурація; конфіги зливаються ArrayHelper::merge у порядку common → common-local → app → app-local, а -local файли розкладає php init з environments. Звідси випливає одне практичне правило: код, потрібний і вебу, і консолі, має лежати в common і не залежати від Yii::$app->request чи Yii::$app->user, бо в консольному застосунку цих компонентів у звичному вигляді немає. Так само на спільному домені frontend і backend мусять мати різні session['name'], csrfParam і identityCookie, інакше дві частини сайту ділять одну сесію.
// ── web/index.php: єдина точка входу, усі URL веб-сервер переписує сюди
defined('YII_DEBUG') or define('YII_DEBUG', true); // трейс замість голого 500
defined('YII_ENV') or define('YII_ENV', 'dev'); // далі доступне як YII_ENV_DEV
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/../vendor/yiisoft/yii2/Yii.php'; // автолоадер Yii + Yii::$container
$config = require __DIR__ . '/../config/web.php';
(new yii\web\Application($config))->run(); // конструктор ставить Yii::$app
// ── config/web.php: застосунок описується масивом, а не кодом
return [
'id' => 'app-frontend',
'basePath' => dirname(__DIR__), // з нього виводиться аліас @app
'bootstrap' => ['log'], // піднімається одразу, до маршрутизації
'components' => [
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false, // вимагає rewrite на index.php
'rules' => [
'post/<id:\d+>' => 'post/view', // → PostController::actionView($id)
],
],
'db' => require __DIR__ . '/db.php', // зʼєднання лише при Yii::$app->db
],
'modules' => [
'admin' => ['class' => 'app\modules\admin\Module'], // маршрут admin/user/view
],
'params' => require __DIR__ . '/params.php',
];
// ── modules/admin/controllers/UserController.php
namespace app\modules\admin\controllers; // controllerNamespace модуля за замовчуванням
class UserController extends \yii\web\Controller
{
// Маршрут admin/user/view → цей метод; аргументи беруться з $_GET за іменами
public function actionView(int $id, string $tab = 'profile'): string
{
// Без ?id=... буде BadRequestHttpException (400), а не 404
return $this->render('view', ['id' => $id, 'tab' => $tab]);
}
}
Проговоріть шлях запиту вголос по кроках: `index.php` → конфіг → `Application::run()` → `UrlManager::parseRequest()` → маршрут → модуль → контролер → дія → `Response::send()`. Далі додайте одну деталь, яку junior зазвичай не знає: компоненти ліниві, а `bootstrap` - це виняток з цього правила.