Your IP : 216.73.216.79


Current Path : /var/www/html/reddsis/app/Services/
Upload File :
Current File : /var/www/html/reddsis/app/Services/ControllerScanner.php

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use ReflectionClass;
use ReflectionMethod;

class ControllerScanner
{
    protected string $controllersPath;

    /**
     * Method HTTP default untuk resource controller.
     * index=GET, store=POST, update=PUT, destroy=DELETE.
     */
    protected array $resourceMethods = [
        'index' => 'GET',
        'create' => 'GET',
        'store' => 'POST',
        'show' => 'GET',
        'edit' => 'GET',
        'update' => 'PUT',
        'destroy' => 'DELETE',
    ];

    public function __construct()
    {
        $this->controllersPath = app_path('Http/Controllers');
    }

    /**
     * Scan semua controller dan generate route data.
     * Return array of routes dengan controller, action, uri, method.
     */
    public function scan(): array
    {
        $controllers = $this->findControllers($this->controllersPath);
        $routes = [];

        foreach ($controllers as $controller) {
            $routes = array_merge($routes, $this->processController($controller));
        }

        return $routes;
    }

    /**
     * Cari semua file Controller.php dalam direktori.
     * Recursive — scan subfolder jugak.
     */
    protected function findControllers(string $path): array
    {
        $controllers = [];
        $items = glob($path . '/*');

        foreach ($items as $item) {
            if (is_dir($item)) {
                $controllers = array_merge($controllers, $this->findControllers($item));
            } elseif (is_file($item) && str_ends_with($item, 'Controller.php')) {
                $controllers[] = $item;
            }
        }

        return $controllers;
    }

    /**
     * Baca file controller, reflect class, extract public methods jadi route.
     */
    protected function processController(string $filePath): array
    {
        $className = $this->getFullClassName($filePath);
        if (!$className || !class_exists($className)) {
            return [];
        }

        $reflection = new ReflectionClass($className);
        if ($reflection->isAbstract()) {
            return [];
        }

        $methods = $reflection->getMethods(ReflectionMethod::IS_PUBLIC);
        $routes = [];

        foreach ($methods as $method) {
            if ($method->getDeclaringClass()->getName() !== $className) {
                continue;
            }

            if ($this->shouldSkipMethod($method->getName())) {
                continue;
            }

            $routes[] = $this->generateRouteData($className, $method);
        }

        return $routes;
    }

    /**
     * Skip magic methods (__construct, __call, etc) dan middleware methods.
     */
    protected function shouldSkipMethod(string $methodName): bool
    {
        $skipMethods = [
            '__construct', '__destruct', '__call', '__callStatic',
            '__get', '__set', '__isset', '__unset',
            '__sleep', '__wakeup', '__serialize', '__unserialize',
            '__toString', '__invoke', '__set_state',
            'Middleware', 'middleware',
        ];

        if (str_starts_with($methodName, 'middleware')) {
            return true;
        }

        return in_array($methodName, $skipMethods);
    }

    /**
     * Bina array route untuk satu method controller.
     */
    protected function generateRouteData(string $className, ReflectionMethod $method): array
    {
        $shortName = $this->getShortControllerName($className);
        $methodName = $method->getName();
        $baseUri = $this->generateUriFromController($className, $methodName);
        $methodType = $this->determineMethodType($methodName);

        return [
            'controller' => $className,
            'action' => $methodName,
            'uri' => $baseUri,
            'method' => $methodType,
            'type' => $this->determineTypeFromNamespace($className),
        ];
    }

    /**
     * Baca namespace dari file PHP guna regex.
     */
    protected function getFullClassName(string $filePath): ?string
    {
        $content = file_get_contents($filePath);
        if (preg_match('/namespace\s+([^;]+);/', $content, $matches)) {
            $namespace = $matches[1];
            $className = basename($filePath, '.php');
            return $namespace . '\\' . $className;
        }
        return null;
    }

    protected function getShortControllerName(string $fullClassName): string
    {
        return class_basename($fullClassName);
    }

    /**
     * Generate URI based on controller name + method.
     * Contoh: UserController@index → /user, UserController@create → /user/create
     */
    protected function generateUriFromController(string $className, string $methodName): string
    {
        $baseName = class_basename($className);
        $controllerName = str_replace('Controller', '', $baseName);
        $controllerUri = Str::kebab($controllerName);

        if ($methodName === 'index') {
            return '/' . $controllerUri;
        }

        if (isset($this->resourceMethods[$methodName])) {
            if ($methodName === 'create') {
                return '/' . $controllerUri . '/create';
            }
            if ($methodName === 'edit') {
                return '/' . $controllerUri . '/{' . Str::camel(Str::singular($controllerUri)) . '}/edit';
            }
            if ($methodName === 'show') {
                return '/' . $controllerUri . '/{' . Str::camel(Str::singular($controllerUri)) . '}';
            }
            if ($methodName === 'store') {
                return '/' . $controllerUri;
            }
            if ($methodName === 'update') {
                return '/' . $controllerUri . '/{' . Str::camel(Str::singular($controllerUri)) . '}';
            }
            if ($methodName === 'destroy') {
                return '/' . $controllerUri . '/{' . Str::camel(Str::singular($controllerUri)) . '}';
            }
        }

        return '/' . $controllerUri . '/' . Str::kebab($methodName);
    }

    /**
     * Tentukan HTTP method berdasarkan nama method.
     * Resource method dah mapping, lain-lain default GET.
     */
    protected function determineMethodType(string $methodName): string
    {
        return $this->resourceMethods[$methodName] ?? 'GET';
    }

    /**
     * Tentukan type route (admin/api/web) dari namespace controller.
     */
    protected function determineTypeFromNamespace(string $className): string
    {
        if (str_contains($className, 'Admin\\')) {
            return 'admin';
        }
        if (str_contains($className, 'Api\\') || str_contains($className, 'Controllers\\Api\\')) {
            return 'api';
        }
        return 'web';
    }

    /**
     * Scan + simpan dalam cache (1 hari).
     */
    public function scanAndCache(): array
    {
        $routes = $this->scan();
        Cache::put('scanned_controllers', $routes, now()->addDay());
        return $routes;
    }

    public function getCached(): ?array
    {
        return Cache::get('scanned_controllers');
    }

    public function clearCache(): bool
    {
        return Cache::forget('scanned_controllers');
    }

    /**
     * Clear cache + rescan.
     */
    public function refresh(): array
    {
        $this->clearCache();
        return $this->scanAndCache();
    }
}