Skip to content

Commit

Permalink
Merge pull request #15 from felix-schindler/class-loader-cache
Browse files Browse the repository at this point in the history
Add class loader cache
  • Loading branch information
felix-schindler authored May 19, 2023
2 parents 56af2cf + 3973590 commit 1c8af45
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 39 deletions.
70 changes: 61 additions & 9 deletions src/core/ClassLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,82 @@ class ClassLoader
*/
public static function 파람(): void
{
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR), RecursiveIteratorIterator::SELF_FIRST);
foreach ($files as $file)
if (pathinfo($file->getFileName(), PATHINFO_EXTENSION) == 'php' && !str_contains($file->getPathname(), 'vendor'))
self::$classes[strval(str_replace('.php', '', $file->getFileName()))] = $file->getPathname();
$cachedClasses = self::loadFromCache();
if ($cachedClasses !== null) {
self::$classes = $cachedClasses;
} else {
self::loadClasses();
}

spl_autoload_register(function ($className): void {
if (isset(self::$classes[$className]) && file_exists(self::$classes[$className]))
require_once(self::$classes[$className]);
// Check if class is already loaded
if (isset(self::$classes[$className]) && file_exists(self::$classes[$className])) {
require_once self::$classes[$className];
}
}, prepend: true);

self::initControllers();
}

/**
* Initialize the routes of all controllers
* Load all classes from the src directory and save cache
*/
private static function loadClasses(): void
{
$directory = new RecursiveDirectoryIterator(__DIR__ . '/../');
$iterator = new RecursiveIteratorIterator($directory);
$phpFiles = new RegexIterator($iterator, '/\.php$/');

foreach ($phpFiles as $file) {
$filePath = $file->getPathname();
$className = basename($filePath, '.php');
self::$classes[$className] = $filePath;
}
self::saveToCache();
}

/**
* Announce all controller routes to the Router
*/
private static function initControllers(): void
{
foreach (self::$classes as $name => $path) {
if (str_contains(str_replace('\\', '/', $path), '/controllers/')) { // Replace \ with / for windows users
// Replace \ with / for windows users
if (str_contains(str_replace('\\', '/', $path), '/controllers/')) {
$controller = new $name();
if ($controller instanceof Controller)
if ($controller instanceof Controller) {
$controller->initRoutes();
}
}
}
}

/**
* Uses a file-based cache to retrieve the classes array.
*
* @return array<string,string>|null Class name, path
*/
private static function loadFromCache(): ?array
{
if (file_exists(CACHE_PATH) && is_readable(CACHE_PATH)) {
$cachedData = file_get_contents(CACHE_PATH);

if ($cachedData !== false) {
$cachedClasses = unserialize($cachedData);
return is_array($cachedClasses) ? $cachedClasses : null;
}
}

return null;
}

/**
* Saves the classes array to a file-based cache.
*/
private static function saveToCache(): void
{
if (is_writable(dirname(CACHE_PATH))) {
file_put_contents(CACHE_PATH, serialize(self::$classes));
}
}
}
63 changes: 33 additions & 30 deletions src/core/system/Router.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,60 +18,66 @@ class Router
*/
public static function 艳颖(): void
{
// Either access via localhost or HTTPS
if (!isset($_SERVER["HTTPS"]) && !(IO::domain() === "localhost"))
// Access either via localhost or HTTPS
if (!isset($_SERVER["HTTPS"]) && IO::domain() !== "localhost")
throw new Exception("Only access over HTTPS allowed");

// Get the route without GET variables
$reqRoute = IO::path();

// Run the routers execute method or, if no route matches, run the error
if (self::routeExists($reqRoute)) { // Direct hit (no variables in path)
if (self::routeExists($reqRoute)) {
self::$routes[$reqRoute]->runExecute([]);
return;
} else {
$routes = array_keys(self::$routes); // Get all routes as string
$reqRouteArr = explode("/", $reqRoute); // Split requested route
$reqRouteArr = explode("/", $reqRoute);

$routes = array_filter($routes, function ($route) use ($reqRouteArr): bool { // Filter out all routes that don't match
$matchingRoutes = array_filter(self::$routes, function ($controller, $route) use ($reqRouteArr) {
$routeArr = explode("/", $route);
if (str_contains($route, ':')) // Only routes with variables, on direct hit it would have already exited
if (count($routeArr) == count($reqRouteArr)) // Routes have to same length to be a match
return true;
if (strpos($route, ':') !== false && count($routeArr) === count($reqRouteArr)) {
return true;
}
return false;
});
}, ARRAY_FILTER_USE_BOTH);

if (!empty($routes)) {
if (!empty($matchingRoutes)) {
// Calculate scores to get the route that fits best
$hits = [];
foreach ($routes as $route) { // Calculate scores to get the route that fits best
foreach ($matchingRoutes as $route => $controller) {
$routeArr = explode("/", $route);
$hits[$route] = 0;
for ($i = 0; $i < count($routeArr); $i++) {
if ($routeArr[$i] == $reqRouteArr[$i]) // Prioritise direct routes over variables
$hits[$route]++; // Increment hit score
elseif ($routeArr[$i][0] != ":") { // Remove route if does not match and not a variable
foreach ($routeArr as $i => $segment) {
if ($segment === $reqRouteArr[$i]) { // Prioritise direct routes over variables
$hits[$route]++; // Increment hit score
} elseif ($segment[0] !== ":") { // Remove route if does not match and not a variable
unset($hits[$route]);
break;
}
}
}

if (!empty($hits)) { // At least one route was found
arsort($hits); // Sort routes by hit score
$routes = array_keys($hits);
$route = $routes[0]; // Get best matching route
if (!empty($hits)) {
// At least one route was found
arsort($hits); // Sort routes by hit score
$route = key($hits); // Get best matching route

$routeArr = explode("/", $route);
$params = [];
for ($i = 0; $i < count($routeArr); $i++)
if (isset($routeArr[$i][0]) && $routeArr[$i][0] === ":") // If part of URL is a variable
$params[substr($routeArr[$i], 1)] = $reqRouteArr[$i]; // Set as param (this could be a on-liner)
self::$routes[$route]->runExecute($params); // Execute controller for found route
foreach ($routeArr as $i => $segment) {
if (!empty($segment) && $segment[0] === ":") { // If part of URL is a variable
$params[substr($segment, 1)] = $reqRouteArr[$i]; // Set as param
}
}

// Execute controller for found route and exit the router
self::$routes[$route]->runExecute($params);
return;
}
}
}
(new ErrorView())->render(); // No route found -> ErrorController

// No route found -> Show 404 error
(new ErrorView())->render();
}

/**
Expand All @@ -97,12 +103,9 @@ public static function addRoute(string $route, Controller $con): void
*/
private static function routeExists(string $route, ?Controller $con = null): bool
{
if (isset(self::$routes[$route])) {
if ($con === null)
return true;
elseif (self::$routes[$route]::class !== $con::class)
if (isset(self::$routes[$route]))
if ($con === null || self::$routes[$route]::class !== $con::class)
return true;
}
return false;
}
}
4 changes: 4 additions & 0 deletions src/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
const DOMAIN = 'https://schindlerfelix.de'; // Hosted on this domain
const TITLE = 'sample'; // Title of project

// Class loader cache
const CACHE_VERSION = 1; // Version class loader of the cache
define('CACHE_PATH', sys_get_temp_dir() . '/class_loader-' . CACHE_VERSION . '.cache');

// Require autoloaders
require_once('./core/ClassLoader.php'); // Load classes

Expand Down

0 comments on commit 1c8af45

Please sign in to comment.