From fee7709e8055c13f4a5b7b3418dbe90b78f48a67 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 13:09:23 +0300 Subject: [PATCH 1/8] add actionRegexGet --- tests/Stub/Controllers/MainController.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Stub/Controllers/MainController.php b/tests/Stub/Controllers/MainController.php index a2bbb0ce..d9cbc17f 100755 --- a/tests/Stub/Controllers/MainController.php +++ b/tests/Stub/Controllers/MainController.php @@ -61,6 +61,11 @@ public function delete($params) Rudra::config()->set(["delete" => "delete"]); } + public function actionRegexGet() + { + Rudra::config()->set(["regex" => "regex"]); + } + public function shipInit() {} public function containerInit() {} public function init() {} From 370b68d159710b60042d5b98c4e77eae39a19047 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 13:09:57 +0300 Subject: [PATCH 2/8] update --- .github/workflows/php.yml | 87 +++++++++------ README.md | 213 ++++++++++-------------------------- src/MiddlewareInterface.php | 19 ---- tests/RouterTest.php | 67 ++++-------- 4 files changed, 136 insertions(+), 250 deletions(-) delete mode 100755 src/MiddlewareInterface.php diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index e4222875..310300bc 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,46 +1,71 @@ -name: PHPunit +name: PHPUnit on: push: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: permissions: contents: read jobs: build: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Install dependencies - run: composer install --prefer-dist --no-progress - - # Add a test script to composer.json, for instance: "test": "vendor/bin/phpunit" - # Docs: https://getcomposer.org/doc/articles/scripts.md - - # - name: Run test suite - # run: composer run-script test - - name: PHPUnit (graychen) - # You may pin to the exact commit or the version. - # uses: Graychen/phpunit-action@026f8104f1a6b5bc646df2d495bc8b34080c3e03 - uses: Graychen/phpunit-action@v1.0.0 - with: - # Configuration file location - config: phpunit.xml + # Шаг 1: Клонирование репозитория + - name: Checkout code + uses: actions/checkout@v3 + + # Шаг 2: Настройка PHP и расширений + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: xdebug + + # Шаг 3: Валидация composer.json и composer.lock + - name: Validate composer.json and composer.lock + run: composer validate --strict + + # Шаг 4: Кэширование зависимостей Composer + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + # Шаг 5: Установка зависимостей + - name: Install dependencies + run: composer install --prefer-dist --no-progress + + # Шаг 6: Загрузка PHPUnit PHAR + - name: Download PHPUnit PHAR + run: | + wget https://phar.phpunit.de/phpunit.phar + chmod +x phpunit.phar + mv phpunit.phar /usr/local/bin/phpunit + + # Шаг 7: Проверка версии PHPUnit + - name: Check PHPUnit version + run: phpunit --version + + # Шаг 8: Запуск тестов с генерацией отчета о покрытии + - name: Run PHPUnit tests with coverage + run: | + mkdir -p build/logs + phpunit --configuration phpunit.xml --coverage-clover build/logs/clover.xml --debug + env: + XDEBUG_MODE: coverage + + # Шаг 9: Отправка данных о покрытии в Coveralls + - name: Send coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + path-to-lcov: build/logs/clover.xml \ No newline at end of file diff --git a/README.md b/README.md index 776130db..0f1120ed 100755 --- a/README.md +++ b/README.md @@ -1,60 +1,66 @@ [![PHPunit](https://github.com/Jagepard/Rudra-Router/actions/workflows/php.yml/badge.svg)](https://github.com/Jagepard/Rudra-Router/actions/workflows/php.yml) -[![Code Climate](https://codeclimate.com/github/Jagepard/Rudra-Router/badges/gpa.svg)](https://codeclimate.com/github/Jagepard/Rudra-Router) +[![Maintainability](https://qlty.sh/badges/d9252114-5cc4-405e-bbf7-6419ec50266f/maintainability.svg)](https://qlty.sh/gh/Jagepard/projects/Rudra-Router) [![CodeFactor](https://www.codefactor.io/repository/github/jagepard/rudra-router/badge)](https://www.codefactor.io/repository/github/jagepard/rudra-router) +[![Coverage Status](https://coveralls.io/repos/github/Jagepard/Rudra-Router/badge.svg?branch=master)](https://coveralls.io/github/Jagepard/Rudra-Router?branch=master) ----- # Rudra-Router -#### Устанавливаем маршрут callback/:name для http метода GET -_выполняет лямбда-функцию_ +#### Basic installation / Базовая установка +```php +use Rudra\Router\Router; +use Rudra\Container\Rudra; + +$router = new Router(Rudra::run()); +``` +#### Installation for facade use / Установка для использования фасада +```php +use Rudra\Container\Facades\Rudra; +use Rudra\Router\RouterFacade as Router; +use Rudra\Container\Interfaces\RudraInterface; + +Rudra::binding()->set([RudraInterface::class => Rudra::run()]); +``` + +#### Setting the route / Устанавливаем маршрут callback/:name ```php $router->get('callback/:name', function ($name) { echo "Hello $name!"; }); ``` -_Для вызова через Фасад Rudra-Container_ +_with Regex_ ```php -use Rudra\Router\RouterFacade as Router; - -Router::get('callback/:name', function ($name) { +$router->get('callback/:[\d]{1,3}', function ($name) { echo "Hello $name!"; }); ``` -_вызывает MainController::read_ +_To call through the Facade / Для вызова через Фасад_ ```php -$router->get('read/:id', [MainController::class, 'read']); +Router::get('callback/:name', function ($name) { + echo "Hello $name!"; +}); ``` -_вызывает MainController::read при помощи добавления аннотаций к MainController_ +_with Regex_ ```php -/** - * @Routing(url = ''read/:id') - */ -public function read($id) +Router::get('callback/:[\d]{1,3}', function ($name) { + echo "Hello $name!"; +}); ``` -_вызывает MainController::read_ и добавляет middleware с ключами before или after соответственно_ +_call / вызывает MainController::read_ ```php -$router->get('read/page', [MainController::class, 'read'], ['before' => [Middleware::class]); +$router->get('read/:id', [MainController::class, 'read']); ``` -_в аннотациях_ +_To call through the Facade / Для вызова через Фасад_ ```php -/** - * @Routing(url = 'read/page') - * @Middleware(name = 'App\Middleware\Middleware') - */ -public function read() +Router::get('read/:id', [MainController::class, 'read']); ``` -_Для сбора аннотаций необходимо передать массив классов в которых есть аннотации в annotationCollector_ +_call MainController::read with middleware_ ```php -$router->annotationCollector([ - \App\Controllers\MainController::class, - \App\Controllers\SecondController::class, -]); +$router->get('read/page', [MainController::class, 'read'], ['before' => [Middleware::class]); ``` +_To call through the Facade / Для вызова через Фасад_ ```php -Router::annotationCollector([ - \App\Controllers\MainController::class, - \App\Controllers\SecondController::class, -]); +Router::get('read/page', [MainController::class, 'read'], ['before' => [Middleware::class]); ``` _С параметрами для middleware_ ```php @@ -63,163 +69,60 @@ $router->get('', [MainController::class, 'read'], [ 'after' => [FirstMidddleware::class, [SecondMidddleware::class, ['int' => 456, new \stdClass]]] ]); ``` -_в аннотациях_ -```php -/** - * @Routing(url = '') - * @Middleware(name = 'App\Middleware\FirstMidddleware') - * @Middleware(name = 'App\Middleware\SecondMidddleware', params = {int : '456'}) - * @AfterMiddleware(name = 'App\Middleware\FirstMidddleware') - * @AfterMiddleware(name = 'App\Middleware\SecondMidddleware', params = {int : '456'}) - */ -public function read() -``` -_При передаче параметров в middleware необходимо добавлять параметр "array $params"_ -```php -public function __invoke(array $params, array $middlewares) -``` -_Если параметры не передаются, то:_ -```php -public function __invoke(array $middlewares) -``` -_Следующие вызовы без параметров равны_ -```php -'before' => [FirstMidddleware::class, SecondMidddleware::class]], -'before' => [[FirstMidddleware::class], [SecondMidddleware::class]] -``` -#### Устанавливаем маршрут create/:id для http метода POST -_вызывает MainController::create_ +_call / вызывает MainController::create_ ```php $router->post('create/:id', [MainController::class, 'create']); ``` -_в аннотациях_ -```php -/** - * @Routing(url = 'create/:id', method = 'POST') - */ -public function create($id) -``` -#### Устанавливаем маршрут update/:id для http метода PUT -_вызывает MainController::update_ +_call / вызывает MainController::update_ ```php $router->put('update/:id', [MainController::class, 'update']); ``` -_в аннотациях_ -```php -/** - * @Routing(url = 'update/:id', method = 'PUT') - */ -public function update($id) -``` -#### Устанавливаем маршрут update/:id для http метода PATCH -_вызывает MainController::update_ +_call / вызывает MainController::update_ ```php $router->patch('update/:id', [MainController::class, 'update']); ``` -_в аннотациях_ -```php -/** - * @Routing(url = 'update/:id', method = 'PATCH') - */ -public function update($id) -``` -#### Устанавливаем маршрут delete/:id для http метода DELETE -_вызывает MainController::delete_ +_call / вызывает MainController::delete_ ```php $router->delete('delete/:id', [MainController::class, 'delete']); ``` -_в аннотациях_ -```php -/** - * @Routing(url = 'delete/:id', method = 'DELETE') - */ -public function delete($id) -``` -#### Устанавливаем маршрут any/:id для http методов GET|POST|PUT|PATCH|DELETE -_вызывает MainController::any_ +_call / вызывает MainController::any 'GET|POST|PUT|PATCH|DELETE'_ ```php $router->any('any/:id', [MainController::class, 'any']); ``` -_в аннотациях_ -```php -/** - * @Routing(url = 'any/:id', method = 'GET|POST|PUT|PATCH|DELETE') - */ -public function any($id) -``` -#### Устанавливаем ресурс для маршрута api/:id, методы GET|POST|PUT|DELETE -_вызывает MainController::read для GET_ +_call / вызывает MainController::read для GET_ -_вызывает MainController::create для POST_ +_call / вызывает MainController::create для POST_ -_вызывает MainController::update для PUT_ +_call / вызывает MainController::update для PUT_ -_вызывает MainController::delete для DELETE_ +_call / вызывает MainController::delete для DELETE_ ```php $router->resource('api/:id', MainController::class); ``` -Изменить методы контроллера по умолчанию можно передав массив с вашими именами +Изменить методы контроллера по умолчанию можно передав массив с вашими именами\ +You can change the default controller methods by passing an array with your names ```php $router->resource('api/:id', MainController::class, ['actionIndex', 'actionAdd', 'actionUpdate', 'actionDrop']); ``` -##### Вариант объявления маршрута методом set -#### Устанавливаем маршрут /test/:id для http методов DELETE|PUT -_выполняет лямбда-функцию_ -```php -$router->set(['/test/page', 'POST|PUT', function () { - echo 'Hello world!'; - } -]); -``` -_вызывает MainController::actionIndex_ +#### A variant of declaring a route using the set method / Вариант объявления маршрута методом set +_call / вызывает MainController::actionIndex_ ```php $router->set(['/test/:id', 'DELETE|PUT', [MainController::class, 'actionIndex'], [ 'before' => [First::class, Second::class], 'after' => [[First::class], [Second::class]] ]]); ``` -_Пример Middleware_ +_Exemple / Пример Middleware_ ```php -next($middlewares); - } - - public function next(array $middlewares): void - { - $this->handleMiddleware($middlewares); - } -} -``` -_Пример Middleware с параметрами с использованием Фасада_ -```php -next($middlewares); - } - - public function next(array $middlewares): void + public function __invoke($params, $next) { - Router::handleMiddleware($middlewares); + // Logic here + $next(); } } ``` diff --git a/src/MiddlewareInterface.php b/src/MiddlewareInterface.php deleted file mode 100755 index 2ac95b2d..00000000 --- a/src/MiddlewareInterface.php +++ /dev/null @@ -1,19 +0,0 @@ - - * @license https://mit-license.org/ MIT - */ - -namespace Rudra\Router; - -interface MiddlewareInterface -{ - /** - * @param array $chainOfMiddlewares - * @return void - */ - public function next(array $chainOfMiddlewares): void; -} diff --git a/tests/RouterTest.php b/tests/RouterTest.php index 91f785d4..77410774 100755 --- a/tests/RouterTest.php +++ b/tests/RouterTest.php @@ -34,14 +34,7 @@ protected function setRouteEnvironment(string $requestUri, string $requestMethod $action = "action" . ucfirst($method); $this->setContainer(); - $route = [ - 'url' => $pattern, - 'method' => $requestMethod, - 'action' => "action" . ucfirst($method), - 'controller' => $controller - ]; - - \Rudra\Router\RouterFacade::$method($route); + \Rudra\Router\RouterFacade::$method($pattern, [$controller, "action" . ucfirst($method)]); $this->assertEquals($requestMethod, Rudra::config()->get($action)); } @@ -86,12 +79,7 @@ public function testAny(): void $_SERVER["REQUEST_METHOD"] = "PATCH"; $this->setContainer(); - Router::any([ - 'url' => '/test/page', - 'action' => 'actionAny', - 'controller' => MainController::class - ]); - + Router::any('/test/page',[MainController::class, 'actionAny']); $this->assertEquals("ANY", Rudra::config()->get("actionAny")); } @@ -100,12 +88,7 @@ protected function setRouteResourceEnvironment(string $requestMethod, string $ac $_SERVER["REQUEST_URI"] = "api/123"; $_SERVER["REQUEST_METHOD"] = $requestMethod; $this->setContainer(); - - Router::resource([ - 'url' => "api/:id", - 'controller' => MainController::class - ]); - + Router::resource("api/:id", MainController::class); $this->assertEquals($action, Rudra::config()->get($action)); } @@ -123,12 +106,7 @@ protected function setRouteResourcePostEnvironment(string $requestMethod, string $_SERVER["REQUEST_METHOD"] = "POST"; $_POST["_method"] = $requestMethod; $this->setContainer(); - - Router::resource([ - 'url' => "api/:id", - 'controller' => MainController::class - ]); - + Router::resource("api/:id", MainController::class); $this->assertEquals($action, Rudra::config()->get($action)); } @@ -148,11 +126,7 @@ protected function setRoutePostEnvironment(string $requestMethod, string $action $method = strtolower($requestMethod); - Router::resource([ - 'url' => "api/:id", - 'controller' => MainController::class - ]); - + Router::resource("api/:id", MainController::class); $this->assertEquals($action, Rudra::config()->get($action)); } @@ -169,17 +143,15 @@ public function testMiddleware() $_SERVER["REQUEST_METHOD"] = "GET"; $this->setContainer(); - Router::get([ - 'url' => "123/:id", - 'controller' => MainController::class, - 'action' => 'read', - 'middleware' => [ + Router::get("123/:id", [MainController::class,'read'], + [ "before" => [[Middleware::class]], - "after" => [[Middleware::class]] + "after" => [function () { Rudra::config()->set(["after" => __FUNCTION__]); }] ] - ]); + ); $this->assertEquals(Middleware::class, Rudra::config()->get("middleware")); + $this->assertEquals("{closure:Rudra\Router\Tests\RouterTest::testMiddleware():149}", Rudra::config()->get("after")); } public function testClosure() @@ -188,15 +160,20 @@ public function testClosure() $_SERVER["REQUEST_METHOD"] = "GET"; $this->setContainer(); - Rudra::config()->set(["environment" => "test"]); - - Router::get([ - 'url' => "test/page", - 'controller' => function () { + Router::get("test/page", function () { Rudra::config()->set(["closure" => "closure"]); - } - ]); + }); $this->assertEquals("closure", Rudra::config()->get("closure")); } + + public function testRegex(): void + { + $_SERVER["REQUEST_URI"] = "test/12"; + $_SERVER["REQUEST_METHOD"] = "GET"; + $this->setContainer(); + + Router::get("test/:[\d]{1,3}", [MainController::class, 'actionRegexGet']); + $this->assertEquals('regex', Rudra::config()->get("regex")); + } } From 3d7297a39c41d55c4e02cc3f35821950b124165c Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:35:37 +0300 Subject: [PATCH 3/8] update --- docs.md | 76 +++++++++++++++++++++++++-------------------------------- 1 file changed, 33 insertions(+), 43 deletions(-) diff --git a/docs.md b/docs.md index 50ecb63f..4a1f5542 100644 --- a/docs.md +++ b/docs.md @@ -1,5 +1,4 @@ ## Table of contents -- [Rudra\Router\MiddlewareInterface](#rudra_router_middlewareinterface) - [Rudra\Router\Router](#rudra_router_router) - [Rudra\Router\RouterFacade](#rudra_router_routerfacade) - [Rudra\Router\RouterInterface](#rudra_router_routerinterface) @@ -8,40 +7,32 @@ - [Rudra\Router\Traits\RouterRequestMethodTrait](#rudra_router_traits_routerrequestmethodtrait)
- - -### Class: Rudra\Router\MiddlewareInterface -| Visibility | Function | -|:-----------|:---------| -|abstract public|next( array $chainOfMiddlewares ): void
| - - ### Class: Rudra\Router\Router -##### implements [Rudra\Router\RouterInterface](#rudra_router_routerinterface) | Visibility | Function | |:-----------|:---------| -|public|set( array $route ): void
| -|private|handleRequestUri( array $route ): void
| -|private|handleRequestMethod(): void
| -|private|handlePattern( array $route array $request ): array
| -|private|setCallable( array $route $params ): void
| -|public|directCall( array $route $params ): void
| -|private|callActionThroughReflection( ?array $params string $action object $controller ): void
| -|private|callActionThroughException( $params $action $controller ): void
| -|public|handleMiddleware( array $chainOfMiddlewares ): void
| -|public|annotationCollector( array $controllers bool $getter bool $attributes ): ?array
| -|protected|handleAnnotationMiddleware( array $annotation ): array
| -|public|__construct( Rudra\Container\Interfaces\RudraInterface $rudra )
| -|public|rudra(): Rudra\Container\Interfaces\RudraInterface
| -|public|get( array $route ): void
| -|public|post( array $route ): void
| -|public|put( array $route ): void
| -|public|patch( array $route ): void
| -|public|delete( array $route ): void
| -|public|any( array $route ): void
| -|public|resource( array $route array $actions ): void
| +| public | `set(array $route): void`
Sets the route, parsing HTTP methods (if multiple are specified via \|).
Registers a route handler for each method.
-------------------------
Устанавливает маршрут, разбирая HTTP-методы (если указано несколько через \|).
Для каждого метода регистрирует обработчик маршрута. | +| private | `handleRequestUri(array $route): void`
Processes the incoming URI request and checks if it matches the current route.
-------------------------
Обрабатывает входящий URI-запрос и проверяет его совпадение с текущим маршрутом. | +| private | `handleRequestMethod(): void`
Processes the HTTP request method, including spoofing via _method (for PUT/PATCH/DELETE)
-------------------------
Обрабатывает HTTP-метод запроса, включая spoofing через _method (для PUT/PATCH/DELETE) | +| private | `handlePattern(array $route, array $request): array`
Matches the URI from the route with the actual request, processing parameters of the form :param and :regexp.
This method is used to extract dynamic segments from a URI pattern:
-------------------------
Сопоставляет URI из маршрута с фактическим запросом, обрабатывая параметры вида :param и :regexp.
Метод извлекает динамические сегменты из URL-шаблона: | +| private | `setCallable(array $route, ?array $params): void`
Calls the controller associated with the route — either a Closure or a controller method.
-------------------------
Вызывает контроллер, связанный с маршрутом — либо Closure, либо метод контроллера. | +| public | `directCall(array $route, ?array $params): void`
Calls the controller and its method directly, performing the full lifecycle:
This method is used to fully dispatch a route after matching it with the current request.
-------------------------
Вызывает контроллер и его метод напрямую, выполняя полный жизненный цикл:
Метод используется для полной диспетчеризации маршрута после его совпадения с текущим запросом. | +| private | `callActionThroughReflection(?array $params, string $action, object $controller): void`
Calls the controller method using Reflection, performing automatic parameter injection based on type hints.
This method is typically used when the zend.exception_ignore_args setting is enabled,
allowing for more flexible and type-safe dependency resolution.
-------------------------
Вызывает метод контроллера с помощью Reflection, выполняя автоматическое внедрение параметров на основе типизации.
Этот метод обычно используется, когда включена настройка zend.exception_ignore_args,
что позволяет более гибко и безопасно разрешать зависимости по типам. | +| private | `callActionThroughException(?array $params, string $action, object $controller): void`
Calls the specified controller method directly.
If the argument type or number does not match — tries to automatically inject required dependencies.
This is a fallback mechanism for cases where Reflection-based injection is disabled or unavailable.
Handles two types of errors during invocation:
- \ArgumentCountError — thrown when the number of arguments doesn't match the method signature.
- \TypeError — thrown when an argument is not compatible with the expected type.
In both cases, Rudra's autowire system attempts to resolve and inject the correct dependencies.
-------------------------
Вызывает указанный метод контроллера напрямую.
Если тип или количество аргументов не совпадает — пытается автоматически внедрить нужные зависимости.
Это механизм отката, используемый, когда недоступен вызов через Reflection.
Обрабатываются следующие ошибки:
- \ArgumentCountError — выбрасывается, если количество аргументов не совпадает с ожидаемым.
- \TypeError — выбрасывается, если тип аргумента не соответствует ожидаемому.
В обоих случаях система автовайринга Rudra пытается разрешить и внедрить правильные зависимости. | +| public | `handleMiddleware(array $chain): void`
Executes a chain of middleware, recursively calling each element.
Middleware can be specified in one of the supported formats:
- 'MiddlewareClass' (string) — a simple class name to call without parameters.
- ['MiddlewareClass'] (array with class name) — same as above, allows for future extensions.
- ['MiddlewareClass', \$parameter] (array with class and parameter) — passes the parameter to the middleware.
Each middleware must implement the __invoke() method to be callable.
--------------------
Выполняет цепочку middleware, рекурсивно вызывая каждый элемент.
Middleware может быть указан в одном из поддерживаемых форматов:
- 'MiddlewareClass' (строка) — простое имя класса без параметров.
- ['MiddlewareClass'] (массив с именем класса) — аналогично предыдущему, удобно для расширения.
- ['MiddlewareClass', \$parameter] (массив с классом и параметром) — передаёт параметр в middleware.
Каждый middleware должен реализовывать метод __invoke(), чтобы быть вызываемым. | +| public | `annotationCollector(array $controllers, bool $getter, bool $attributes): ?array`
Collects and processes annotations from the specified controllers.
This method scans each controller class for Routing and Middleware annotations,
builds route definitions based on those annotations, and either:
- Registers them directly via `set()` (if \$getter = false), or
- Returns them as an array (if \$getter = true).
--------------------
Собирает и обрабатывает аннотации указанных контроллеров.
Метод сканирует каждый контроллер на наличие аннотаций Routing и Middleware,
формирует определения маршрутов и либо:
- Регистрирует их напрямую через `set()` (если \$getter = false),
- Возвращает как массив (если \$getter = true). | +| protected | `handleAnnotationMiddleware(array $annotation): array`
Processes middleware annotations into a valid middleware format.
--------------------
Обрабатывает аннотации middleware в поддерживаемый формат.
```#[Middleware(name: "Auth", params: "admin")]```
в:
```['Auth', 'admin']``` | +| public | `__construct(Rudra\Container\Interfaces\RudraInterface $rudra)`
| +| public | `rudra(): Rudra\Container\Interfaces\RudraInterface`
| +| public | `get(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the GET HTTP method.
--------------------
Регистрирует маршрут с использованием метода GET. | +| public | `post(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the POST HTTP method.
--------------------
Регистрирует маршрут с использованием метода POST. | +| public | `put(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the PUT HTTP method.
--------------------
Регистрирует маршрут с использованием метода PUT. | +| public | `patch(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the PATCH HTTP method.
--------------------
Регистрирует маршрут с использованием метода PATCH. | +| public | `delete(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the DELETE HTTP method.
--------------------
Регистрирует маршрут с использованием метода DELETE. | +| public | `any(string $pattern, callable\|array $target, array $middleware): void`
Registers a route that supports all HTTP methods.
Sets the method to a pipe-separated string ('GET\|POST\|PUT\|PATCH\|DELETE'),
allowing the same route to handle multiple request types.
--------------------
Регистрирует маршрут, поддерживающий все HTTP-методы.
Устанавливает метод как строку с разделителем \| ('GET\|POST\|PUT\|PATCH\|DELETE'),
что позволяет использовать один маршрут для нескольких типов запросов. | +| public | `resource(string $pattern, string $controller, array $actions): void`
Registers a resource route, mapping standard actions to controller methods.
Supports common CRUD operations by default:
- GET=> read
- POST => create
- PUT=> update
- DELETE => delete
Can be customized with an optional \$actions array.
--------------------
Регистрирует ресурсный маршрут, связывая стандартные действия с методами контроллера.
По умолчанию поддерживает CRUD-операции:
- GET=> read
- POST => create
- PUT=> update
- DELETE => delete
Может быть переопределён с помощью массива \$actions. | +| protected | `setRoute(string $pattern, $target, string $httpMethod, array $middleware): void`
The method constructs a route definition and passes it to the `set()` method for registration.
--------------------
Метод формирует определение маршрута и передает его в метод `set()` для регистрации. | @@ -49,7 +40,7 @@ ### Class: Rudra\Router\RouterFacade | Visibility | Function | |:-----------|:---------| -|public static|__callStatic( string $method array $parameters ): mixed
| +| public static | `__callStatic(string $method, array $parameters): ?mixed`
| @@ -57,8 +48,7 @@ ### Class: Rudra\Router\RouterInterface | Visibility | Function | |:-----------|:---------| -|abstract public|set( array $route ): void
| -|abstract public|directCall( array $route $params ): void
| +| abstract public | `directCall(array $route, ?array $params): void`
Calls the controller and its method directly, performing the full lifecycle:
This method is used to fully dispatch a route after matching it with the current request.
-------------------------
Вызывает контроллер и его метод напрямую, выполняя полный жизненный цикл:
Метод используется для полной диспетчеризации маршрута после его совпадения с текущим запросом. | @@ -73,8 +63,8 @@ ### Class: Rudra\Router\Traits\RouterAnnotationTrait | Visibility | Function | |:-----------|:---------| -|public|annotationCollector( array $controllers bool $getter bool $attributes ): ?array
| -|protected|handleAnnotationMiddleware( array $annotation ): array
| +| public | `annotationCollector(array $controllers, bool $getter, bool $attributes): ?array`
Collects and processes annotations from the specified controllers.
This method scans each controller class for Routing and Middleware annotations,
builds route definitions based on those annotations, and either:
- Registers them directly via `set()` (if \$getter = false), or
- Returns them as an array (if \$getter = true).
--------------------
Собирает и обрабатывает аннотации указанных контроллеров.
Метод сканирует каждый контроллер на наличие аннотаций Routing и Middleware,
формирует определения маршрутов и либо:
- Регистрирует их напрямую через `set()` (если \$getter = false),
- Возвращает как массив (если \$getter = true). | +| protected | `handleAnnotationMiddleware(array $annotation): array`
Processes middleware annotations into a valid middleware format.
--------------------
Обрабатывает аннотации middleware в поддерживаемый формат.
```#[Middleware(name: "Auth", params: "admin")]```
в:
```['Auth', 'admin']``` | @@ -82,14 +72,14 @@ ### Class: Rudra\Router\Traits\RouterRequestMethodTrait | Visibility | Function | |:-----------|:---------| -|abstract public|set( array $route ): void
| -|public|get( array $route ): void
| -|public|post( array $route ): void
| -|public|put( array $route ): void
| -|public|patch( array $route ): void
| -|public|delete( array $route ): void
| -|public|any( array $route ): void
| -|public|resource( array $route array $actions ): void
| +| public | `get(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the GET HTTP method.
--------------------
Регистрирует маршрут с использованием метода GET. | +| public | `post(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the POST HTTP method.
--------------------
Регистрирует маршрут с использованием метода POST. | +| public | `put(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the PUT HTTP method.
--------------------
Регистрирует маршрут с использованием метода PUT. | +| public | `patch(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the PATCH HTTP method.
--------------------
Регистрирует маршрут с использованием метода PATCH. | +| public | `delete(string $pattern, callable\|array $target, array $middleware): void`
Registers a route with the DELETE HTTP method.
--------------------
Регистрирует маршрут с использованием метода DELETE. | +| public | `any(string $pattern, callable\|array $target, array $middleware): void`
Registers a route that supports all HTTP methods.
Sets the method to a pipe-separated string ('GET\|POST\|PUT\|PATCH\|DELETE'),
allowing the same route to handle multiple request types.
--------------------
Регистрирует маршрут, поддерживающий все HTTP-методы.
Устанавливает метод как строку с разделителем \| ('GET\|POST\|PUT\|PATCH\|DELETE'),
что позволяет использовать один маршрут для нескольких типов запросов. | +| public | `resource(string $pattern, string $controller, array $actions): void`
Registers a resource route, mapping standard actions to controller methods.
Supports common CRUD operations by default:
- GET=> read
- POST => create
- PUT=> update
- DELETE => delete
Can be customized with an optional \$actions array.
--------------------
Регистрирует ресурсный маршрут, связывая стандартные действия с методами контроллера.
По умолчанию поддерживает CRUD-операции:
- GET=> read
- POST => create
- PUT=> update
- DELETE => delete
Может быть переопределён с помощью массива \$actions. | +| protected | `setRoute(string $pattern, $target, string $httpMethod, array $middleware): void`
The method constructs a route definition and passes it to the `set()` method for registration.
--------------------
Метод формирует определение маршрута и передает его в метод `set()` для регистрации. |
###### created with [Rudra-Documentation-Collector](#https://github.com/Jagepard/Rudra-Documentation-Collector) From 3df03da376c10213b552f14e24ff7f8c7d71b3e5 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:36:07 +0300 Subject: [PATCH 4/8] back to string $pattern, array|callable $target, array $middleware = [] --- src/Traits/RouterRequestMethodTrait.php | 150 ++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 13 deletions(-) diff --git a/src/Traits/RouterRequestMethodTrait.php b/src/Traits/RouterRequestMethodTrait.php index f4323ecf..80636c02 100755 --- a/src/Traits/RouterRequestMethodTrait.php +++ b/src/Traits/RouterRequestMethodTrait.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router\Traits; @@ -12,26 +12,112 @@ trait RouterRequestMethodTrait { /** + * Registers a route with the GET HTTP method. + * -------------------- + * Регистрирует маршрут с использованием метода GET. + * + * @param string $pattern + * @param array|callable $target + * @param array $middleware + */ + public function get(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'GET', $middleware); + } + + /** + * Registers a route with the POST HTTP method. + * -------------------- + * Регистрирует маршрут с использованием метода POST. + * * @param array $route */ - abstract public function set(array $route): void; + public function post(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'POST', $middleware); + } - public function get(array $route): void { $route['method'] = 'GET'; $this->set($route); } - public function post(array $route): void { $route['method'] = 'POST'; $this->set($route); } - public function put(array $route): void { $route['method'] = 'PUT'; $this->set($route); } - public function patch(array $route): void { $route['method'] = 'PATCH'; $this->set($route); } - public function delete(array $route): void { $route['method'] = 'DELETE'; $this->set($route); } + /** + * Registers a route with the PUT HTTP method. + * -------------------- + * Регистрирует маршрут с использованием метода PUT. + * + * @param array $route + */ + public function put(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'PUT', $middleware); + } - public function any(array $route): void { - $route['method'] = 'GET|POST|PUT|PATCH|DELETE'; - $this->set($route); + /** + * Registers a route with the PATCH HTTP method. + * -------------------- + * Регистрирует маршрут с использованием метода PATCH. + * + * @param array $route + */ + public function patch(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'PATCH', $middleware); } /** + * Registers a route with the DELETE HTTP method. + * -------------------- + * Регистрирует маршрут с использованием метода DELETE. + * * @param array $route - * @param array $actions */ - public function resource(array $route, array $actions = ['read', 'create', 'update', 'delete']): void + public function delete(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'DELETE', $middleware); + } + + /** + * Registers a route that supports all HTTP methods. + * + * Sets the method to a pipe-separated string ('GET|POST|PUT|PATCH|DELETE'), + * allowing the same route to handle multiple request types. + * -------------------- + * Регистрирует маршрут, поддерживающий все HTTP-методы. + * + * Устанавливает метод как строку с разделителем | ('GET|POST|PUT|PATCH|DELETE'), + * что позволяет использовать один маршрут для нескольких типов запросов. + * + * @param array $route + */ + public function any(string $pattern, array|callable $target, array $middleware = []): void + { + $this->setRoute($pattern, $target, 'GET|POST|PUT|PATCH|DELETE', $middleware); + } + + /** + * Registers a resource route, mapping standard actions to controller methods. + * + * Supports common CRUD operations by default: + * - GET => read + * - POST => create + * - PUT => update + * - DELETE => delete + * + * Can be customized with an optional $actions array. + * -------------------- + * Регистрирует ресурсный маршрут, связывая стандартные действия с методами контроллера. + * + * По умолчанию поддерживает CRUD-операции: + * - GET => read + * - POST => create + * - PUT => update + * - DELETE => delete + * + * Может быть переопределён с помощью массива $actions. + * + * @param string $pattern + * @param string $controller + * @param array $actions + * @return void + */ + public function resource(string $pattern, string $controller, array $actions = ['read', 'create', 'update', 'delete']): void { $request = $this->rudra->request(); $server = $request->server(); @@ -64,6 +150,44 @@ public function resource(array $route, array $actions = ['read', 'create', 'upda return; // Неизвестный метод — игнорируем } + $route['url'] = $pattern; + $route['controller'] = $controller; + + $this->set($route); + } + + /** + * The method constructs a route definition and passes it to the `set()` method for registration. + * -------------------- + * Метод формирует определение маршрута и передает его в метод `set()` для регистрации. + * + * @param string $pattern + * @param mixed $target + * @param string $httpMethod + * @param array $middleware + */ + protected function setRoute(string $pattern, $target, string $httpMethod, array $middleware = []): void + { + $route['method'] = $httpMethod; + $route['url'] = $pattern; + + if (count($middleware)) { + if (array_key_exists('before', $middleware)) { + $route['middleware']['before'] = $middleware['before']; + } + + if (array_key_exists('after', $middleware)) { + $route['middleware']['after'] = $middleware['after']; + } + } + + if (is_callable($target)) { + $route['controller'] = $target; + } elseif (is_array($target)) { + $route['controller'] = $target[0]; + $route['action'] = $target[1]; + } + $this->set($route); } } From 53bb019aed96b68ce71913cefef57b61c8073748 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:36:25 +0300 Subject: [PATCH 5/8] update --- src/Traits/RouterAnnotationTrait.php | 48 ++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Traits/RouterAnnotationTrait.php b/src/Traits/RouterAnnotationTrait.php index 6582487b..c546562b 100755 --- a/src/Traits/RouterAnnotationTrait.php +++ b/src/Traits/RouterAnnotationTrait.php @@ -3,8 +3,8 @@ declare(strict_types=1); /** - * @author : Jagepard - * @license https://mit-license.org/ MIT + * @author : Jagepard + * @license https://mit-license.org/ MIT */ namespace Rudra\Router\Traits; @@ -15,10 +15,24 @@ trait RouterAnnotationTrait { /** - * @param array $controllers - * @param boolean $getter - * @param boolean $attributes - * @return void + * Collects and processes annotations from the specified controllers. + * + * This method scans each controller class for Routing and Middleware annotations, + * builds route definitions based on those annotations, and either: + * - Registers them directly via `set()` (if $getter = false), or + * - Returns them as an array (if $getter = true). + * -------------------- + * Собирает и обрабатывает аннотации указанных контроллеров. + * + * Метод сканирует каждый контроллер на наличие аннотаций Routing и Middleware, + * формирует определения маршрутов и либо: + * - Регистрирует их напрямую через `set()` (если $getter = false), + * - Возвращает как массив (если $getter = true). + * + * @param array $controllers + * @param bool $getter + * @param bool $attributes + * @return array|null */ public function annotationCollector(array $controllers, bool $getter = false, bool $attributes = false): ?array { @@ -70,21 +84,29 @@ public function annotationCollector(array $controllers, bool $getter = false, bo } /** + * Processes middleware annotations into a valid middleware format. + * -------------------- + * Обрабатывает аннотации middleware в поддерживаемый формат. + * + * ```#[Middleware(name: "Auth", params: "admin")]``` + * в: + * ```['Auth', 'admin']``` + * * @param array $annotation * @return array */ protected function handleAnnotationMiddleware(array $annotation): array { - $middleware = []; + $output = []; - foreach ($annotation as $item) { - $entry = [$item['name']]; - if (isset($item['params'])) { - $entry[] = $item['params']; + foreach ($annotation as $middleware) { + if (!isset($middleware['params'])) { + $output[] = $middleware['name']; + } else { + $output[] = [$middleware['name'], $middleware['params']]; } - $middleware[] = $entry; } - return $middleware; + return $output; } } From d3477fd70beb5d7249444762b59f975c8ffc5d54 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:38:24 +0300 Subject: [PATCH 6/8] fix --- docs.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs.md b/docs.md index 4a1f5542..02f23775 100644 --- a/docs.md +++ b/docs.md @@ -48,6 +48,7 @@ ### Class: Rudra\Router\RouterInterface | Visibility | Function | |:-----------|:---------| +| abstract public | `set(array $route): void`
Sets the route, parsing HTTP methods (if multiple are specified via \|).
Registers a route handler for each method.
-------------------------
Устанавливает маршрут, разбирая HTTP-методы (если указано несколько через \|).
Для каждого метода регистрирует обработчик маршрута. | | abstract public | `directCall(array $route, ?array $params): void`
Calls the controller and its method directly, performing the full lifecycle:
This method is used to fully dispatch a route after matching it with the current request.
-------------------------
Вызывает контроллер и его метод напрямую, выполняя полный жизненный цикл:
Метод используется для полной диспетчеризации маршрута после его совпадения с текущим запросом. | From 90dc2e919d3e03407dfe62dfa386b6fd20213b9b Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:38:38 +0300 Subject: [PATCH 7/8] add comments --- src/RouterInterface.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/RouterInterface.php b/src/RouterInterface.php index 3769528d..9ba96309 100755 --- a/src/RouterInterface.php +++ b/src/RouterInterface.php @@ -13,6 +13,29 @@ interface RouterInterface { + /** + * Sets the route, parsing HTTP methods (if multiple are specified via |). + * Registers a route handler for each method. + * ------------------------- + * Устанавливает маршрут, разбирая HTTP-методы (если указано несколько через |). + * Для каждого метода регистрирует обработчик маршрута. + * + * @param array $route + * @return void + */ public function set(array $route): void; - public function directCall(array $route, $params = null): void; + + /** + * Calls the controller and its method directly, performing the full lifecycle: + * This method is used to fully dispatch a route after matching it with the current request. + * ------------------------- + * Вызывает контроллер и его метод напрямую, выполняя полный жизненный цикл: + * Метод используется для полной диспетчеризации маршрута после его совпадения с текущим запросом. + * + * @param array $route + * @param array|null $params + * @return void + * @throws RouterException + */ + public function directCall(array $route, ?array $params = null): void; } From 5232c9717c4c23b439ac73fdf92917bdce8e5414 Mon Sep 17 00:00:00 2001 From: Jagepard Date: Thu, 26 Jun 2025 15:39:23 +0300 Subject: [PATCH 8/8] update --- src/Router.php | 215 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 177 insertions(+), 38 deletions(-) diff --git a/src/Router.php b/src/Router.php index 3a6a1301..7592ebe3 100755 --- a/src/Router.php +++ b/src/Router.php @@ -10,8 +10,8 @@ namespace Rudra\Router; use ReflectionException; -use Rudra\Exceptions\RouterException; use Rudra\Container\Traits\SetRudraContainersTrait; +use Rudra\Exceptions\{MiddlewareException, RouterException}; use Rudra\Router\Traits\{RouterAnnotationTrait, RouterRequestMethodTrait}; class Router implements RouterInterface @@ -22,6 +22,16 @@ class Router implements RouterInterface private array $reflectionCache = []; + /** + * Sets the route, parsing HTTP methods (if multiple are specified via |). + * Registers a route handler for each method. + * ------------------------- + * Устанавливает маршрут, разбирая HTTP-методы (если указано несколько через |). + * Для каждого метода регистрирует обработчик маршрута. + * + * @param array $route + * @return void + */ public function set(array $route): void { $httpMethods = str_contains($route['method'], '|') @@ -34,32 +44,46 @@ public function set(array $route): void } } + /** + * Processes the incoming URI request and checks if it matches the current route. + * ------------------------- + * Обрабатывает входящий URI-запрос и проверяет его совпадение с текущим маршрутом. + * + * @param array $route + * @return void + */ private function handleRequestUri(array $route): void { $this->handleRequestMethod(); $request = $this->rudra->request(); - $server = $request->server(); + $server = $request->server(); if ($route['method'] !== $server->get('REQUEST_METHOD')) { return; } - $uriRaw = $server->get('REQUEST_URI'); - $parsed = parse_url($uriRaw); - $requestPath = $parsed && isset($parsed['path']) ? ltrim($parsed['path'], '/') : ''; - $uriSegments = explode('/', $requestPath); + $uriRaw = $server->get('REQUEST_URI'); + $parsed = parse_url($uriRaw); + $requestPath = $parsed && isset($parsed['path']) ? ltrim($parsed['path'], '/') : ''; + $uriSegments = explode('/', $requestPath); [$uri, $params] = $this->handlePattern($route, $uriSegments); - if ($uri === $uriSegments) { $this->setCallable($route, $params); } } + /** + * Processes the HTTP request method, including spoofing via _method (for PUT/PATCH/DELETE) + * ------------------------- + * Обрабатывает HTTP-метод запроса, включая spoofing через _method (для PUT/PATCH/DELETE) + * + * @return void + */ private function handleRequestMethod(): void { - $request = $this->rudra->request(); + $request = $this->rudra->request(); $requestMethod = $request->server()->get('REQUEST_METHOD'); // Spoofing the method via _method parameter in POST requests @@ -79,17 +103,28 @@ private function handleRequestMethod(): void } } + /** + * Matches the URI from the route with the actual request, processing parameters of the form :param and :regexp. + * This method is used to extract dynamic segments from a URI pattern: + * ------------------------- + * Сопоставляет URI из маршрута с фактическим запросом, обрабатывая параметры вида :param и :regexp. + * Метод извлекает динамические сегменты из URL-шаблона: + * + * @param array $route + * @param array $request + * @return array + */ private function handlePattern(array $route, array $request): array { - $uri = []; - $params = null; + $uri = []; + $params = null; $subject = explode('/', ltrim($route['url'], '/')); - $count = count($subject); + $count = count($subject); for ($i = 0; $i < $count; $i++) { if (preg_match("/^:[a-zA-Z0-9_-]+$/", $subject[$i]) > 0 && array_key_exists($i, $request)) { - $value = $request[$i]; - $uri[] = $value; + $value = $request[$i]; + $uri[] = $value; $params[] = $value; continue; } @@ -98,7 +133,7 @@ private function handlePattern(array $route, array $request): array if (array_key_exists($i, $request)) { $pattern = $matches[1]; if (preg_match("/^$pattern$/", $request[$i])) { - $uri[] = $request[$i]; + $uri[] = $request[$i]; $params[] = $request[$i]; } else { $uri[] = '!@#$%^&*'; @@ -114,7 +149,16 @@ private function handlePattern(array $route, array $request): array return [$uri, $params]; } - private function setCallable(array $route, $params): void + /** + * Calls the controller associated with the route — either a Closure or a controller method. + * ------------------------- + * Вызывает контроллер, связанный с маршрутом — либо Closure, либо метод контроллера. + * + * @param array $route + * @param array|null $params + * @return void + */ + private function setCallable(array $route, ?array $params): void { if ($route['controller'] instanceof \Closure) { if (is_array($params)) { @@ -123,16 +167,32 @@ private function setCallable(array $route, $params): void $route['controller']($params); } - exit(); + if ($this->rudra->config()->get('environment') !== 'test') { + exit(); + } + + return; } $this->directCall($route, $params); } - public function directCall(array $route, $params = null): void + /** + * Calls the controller and its method directly, performing the full lifecycle: + * This method is used to fully dispatch a route after matching it with the current request. + * ------------------------- + * Вызывает контроллер и его метод напрямую, выполняя полный жизненный цикл: + * Метод используется для полной диспетчеризации маршрута после его совпадения с текущим запросом. + * + * @param array $route + * @param array|null $params + * @return void + * @throws RouterException + */ + public function directCall(array $route, ?array $params = null): void { $controller = $this->rudra->get($route['controller']); - $action = $route['action']; + $action = $route['action']; if (!method_exists($controller, $action)) { throw new RouterException("503"); @@ -163,6 +223,23 @@ public function directCall(array $route, $params = null): void } } + /** + * Calls the controller method using Reflection, performing automatic parameter injection based on type hints. + * + * This method is typically used when the zend.exception_ignore_args setting is enabled, + * allowing for more flexible and type-safe dependency resolution. + * ------------------------- + * Вызывает метод контроллера с помощью Reflection, выполняя автоматическое внедрение параметров на основе типизации. + * + * Этот метод обычно используется, когда включена настройка zend.exception_ignore_args, + * что позволяет более гибко и безопасно разрешать зависимости по типам. + * + * @param array|null $params + * @param string action + * @param object $controller + * @return void + * @throws RouterException + */ private function callActionThroughReflection(?array $params, string $action, object $controller): void { if ($params && in_array('', $params, true)) { @@ -170,6 +247,7 @@ private function callActionThroughReflection(?array $params, string $action, obj } $cacheKey = get_class($controller) . "::$action"; + if (!isset($this->reflectionCache[$cacheKey])) { $this->reflectionCache[$cacheKey] = [ 'method' => new \ReflectionMethod($controller, $action), @@ -181,7 +259,36 @@ private function callActionThroughReflection(?array $params, string $action, obj $method->invokeArgs($controller, $arguments); } - private function callActionThroughException($params, $action, $controller): void + /** + * Calls the specified controller method directly. + * + * If the argument type or number does not match — tries to automatically inject required dependencies. + * This is a fallback mechanism for cases where Reflection-based injection is disabled or unavailable. + * + * Handles two types of errors during invocation: + * - \ArgumentCountError — thrown when the number of arguments doesn't match the method signature. + * - \TypeError — thrown when an argument is not compatible with the expected type. + * + * In both cases, Rudra's autowire system attempts to resolve and inject the correct dependencies. + * ------------------------- + * Вызывает указанный метод контроллера напрямую. + * + * Если тип или количество аргументов не совпадает — пытается автоматически внедрить нужные зависимости. + * Это механизм отката, используемый, когда недоступен вызов через Reflection. + * + * Обрабатываются следующие ошибки: + * - \ArgumentCountError — выбрасывается, если количество аргументов не совпадает с ожидаемым. + * - \TypeError — выбрасывается, если тип аргумента не соответствует ожидаемому. + * + * В обоих случаях система автовайринга Rudra пытается разрешить и внедрить правильные зависимости. + * + * @param array|null $params + * @param string $action + * @param object $controller + * @return void + * @throws RouterException + */ + private function callActionThroughException(?array $params, string $action, object $controller): void { if (isset($params) && in_array('', $params)) { throw new RouterException("404"); @@ -202,32 +309,64 @@ private function callActionThroughException($params, $action, $controller): void } } - public function handleMiddleware(array $chainOfMiddlewares): void + /** + * Executes a chain of middleware, recursively calling each element. + * + * Middleware can be specified in one of the supported formats: + * - 'MiddlewareClass' (string) — a simple class name to call without parameters. + * - ['MiddlewareClass'] (array with class name) — same as above, allows for future extensions. + * - ['MiddlewareClass', $parameter] (array with class and parameter) — passes the parameter to the middleware. + * + * Each middleware must implement the __invoke() method to be callable. + * -------------------- + * Выполняет цепочку middleware, рекурсивно вызывая каждый элемент. + * + * Middleware может быть указан в одном из поддерживаемых форматов: + * - 'MiddlewareClass' (строка) — простое имя класса без параметров. + * - ['MiddlewareClass'] (массив с именем класса) — аналогично предыдущему, удобно для расширения. + * - ['MiddlewareClass', $parameter] (массив с классом и параметром) — передаёт параметр в middleware. + * + * Каждый middleware должен реализовывать метод __invoke(), чтобы быть вызываемым. + * + * @param array $chain + * @return void + * @throws \Rudra\Router\Exceptions\MiddlewareException + */ + public function handleMiddleware(array $chain): void { - if (!$chainOfMiddlewares) { + if (!$chain) { return; } - $current = array_shift($chainOfMiddlewares); + $current = array_shift($chain); - if (is_array($current) && count($current) === 2 && is_string($current[0])) { - $middleware = new $current[0](); - $middleware($current[1], $chainOfMiddlewares); - return; - } + try { + if (is_array($current) && count($current) === 2 && is_string($current[0])) { + $middleware = new $current[0](); + $middleware($chain, ...$current[1]); + return; + } - if (is_array($current) && is_string($current[0])) { - $middleware = new $current[0](); - $middleware($chainOfMiddlewares); - return; - } + if (is_array($current) && is_string($current[0])) { + $middleware = new $current[0](); + $middleware($chain); + return; + } - if (is_string($current)) { - $middleware = new $current(); - $middleware($chainOfMiddlewares); - return; - } + if (is_string($current)) { + $middleware = new $current(); + $middleware($chain); + return; + } - throw new \InvalidArgumentException('Invalid middleware format'); + if (is_callable($current)) { + $current($chain); + return; + } + + throw new MiddlewareException('Invalid middleware format'); + } catch (\Throwable $e) { + throw new MiddlewareException("Failed to process middleware: " . json_encode($current), 0, $e); + } } }