Skip to content

Commit ce47361

Browse files
committed
refactor some code and fix issue when multiple routes added with the same pattern but with different method
1 parent 10274a4 commit ce47361

File tree

4 files changed

+465
-498
lines changed

4 files changed

+465
-498
lines changed

src/Core/HashTable.php

+51-68
Original file line numberDiff line numberDiff line change
@@ -4,81 +4,64 @@
44

55
class HashTable
66
{
7-
public array $routes;
7+
/** @var array<string, Route> */
8+
private array $routes = [];
89

9-
public function __construct()
10-
{
11-
$this->routes = [];
12-
}
10+
/** @var array<string, Route> */
11+
private array $routesByName = [];
1312

14-
public function hash(URI $URI)
15-
{
16-
$computed = substr(md5(""), 0, 2);
13+
public function hash(Route $route): string
14+
{
15+
$uri = $route->getURI();
16+
$method = $route->getMethod();
17+
$segments = $uri->getSegments();
1718

18-
foreach ($URI->getSegments() as $seg) {
19-
$computed .= substr(md5($seg), 0, 2);
20-
}
19+
$hash = $method[0]; // first letter of the method
20+
$segmentCount = count($segments);
21+
$hash .= chr(($segmentCount % 26) + 97); // lowercase letter representing segment count
2122

22-
$computed .= count($URI->getSegments());
23+
foreach ($segments as $segment) {
24+
if ($segment !== '' && $segment[0] === '{' && $segment[-1] === '}') {
25+
$hash .= '_'; // placeholder for parameters
26+
} elseif ($segment !== '') {
27+
$hash .= $segment[0] . strlen($segment); // 1st char and length for normal segments
28+
} else {
29+
$hash .= '/1'; // home page
30+
}
31+
}
2332

24-
return $computed;
25-
}
33+
return $hash;
34+
}
2635

27-
public function add(Route $route): void
28-
{
29-
$hash = $this->hash($route->getURI());
30-
if (!$this->searchInternal($route->getURI())) {
31-
$this->routes[$hash] = $route;
32-
} else {
33-
// TODO: Route Already Exists
34-
die("route already exists");
35-
}
36-
}
36+
public function add(Route $route): void
37+
{
38+
$hash = $this->hash($route);
39+
if (isset($this->routes[$hash])) {
40+
throw new \RuntimeException("Route already exists");
41+
}
42+
$this->routes[$hash] = $route;
43+
$this->routesByName[$route->getName()] = $route;
44+
}
3745

38-
private function searchInternal(URI $uri): ?Route
39-
{
40-
$hash = $this->hash($uri);
41-
if (isset($this->routes[$hash])) {
42-
return $this->routes[$hash];
43-
} else {
44-
return NULL;
45-
}
46-
}
46+
public function search(Route $route): ?Route
47+
{
48+
return $this->routes[$this->hash($route)] ?? null;
49+
}
4750

48-
public function search(URI $uri): ?Route
49-
{
50-
foreach ($this->routes as $route) {
51-
if ($route->match($uri)) {
52-
return $route;
53-
}
54-
}
51+
public function searchByName(string $name): ?Route
52+
{
53+
return $this->routesByName[$name] ?? null;
54+
}
5555

56-
return null;
57-
}
56+
public function mergeAtTail(iterable $list): void
57+
{
58+
foreach ($list as $route) {
59+
$this->add($route);
60+
}
61+
}
5862

59-
public function searchByName(string $name): ?Route
60-
{
61-
foreach ($this->routes as $route) {
62-
if ($route->getName() === $name) {
63-
return $route;
64-
}
65-
}
66-
67-
return null;
68-
}
69-
70-
// for compatibility reasons with the old linked list
71-
public function mergeAtTail($list): void
72-
{
73-
$list->forEach(function ($el) {
74-
$this->add($el);
75-
});
76-
}
77-
78-
public function forEach($callback): void
79-
{
80-
foreach ($this->routes as $route) {
81-
call_user_func($callback, $route);
82-
}
83-
}
84-
}
63+
public function forEach(callable $callback): void
64+
{
65+
array_walk($this->routes, $callback);
66+
}
67+
}

src/Core/Route.php

+114-123
Original file line numberDiff line numberDiff line change
@@ -6,127 +6,118 @@
66
use Middleware;
77
use Mrfoo\PHPRouter\Core\Middleware as CoreMiddleware;
88

9-
class Route
10-
{
11-
private URI $uri;
12-
private $handler;
13-
private string $method;
14-
private bool $isRedirect;
15-
private URI $redirectUri;
16-
private int $redirectStatus;
17-
private ?string $name = null;
18-
private array $middlewares;
19-
20-
public function __construct(string $uri, $handler, string $method)
21-
{
22-
$this->uri = new URI($uri);
23-
$this->handler = $handler;
24-
$this->method = $method;
25-
$this->middlewares = [];
26-
}
27-
28-
public function match(URI $uri): bool
29-
{
30-
return $this->uri->match($uri);
31-
}
32-
33-
public function handle()
34-
{
35-
if (is_callable($this->handler)) {
36-
$this->preHandleMiddlewares();
37-
return call_user_func_array($this->handler, $this->uri->getParameters());
38-
}
39-
40-
if (is_array($this->handler) && count($this->handler) === 2) {
41-
$class = $this->handler[0];
42-
$method = $this->handler[1];
43-
if (class_exists($class) && method_exists($class, $method)) {
44-
return (new $class)->$method();
45-
}
46-
}
47-
48-
throw new Exception('Invalid handler provided.');
49-
}
50-
51-
public function handleRedirect()
52-
{
53-
return die('unimplemented function: ' . __FUNCTION__);
54-
}
55-
56-
public function name(string $name): Route
57-
{
58-
$this->name = $name;
59-
60-
return $this;
61-
}
62-
63-
public function where(string $segmentName, string $regex): Route
64-
{
65-
$this->uri->registerWhereOnSegment($segmentName, $regex);
66-
67-
return $this;
68-
}
69-
70-
public function getMethod()
71-
{
72-
return $this->method;
73-
}
74-
75-
public function applyPrefix($prefix)
76-
{
77-
$this->uri = new URI($prefix . $this->uri->getUri());
78-
}
79-
80-
public function redirect(string $route, int $status): Route
81-
{
82-
$this->isRedirect = true;
83-
$this->redirectUri = new URI($route);
84-
$this->redirectStatus = $status;
85-
86-
return $this;
87-
}
88-
89-
public function getURI()
90-
{
91-
return $this->uri;
92-
}
93-
94-
public function getName()
95-
{
96-
return $this->name;
97-
}
98-
99-
private function configureMiddlewares(array $middlewares)
100-
{
101-
foreach ($middlewares as $middleware) {
102-
if (class_exists($middleware, true)) {
103-
$obj = new $middleware;
104-
105-
if ($obj instanceof \Mrfoo\PHPRouter\Core\Middleware) {
106-
array_push($this->middlewares, $obj);
107-
}
108-
}
109-
}
110-
}
111-
112-
public function middleware(string|array $middlewares)
113-
{
114-
if (gettype($middlewares) == 'array')
115-
$this->configureMiddlewares($middlewares);
116-
else if (gettype($middlewares) == 'string')
117-
$this->configureMiddlewares([$middlewares]);
118-
}
119-
120-
private function preHandleMiddlewares() {
121-
foreach($this->middlewares as $m) {
122-
$m->handle();
123-
}
124-
}
125-
126-
// should be public because we are trying to
127-
public function postHandleMiddlewares() {
128-
foreach($this->middlewares as $m) {
129-
$m->terminate();
130-
}
131-
}
9+
class Route {
10+
private URI $uri;
11+
private $handler;
12+
private string $method;
13+
private bool $isRedirect;
14+
private URI $redirectUri;
15+
private int $redirectStatus;
16+
private ?string $name = null;
17+
private array $middlewares;
18+
19+
public function __construct( string $uri, $handler, string $method ) {
20+
$this->uri = new URI( $uri );
21+
$this->handler = $handler;
22+
$this->method = $method;
23+
$this->middlewares = [];
24+
}
25+
26+
public function match( URI $uri ): bool {
27+
return $this->uri->match( $uri );
28+
}
29+
30+
/**
31+
* @throws Exception
32+
*/
33+
public function handle() {
34+
if ( is_callable( $this->handler ) ) {
35+
$this->preHandleMiddlewares();
36+
37+
return call_user_func_array( $this->handler, $this->uri->getParameters() );
38+
}
39+
40+
if ( is_array( $this->handler ) && count( $this->handler ) === 2 ) {
41+
$class = $this->handler[0];
42+
$method = $this->handler[1];
43+
if ( class_exists( $class ) && method_exists( $class, $method ) ) {
44+
return ( new $class )->$method();
45+
}
46+
}
47+
48+
throw new Exception( 'Invalid handler provided.' );
49+
}
50+
51+
public function handleRedirect() {
52+
return die( 'unimplemented function: ' . __FUNCTION__ );
53+
}
54+
55+
public function name( string $name ): Route {
56+
$this->name = $name;
57+
58+
return $this;
59+
}
60+
61+
public function where( string $segmentName, string $regex ): Route {
62+
$this->uri->registerWhereOnSegment( $segmentName, $regex );
63+
64+
return $this;
65+
}
66+
67+
public function getMethod(): string {
68+
return $this->method;
69+
}
70+
71+
public function applyPrefix( $prefix ): void {
72+
$this->uri = new URI( $prefix . $this->uri->getUri() );
73+
}
74+
75+
public function redirect( string $route, int $status ): Route {
76+
$this->isRedirect = true;
77+
$this->redirectUri = new URI( $route );
78+
$this->redirectStatus = $status;
79+
80+
return $this;
81+
}
82+
83+
public function getURI(): URI {
84+
return $this->uri;
85+
}
86+
87+
public function getName(): ?string {
88+
return $this->name;
89+
}
90+
91+
private function configureMiddlewares( array $middlewares ): void {
92+
foreach ( $middlewares as $middleware ) {
93+
if ( class_exists( $middleware, true ) ) {
94+
$obj = new $middleware;
95+
96+
if ( $obj instanceof \Mrfoo\PHPRouter\Core\Middleware ) {
97+
$this->middlewares[] = $obj;
98+
}
99+
}
100+
}
101+
}
102+
103+
public function middleware( string|array $middlewares ): void {
104+
if ( gettype( $middlewares ) == 'array' ) {
105+
$this->configureMiddlewares( $middlewares );
106+
} else if ( gettype( $middlewares ) == 'string' ) {
107+
$this->configureMiddlewares( [ $middlewares ] );
108+
}
109+
}
110+
111+
private function preHandleMiddlewares(): void {
112+
foreach ( $this->middlewares as $m ) {
113+
$m->handle();
114+
}
115+
}
116+
117+
// should be public because we are trying to
118+
public function postHandleMiddlewares(): void {
119+
foreach ( $this->middlewares as $m ) {
120+
$m->terminate();
121+
}
122+
}
132123
}

0 commit comments

Comments
 (0)