<!--
	covers: applications base components controllers/Controller.php helpers/JsonLdHelper.php helpers/Language.php web/index.php yii
	verified: 54fd0a6
-->
# Request lifecycle

From entry script to the bytes on the wire.

## 1. Entry script

[`web/index.php`](../../web/index.php) — the only web entry point.

```php
require __DIR__ . '/../env.php';        // every global constant
require PATH_ROOT . 'functions.php';    // t() bt() p() e() f() num() amount() array_find() i()
require PATH_VENDOR . 'autoload.php';
require PATH_VENDOR_YII . 'Yii.php';
date_default_timezone_set( APP_TIMEZONE );
$config = require PATH_CONFIG_ENVIRONMENTS . 'web.php';
( new WebApplication( $config ) )->run();
```

Order matters: `env.php` defines `PATH_*`, which every later `require` uses. `functions.php` is loaded
**before** the autoloader, so the global helpers exist everywhere including config files.

`InvalidConfigException` is caught and `error_log`ged — a broken config produces a blank page, not a stack
trace.

The console equivalent is [`yii`](../../yii), identical except for the config file and an explicit
`exit( $exitCode )`.

## 2. Application construction

[`WebApplication::__construct()`](../../applications/WebApplication.php) runs `parent::__construct()` and then:

```php
$this->language = Language::fromPath( $this->request->pathInfo );
```

[`Language::fromPath()`](../../helpers/Language.php) takes the first path segment; if it is a key of
`APP_LANGUAGES` **and** not the default language, that is the language. Otherwise the default.

So `de/lager` → `de`, `hu/…` → `hu` (the default; the `hu` prefix is not a real route), `tarolok` → `hu`.

This is a *pre-routing* guess so that anything running before the controller — including error pages — has a
language. `UrlManager` overwrites it in step 3 from the matched rule.

## 3. Routing

[`app\base\UrlManager`](../../base/UrlManager.php) extends the framework's URL manager with two overrides:

- **`parseRequest()`** — after the parent matches a rule, reads `language` out of the matched parameters and
  assigns `Yii::$app->language`. Every localized rule carries `'defaults' => [ 'language' => $language ]`,
  so the match itself names the language.
- **`createUrl()`** — when the route being generated is in `$localizedRoutes`, injects
  `language => Yii::$app->language` unless the caller passed one. This is what makes `Url::to( [ 'articles/display', … ] )`
  produce a German URL while rendering the German page, without every call site saying so.

The rule set is **generated**, not written out — see [I18N-AND-URLS.md](I18N-AND-URLS.md).

## 4. Controller

Frontend controllers extend [`app\controllers\Controller`](../../controllers/Controller.php), which is thin by
design. It holds no logic beyond:

- `beforeAction()` — picks the layout: `ajax` when `Yii::$app->request->isAjax`, otherwise `main`.
- `runAction()` — captures a `slug` route parameter into `$this->slug`.
- `autoSettings( $title )` — sets the title and a two-item breadcrumb (site name → this page).
- `getAlternateUrls()` / `urlInLanguage()` — the language switcher and `hreflang` source.
- `getJsConfig()` — the `APP_CONFIG` object handed to the browser.

Its real job is to be a **bag of SEO state** that `View` reads later:

| Property | Used for |
|----------|----------|
| `$modelClass` | which model this controller is about |
| `$model`, `$models` | the displayed record / list — `View::getMeta()` falls back to these |
| `$title`, `$keywords`, `$description`, `$image` | `<title>` and meta tags |
| `$breadcrumb` | the breadcrumb widget and JSON-LD |
| `$canonical`, `$noindex`, `$nofollow` | robots and canonical link |
| `$defaultJsConfig` | merged into `APP_CONFIG` |

Actions carry `@api` so the IDE does not report them unused.

## 5. View and `<head>`

[`app\components\View`](../../components/View.php) overrides `head()` and emits, in order: `<title>`,
`<base href="/">`, CSRF tags, charset/compat meta, `robots`, `canonical`, **hreflang alternates**, viewport,
keywords, description, OpenGraph, Twitter cards, icons, then the Encore stylesheets.

`getMeta( $property, $attribute, $default )` (`components/View.php:279`) resolves each value through a
fallback chain — **later wins**:

```
t( 'meta<Property><Controller><Action>' )   // e.g. metaTitleArticlesIndex
  → t( 'meta<Property>' )                   // e.g. metaTitle
  → $controller->models[ 0 ]->$attribute
  → $controller->model->$attribute
  → $controller->$property
```

Because `t()` returns `''` for a missing key, an absent translation simply falls through instead of printing
the key.

### Styles are inlined, scripts are linked

`registerEncoreStyles()` reads the CSS files off disk and echoes them inside `<style>` tags — the page ships
its CSS inline. Only if `file_get_contents()` fails does it fall back to a `<link>`. Scripts are always
`<script src>`, preceded by an inline `const APP_CONFIG = …`.

Both read `web/build/entrypoints.json`, cached in a static property for the request. **A missing
`entrypoints.json` degrades silently to no assets at all** (`components/View.php:439`).

The entry name is derived, never configured: `<controllerId><ActionId>` (`articlesIndex`), or the literal
`admin` inside the admin module, or `$view->entryName` when a page forces one. `common` is always prepended.

## 6. Widgets

Page markup is assembled from [`app\base\Widget`](../../base/Widget.php) subclasses. See
[WIDGETS.md](WIDGETS.md) for the naming chain and the caching rules.

## 7. Output rewriting

This is the step that surprises people. `View::beginPage()` opens an output buffer;
`View::endPage()` closes it and runs the whole document through:

1. **`applyReplaces()`** — `str_replace` of `{address}`, `{email}`, `{phone}`, `{phone_tel}`,
   `{cookie_link}`, `{terms_link}`, `{privacy_link}`, **plus every `Settings` attribute** as
   `{snake_case_name}`. Numeric settings are formatted with `f()->asDecimal()`.
2. **JSON-LD injection** — `{__JSON_LD_PLACEHOLDER__}`, echoed inside `<head>`, is replaced with the
   `application/ld+json` script built by [`JsonLdHelper`](../../helpers/JsonLdHelper.php). Admin and mail
   views skip this.
3. **Whitespace collapse** — `preg_replace( '~>\s+<~', '><', … )`. Inter-tag whitespace is destroyed, so
   layouts must not rely on it for spacing.
4. **HTML comment stripping** — comments of the form `<!--{…}-->` are removed.
5. **Host rewriting** (frontend only) — `www.raktar24` and `eles.raktar24` collapse to `raktar24`;
   `new.raktar24` too unless `YII_ENV_TEST`; `http://raktar24` becomes `https://raktar24` unless
   `YII_ENV_DEV`.

Consequences worth remembering:

- A literal `{something}` in markup or in content will be eaten if it matches a `Settings` attribute name.
- Inline JavaScript containing `>` followed by whitespace and `<` can be mangled by the whitespace collapse.
- Widget output goes through the same collapse a second time (`base/Widget.php:112`).

## AJAX

`beforeAction()` swaps the layout to `ajax` for XHR requests. The `ajax` layout emits the widget output
without `<head>`, so none of the head machinery runs — but `endPage()` rewriting still does.
