263 lines
8.0 KiB
PHP
263 lines
8.0 KiB
PHP
<?php
|
|
|
|
namespace Modules\Common\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\File;
|
|
use Modules\AnnoRoute\Attribute\DeleteRoute;
|
|
use Modules\AnnoRoute\Attribute\GetRoute;
|
|
use Modules\AnnoRoute\Attribute\PostRoute;
|
|
use Modules\AnnoRoute\Attribute\PutRoute;
|
|
use Modules\AnnoRoute\Attribute\RequestAttribute;
|
|
use ReflectionClass;
|
|
use ReflectionException;
|
|
use Symfony\Component\Finder\Finder;
|
|
|
|
class GenerateRouteHelperCommand extends Command
|
|
{
|
|
protected $signature = 'route:helper';
|
|
|
|
protected $description = 'Scan annotation routes and generate IDE route helper files for Laravel Idea';
|
|
|
|
private static array $mapping = [
|
|
GetRoute::class,
|
|
PostRoute::class,
|
|
PutRoute::class,
|
|
DeleteRoute::class,
|
|
];
|
|
|
|
private static array $httpMethodMap = [
|
|
GetRoute::class => 'get',
|
|
PostRoute::class => 'post',
|
|
PutRoute::class => 'put',
|
|
DeleteRoute::class => 'delete',
|
|
];
|
|
|
|
/** @var array<string, list<array{method: string, uri: string, controller: string, action: string, middleware: array}>> */
|
|
private array $controllerRoutes = [];
|
|
|
|
public function handle(): int
|
|
{
|
|
$paths = [
|
|
app_path('Http/Controllers'),
|
|
base_path('modules'),
|
|
];
|
|
|
|
$totalRoutes = 0;
|
|
|
|
foreach ($paths as $path) {
|
|
if (!is_dir($path)) {
|
|
continue;
|
|
}
|
|
|
|
$finder = new Finder();
|
|
$finder->files()->in($path)->name('*Controller.php');
|
|
|
|
foreach ($finder as $file) {
|
|
$className = $this->getClassNameFromFile(
|
|
$file->getRealPath(),
|
|
$file->getPath()
|
|
);
|
|
|
|
if ($className && class_exists($className)) {
|
|
$routes = $this->scanController($className);
|
|
if (!empty($routes)) {
|
|
$this->controllerRoutes[$className] = $routes;
|
|
$totalRoutes += count($routes);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($this->controllerRoutes)) {
|
|
$this->warn('No annotation routes found.');
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$this->writeHelperFile();
|
|
$this->info("Routes generated to: " . base_path('routes/api.php'));
|
|
$this->info("Found {$totalRoutes} routes.");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function scanController(string $className): array
|
|
{
|
|
$routes = [];
|
|
|
|
try {
|
|
$classRef = new ReflectionClass($className);
|
|
} catch (ReflectionException) {
|
|
return $routes;
|
|
}
|
|
|
|
$classAttrs = collect($classRef->getAttributes());
|
|
if ($classAttrs->isEmpty()) {
|
|
return $routes;
|
|
}
|
|
|
|
$hasRequestAttr = $classAttrs->some(
|
|
fn($attr) => $attr->getName() === RequestAttribute::class
|
|
);
|
|
if (!$hasRequestAttr) {
|
|
return $routes;
|
|
}
|
|
|
|
$requestAttr = $classAttrs->first(
|
|
fn($attr) => $attr->getName() === RequestAttribute::class
|
|
);
|
|
$requestInstance = $requestAttr->newInstance();
|
|
$routePrefix = $requestInstance->routePrefix ?? '';
|
|
$authGuard = $requestInstance->authGuard ?? null;
|
|
$abilitiesPrefix = $requestInstance->abilitiesPrefix ?? '';
|
|
$classMiddleware = $this->normalizeMiddleware($requestInstance->middleware);
|
|
|
|
foreach ($classRef->getMethods() as $method) {
|
|
$attrs = $method->getAttributes();
|
|
if (empty($attrs)) {
|
|
continue;
|
|
}
|
|
|
|
$methodName = $method->getName();
|
|
|
|
foreach ($attrs as $attr) {
|
|
if (in_array($attr->getName(), self::$mapping)) {
|
|
$instance = $attr->newInstance();
|
|
|
|
$authMiddleware = $this->buildAuthMiddleware(
|
|
$instance->authorize,
|
|
$authGuard,
|
|
$abilitiesPrefix,
|
|
);
|
|
|
|
$allMiddleware = array_values(array_unique(array_merge(
|
|
$authMiddleware,
|
|
$this->normalizeMiddleware($instance->middleware),
|
|
$classMiddleware,
|
|
)));
|
|
|
|
$routes[] = [
|
|
'method' => self::$httpMethodMap[$attr->getName()],
|
|
'uri' => $instance->route,
|
|
'prefix' => trim($routePrefix, '/'),
|
|
'controller' => $className,
|
|
'action' => $methodName,
|
|
'middleware' => $allMiddleware,
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
return $routes;
|
|
}
|
|
|
|
private function buildAuthMiddleware(string|bool $authorize, ?string $authGuard, string $abilitiesPrefix): array
|
|
{
|
|
if (empty($authorize) || $authorize === false) {
|
|
return [];
|
|
}
|
|
|
|
$authMiddleware = ['auth:sanctum'];
|
|
|
|
if (!empty($authGuard)) {
|
|
$authMiddleware[] = 'authGuard:' . $authGuard;
|
|
} else {
|
|
$authMiddleware[] = 'authGuard';
|
|
}
|
|
|
|
if (is_string($authorize) && !empty($abilitiesPrefix)) {
|
|
$authMiddleware[] = 'abilities:' . $abilitiesPrefix . '.' . $authorize;
|
|
} else if ($authorize !== true) {
|
|
$authMiddleware[] = 'abilities:' . $authorize ;
|
|
}
|
|
|
|
return $authMiddleware;
|
|
}
|
|
|
|
private function normalizeMiddleware(string|array $middleware): array
|
|
{
|
|
if (empty($middleware)) {
|
|
return [];
|
|
}
|
|
if (is_array($middleware)) {
|
|
return $middleware;
|
|
}
|
|
return [$middleware];
|
|
}
|
|
|
|
private function getClassNameFromFile(string $filePath, string $fileDir): ?string
|
|
{
|
|
$content = file_get_contents($filePath);
|
|
|
|
if (!preg_match('/namespace\s+([^;]+);/', $content, $namespaceMatch)) {
|
|
return $this->guessClassNameFromPath($filePath, $fileDir);
|
|
}
|
|
|
|
$namespace = trim($namespaceMatch[1]);
|
|
$className = basename($filePath, '.php');
|
|
|
|
return $namespace . '\\' . $className;
|
|
}
|
|
|
|
private function guessClassNameFromPath(string $filePath, string $fileDir): ?string
|
|
{
|
|
$basePath = base_path();
|
|
$relativePath = str_replace($basePath, '', $fileDir);
|
|
$namespace = str_replace('/', '\\', ltrim($relativePath, '/'));
|
|
$namespace = trim($namespace, '\\');
|
|
$className = basename($filePath, '.php');
|
|
|
|
return empty($namespace) ? $className : $namespace . '\\' . $className;
|
|
}
|
|
|
|
private function writeHelperFile(): void
|
|
{
|
|
$outputPath = base_path('routes/api.php');
|
|
|
|
$lines = [
|
|
'<?php',
|
|
'',
|
|
'use Illuminate\Support\Facades\Route;',
|
|
'',
|
|
'Route::get(\'/\', function () {',
|
|
' return "Hello, thank you for using XinAdmin. ";',
|
|
'});',
|
|
'',
|
|
];
|
|
|
|
// Each controller gets its own Route::controller group
|
|
foreach ($this->controllerRoutes as $className => $routes) {
|
|
$prefix = $routes[0]['prefix'];
|
|
|
|
$shortName = substr(strrchr($className, '\\'), 1);
|
|
$lines[] = "// {$shortName}";
|
|
|
|
$chain = "Route::controller({$className}::class)";
|
|
if ($prefix !== '') {
|
|
$chain .= "->prefix('{$prefix}')";
|
|
}
|
|
$chain .= '->group(function () {';
|
|
$lines[] = $chain;
|
|
|
|
foreach ($routes as $route) {
|
|
$uri = $route['uri'];
|
|
if ($uri === '') {
|
|
$uri = '/';
|
|
}
|
|
|
|
$middlewareStr = '';
|
|
if (!empty($route['middleware'])) {
|
|
$middlewareStr = "->middleware(['" . implode("', '", $route['middleware']) . "'])";
|
|
}
|
|
|
|
$lines[] = " Route::{$route['method']}('{$uri}', '{$route['action']}'){$middlewareStr};";
|
|
}
|
|
|
|
$lines[] = '});';
|
|
$lines[] = '';
|
|
}
|
|
|
|
File::put($outputPath, implode("\n", $lines));
|
|
}
|
|
}
|