Skip to content

Commit 94301ce

Browse files
author
Devcoder-xyz
committedDec 15, 2022
migrate to php7.4
1 parent 879fcce commit 94301ce

12 files changed

+1820
-1617
lines changed
 

Diff for: ‎README.md

+26-17
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ composer require devcoder-xyz/php-router
1313

1414
## Requirements
1515

16-
* PHP version 7.3
16+
* PHP version 7.4
1717
* Enable URL rewriting on your web server
18-
* Need package for PSR-7 HTTP Message
18+
* Optional : Need package for PSR-7 HTTP Message
1919
(example : guzzlehttp/psr7 )
2020

2121
**How to use ?**
@@ -61,42 +61,48 @@ class ArticleController {
6161
}
6262
}
6363

64-
$router = new \DevCoder\Router([
64+
$routes = [
6565
new \DevCoder\Route('home_page', '/', [IndexController::class]),
6666
new \DevCoder\Route('api_articles_collection', '/api/articles', [ArticleController::class, 'getAll']),
6767
new \DevCoder\Route('api_articles', '/api/articles/{id}', [ArticleController::class, 'get']),
68-
]);
68+
];
69+
$router = new \DevCoder\Router($routes, 'http://localhost');
6970
```
70-
##Example
71-
$_SERVER['REQUEST_URI'] = '/api/articles/2'
72-
$_SERVER['REQUEST_METHOD'] = 'GET'
71+
72+
## Example
73+
7374
```php
7475
try {
7576
// Example
77+
7678
// \Psr\Http\Message\ServerRequestInterface
77-
//$route = $router->match(ServerRequestFactory::fromGlobals());
79+
$route = $router->match(ServerRequestFactory::fromGlobals());
7880
// OR
79-
81+
8082
// $_SERVER['REQUEST_URI'] = '/api/articles/2'
8183
// $_SERVER['REQUEST_METHOD'] = 'GET'
8284
$route = $router->matchFromPath($_SERVER['REQUEST_URI'], $_SERVER['REQUEST_METHOD']);
8385

84-
$parameters = $route->getParameters();
85-
// $arguments = ['id' => 2]
86-
$arguments = $route->getVars();
86+
$handler = $route->getHandler();
87+
// $attributes = ['id' => 2]
88+
$attributes = $route->getAttributes();
8789

88-
$controllerName = $parameters[0];
89-
$methodName = $parameters[1] ?? null;
90+
$controllerName = $handler[0];
91+
$methodName = $handler[1] ?? null;
9092

9193
$controller = new $controllerName();
9294
if (!is_callable($controller)) {
9395
$controller = [$controller, $methodName];
9496
}
9597

96-
echo $controller(...array_values($arguments));
98+
echo $controller(...array_values($attributes));
9799

98-
} catch (\Exception $exception) {
100+
} catch (\DevCoder\Exception\MethodNotAllowed $exception) {
101+
header("HTTP/1.0 405 Method Not Allowed");
102+
exit();
103+
} catch (\DevCoder\Exception\RouteNotFound $exception) {
99104
header("HTTP/1.0 404 Not Found");
105+
exit();
100106
}
101107
```
102108
How to Define Route methods
@@ -115,8 +121,11 @@ echo $router->generateUri('home_page');
115121
// /
116122
echo $router->generateUri('api_articles', ['id' => 1]);
117123
// /api/articles/1
124+
125+
echo $router->generateUri('api_articles', ['id' => 1], true);
126+
// http://localhost/api/articles/1
118127
```
119128

120-
Ideal for small project
129+
Ideal for small project.
121130
Simple and easy!
122131
[https://github.com/devcoder-xyz/php-router](https://github.com/devcoder-xyz/php-router)

Diff for: ‎composer.json

+8-3
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616
}
1717
],
1818
"require": {
19-
"php": ">=7.3",
19+
"php": ">=7.4",
2020
"psr/http-message": "^1.0",
2121
"psr/http-server-middleware": "^1.0",
2222
"psr/http-factory": "^1.0"
2323
},
2424
"require-dev": {
25-
"phpunit/phpunit": "~7.1",
26-
"nunomaduro/phpinsights": "^1.14"
25+
"phpunit/phpunit": "^9.5",
26+
"nunomaduro/phpinsights": "^2.6"
27+
},
28+
"config": {
29+
"allow-plugins": {
30+
"dealerdirect/phpcodesniffer-composer-installer": false
31+
}
2732
}
2833
}

Diff for: ‎composer.lock

+1,661-1,492
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: ‎src/Exception/MethodNotAllowed.php

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DevCoder\Exception;
6+
7+
final class MethodNotAllowed extends \Exception
8+
{
9+
}

Diff for: ‎src/Helper.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ public static function trimPath(string $path): string
88
{
99
return '/' . rtrim(ltrim(trim($path), '/'), '/');
1010
}
11-
}
11+
}

Diff for: ‎src/Route.php

+15-38
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
namespace DevCoder;
66

77
use DevCoder\Traits\RouteTrait;
8+
use InvalidArgumentException;
9+
use function array_filter;
10+
use function is_string;
11+
use function preg_match;
12+
use function preg_match_all;
13+
use function reset;
14+
use function str_replace;
15+
use function trim;
816

917
/**
1018
* Class Route
@@ -14,15 +22,8 @@ final class Route
1422
{
1523
use RouteTrait;
1624

17-
/**
18-
* @var string
19-
*/
20-
private $name;
21-
22-
/**
23-
* @var string
24-
*/
25-
private $path;
25+
private string $name;
26+
private string $path;
2627

2728
/**
2829
* @var mixed
@@ -32,7 +33,7 @@ final class Route
3233
/**
3334
* @var array<string>
3435
*/
35-
private $methods = [];
36+
private array $methods = [];
3637

3738
/**
3839
* @var array<string>
@@ -53,23 +54,23 @@ final class Route
5354
public function __construct(string $name, string $path, $handler, array $methods = ['GET'])
5455
{
5556
if ($methods === []) {
56-
throw new \InvalidArgumentException('HTTP methods argument was empty; must contain at least one method');
57+
throw new InvalidArgumentException('HTTP methods argument was empty; must contain at least one method');
5758
}
5859
$this->name = $name;
59-
$this->path = $path;
60+
$this->path = Helper::trimPath($path);
6061
$this->handler = $handler;
6162
$this->methods = $methods;
6263
}
6364

64-
public function match(string $path, string $method): bool
65+
public function match(string $path): bool
6566
{
6667
$regex = $this->getPath();
6768
foreach ($this->getVarsNames() as $variable) {
6869
$varName = trim($variable, '{\}');
6970
$regex = str_replace($variable, '(?P<' . $varName . '>[^/]++)', $regex);
7071
}
7172

72-
if (in_array($method, $this->getMethods()) && preg_match('#^' . $regex . '$#sD', Helper::trimPath($path), $matches)) {
73+
if (preg_match('#^' . $regex . '$#sD', Helper::trimPath($path), $matches)) {
7374
$values = array_filter($matches, static function ($key) {
7475
return is_string($key);
7576
}, ARRAY_FILTER_USE_KEY);
@@ -91,14 +92,6 @@ public function getPath(): string
9192
return $this->path;
9293
}
9394

94-
/**
95-
* @deprecated use getHandler()
96-
*/
97-
public function getParameters()
98-
{
99-
return $this->getHandler();
100-
}
101-
10295
public function getHandler()
10396
{
10497
return $this->handler;
@@ -115,27 +108,11 @@ public function getVarsNames(): array
115108
return reset($matches) ?? [];
116109
}
117110

118-
/**
119-
* @deprecated use hasAttributes()
120-
*/
121-
public function hasVars(): bool
122-
{
123-
return $this->hasAttributes();
124-
}
125-
126111
public function hasAttributes(): bool
127112
{
128113
return $this->getVarsNames() !== [];
129114
}
130115

131-
/**
132-
* @deprecated use getAttributes()
133-
*/
134-
public function getVars(): array
135-
{
136-
return $this->getAttributes();
137-
}
138-
139116
/**
140117
* @return array<string>
141118
*/

Diff for: ‎src/Router.php

+20-15
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,26 @@
44

55
namespace DevCoder;
66

7+
use DevCoder\Exception\MethodNotAllowed;
78
use DevCoder\Exception\RouteNotFound;
89
use Psr\Http\Message\ServerRequestInterface;
910

1011
final class Router implements RouterInterface
1112
{
1213
private const NO_ROUTE = 404;
14+
private const METHOD_NOT_ALLOWED = 405;
1315

14-
/**
15-
* @var \ArrayObject<Route>
16-
*/
17-
private $routes;
18-
19-
/**
20-
* @var UrlGenerator
21-
*/
22-
private $urlGenerator;
16+
private \ArrayObject $routes;
17+
private UrlGenerator $urlGenerator;
2318

2419
/**
2520
* Router constructor.
2621
* @param $routes array<Route>
2722
*/
28-
public function __construct(array $routes = [])
23+
public function __construct(array $routes = [], string $defaultUri = 'http://localhost')
2924
{
3025
$this->routes = new \ArrayObject();
31-
$this->urlGenerator = new UrlGenerator($this->routes);
26+
$this->urlGenerator = new UrlGenerator($this->routes, $defaultUri);
3227
foreach ($routes as $route) {
3328
$this->add($route);
3429
}
@@ -47,22 +42,32 @@ public function match(ServerRequestInterface $serverRequest): Route
4742

4843
public function matchFromPath(string $path, string $method): Route
4944
{
45+
/**
46+
* @var Route $route
47+
*/
5048
foreach ($this->routes as $route) {
51-
if ($route->match($path, $method) === false) {
49+
if ($route->match($path) === false) {
5250
continue;
5351
}
52+
53+
if (!in_array($method, $route->getMethods())) {
54+
throw new MethodNotAllowed(
55+
'Method Not Allowed : ' . $method,
56+
self::METHOD_NOT_ALLOWED
57+
);
58+
}
5459
return $route;
5560
}
5661

5762
throw new RouteNotFound(
58-
'No route found for ' . $method,
63+
'No route found for ' . $path,
5964
self::NO_ROUTE
6065
);
6166
}
6267

63-
public function generateUri(string $name, array $parameters = []): string
68+
public function generateUri(string $name, array $parameters = [], bool $absoluteUrl = false): string
6469
{
65-
return $this->urlGenerator->generate($name, $parameters);
70+
return $this->urlGenerator->generate($name, $parameters, $absoluteUrl);
6671
}
6772

6873
public function getUrlGenerator(): UrlGenerator

Diff for: ‎src/RouterMiddleware.php

+10-13
Original file line numberDiff line numberDiff line change
@@ -4,45 +4,40 @@
44

55
namespace DevCoder;
66

7+
use DevCoder\Exception\MethodNotAllowed;
78
use DevCoder\Exception\RouteNotFound;
89
use Psr\Http\Message\ResponseFactoryInterface;
910
use Psr\Http\Message\ResponseInterface;
1011
use Psr\Http\Message\ServerRequestInterface;
1112
use Psr\Http\Server\MiddlewareInterface;
1213
use Psr\Http\Server\RequestHandlerInterface;
14+
use Throwable;
1315

1416
final class RouterMiddleware implements MiddlewareInterface
1517
{
1618
public const CONTROLLER = '_controller';
1719
public const ACTION = '_action';
1820
public const NAME = '_name';
1921

20-
/**
21-
* @var RouterInterface
22-
*/
23-
private $router;
24-
25-
/**
26-
* @var ResponseFactoryInterface
27-
*/
28-
private $responseFactory;
22+
private RouterInterface $router;
23+
private ResponseFactoryInterface $responseFactory;
2924

3025
public function __construct(
31-
RouterInterface $router,
26+
RouterInterface $router,
3227
ResponseFactoryInterface $responseFactory)
3328
{
3429
$this->router = $router;
3530
$this->responseFactory = $responseFactory;
3631
}
3732

3833
public function process(
39-
ServerRequestInterface $request,
34+
ServerRequestInterface $request,
4035
RequestHandlerInterface $handler): ResponseInterface
4136
{
4237
try {
4338
$route = $this->router->match($request);
4439
$routeHandler = $route->getHandler();
45-
$attributes = array_merge([
40+
$attributes = \array_merge([
4641
self::CONTROLLER => $routeHandler[0],
4742
self::ACTION => $routeHandler[1] ?? null,
4843
self::NAME => $route->getName(),
@@ -51,9 +46,11 @@ public function process(
5146
foreach ($attributes as $key => $value) {
5247
$request = $request->withAttribute($key, $value);
5348
}
49+
} catch (MethodNotAllowed $exception) {
50+
return $this->responseFactory->createResponse(405);
5451
} catch (RouteNotFound $exception) {
5552
return $this->responseFactory->createResponse(404);
56-
} catch (\Throwable $exception) {
53+
} catch (Throwable $exception) {
5754
throw $exception;
5855
}
5956
return $handler->handle($request);

Diff for: ‎src/Traits/RouteTrait.php

+8-8
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@
66

77
trait RouteTrait
88
{
9-
public static function get(string $name, string $path, array $parameters): BaseRoute
9+
public static function get(string $name, string $path, $handler): BaseRoute
1010
{
11-
return new BaseRoute($name, $path, $parameters);
11+
return new BaseRoute($name, $path, $handler);
1212
}
1313

14-
public static function post(string $name, string $path, array $parameters): BaseRoute
14+
public static function post(string $name, string $path, $handler): BaseRoute
1515
{
16-
return new BaseRoute($name, $path, $parameters, ['POST']);
16+
return new BaseRoute($name, $path, $handler, ['POST']);
1717
}
1818

19-
public static function put(string $name, string $path, array $parameters): BaseRoute
19+
public static function put(string $name, string $path, $handler): BaseRoute
2020
{
21-
return new BaseRoute($name, $path, $parameters, ['PUT']);
21+
return new BaseRoute($name, $path, $handler, ['PUT']);
2222
}
2323

24-
public static function delete(string $name, string $path, array $parameters): BaseRoute
24+
public static function delete(string $name, string $path, $handler): BaseRoute
2525
{
26-
return new BaseRoute($name, $path, $parameters, ['DELETE']);
26+
return new BaseRoute($name, $path, $handler, ['DELETE']);
2727
}
2828
}

Diff for: ‎src/UrlGenerator.php

+23-11
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,45 @@
44

55
namespace DevCoder;
66

7+
use ArrayAccess;
8+
use InvalidArgumentException;
9+
use function array_key_exists;
10+
use function implode;
11+
use function sprintf;
12+
use function str_replace;
13+
use function trim;
14+
715
final class UrlGenerator
816
{
9-
/**
10-
* @var \ArrayAccess<Route>
11-
*/
12-
private $routes;
17+
private ArrayAccess $routes;
18+
private string $defaultUri;
1319

14-
public function __construct(\ArrayAccess $routes)
20+
public function __construct(ArrayAccess $routes, string $defaultUri = '')
1521
{
1622
$this->routes = $routes;
23+
$this->defaultUri = $defaultUri;
1724
}
1825

19-
public function generate(string $name, array $parameters = []): string
26+
public function generate(string $name, array $parameters = [], bool $absoluteUrl = false): string
2027
{
2128
if ($this->routes->offsetExists($name) === false) {
22-
throw new \InvalidArgumentException(
29+
throw new InvalidArgumentException(
2330
sprintf('Unknown %s name route', $name)
2431
);
2532
}
2633
/*** @var Route $route */
2734
$route = $this->routes[$name];
2835
if ($route->hasAttributes() === true && $parameters === []) {
29-
throw new \InvalidArgumentException(
36+
throw new InvalidArgumentException(
3037
sprintf('%s route need parameters: %s', $name, implode(',', $route->getVarsNames()))
3138
);
3239
}
33-
return self::resolveUri($route, $parameters);
40+
41+
$url = self::resolveUri($route, $parameters);
42+
if ($absoluteUrl === true) {
43+
$url = ltrim(Helper::trimPath($this->defaultUri), '/') . $url;
44+
}
45+
return $url;
3446
}
3547

3648
private static function resolveUri(Route $route, array $parameters): string
@@ -39,12 +51,12 @@ private static function resolveUri(Route $route, array $parameters): string
3951
foreach ($route->getVarsNames() as $variable) {
4052
$varName = trim($variable, '{\}');
4153
if (array_key_exists($varName, $parameters) === false) {
42-
throw new \InvalidArgumentException(
54+
throw new InvalidArgumentException(
4355
sprintf('%s not found in parameters to generate url', $varName)
4456
);
4557
}
4658
$uri = str_replace($variable, $parameters[$varName], $uri);
4759
}
4860
return $uri;
4961
}
50-
}
62+
}

Diff for: ‎tests/RouteTest.php

+3-4
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,9 @@ public function testMatchRoute()
3030
$routeWithAttributes = new Route('view_article_page','/view/article/{article}/{page}', ['App\\Controller\\HomeController', 'home']);
3131
$routeWithoutAttribute = new Route('view_articles','/view/article', ['App\\Controller\\HomeController', 'home']);
3232

33-
$this->assertTrue($routeWithAttribute->match('/view/article/1', 'GET'));
34-
$this->assertTrue(!$routeWithAttribute->match('/view/article/1', 'PUT'));
35-
$this->assertTrue($routeWithAttributes->match('/view/article/1/24','GET'));
36-
$this->assertTrue($routeWithoutAttribute->match('/view/article/','GET'));
33+
$this->assertTrue($routeWithAttribute->match('/view/article/1'));
34+
$this->assertTrue($routeWithAttributes->match('/view/article/1/24'));
35+
$this->assertTrue($routeWithoutAttribute->match('/view/article/'));
3736
}
3837

3938
public function testException()

Diff for: ‎tests/RouterTest.php

+36-15
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@
22

33
namespace Test\DevCoder;
44

5+
use DevCoder\Exception\MethodNotAllowed;
56
use DevCoder\Exception\RouteNotFound;
6-
use PHPUnit\Framework\TestCase;
77
use DevCoder\Route;
88
use DevCoder\Router;
9+
use InvalidArgumentException;
10+
use PHPUnit\Framework\TestCase;
911

1012
/**
1113
* Class RouterTest
1214
* @package Test\Webbym\Routing
1315
*/
1416
class RouterTest extends TestCase
1517
{
16-
/**
17-
* @var Router
18-
*/
19-
private $router;
18+
private Router $router;
2019

2120
public function __construct($name = null, array $data = [], $dataName = '')
2221
{
@@ -33,24 +32,31 @@ public function __construct($name = null, array $data = [], $dataName = '')
3332
->add($routeArticleWithParams);
3433
}
3534

36-
public function testMatchRoute() {
37-
35+
public function testMatchRoute()
36+
{
3837
$route = $this->router->matchFromPath('/view/article/25', 'GET');
3938
$this->assertInstanceOf(Route::class, $route);
4039

41-
$this->assertNotEmpty($route->getParameters());
40+
$this->assertNotEmpty($route->getHandler());
4241
$this->assertNotEmpty($route->getMethods());
43-
$this->assertSame(['id' => '25'], $route->getVars());
44-
45-
42+
$this->assertSame(['id' => '25'], $route->getAttributes());
4643
$this->assertInstanceOf(Route::class, $this->router->matchFromPath('/home', 'GET'));
47-
$this->expectException(RouteNotFound::class);
48-
$this->router->matchFromPath('/home', 'PUT');
44+
}
4945

46+
public function testNotFoundException()
47+
{
48+
$this->expectException(RouteNotFound::class);
49+
$this->router->matchFromPath('/homes', 'GET');
5050
}
5151

52-
public function testGenerateUrl() {
52+
public function testMethodNotAllowedException()
53+
{
54+
$this->expectException(MethodNotAllowed::class);
55+
$this->router->matchFromPath('/home', 'PUT');
56+
}
5357

58+
public function testGenerateUrl()
59+
{
5460
$urlHome = $this->router->generateUri('home_page');
5561
$urlArticle = $this->router->generateUri('article_page');
5662
$urlArticleWithParam = $this->router->generateUri('article_page_by_id', ['id' => 25]);
@@ -61,8 +67,23 @@ public function testGenerateUrl() {
6167
$this->assertSame($urlArticleWithParam, '/view/article/25');
6268
$this->assertSame($routeArticleWithParams, '/view/article/25/3');
6369

64-
$this->expectException(\InvalidArgumentException::class);
70+
$this->expectException(InvalidArgumentException::class);
6571
$this->router->generateUri('article_page_by_id_and_page', ['id' => 25]);
72+
}
73+
74+
public function testGenerateAbsoluteUrl()
75+
{
76+
$urlHome = $this->router->generateUri('home_page', [], true);
77+
$urlArticle = $this->router->generateUri('article_page', [], true);
78+
$urlArticleWithParam = $this->router->generateUri('article_page_by_id', ['id' => 25], true);
79+
$routeArticleWithParams = $this->router->generateUri('article_page_by_id_and_page', ['id' => 25, 'page' => 3], true);
6680

81+
$this->assertSame($urlHome, 'http://localhost/home');
82+
$this->assertSame($urlArticle, 'http://localhost/view/article');
83+
$this->assertSame($urlArticleWithParam, 'http://localhost/view/article/25');
84+
$this->assertSame($routeArticleWithParams, 'http://localhost/view/article/25/3');
85+
86+
$this->expectException(InvalidArgumentException::class);
87+
$this->router->generateUri('article_page_by_id_and_page', ['id' => 25]);
6788
}
6889
}

0 commit comments

Comments
 (0)
Please sign in to comment.