Огляд ядра

Пакет php-concept/core-2 (Concept\Core) — мінімальний HTTP runtime для PHP 8.4+. Він свідомо не тягне View, Session чи БД: ядро відповідає лише за «отримати request → знайти маршрут → викликати handler → повернути response». Усе інше підключається extensions і збіркою додатку (Stack / Bootstrap), коли воно справді потрібне.

Навіщо так тонко: щоб можна було зібрати API без Twig, CLI без HTTP, тести без «повного фреймворку», і щоб межа між runtime і предметною логікою була видимою. Core дає DI (League Container), маршрутизацію (League Route), dispatch і контракти для кастомізації — не «фреймворк із коробки».

Структура пакета

Дерево нижче — не «все, що є в додатку», а лише те, без чого не стартує HTTP-цикл. Якщо файлу немає в цьому списку, його шукайте в extension або у своєму App-шарі.

src/
├── App.php
├── Container/
│   ├── ContainerDependency.php
│   └── Exception/ContainerDependencyException.php
├── Events/Http/
│   ├── RequestHandled.php
│   ├── RouteHandlerInvoked.php
│   └── RouteInterceptorInvoked.php
├── Http/
│   ├── Contracts/
│   │   ├── ArgumentResolverInterface.php
│   │   ├── RequestContextInterface.php
│   │   └── RouteInterceptorInterface.php
│   ├── Request/RequestContext.php
│   └── Routing/
│       ├── RouteStrategy.php
│       └── Resolvers/
│           ├── ServerRequestArgumentResolver.php
│           └── RouteParameterArgumentResolver.php
└── Providers/Http/HttpKernelServiceProvider.php

Точка входу

App::create() піднімає контейнер; providers реєструєте ви — ядро не «вгадує» стек. handle() — єдиний публічний крок «обробити HTTP»: взяти request і Router з контейнера, викликати Router::dispatch(), повернути ResponseInterface. Emit у браузер лишається за entry point (SapiEmitter тощо), щоб той самий App можна було ганяти в тестах без SAPI.

use Concept\Core\App;

$app = App::create();
$app->registerServiceProviders([/* ... */]);
$response = $app->handle();

HttpKernelServiceProvider

Єдиний вбудований service provider — і це навмисно. Без нього в контейнері немає Router і ServerRequest: HTTP з’являється лише коли ви явно підключили kernel. Він реєструє:

  • ServerRequestInterface — з globals через ServerRequestFactory::fromGlobals(): entry point для App::handle(), не «магічний» request у кожному сервісі
  • RequestContextInterface — scoped enriched request під час handler: коли сервісу потрібен поточний request без прокидання параметром у кожен метод
  • Router — League Route з RouteStrategy: маршрути підключаєте шляхами до файлів, без прихованих auto-routes

RouteStrategy

Розширює League ApplicationStrategy, щоб після middleware не було «чорної скриньки»: видно, що саме відбувається перед викликом handler-а. Порядок фіксований — це контракт життєвого циклу:

  1. route interceptors (якщо передані) — fail-fast за метаданими маршруту
  2. prepareRequest() — route vars → PSR-7 attributes
  3. argument resolvers → invoke handler
  4. перевірка, що результат — ResponseInterface

Коли розширюєте поведінку (FormRequest, casting), додаєте resolver або interceptor у chain — не патчите RouteStrategy.

Залежності

League Container, League Route, Laminas Diactoros (PSR-7 factory), PSR Event Dispatcher (опційно). Жодних додаткових пакетів Concept: Core не тягне extensions «транзитивно». Якщо потрібен View чи Session — це окремий пакет і явний крок збірки.

Практичний старт: Мінімальний додаток — повний приклад лише на core-2, без Stack і extensions. Як зібрати «справжній» додаток покроково — Огляд.