Skip to content

Instantly share code, notes, and snippets.

@finagin
Created July 2, 2022 06:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save finagin/6cd80497bea2eb3563124a21bacd9027 to your computer and use it in GitHub Desktop.
Save finagin/6cd80497bea2eb3563124a21bacd9027 to your computer and use it in GitHub Desktop.
Attribute Routing
<?php
namespace App\Services;
use App\Attributes\Route as RouteAttribute;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Route as RouteFacade;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionMethod;
class AttributeRouts
{
public const BASE_CONTROLLER = \App\Http\Controllers\Controller::class;
public static function scan(Router $router)
{
foreach (static::getIterator() as $file) {
/** @var \SplFileInfo $file */
if ($file->isDir()) {
continue;
}
$class = static::getClass($file);
$controller = new ReflectionClass($class);
$router = static::deep($controller);
$router(function (Router $router) use ($class, $controller) {
$classAttributes = static::getAttributes($controller);
$router->group($classAttributes, function ($router) use ($class, $controller, $classAttributes) {
if (! $controller->isAbstract() && array_key_exists('method', $classAttributes)) {
$router->match($classAttributes['method'], '/', $class);
}
foreach ($controller->getMethods() as $method) {
$methodAttributes = static::getAttributes($method);
if (array_key_exists('method', $methodAttributes)) {
$router->group($methodAttributes,
fn(Router $router) => $router->match(
$methodAttributes['method'],
'/',
$class.'@'.$method->getName()
));
}
}
});
});
}
}
private static function getIterator(): iterable
{
return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator(app_path('Http/Controllers')));
}
public static function getClass(\SplFileInfo $file): string
{
return Str::replace([app_path(), '/', '.php'], ['App', '\\', ''], $file->getPathname());
}
public static function deep(ReflectionClass $controller)
{
if ($controller->isAbstract() || $controller->getName() === static::BASE_CONTROLLER) {
return fn($routes) => RouteFacade::group(static::getAttributes($controller, false), $routes);
}
return static::deep($controller->getParentClass());
}
public static function getAttributes(ReflectionClass|ReflectionMethod $controller, bool $normalize = true)
{
$attributes = collect($controller->getAttributes(RouteAttribute::class));
$arguments = collect(optional($attributes->first())->getArguments());
return $normalize
? static::normalizeAttributes($arguments->toArray(), $controller)
: $arguments->toArray();
}
public static function normalizeAttributes(array $attributes, ReflectionClass|ReflectionMethod $attributable)
{
$slug = Str::slug(class_basename($attributable->getName()));
$attributes['path'] ??= $slug;
$attributes['as'] ??= $slug;
$attributes['prefix'] ??= $attributes['path'];
$attributes['excluded_middleware'] ??= $attributes['withoutMiddleware'] ?? [];
return $attributes;
}
}
<?php
namespace App\Attributes;
use Attribute;
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD)]
class Route
{
public array $args;
public function __construct(
string $method = '',
string $path = '',
string $as = '',
string $prefix = '',
string $domain = '',
array $where = [],
array $middleware = [],
array $withoutMiddleware = [],
) {
$this->args = func_get_args();
}
}
<?php
namespace App\Providers;
use App\Attributes\Route as RouteAttribute;
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use Illuminate\Http\Request;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use ReflectionClass;
class RouteServiceProvider extends ServiceProvider
{
/**
* The path to the "home" route for your application.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/home';
/**
* Define your route model bindings, pattern filters, and other route configuration.
*
* @return void
*/
public function boot()
{
$this->configureRateLimiting();
Route::middleware(null)
->group(fn(Router $router) => \App\Services\AttributeRouts::scan($router));
Route::middleware('api')
->prefix('api')
->group(base_path('routes/api.php'));
Route::middleware('web')
->group(base_path('routes/web.php'));
}
/**
* Configure the rate limiters for the application.
*
* @return void
*/
protected function configureRateLimiting()
{
RateLimiter::for('api', function (Request $request) {
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment