Skip to content

Commit 9d34f8b

Browse files
1.99.0
- [R] Real PHP8 compatible release. - [!] эта минорная версия - latest перед полным переходом на PHP8 - [+] добавлена зависимость `psr/log: ^1 | ^2 | ^3` - [+] внедрен код библиотеки FastRouter (отказ от внешней зависимости) - [*] исправлена проблема с доп.информацией в Exceptions - [*] если в dispatch выяснилось, что handler пустой - кидаем исключение `AppRouterHandlerError` - [*] добавлена проверка в `is_handler()`, переданный пустой массив - ошибка (false) - [*] исправлен `compileHandler()` - пустой массив является ошибкой - [*] исправлен `getInternalRuleKey()` - для пустого массива или неуказанного класса генерится ключ на основе роута и метода.
1 parent 65276e8 commit 9d34f8b

26 files changed

+1089
-53
lines changed

composer.json

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@
1010
},
1111
"autoload": {
1212
"psr-4": {
13-
"Arris\\": "src/"
14-
}
13+
"Arris\\": "src/",
14+
"Arris\\AppRouter\\": "src/AppRouter"
15+
},
16+
"files": [
17+
"src/AppRouter/FastRoute/functions.php"
18+
]
1519
},
1620
"authors": [
1721
{
@@ -21,8 +25,7 @@
2125
],
2226
"require": {
2327
"php": ">=7.4 || 8.*",
24-
"nikic/fast-route": "^1.3",
25-
"psr/log": "^1.1"
28+
"psr/log": "^1 | ^2 | ^3"
2629
},
2730
"require-dev": {
2831
"karelwintersky/arris": "*"

src/AppRouter.php

Lines changed: 52 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22

33
namespace Arris;
44

5+
use Arris\AppRouter\FastRoute\Dispatcher;
6+
use Arris\AppRouter\FastRoute\RouteCollector;
57
use Arris\AppRouter\Stack;
68
use Arris\Exceptions\AppRouterHandlerError;
79
use Arris\Exceptions\AppRouterMethodNotAllowedException;
810
use Arris\Exceptions\AppRouterNotFoundException;
9-
use FastRoute\Dispatcher;
10-
use FastRoute\RouteCollector;
1111
use Psr\Log\LoggerInterface;
1212
use Psr\Log\NullLogger;
1313

@@ -146,7 +146,7 @@ public static function init(LoggerInterface $logger = null, array $options = [])
146146
self::$httpMethod = $_SERVER['REQUEST_METHOD'];
147147

148148
$uri = $_SERVER['REQUEST_URI'];
149-
if (false !== $pos = strpos($uri, '?')) {
149+
if (false !== $pos = \strpos($uri, '?')) {
150150
$uri = \substr($uri, 0, $pos);
151151
}
152152
self::$uri = \rawurldecode($uri);
@@ -206,7 +206,7 @@ public static function get($route, $handler, $name = null)
206206
return;
207207
}
208208

209-
$key = self::getInternalRuleKey('GET', $handler);
209+
$key = self::getInternalRuleKey('GET', $handler, $route);
210210

211211
if (!\is_null($name)) {
212212
self::$route_names[$name] = self::$current_prefix . $route;
@@ -231,7 +231,7 @@ public static function post($route, $handler, $name = null)
231231
return;
232232
}
233233

234-
$key = self::getInternalRuleKey('POST', $handler);
234+
$key = self::getInternalRuleKey('POST', $handler, $route);
235235

236236
if (!\is_null($name)) {
237237
self::$route_names[$name] = self::$current_prefix . $route;
@@ -256,7 +256,7 @@ public static function put($route, $handler, $name = null)
256256
return;
257257
}
258258

259-
$key = self::getInternalRuleKey('PUT', $handler);
259+
$key = self::getInternalRuleKey('PUT', $handler, $route);
260260

261261
if (!\is_null($name)) {
262262
self::$route_names[$name] = self::$current_prefix . $route;
@@ -281,7 +281,7 @@ public static function patch($route, $handler, $name = null)
281281
return;
282282
}
283283

284-
$key = self::getInternalRuleKey('PATCH', $handler);
284+
$key = self::getInternalRuleKey('PATCH', $handler, $route);
285285

286286
if (!\is_null($name)) {
287287
self::$route_names[$name] = self::$current_prefix . $route;
@@ -306,7 +306,7 @@ public static function delete($route, $handler, $name = null)
306306
return;
307307
}
308308

309-
$key = self::getInternalRuleKey('DELETE', $handler);
309+
$key = self::getInternalRuleKey('DELETE', $handler, $route);
310310

311311
if (!\is_null($name)) {
312312
self::$route_names[$name] = self::$current_prefix . $route;
@@ -331,7 +331,7 @@ public static function head($route, $handler, $name = null)
331331
return;
332332
}
333333

334-
$key = self::getInternalRuleKey('HEAD', $handler);
334+
$key = self::getInternalRuleKey('HEAD', $handler, $route);
335335

336336
if (!\is_null($name)) {
337337
self::$route_names[$name] = self::$current_prefix . $route;
@@ -362,7 +362,7 @@ public static function any($route, $handler, $name = null)
362362
self::$route_names["{$method}.{$name}"] = self::$current_prefix . $route;
363363
}
364364

365-
$key = self::getInternalRuleKey($method, $handler);
365+
$key = self::getInternalRuleKey($method, $handler, $route);
366366

367367
self::$rules[ $key ] = [
368368
'httpMethod' => $method,
@@ -386,7 +386,7 @@ public static function addRoute($httpMethod, $route, $handler, $name = null)
386386

387387
foreach ((array)$httpMethod as $method) {
388388
$httpMethod = $method;
389-
$key = self::getInternalRuleKey($httpMethod, $handler);
389+
$key = self::getInternalRuleKey($httpMethod, $handler, $route);
390390

391391
if (!\is_null($name)) {
392392
self::$route_names[$name] = self::$current_prefix . $route;
@@ -406,7 +406,7 @@ public static function addRoute($httpMethod, $route, $handler, $name = null)
406406
}
407407

408408

409-
if (!is_null($name)) {
409+
if (!\is_null($name)) {
410410
self::$route_names[$name] = self::$current_prefix . $route;
411411
}
412412
}
@@ -525,7 +525,7 @@ public static function getRoutersNames()
525525

526526
public static function dispatch()
527527
{
528-
self::$dispatcher = \FastRoute\simpleDispatcher(function (RouteCollector $r) {
528+
self::$dispatcher = \Arris\AppRouter\FastRoute\simpleDispatcher(function (RouteCollector $r) {
529529
foreach (self::$rules as $rule) {
530530
$handler
531531
= (\is_string($rule['handler']) && !empty($rule['namespace']))
@@ -541,20 +541,29 @@ public static function dispatch()
541541

542542
// list($state, $handler, $method_parameters) = $routeInfo;
543543
// PHP8+ good practice:
544-
$state = $routeInfo[0];
544+
$state = $routeInfo[0]; // тут ВСЕГДА что-то есть
545545
$handler = $routeInfo[1] ?? [];
546546
$method_parameters = $routeInfo[2] ?? [];
547547

548+
// Handler пустой или некорректный
549+
if (empty($handler)) {
550+
throw new AppRouterHandlerError("Handler not found or empty", 500, [
551+
'uri' => self::$uri,
552+
'method' => self::$httpMethod,
553+
'info' => self::$routeInfo
554+
]);
555+
}
556+
548557
// dispatch errors
549558
if ($state === Dispatcher::NOT_FOUND) {
550-
throw new AppRouterNotFoundException("URL not found", 404, null, [
559+
throw new AppRouterNotFoundException("URL not found", 404, [
551560
'method' => self::$httpMethod,
552561
'uri' => self::$uri
553562
]);
554563
}
555564

556565
if ($state === Dispatcher::METHOD_NOT_ALLOWED) {
557-
throw new AppRouterMethodNotAllowedException("Method " . self::$httpMethod . " not allowed for URI " . self::$uri, 405, null, [
566+
throw new AppRouterMethodNotAllowedException("Method " . self::$httpMethod . " not allowed for URI " . self::$uri, 405, [
558567
'uri' => self::$uri,
559568
'method' => self::$httpMethod,
560569
'info' => self::$routeInfo
@@ -639,6 +648,10 @@ public static function getRoutingRules(): array
639648
}
640649

641650
/**
651+
* @todo: объединить is_handler и compileHandler - они выполняют одну и ту же работу, но сначала делается is_handler, а потом compileHandler перед выполнением
652+
* Надо сразу компилировать хэндлэр сразу с проверкой и кидать исключение на раннем этапе. Возможно, вообще на этапе прописывания роута!
653+
*
654+
*
642655
* Выясняет, является ли передаваемый аргумент допустимым хэндлером
643656
*
644657
* @todo: переделать код так, чтобы хэндлер `\Path\To\Class::method` вызывал не сразу статический класс,
@@ -656,6 +669,10 @@ public static function is_handler($handler = null, bool $validate_handlers = fal
656669
} elseif ($handler instanceof \Closure) {
657670
return true;
658671
} elseif (\is_array($handler)) {
672+
if (empty($handler)) {
673+
return false;
674+
}
675+
659676
// [ \Path\To\Class:class, "method" ]
660677

661678
$class = $handler[0];
@@ -706,7 +723,7 @@ public static function is_handler($handler = null, bool $validate_handlers = fal
706723

707724
return true;
708725
}
709-
return false;
726+
710727
} // is_handler()
711728

712729
/**
@@ -721,6 +738,10 @@ private static function compileHandler($handler)
721738
if ($handler instanceof \Closure) {
722739
$actor = $handler;
723740
} elseif (\is_array($handler)) {
741+
if (empty($handler)) {
742+
return [];
743+
}
744+
724745
// [ \Path\To\Class:class, "method" ]
725746

726747
$class = $handler[0];
@@ -774,10 +795,11 @@ private static function compileHandler($handler)
774795
*
775796
* @param $httpMethod
776797
* @param $handler
798+
* @param $route
777799
* @param bool $append_namespace
778800
* @return string
779801
*/
780-
private static function getInternalRuleKey($httpMethod, $handler, bool $append_namespace = true): string
802+
private static function getInternalRuleKey($httpMethod, $handler, $route, bool $append_namespace = true): string
781803
{
782804
$namespace = '';
783805
if ($append_namespace) {
@@ -788,12 +810,16 @@ private static function getInternalRuleKey($httpMethod, $handler, bool $append_n
788810
if ($handler instanceof \Closure) {
789811
$internal_name = self::getClosureInternalName($handler);
790812
} elseif (\is_array($handler)) {
791-
$class = $handler[0];
792-
$method = $handler[1] ?: '__invoke';
813+
814+
// хэндлер может быть массивом, но пустым. Вообще, это ошибка, поэтому генерим имя на основе md5 роута и метода.
815+
$class = $handler[0] ?? \md5(self::$httpMethod . ':' . self::$current_prefix . $route);
816+
$method = $handler[1] ?? '__invoke';
793817

794818
$internal_name = "{$namespace}{$class}@{$method}";
795819
} elseif (\is_string($handler)) {
796820
$internal_name = "{$namespace}{$handler}";
821+
} elseif (\is_null($handler)) {
822+
return \md5(self::$httpMethod . ':' . self::$current_prefix . $route);
797823
} else {
798824
// function by name
799825
$internal_name = $handler;
@@ -814,7 +840,7 @@ private static function checkClassExists($class, bool $is_static = false)
814840
if (!\class_exists($class)){
815841
$is_static = $is_static ? 'Static' : '';
816842
self::$logger->error("{$is_static} Class {$class} not defined.", [ self::$uri, self::$httpMethod, $class ]);
817-
throw new AppRouterHandlerError("{$is_static} Class {$class} not defined", 500, null, [
843+
throw new AppRouterHandlerError("{$is_static} Class {$class} not defined", 500, [
818844
'uri' => self::$uri,
819845
'method' => self::$httpMethod,
820846
'info' => self::$routeInfo
@@ -835,18 +861,17 @@ private static function checkMethodExists($class, $method, bool $is_static = fal
835861
$is_static = $is_static ? 'static ' : '';
836862

837863
if (empty($method)) {
838-
self::$logger->error("Method can't be empty at {$is_static}class {$class}", [ self::$uri, self::$httpMethod, $class ]);
839-
throw new AppRouterHandlerError("Method can't be empty at {$is_static}class {$class}", 500, null, [
864+
self::$logger->error("Method can't be empty at {$is_static}{$class} class", [ self::$uri, self::$httpMethod, $class ]);
865+
throw new AppRouterHandlerError("Method can't be empty at {$is_static} class {$class}", 500, [
840866
'uri' => self::$uri,
841867
'method' => self::$httpMethod,
842868
'info' => self::$routeInfo
843869
]);
844870
}
845871

846872
if (!\method_exists($class, $method)){
847-
848-
self::$logger->error("Method {$method} not declared at {$is_static} {$class} class", [ self::$uri, self::$httpMethod, $class ]);
849-
throw new AppRouterHandlerError("Method {$method} not declared at {$is_static} class {$class}", 500, null, [
873+
self::$logger->error("Method `{$method}` not declared at {$is_static}{$class} class", [ self::$uri, self::$httpMethod, $class ]);
874+
throw new AppRouterHandlerError("Method `{$method}` not declared at {$is_static}class {$class}", 500, [
850875
'uri' => self::$uri,
851876
'method' => self::$httpMethod,
852877
'info' => self::$routeInfo
@@ -865,7 +890,7 @@ private static function checkFunctionExists($handler)
865890
{
866891
if (!\function_exists($handler)){
867892
self::$logger->error("Handler function {$handler} not found", [ self::$uri, self::$httpMethod, $handler ]);
868-
throw new AppRouterHandlerError("Handler function {$handler} not found", 500, null, [
893+
throw new AppRouterHandlerError("Handler function {$handler} not found", 500, [
869894
'uri' => self::$uri,
870895
'method' => self::$httpMethod,
871896
'info' => self::$routeInfo
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
namespace Arris\AppRouter\FastRoute;
4+
5+
class BadRouteException extends \LogicException
6+
{
7+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Arris\AppRouter\FastRoute;
4+
5+
interface DataGenerator
6+
{
7+
/**
8+
* Adds a route to the data generator. The route data uses the
9+
* same format that is returned by RouterParser::parser().
10+
*
11+
* The handler doesn't necessarily need to be a callable, it
12+
* can be arbitrary data that will be returned when the route
13+
* matches.
14+
*
15+
* @param string $httpMethod
16+
* @param array $routeData
17+
* @param mixed $handler
18+
*/
19+
public function addRoute(string $httpMethod, array $routeData, $handler);
20+
21+
/**
22+
* Returns dispatcher data in some unspecified format, which
23+
* depends on the used method of dispatch.
24+
*/
25+
public function getData();
26+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace Arris\AppRouter\FastRoute\DataGenerator;
4+
5+
use Arris\AppRouter\FastRoute\DataGenerator\RegexBasedAbstract;
6+
7+
class CharCountBased extends RegexBasedAbstract
8+
{
9+
protected function getApproxChunkSize()
10+
{
11+
return 30;
12+
}
13+
14+
protected function processChunk($regexToRoutesMap)
15+
{
16+
$routeMap = [];
17+
$regexes = [];
18+
19+
$suffixLen = 0;
20+
$suffix = '';
21+
$count = \count($regexToRoutesMap);
22+
foreach ($regexToRoutesMap as $regex => $route) {
23+
$suffixLen++;
24+
$suffix .= "\t";
25+
26+
$regexes[] = '(?:' . $regex . '/(\t{' . $suffixLen . '})\t{' . ($count - $suffixLen) . '})';
27+
$routeMap[$suffix] = [$route->handler, $route->variables];
28+
}
29+
30+
$regex = '~^(?|' . \implode('|', $regexes) . ')$~';
31+
return ['regex' => $regex, 'suffix' => '/' . $suffix, 'routeMap' => $routeMap];
32+
}
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
namespace Arris\AppRouter\FastRoute\DataGenerator;
4+
5+
use Arris\AppRouter\FastRoute\DataGenerator\RegexBasedAbstract;
6+
7+
class GroupCountBased extends RegexBasedAbstract
8+
{
9+
protected function getApproxChunkSize()
10+
{
11+
return 10;
12+
}
13+
14+
protected function processChunk($regexToRoutesMap)
15+
{
16+
$routeMap = [];
17+
$regexes = [];
18+
$numGroups = 0;
19+
foreach ($regexToRoutesMap as $regex => $route) {
20+
$numVariables = \count($route->variables);
21+
$numGroups = \max($numGroups, $numVariables);
22+
23+
$regexes[] = $regex . \str_repeat('()', $numGroups - $numVariables);
24+
$routeMap[$numGroups + 1] = [$route->handler, $route->variables];
25+
26+
++$numGroups;
27+
}
28+
29+
$regex = '~^(?|' . \implode('|', $regexes) . ')$~';
30+
return ['regex' => $regex, 'routeMap' => $routeMap];
31+
}
32+
}

0 commit comments

Comments
 (0)