<?php declare(strict_types=1); class Autoloader { private const PATH_CLASSES = 'backend/classes'; private const PATH_CONTROLLERS = self::PATH_CLASSES . '/controller'; private const PATH_CACHE = 'backend/cache/'; public function __construct(string $cachePath = self::PATH_CACHE) { if ($cachePath !== self::PATH_CACHE) { $cachePath = substr($cachePath, -1) === '/' ? $cachePath : $cachePath . '/'; } $routesFound = @include($cachePath . 'routes.php'); $classesFound = @include($cachePath . 'classes.php'); if (!$routesFound || !$classesFound) { throw new Exception( sprintf( 'Autoloader cache not found! Please generate it with %s::BuildCache() at first!', self::class ) ); } spl_autoload_register( function (string $className) { if (!$this->loadClass($className)) { throw new Exception(sprintf('Class %s couldn\'t be loaded!', $className)); } } ); } public static function BuildCache(): void { self::BuildClassCache(); self::BuildRouteCache(); } public static function BuildClassCache(): void { $classesResult = self::scanForClasses(); $cacheContent = ''; foreach ($classesResult as $className => $path) { $cacheContent .= sprintf("\t\t'%s' => '%s',\n", $className, $path); } $cacheContent .= "\t]\n);"; self::buildCacheFile($cacheContent, 'classes'); } private function loadClass(string $className): bool { if (!isset(CLASSES[$className]) || !@include(CLASSES[$className])) { return false; } return true; } public static function BuildRouteCache(): void { $controllersResult = self::scanForControllers(); $controllerMethods = [ 'GET' => [], 'POST' => [], 'PUT' => [], 'DELETE' => [], ]; foreach ($controllersResult as $className => $path) { $file = fopen($path, 'r'); $content = fread($file, filesize($path)); fclose($file); preg_match_all('/(?<=private )\w+ \$\w+(?=;)/', $content, $matches); $params = []; foreach ($matches[0] as $match) { $parts = explode(' ', $match); $params[] = [ 'type' => $parts[0], 'name' => $parts[1], ]; } preg_match('/(?<=protected string \$route = \').*(?=\';)/', $content, $matches); $route = $matches[0]; preg_match('/[A-Z][a-z]+(?=Controller)/', $className, $matches); $method = strtoupper($matches[0]); $controllerMethods[$method][$route] = [ 'name' => $className, 'params' => $params, ]; } $cacheContent = ''; foreach ($controllerMethods as $method => $controllers) { $cacheContent .= self::createRoutesForMethod($method, $controllers); } $cacheContent .= "\t]\n);"; self::buildCacheFile($cacheContent, 'routes'); } private static function createRoutesForMethod(string $method, array $routes): string { krsort($routes); $stringRoutes = ''; foreach ($routes as $route => $params) { $stringRoutes .= sprintf( "'%s' => [ 'controller' => %s::class, 'params' => [ %s ], ], ", $route, $params['name'], self::createRouteParams($params['params']) ); } return sprintf( " '%s' => [ %s ], ", $method, $stringRoutes, ); } private static function createRouteParams(array $params): string { $string = ''; foreach ($params as $param) { $string .= sprintf( " '%s' => [ 'type' => '%s', ], ", str_replace('$', '', $param['name']), $param['type'] ); } return $string; } private static function reformatCacheFileContent(string $content): string { $depth = 0; $reformatted = ''; $replace = ''; // Removing indents foreach (explode("\n", $content) as $line) { $trim = trim($line); if ($trim !== '') { $replace .= $trim . "\n"; } } for ($i = 0; $i < strlen($replace); $i++) { if (in_array($replace[$i], [')', ']'])) { $depth--; } if ($replace[$i - 1] === "\n") { $reformatted .= str_repeat("\t", $depth); } $reformatted .= $replace[$i]; if (in_array($replace[$i], ['(', '['])) { $depth++; } } return $reformatted; } private static function buildCacheFile(string $content, string $cacheName): void { $cacheContent = sprintf( "<?php\n\n/*\n * This file was auto generated on %s\n */\n\ndefine(\n\t'%s',\n\t[\n", (new DateTime())->format('Y-m-d H:i:s'), strtoupper($cacheName) ); $cacheContent .= $content; $file = fopen(getcwd() . '/' . self::PATH_CACHE . $cacheName . '.php', 'w'); fwrite($file, self::reformatCacheFileContent($cacheContent)); fclose($file); } private static function scanForFiles(string $folder): array { $folder = substr($folder, -1) === '/' ? substr($folder, 0, -1) : $folder; $files = []; $handler = opendir($folder); while ($file = readdir($handler)) { $path = $folder . '/' . $file; if (is_dir($path) && $file !== '.' && $file !== '..') { $files = array_merge($files, self::scanForFiles($path)); } elseif (is_file($path) && substr($path, -4) === '.php') { $className = substr($file, 0, -4); $files[$className] = $path; } } return $files; } private static function scanForClasses(): array { return self::scanForFiles(getcwd() . '/' . self::PATH_CLASSES); } private static function scanForControllers(): array { return self::scanForFiles(getcwd() . '/' . self::PATH_CONTROLLERS); } }