Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 4fdc784

Browse files
add PHP 8 attributes support and make library PHP 8 compatible
1 parent 47a3924 commit 4fdc784

12 files changed

+488
-15
lines changed

‎.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
/vendor/
22
/.idea/
3+
composer.lock
4+
/tests/cache

‎README.md

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
# PHP Router
2-
3-
PHP Router is a simple and efficient routing library designed for PHP applications. It provides a straightforward way to define routes, handle HTTP requests, and generate URLs. Built with PSR-7 message implementation in mind, it seamlessly integrates with PHP applications.
1+
# PHP Router
42

3+
PHP Router is a simple and efficient routing library designed for PHP applications. It provides a straightforward way to
4+
define routes, handle HTTP requests, and generate URLs. Built with PSR-7 message implementation in mind, it seamlessly
5+
integrates with PHP applications.
56

67
## Installation
78

89
You can install PHP Router via Composer. Just run:
910

1011
### Composer Require
12+
1113
```
1214
composer require phpdevcommunity/php-router
1315
```
@@ -30,20 +32,23 @@ composer require phpdevcommunity/php-router
3032

3133
5. **Generate URLs**: Generate URLs for named routes.
3234

33-
3435
## Example
36+
3537
```php
3638
<?php
3739
class IndexController {
3840

41+
// PHP > 8.0
42+
#[\PhpDevCommunity\Attribute\Route(path: '/', name: 'home_page')]
3943
public function __invoke()
4044
{
4145
return 'Hello world!!';
4246
}
4347
}
4448

4549
class ArticleController {
46-
50+
// PHP > 8.0
51+
#[\PhpDevCommunity\Attribute\Route(path: '/api/articles', name: 'api_articles_collection')]
4752
public function getAll()
4853
{
4954
// db get all post
@@ -53,7 +58,8 @@ class ArticleController {
5358
['id' => 3]
5459
]);
5560
}
56-
61+
// PHP > 8.0
62+
#[\PhpDevCommunity\Attribute\Route(path: '/api/articles/{id}', name: 'api_articles')]
5763
public function get(int $id)
5864
{
5965
// db get post by id
@@ -76,11 +82,20 @@ class ArticleController {
7682

7783
```php
7884
// Define your routes
79-
$routes = [
80-
new \PhpDevCommunity\Route('home_page', '/', [IndexController::class]),
81-
new \PhpDevCommunity\Route('api_articles_collection', '/api/articles', [ArticleController::class, 'getAll']),
82-
new \PhpDevCommunity\Route('api_articles', '/api/articles/{id}', [ArticleController::class, 'get']),
83-
];
85+
86+
if (PHP_VERSION_ID >= 80000) {
87+
$attributeRouteCollector = new AttributeRouteCollector([
88+
IndexController::class,
89+
ArticleController::class
90+
]);
91+
$routes = $attributeRouteCollector->collect();
92+
}else {
93+
$routes = [
94+
new \PhpDevCommunity\Route('home_page', '/', [IndexController::class]),
95+
new \PhpDevCommunity\Route('api_articles_collection', '/api/articles', [ArticleController::class, 'getAll']),
96+
new \PhpDevCommunity\Route('api_articles', '/api/articles/{id}', [ArticleController::class, 'get']),
97+
];
98+
}
8499

85100
// Initialize the router
86101
$router = new \PhpDevCommunity\Router($routes, 'http://localhost');
@@ -120,15 +135,18 @@ try {
120135

121136
## Route Definition
122137

123-
Routes can be defined using the `Route` class provided by PHP Router. You can specify HTTP methods, attribute constraints, and handler methods for each route.
138+
Routes can be defined using the `Route` class provided by PHP Router. You can specify HTTP methods, attribute
139+
constraints, and handler methods for each route.
124140

125141
```php
126142
$route = new \PhpDevCommunity\Route('api_articles_post', '/api/articles', [ArticleController::class, 'post'], ['POST']);
127143
$route = new \PhpDevCommunity\Route('api_articles_put', '/api/articles/{id}', [ArticleController::class, 'put'], ['PUT']);
128144
```
145+
129146
### Easier Route Definition with Static Methods
130147

131-
To make route definition even simpler and more intuitive, the `RouteTrait` provides static methods for creating different types of HTTP routes. Here's how to use them:
148+
To make route definition even simpler and more intuitive, the `RouteTrait` provides static methods for creating
149+
different types of HTTP routes. Here's how to use them:
132150

133151
#### Method `get()`
134152

@@ -224,7 +242,8 @@ $route = Route::delete('delete_item', '/item/{id}', [ItemController::class, 'del
224242

225243
### Using `where` Constraints in the Route Object
226244

227-
The `Route` object allows you to define constraints on route parameters using the `where` methods. These constraints validate and filter parameter values based on regular expressions. Here's how to use them:
245+
The `Route` object allows you to define constraints on route parameters using the `where` methods. These constraints
246+
validate and filter parameter values based on regular expressions. Here's how to use them:
228247

229248
#### Method `whereNumber()`
230249

@@ -535,7 +554,8 @@ Example Usage:
535554
$route = (new Route('product', '/product/{code}'))->where('code', '\d{4}');
536555
```
537556

538-
By using these `where` methods, you can apply precise constraints on your route parameters, ensuring proper validation of input values.
557+
By using these `where` methods, you can apply precise constraints on your route parameters, ensuring proper validation
558+
of input values.
539559

540560
## Generating URLs
541561

@@ -546,6 +566,7 @@ echo $router->generateUri('home_page'); // /
546566
echo $router->generateUri('api_articles', ['id' => 1]); // /api/articles/1
547567
echo $router->generateUri('api_articles', ['id' => 1], true); // http://localhost/api/articles/1
548568
```
569+
549570
## Contributing
550571

551572
Contributions are welcome! Feel free to open issues or submit pull requests to help improve the library.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
<?php
2+
3+
namespace PhpDevCommunity\Attribute;
4+
5+
use PhpDevCommunity\Helper;
6+
7+
final class AttributeRouteCollector
8+
{
9+
private array $classes;
10+
private ?string $cacheDir;
11+
12+
public function __construct(array $classes, ?string $cacheDir = null)
13+
{
14+
if (PHP_VERSION_ID < 80000) {
15+
throw new \LogicException('Attribute routes are only supported in PHP 8.0+');
16+
}
17+
$this->classes = array_unique($classes);
18+
$this->cacheDir = $cacheDir;
19+
if ($this->cacheDir && !is_dir($this->cacheDir)) {
20+
throw new \InvalidArgumentException(sprintf(
21+
'Cache directory "%s" does not exist',
22+
$this->cacheDir
23+
));
24+
}
25+
}
26+
27+
public function generateCache(): void
28+
{
29+
if (!$this->cacheIsEnabled()) {
30+
throw new \LogicException('Cache is not enabled, if you want to enable it, please set the cacheDir on the constructor');
31+
}
32+
$this->collect();
33+
}
34+
35+
public function clearCache(): void
36+
{
37+
if (!$this->cacheIsEnabled()) {
38+
throw new \LogicException('Cache is not enabled, if you want to enable it, please set the cacheDir on the constructor');
39+
}
40+
41+
foreach ($this->classes as $class) {
42+
$cacheFile = $this->getCacheFile($class);
43+
if (file_exists($cacheFile)) {
44+
unlink($cacheFile);
45+
}
46+
}
47+
}
48+
49+
/**
50+
* @return array<\PhpDevCommunity\Route
51+
* @throws \ReflectionException
52+
*/
53+
public function collect(): array
54+
{
55+
$routes = [];
56+
foreach ($this->classes as $class) {
57+
$routes = array_merge($routes, $this->getRoutes($class));
58+
}
59+
return $routes;
60+
}
61+
62+
63+
private function getRoutes(string $class): array
64+
{
65+
if ($this->cacheIsEnabled() && ( $cached = $this->get($class))) {
66+
return $cached;
67+
}
68+
$refClass = new \ReflectionClass($class);
69+
$routes = [];
70+
71+
$controllerAttr = $refClass->getAttributes(
72+
ControllerRoute::class,
73+
\ReflectionAttribute::IS_INSTANCEOF
74+
)[0] ?? null;
75+
$controllerRoute = $controllerAttr ? $controllerAttr->newInstance() : new ControllerRoute('');
76+
foreach ($refClass->getMethods() as $method) {
77+
foreach ($method->getAttributes(
78+
Route::class,
79+
\ReflectionAttribute::IS_INSTANCEOF
80+
) as $attr) {
81+
/**
82+
* @var Route $instance
83+
*/
84+
$instance = $attr->newInstance();
85+
$route = new \PhpDevCommunity\Route(
86+
$instance->getName(),
87+
$controllerRoute->getPath().$instance->getPath(),
88+
[$class, $method->getName()],
89+
$instance->getMethods()
90+
);
91+
92+
$route->format($instance->getFormat() ?: $controllerRoute->getFormat());
93+
foreach ($instance->getOptions() as $key => $value) {
94+
if (!str_starts_with($key, 'where') || $key === 'where') {
95+
throw new \InvalidArgumentException(
96+
'Invalid option "' . $key . '". Options must start with "where".'
97+
);
98+
}
99+
if (is_array($value)) {
100+
$route->$key(...$value);
101+
continue;
102+
}
103+
$route->$key($value);
104+
}
105+
$routes[$instance->getName()] = $route;
106+
}
107+
}
108+
$routes = array_values($routes);
109+
if ($this->cacheIsEnabled()) {
110+
$this->set($class, $routes);
111+
}
112+
113+
return $routes;
114+
115+
}
116+
117+
private function cacheIsEnabled(): bool
118+
{
119+
return $this->cacheDir !== null;
120+
}
121+
122+
private function get(string $class): ?array
123+
{
124+
$cacheFile = $this->getCacheFile($class);
125+
if (!is_file($cacheFile)) {
126+
return null;
127+
}
128+
129+
return require $cacheFile;
130+
}
131+
132+
private function set(string $class, array $routes): void
133+
{
134+
$cacheFile = $this->getCacheFile($class);
135+
$content = "<?php\n\nreturn " . var_export($routes, true) . ";\n";
136+
file_put_contents($cacheFile, $content);
137+
}
138+
139+
private function getCacheFile(string $class): string
140+
{
141+
return $this->cacheDir . '/' .md5($class) . '.php';
142+
}
143+
}

‎src/Attribute/ControllerRoute.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
namespace PhpDevCommunity\Attribute;
4+
5+
use PhpDevCommunity\Helper;
6+
7+
#[\Attribute(\Attribute::TARGET_CLASS)]
8+
final class ControllerRoute
9+
{
10+
private string $path;
11+
private ?string $format;
12+
13+
public function __construct(string $path, string $format = null)
14+
{
15+
$this->path = Helper::trimPath($path);
16+
$this->format = $format;
17+
}
18+
19+
public function getPath(): string
20+
{
21+
return $this->path;
22+
}
23+
24+
public function getFormat(): ?string
25+
{
26+
return $this->format;
27+
}
28+
}

‎src/Attribute/Route.php

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<?php
2+
3+
namespace PhpDevCommunity\Attribute;
4+
5+
use PhpDevCommunity\Helper;
6+
7+
#[\Attribute(\Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
8+
final class Route
9+
{
10+
private string $path;
11+
private string $name;
12+
/**
13+
* @var array|string[]
14+
*/
15+
private array $methods;
16+
private array $options;
17+
private ?string $format;
18+
19+
public function __construct(string $path, string $name, array $methods = ['GET', 'POST'], array $options = [], string $format = null)
20+
{
21+
$this->path = Helper::trimPath($path);
22+
$this->name = $name;
23+
$this->methods = $methods;
24+
$this->options = $options;
25+
$this->format = $format;
26+
}
27+
28+
public function getPath(): string
29+
{
30+
return $this->path;
31+
}
32+
33+
public function getName(): string
34+
{
35+
return $this->name;
36+
}
37+
38+
public function getMethods(): array
39+
{
40+
return $this->methods;
41+
}
42+
43+
public function getOptions(): array
44+
{
45+
return $this->options;
46+
}
47+
48+
public function getFormat(): ?string
49+
{
50+
return $this->format;
51+
}
52+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /