PHPackages                             coderslairdev/clfw - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. coderslairdev/clfw

ActiveLibrary[Framework](/categories/framework)

coderslairdev/clfw
==================

Simple framework implementation for pure PHP

1.0.6(1mo ago)068MITPHPPHP &gt;=8.2

Since Dec 1Pushed 1mo agoCompare

[ Source](https://github.com/coders-lair-dev/clfw)[ Packagist](https://packagist.org/packages/coderslairdev/clfw)[ Docs](https://coders-lair.com)[ RSS](/packages/coderslairdev-clfw/feed)WikiDiscussions master Synced 3w ago

READMEChangelog (7)Dependencies (6)Versions (13)Used By (0)

Минимальный PHP-фреймворк (ClFw)
================================

[](#минимальный-php-фреймворк-clfw)

Учебно-демонстрационный микрофреймворк. Написан для открытого урока, чтобы показать **изнутри**, как устроены DI-контейнер и роутинг современных PHP-фреймворков (Symfony, Laravel и т.п.) — не как ими *пользоваться*, а как они *работают под капотом*.

> **Это обучающий код.** Ряд механизмов намеренно упрощён ради наглядности - чтобы алгоритм было видно глазами, а не прятать его в магии. Места, где production-решение выглядело бы иначе, отмечены ниже в разделе [«Сознательные упрощения»](#%D1%81%D0%BE%D0%B7%D0%BD%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D1%8B%D0%B5-%D1%83%D0%BF%D1%80%D0%BE%D1%89%D0%B5%D0%BD%D0%B8%D1%8F). Это сделано сознательно: цель кода - быть прочитанным и понятым на уроке.

Требования
----------

[](#требования)

- PHP 8.2+
- nginx + php-fpm (пример конфигурации - ниже)

Что демонстрирует
-----------------

[](#что-демонстрирует)

- **DI-контейнер с autowiring** - автоматическое разрешение зависимостей по типам параметров конструктора, через Reflection.
- **Сканирование сервисов** - рекурсивный обход каталогов приложения и построение карты сервисов из найденных классов.
- **Атрибутный роутинг** - `#[AsController]` / `#[AsRoute]`, как в актуальном Symfony.
- **PSR-совместимость** - PSR-7 (HTTP-сообщения) и PSR-17 (фабрики) на базе `nyholm/psr7`.
- **Middleware pipeline** - запрос проходит через цепочку middleware до dispatch.

Как это работает внутри
-----------------------

[](#как-это-работает-внутри)

При старте `Kernel`:

1. **Сканирует** каталоги, указанные в конфиге (`services`), и для каждого найденного класса строит объект `\ReflectionClass`.
2. **Инстанцирует листья графа** - сначала создаёт все сервисы *без зависимостей*(конструктор отсутствует или без параметров). Только их и можно создать на первом шаге, не имея ещё ничего собранного.
3. **Достраивает граф итеративно.** Пока созданы не все найденные классы, контейнер делает повторные проходы: на каждом проходе пытается собрать сервисы, *все* зависимости которых уже инстанцированы (зависимости берутся по типу параметра конструктора). Так граф зависимостей разворачивается слой за слоем - это наглядная топологическая сортировка через многопроходный fixed-point. Реализация выбрана ради того, чтобы алгоритм было *видно*, а не ради максимальной эффективности.
4. **Строит карту роутов** из атрибутов `#[AsRoute]` на методах контроллеров, помеченных `#[AsController]`.
5. На каждый HTTP-запрос: запрос (PSR-7) проходит через middleware pipeline и попадает в `dispatch()`, который по карте роутов находит контроллер и возвращает PSR-7 `ResponseInterface`.

Сердце DI - трейт `ServiceLoaderTrait` (см. `\CodersLairDev\ClFw\DI\Trait`). Именно там живёт описанный выше цикл разрешения зависимостей.

Сознательные упрощения
----------------------

[](#сознательные-упрощения)

Чтобы не вводить читателя в заблуждение - вот где код намеренно проще, чем боевой, и как это решалось бы в production:

- **Нет детекции циклических зависимостей.** Цикл разрешения предполагает, что граф ацикличен и разрешим. При циклической (`A -> B -> A`) или неразрешимой зависимости production-контейнер обнаружил бы, что за полный проход не добавилось ни одного сервиса, и бросил бы исключение с описанием цикла. Здесь это опущено ради простоты примера.
- **Эффективность принесена в жертву наглядности.** Многопроходный алгоритм в худшем случае близок к O(n^2). На реальных объёмах разумнее однопроходный топологический resolve по построенному графу либо ленивая инстанциация по требованию (как делает Symfony) с компиляцией контейнера.
- **Autowiring - только по типу.** Разрешаются зависимости-объекты по типу параметра конструктора. Скалярные параметры, union-типы, значения по умолчанию и именованные аргументы конфигурации — вне зоны демонстрации.
- **PSR-17 фабрика в контроллере создаётся через `new`** (в примере `RootController`) - ради краткости. По-хорошему фабрика тоже приходит из контейнера; так демонстрация DI замкнулась бы и на сам контроллер.

Регистрация сервисов вне сканирования
-------------------------------------

[](#регистрация-сервисов-вне-сканирования)

Классы, живущие во фреймворке (`vendor/`), сканированием не охватываются, поэтому регистрируются явно через `factories` - это показывает разницу между autowiring и ручной регистрацией фабрикой:

```
'factories' => [
    MiddlewarePipeline::class => fn($c) => new MiddlewarePipeline(),
],
```

Запуск
------

[](#запуск)

Точка входа - `public/index.php`. Конфигурация (каталоги сервисов, фабрики, bootstrap-хуки middleware) задаётся массивом и передаётся в `Kernel`. Пример - в `public/index.php`.

### nginx.conf (для docker-окружения)

[](#nginxconf-для-docker-окружения)

```
server {
    listen 80;
    server_name localhost;

    error_log  /dev/stderr;
    access_log /dev/stdout;

    root /app/public;

    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }

    rewrite ^/index\.php/?(.*)$ /$1 permanent;

    try_files $uri @rewriteapp;

    location @rewriteapp {
        rewrite ^(.*)$ /index.php/$1 last;
    }

    location ~ /\. {
        deny all;
    }

    location ~ ^/index\.php(/|$) {
        internal;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_index index.php;
        send_timeout 1800;
        fastcgi_read_timeout 1800;
        fastcgi_pass php-fpm:9000;
    }
}
```

Пример контроллера
------------------

[](#пример-контроллера)

```
