Skip to content

Commit ceb3d3c

Browse files
Merge pull request #13 from FranklinEkemezie/dev
Improved Routing and Request handling functionalities
2 parents cdec3ea + 402bc49 commit ceb3d3c

File tree

18 files changed

+287
-107
lines changed

18 files changed

+287
-107
lines changed

app/Core/App.php

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
use PHPAether\Core\HTTP\Request;
66
use PHPAether\Core\HTTP\Router;
7+
use PHPAether\Exceptions\Exception;
8+
use PHPAether\Exceptions\RouterExceptions\MethodNotAllowedException;
9+
use PHPAether\Exceptions\RouterExceptions\RouteNotFoundException;
710
use PHPAether\Exceptions\RouterExceptions\RouterException;
811
use PHPAether\Utils\Config;
912

@@ -18,16 +21,41 @@ public function __construct(
1821
}
1922

2023
/**
21-
* @throws RouterException
24+
* @throws Exception
2225
*/
2326
public function run(Request $request): string
2427
{
25-
// Route requests
26-
[
27-
'middlewares' => $middlewares,
28-
'action' => $action,
29-
'params' => $params
30-
] = $this->router->route($request);
28+
29+
try {
30+
31+
// Route requests
32+
[
33+
'middlewares' => $middlewares,
34+
'action' => $action,
35+
'params' => $params
36+
] = $this->router->route($request);
37+
} catch (MethodNotAllowedException $exception) {
38+
// TODO: Handle method not allowed exception
39+
40+
return "Method Not Allowed\n";
41+
} catch (RouteNotFoundException $exception) {
42+
// TODO: Handle route not found exception
43+
44+
return "Route Not Found Exception";
45+
} catch (RouterException $exception) {
46+
// TODO: Handle general router exception
47+
48+
return "An error occurred while routing";
49+
} catch (Exception $exception) {
50+
// TODO: Handle any exception
51+
52+
return "An error occurred";
53+
}
54+
55+
// Ensure the action is callable
56+
if (! is_callable($action)) {
57+
throw new Exception("Invalid request handler. Route action is not callable");
58+
}
3159

3260
// Resolve middlewares
3361
foreach ($middlewares as $middleware) {
@@ -36,6 +64,6 @@ public function run(Request $request): string
3664
$middleware->resolve();
3765
}
3866

39-
return $action($request, ...$params);
67+
return $action($request);
4068
}
4169
}

app/Core/HTTP/Request.php

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,23 @@
1212
class Request
1313
{
1414

15+
/**
16+
* @var string The request path
17+
*/
1518
public readonly string $path;
19+
/**
20+
* @var HTTPRequestMethod The request method
21+
*/
1622
public readonly HTTPRequestMethod $method;
23+
/**
24+
* @var RequestType The request type
25+
*/
1726
public readonly RequestType $type;
27+
/**
28+
* @var array The request data.
29+
* Stores the URL placeholder values, query parameter and `$_GET` values
30+
*/
31+
public readonly array $data;
1832

1933
/**
2034
* @throws Exception
@@ -35,13 +49,47 @@ public function __construct(?array $serverVariables=null, RequestType $requestTy
3549

3650
$routeInfo = parse_url($requestUri);
3751
if ($routeInfo === false) {
38-
throw new Exception('Could not parse request url. URL may be malformed');
52+
throw new \InvalidArgumentException('Could not parse request url. URL may be malformed');
3953
}
4054

55+
// Parse the query parameters
56+
$queryParams = [];
57+
parse_str($routeInfo['query'] ?? '', $queryParams);
58+
4159
$this->path = (string) $routeInfo['path'];
4260
$this->method = HTTPRequestMethod::tryFrom(strtoupper($requestMethod));
4361
$this->type = $requestType;
62+
$this->data = array_merge($queryParams, $_GET);
63+
}
64+
65+
/**
66+
* Get a request data
67+
* @param string|null $key
68+
* @return array
69+
*/
70+
public function getData(?string $key=null): array
71+
{
72+
if (is_null($key)) return $this->data;
73+
if (! isset($this->data[$key])) {
74+
throw new \InvalidArgumentException("No data found with key: $key");
75+
}
76+
77+
return $this->data[$key];
4478
}
4579

80+
/**
81+
* Set request data
82+
* @param array $data
83+
* @return $this
84+
*/
85+
public function setData(array $data): self
86+
{
87+
if (empty($data) || array_is_list($data)) {
88+
throw new \InvalidArgumentException('Invalid data provided');
89+
}
90+
91+
$this->data = array_merge($this->data, $data);
92+
return $this;
93+
}
4694

4795
}

app/Core/HTTP/Router.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,12 +348,16 @@ private function matchRequestPathToRoute(string $path, string $route): array|fal
348348
* @throws MethodNotAllowedException
349349
* @throws RouteNotFoundException
350350
*/
351-
public function route(Request $request): array
351+
public function route(Request &$request): array
352352
{
353353
$routes = $this->getRegisteredRoutes($request->type, $request->method);
354354
foreach ($routes as $route => $routeInfo) {
355355
$params = $this->matchRequestPathToRoute($request->path, $route);
356356
if ($params !== false) {
357+
358+
// Set the params as part of request data
359+
$request->setData($params);
360+
357361
return [
358362
'route' => $route,
359363
'action' => $routeInfo['action'],

tests/MockHTTPRequestTestCase.php

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,37 @@ public static function mockHTTPRequestTestCases(): \Generator
1414
foreach ($testUrls as $requestMethod => $testUrlTestCases) {
1515

1616
foreach ($testUrlTestCases as $testUrlTestCase) {
17-
$testUrlTestCase['method'] = $requestMethod;
1817

19-
$url = $testUrlTestCase['url'];
20-
yield "[$requestMethod] $url" => $testUrlTestCase;
18+
$url = $testUrlTestCase['url'];
19+
$data = $testUrlTestCase['info']['data'] ?? [];
20+
$requestBuilder = fn(self $testCase): Request => (
21+
$testCase->createMockRequest($url, $requestMethod, $data)
22+
);
23+
24+
yield "[$requestMethod] $url" => [
25+
'requestBuilder' => $requestBuilder,
26+
'expected' => $testUrlTestCase['expected']
27+
];
2128
}
2229
}
2330
}
2431

25-
public function createMockRequest(string $url, string $method): Request&MockObject
32+
public function createMockRequest(string $url, string $method, array $data=[]): Request&MockObject
2633
{
2734
// Prepare server variables
2835
$_SERVER['REQUEST_URI'] = $url;
2936
$_SERVER['REQUEST_METHOD'] = $method;
3037

38+
if ($method === 'GET') $_GET = $data;
39+
else $_POST = $data;
40+
3141
return $this->getMockBuilder(Request::class)
3242
->setConstructorArgs([$_SERVER])
3343
->getMock();
3444
}
45+
46+
public static function getExpectedValue(array $expected, string $category, string $key, mixed $default=null): mixed
47+
{
48+
return $expected[$category][$key] ?? $default;
49+
}
3550
}
Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<?php
22

3-
namespace Core\HTTP;
3+
namespace PHPAether\Tests\Unit\Core\HTTP;
44

5+
use Exception;
56
use PHPAether\Core\HTTP\Request;
67
use PHPAether\Tests\MockHTTPRequestTestCase;
78
use PHPUnit\Framework\Attributes\DataProvider;
@@ -10,33 +11,41 @@
1011
class RequestTest extends MockHTTPRequestTestCase
1112
{
1213

13-
public static function requestTestCases(): array
14+
/**
15+
* @throws Exception
16+
*/
17+
public static function getRequest(string $url, string $method): Request
18+
{
19+
$_SERVER['REQUEST_URI'] = $url;
20+
$_SERVER['REQUEST_METHOD'] = $method;
21+
22+
return new Request($_SERVER);
23+
}
24+
25+
/**
26+
* @throws Exception
27+
*/
28+
#[Test]
29+
#[DataProvider('mockHTTPRequestTestCases')]
30+
public function it_gets_route_path(callable $requestBuilder, array $expected): void
1431
{
15-
return [
16-
['/', 'GET', '/'],
17-
['/login?r_url=/user/dashboard', 'POST', '/login'],
18-
['/auth/otp/verify', 'GET', '/auth/otp/verify'],
19-
['/user/profile', 'PUT', '/user/profile']
20-
];
32+
$expectedPath = static::getExpectedValue($expected, 'request', 'path');
33+
$this->assertSame($expectedPath, $requestBuilder($this)->path);
2134
}
2235

2336
/**
24-
* @throws \Exception
37+
* @throws Exception
2538
*/
2639
#[Test]
27-
#[DataProvider('requestTestCases')]
28-
public function it_gets_route_path(
29-
string $requestUri,
30-
string $requestMethod,
31-
string $expectedRoutePath
32-
)
40+
#[DataProvider('mockHTTPRequestTestCases')]
41+
public function it_gets_route_params(callable $requestBuilder, array $expected): void
3342
{
34-
$_SERVER['REQUEST_METHOD'] = $requestMethod;
35-
$_SERVER['REQUEST_URI'] = $requestUri;
43+
$expectedParams = static::getExpectedValue($expected, 'request', 'params', []);
3644

37-
$request = new Request($_SERVER);
45+
$request = $requestBuilder($this);
46+
static::$APP->router->route($request);
3847

39-
$this->assertSame($expectedRoutePath, $request->path);
48+
$this->assertSame($expectedParams, $request->getData);
4049
}
4150

4251
}

tests/Unit/Core/HTTP/RouterTest.php

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
use PHPAether\Tests\MockHTTPRequestTestCase;
1212
use PHPUnit\Framework\Attributes\DataProvider;
1313
use PHPUnit\Framework\Attributes\Test;
14-
use PHPUnit\Framework\MockObject\Exception;
1514

1615
class RouterTest extends MockHTTPRequestTestCase
1716
{
@@ -126,17 +125,19 @@ public function it_registers_cli_routes_from_file(): void
126125
}
127126

128127
/**
129-
* @throws Exception
130-
* @throws RouterException
128+
* @param callable $requestBuilder
129+
* @param array|null $expected
130+
* @throws MethodNotAllowedException
131+
* @throws RouteNotFoundException
131132
*/
132133
#[Test]
133134
#[DataProvider('mockHTTPRequestTestCases')]
134-
public function it_routes_request_to_route(string $url, string $method, ?array $expected=[])
135+
public function it_routes_request_to_route(callable $requestBuilder, ?array $expected=[])
135136
{
136137
$expectedRoute = $expected['router']['route'] ?? null;
137138

138139
// Mock a request object; register routes
139-
$request = $this->createMockRequest($url, $method);
140+
$request = $requestBuilder($this);
140141
$this->router->registerRoutesFromFiles(TESTS_DIR . '/config/routes');
141142

142143
$actualRoute = $this->router->route($request)['route'];
@@ -166,7 +167,8 @@ public function it_throws_method_not_allowed_for_invalid_routes(): void
166167
public function it_throws_route_not_found_for_invalid_routes(): void
167168
{
168169
$this->expectException(RouteNotFoundException::class);
169-
$this->router->route($this->createMockRequest('/tests/some/products/2', 'GET'));
170+
$request = $this->createMockRequest('/tests/some/products/2', 'GET');
171+
$this->router->route($request);
170172
}
171173

172174
/**
@@ -177,10 +179,8 @@ public function it_matches_wildcard_route(): void
177179
{
178180
// Register a wildcard route
179181
$this->router->get('/tests/books/:id', fn() => '');
180-
$this->assertEquals(
181-
['id' => 5],
182-
$this->router->route($this->createMockRequest('/tests/books/5', 'GET'))['params']
183-
);
182+
$request = $this->createMockRequest('/tests/books/5', 'GET');
184183

184+
$this->assertEquals(['id' => 5], $this->router->route($request)['params']);
185185
}
186186
}

tests/urls/DELETE.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[
2+
3+
]

tests/urls/DELETE.php

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)