Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions WebFiori/Framework/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ public static function getRunner() : Runner {
'\\WebFiori\\Framework\\Cli\\Commands\\FreshMigrationsCommand',
'\\WebFiori\\Framework\\Cli\\Commands\\SkipMigrationsCommand',
'\\WebFiori\\Framework\\Cli\\Commands\\StepMigrationsCommand',
'\\WebFiori\\Framework\\Cli\\Commands\\ServicesListCommand',
'\\WebFiori\\Framework\\Cli\\Commands\\RoutesCacheCommand',
'\\WebFiori\\Framework\\Cli\\Commands\\RoutesClearCommand',
];

foreach ($commands as $c) {
Expand Down
48 changes: 48 additions & 0 deletions WebFiori/Framework/Cli/Commands/RoutesCacheCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

/**
* This file is licensed under MIT License.
*
* Copyright (c) 2026-present WebFiori Framework
*
* For more information on the license, please visit:
* https://github.com/WebFiori/.github/blob/main/LICENSE
*
*/
namespace WebFiori\Framework\Cli\Commands;

use WebFiori\Cache\Cache;
use WebFiori\Cache\FileStorage;
use WebFiori\Cli\Command;
use WebFiori\Framework\Router\RouteCache;
use WebFiori\Framework\Router\Router;

/**
* CLI command to build the route cache.
*
* @author Ibrahim
*/
class RoutesCacheCommand extends Command {
public function __construct() {
parent::__construct('routes:cache', [], 'Build the route cache for production.');
}

public function exec(): int {
$cache = $this->createRouteCache();
$cache->setEnabled(true);
$count = $cache->build();
$this->success("Route cache built: $count route(s) cached.");

return 0;
}

private function createRouteCache(): RouteCache {
$storagePath = APP_PATH . 'Storage';

if (!is_dir($storagePath)) {
mkdir($storagePath, 0755, true);
}

return new RouteCache(new Cache(new FileStorage($storagePath)), true);
}
}
42 changes: 42 additions & 0 deletions WebFiori/Framework/Cli/Commands/RoutesClearCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

/**
* This file is licensed under MIT License.
*
* Copyright (c) 2026-present WebFiori Framework
*
* For more information on the license, please visit:
* https://github.com/WebFiori/.github/blob/main/LICENSE
*
*/
namespace WebFiori\Framework\Cli\Commands;

use WebFiori\Cache\Cache;
use WebFiori\Cache\FileStorage;
use WebFiori\Cli\Command;
use WebFiori\Framework\Router\RouteCache;

/**
* CLI command to clear the route cache.
*
* @author Ibrahim
*/
class RoutesClearCommand extends Command {
public function __construct() {
parent::__construct('routes:clear', [], 'Clear the route cache.');
}

public function exec(): int {
$cache = $this->createRouteCache();
$cache->clear();
$this->success('Route cache cleared.');

return 0;
}

private function createRouteCache(): RouteCache {
$storagePath = APP_PATH . 'Storage';

return new RouteCache(new Cache(new FileStorage($storagePath)), true);
}
}
55 changes: 55 additions & 0 deletions WebFiori/Framework/Cli/Commands/ServicesListCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

/**
* This file is licensed under MIT License.
*
* Copyright (c) 2026-present WebFiori Framework
*
* For more information on the license, please visit:
* https://github.com/WebFiori/.github/blob/main/LICENSE
*
*/
namespace WebFiori\Framework\Cli\Commands;

use WebFiori\Cli\Command;
use WebFiori\Framework\Router\ServiceRouter;

/**
* CLI command to list all discovered services.
*
* @author Ibrahim
*/
class ServicesListCommand extends Command {
public function __construct() {
parent::__construct('services:list', [], 'List all auto-discovered API services.');
}

public function exec(): int {
$discovered = ServiceRouter::getDiscovered();

if (empty($discovered)) {
$this->info('No services discovered. Use ServiceRouter::discover() to register services.');

return 0;
}

$this->println('');
$this->println(sprintf(' %-15s %-45s %-10s %s', 'Name', 'Class', 'Type', 'Path'));
$this->println(str_repeat('-', 90));

foreach ($discovered as $name => $entry) {
$this->println(sprintf(
' %-15s %-45s %-10s %s',
$name,
$entry['class'],
$entry['type'],
$entry['path']
));
}

$this->println('');
$this->info('Total: ' . count($discovered) . ' service(s).');

return 0;
}
}
150 changes: 150 additions & 0 deletions WebFiori/Framework/Router/RouteCache.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
<?php

/**
* This file is licensed under MIT License.
*
* Copyright (c) 2026-present WebFiori Framework
*
* For more information on the license, please visit:
* https://github.com/WebFiori/.github/blob/main/LICENSE
*
*/
namespace WebFiori\Framework\Router;

use WebFiori\Cache\Cache;

/**
* Caches route definitions to avoid discovery/registration overhead in production.
*
* @author Ibrahim
*/
class RouteCache {
/**
* @var Cache The cache instance.
*/
private Cache $cache;
/**
* @var string Cache key for route data.
*/
private string $cacheKey;
/**
* @var bool Whether caching is enabled.
*/
private bool $enabled;

/**
* Creates new instance.
*
* @param Cache $cache The cache instance to use.
* @param bool $enabled Whether route caching is enabled.
* @param string $cacheKey The cache key for route data.
*/
public function __construct(Cache $cache, bool $enabled = false, string $cacheKey = 'wf_routes_cache') {
$this->cache = $cache;
$this->enabled = $enabled;
$this->cacheKey = $cacheKey;
}

/**
* Returns whether route caching is enabled.
*
* @return bool
*/
public function isEnabled(): bool {
return $this->enabled;
}

/**
* Sets whether route caching is enabled.
*
* @param bool $enabled
*/
public function setEnabled(bool $enabled): void {
$this->enabled = $enabled;
}

/**
* Checks if a route cache exists.
*
* @return bool
*/
public function isCached(): bool {
return $this->cache->get($this->cacheKey) !== null;
}

/**
* Load cached routes into the Router.
*
* @return bool True if cache was loaded, false if no cache exists.
*/
public function load(): bool {
if (!$this->enabled) {
return false;
}

$data = $this->cache->get($this->cacheKey);

if ($data === null) {
return false;
}

$discovered = $data['discovered'] ?? [];

if (!empty($discovered)) {
ServiceRouter::setDiscovered($discovered);
}

// Re-register routes from the cached service map
$configs = $data['configs'] ?? [];

foreach ($configs as $config) {
ServiceRouter::discover(
$config['namespace'],
$config['basePath'],
$config['options'] ?? [],
$config['directory'] ?? null,
$config['recursive'] ?? false
);
}

return true;
}

/**
* Build route cache from current ServiceRouter state.
*
* @param array $discoverConfigs Array of discover() call configs to replay on load.
*
* @return int Number of discovered services cached.
*/
public function build(array $discoverConfigs = []): int {
$discovered = ServiceRouter::getDiscovered();

$data = [
'discovered' => $discovered,
'configs' => $discoverConfigs,
'built_at' => date('c'),
'total' => count($discovered),
];

$this->cache->set($this->cacheKey, $data, 31536000, true);

return count($discovered);
}

/**
* Clear the route cache.
*/
public function clear(): void {
$this->cache->delete($this->cacheKey);
}

/**
* Returns the cache key.
*
* @return string
*/
public function getCacheKey(): string {
return $this->cacheKey;
}
}
4 changes: 4 additions & 0 deletions WebFiori/Framework/Router/RouteOption.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ class RouteOption {
* An option which is used to set an array of allowed values to route parameters.
*/
const VALUES = 'vars-values';
/**
* An option to specify a namespace for dynamic service resolution.
*/
const NS = 'namespace';
}
Loading
Loading