first commit
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
<?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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user