From bbbacffd93174662a6359dc3b6c51a3e1db74dd6 Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Wed, 20 Nov 2024 14:28:22 +0300 Subject: [PATCH 1/2] feat: Routes Caching --- webfiori/framework/App.php | 11 ++++++ webfiori/framework/cache/FileStorage.php | 24 ++++++------ webfiori/framework/cache/Item.php | 10 ++--- webfiori/framework/router/RouteOption.php | 4 ++ webfiori/framework/router/Router.php | 46 +++++++++++++++++------ webfiori/framework/router/RouterUri.php | 22 +++++++++++ 6 files changed, 90 insertions(+), 27 deletions(-) diff --git a/webfiori/framework/App.php b/webfiori/framework/App.php index 691ebddda..4c41b4a15 100644 --- a/webfiori/framework/App.php +++ b/webfiori/framework/App.php @@ -18,6 +18,7 @@ use webfiori\file\exceptions\FileException; use webfiori\file\File; use webfiori\framework\autoload\ClassLoader; +use webfiori\framework\cache\Cache; use webfiori\framework\config\ConfigurationDriver; use webfiori\framework\config\Controller; use webfiori\framework\exceptions\InitializationException; @@ -179,11 +180,21 @@ private function __construct() { foreach ($uriObj->getMiddleware() as $mw) { $mw->after(Request::get(), Response::get()); } + App::cacheResponse($uriObj->getUri(true, true), $uriObj->getCacheDuration()); } }); //class is now initialized self::$ClassStatus = self::STATUS_INITIALIZED; } + public static function cacheResponse(string $key, int $duration) { + Cache::get($key, function () { + return [ + 'headers' => Response::getHeaders(), + 'http-code' => Response::getCode(), + 'body' => Response::getBody() + ]; + }, $duration); + } /** * Register CLI commands or background tasks. * diff --git a/webfiori/framework/cache/FileStorage.php b/webfiori/framework/cache/FileStorage.php index 17b40b5f2..500a39ee5 100644 --- a/webfiori/framework/cache/FileStorage.php +++ b/webfiori/framework/cache/FileStorage.php @@ -33,19 +33,21 @@ public function __construct() { * @param Item $item An item that will be added to the cache. */ public function cache(Item $item) { - $filePath = $this->getPath().DS.md5($item->getKey()).'.cache'; - $encryptedData = $item->getDataEncrypted(); + if ($item->getTTL() > 0) { + $filePath = $this->getPath().DS.md5($item->getKey()).'.cache'; + $encryptedData = $item->getDataEncrypted(); - if (!is_dir($this->getPath())) { - mkdir($this->getPath(), 0755, true); + if (!is_dir($this->getPath())) { + mkdir($this->getPath(), 0755, true); + } + file_put_contents($filePath, serialize([ + 'data' => $encryptedData, + 'created_at' => time(), + 'ttl' => $item->getTTL(), + 'expires' => $item->getExpiryTime(), + 'key' => $item->getKey() + ])); } - file_put_contents($filePath, serialize([ - 'data' => $encryptedData, - 'created_at' => time(), - 'ttl' => $item->getTTL(), - 'expires' => $item->getExpiryTime(), - 'key' => $item->getKey() - ])); } /** * Removes an item from the cache. diff --git a/webfiori/framework/cache/Item.php b/webfiori/framework/cache/Item.php index c4a887896..c8c206671 100644 --- a/webfiori/framework/cache/Item.php +++ b/webfiori/framework/cache/Item.php @@ -70,10 +70,10 @@ public function getData() { /** * Returns cache item data after performing decryption on it. * - * @return string + * @return mixed */ - public function getDataDecrypted() : string { - return $this->decrypt($this->getData()); + public function getDataDecrypted() { + return unserialize($this->decrypt($this->getData())); } /** * Returns cache data after performing encryption on it. @@ -83,7 +83,7 @@ public function getDataDecrypted() : string { * @return string */ public function getDataEncrypted() : string { - return $this->encrypt($this->getData()); + return $this->encrypt(serialize($this->getData())); } /** * Returns the time at which cache item will expire as Unix timestamp. @@ -169,7 +169,7 @@ public function setSecret(string $secret) { * @param int $ttl Time-to-live of the item in cache. */ public function setTTL(int $ttl) { - if ($ttl > 0) { + if ($ttl >= 0) { $this->timeToLive = $ttl; } } diff --git a/webfiori/framework/router/RouteOption.php b/webfiori/framework/router/RouteOption.php index 0a3827d74..4bcbd3835 100644 --- a/webfiori/framework/router/RouteOption.php +++ b/webfiori/framework/router/RouteOption.php @@ -28,6 +28,10 @@ class RouteOption { * An option which is used to indicate if path is case sensitive or not. */ const CASE_SENSITIVE = 'case-sensitive'; + /** + * An option which is used to set the duration of route cache in seconds. + */ + const CACHE_DURATION = 'cache-ttl'; /** * An option which is used to set an array as closure parameters (applies to routes of type closure only) */ diff --git a/webfiori/framework/router/Router.php b/webfiori/framework/router/Router.php index b8f6a4350..a3860acab 100644 --- a/webfiori/framework/router/Router.php +++ b/webfiori/framework/router/Router.php @@ -15,6 +15,7 @@ use webfiori\cli\Runner; use webfiori\file\exceptions\FileException; use webfiori\file\File; +use webfiori\framework\cache\Cache; use webfiori\framework\exceptions\RoutingException; use webfiori\framework\ui\HTTPCodeView; use webfiori\framework\ui\StarterPage; @@ -507,14 +508,16 @@ public static function incSiteMapRoute() { Response::addHeader('content-type','text/xml'); }; self::closure([ - 'path' => '/sitemap.xml', - 'route-to' => $sitemapFunc, - 'in-sitemap' => true + RouteOption::PATH => '/sitemap.xml', + RouteOption::TO => $sitemapFunc, + RouteOption::SITEMAP => true, + RouteOption::CACHE_DURATION => 86400//1 day ]); self::closure([ - 'path' => '/sitemap', - 'route-to' => $sitemapFunc, - 'in-sitemap' => true + RouteOption::PATH => '/sitemap', + RouteOption::TO => $sitemapFunc, + RouteOption::SITEMAP => true, + RouteOption::CACHE_DURATION => 86400//1 day ]); } /** @@ -529,7 +532,8 @@ public static function notFound() { * Adds new route to a web page. * * Note that the route which created using this method will be added to - * 'global' and 'web' middleware groups. + * 'global' and 'web' middleware groups. Additionally, the routes will + * be cached for one hour. * * @param array $options An associative array that contains route * options. Available options are: @@ -755,13 +759,14 @@ private function addRouteHelper0($options): bool { $asApi = $options[RouteOption::API]; $closureParams = $options[RouteOption::CLOSURE_PARAMS] ; $path = $options[RouteOption::PATH]; + $cache = $options[RouteOption::CACHE_DURATION]; if ($routeType == self::CLOSURE_ROUTE && !is_callable($routeTo)) { return false; } $routeUri = new RouterUri($this->getBase().$path, $routeTo,$caseSensitive, $closureParams); $routeUri->setAction($options[RouteOption::ACTION]); - + $routeUri->setCacheDuration($cache); if (!$this->hasRouteHelper($routeUri)) { if ($asApi === true) { $routeUri->setType(self::API_ROUTE); @@ -928,6 +933,12 @@ private function checkOptionsArr(array $options): array { } else { $caseSensitive = true; } + + if (isset($options[RouteOption::CACHE_DURATION])) { + $cacheDuration = $options[RouteOption::CACHE_DURATION]; + } else { + $cacheDuration = 0; + } $routeType = $options[RouteOption::TYPE] ?? Router::CUSTOMIZED; @@ -978,7 +989,8 @@ private function checkOptionsArr(array $options): array { RouteOption::VALUES => $varValues, RouteOption::MIDDLEWARE => $mdArr, RouteOption::REQUEST_METHODS => $this->getRequestMethodsHelper($options), - RouteOption::ACTION => $action + RouteOption::ACTION => $action, + RouteOption::CACHE_DURATION => $cacheDuration ]; } private function copyOptionsToSub($options, &$subRoute) { @@ -1376,7 +1388,6 @@ private function routeFound(RouterUri $route, bool $loadResource) { if ($route->getType() == self::API_ROUTE && !defined('API_CALL')) { define('API_CALL', true); } - if (is_callable($route->getRouteTo())) { if ($loadResource === true) { call_user_func_array($route->getRouteTo(),$route->getClosureParams()); @@ -1453,6 +1464,16 @@ private function routeFound(RouterUri $route, bool $loadResource) { * @throws RoutingException */ private function searchRoute(RouterUri $routeUri, string $uri, bool $loadResource, bool $withVars = false): bool { + $data = Cache::get($uri); + + if ($data !== null) { + Response::write($data['body']); + Response::setCode($data['http-code']); + foreach ($data['headers'] as $headerObj) { + Response::addHeader($headerObj->getName(), $headerObj->getValue()); + } + return true; + } $pathArray = $routeUri->getPathArray(); $requestMethod = Request::getMethod(); $indexToSearch = 'static'; @@ -1600,7 +1621,10 @@ private static function view(array $options): bool { if (gettype($options) == 'array') { $options[RouteOption::TYPE] = Router::VIEW_ROUTE; self::addToMiddlewareGroup($options, 'web'); - + if (!isset($options[RouteOption::CACHE_DURATION])) { + //Cache pages for 1 hour by default + $options[RouteOption::CACHE_DURATION] = 3600; + } return Router::getInstance()->addRouteHelper1($options); } diff --git a/webfiori/framework/router/RouterUri.php b/webfiori/framework/router/RouterUri.php index d96e4a42b..d2d4305ab 100644 --- a/webfiori/framework/router/RouterUri.php +++ b/webfiori/framework/router/RouterUri.php @@ -46,6 +46,7 @@ class RouterUri extends Uri { */ private $action; private $assignedMiddlewareList; + private $cacheDuration; /** * * @var array @@ -137,6 +138,27 @@ public function __construct(string $requestedUri, $routeTo, bool $caseSensitive $this->incInSiteMap = false; $this->languages = []; $this->addMiddleware('global'); + $this->setCacheDuration(0); + } + /** + * Returns the duration of URI cache. + * + * @return int The duration of URI cache. Default value is zero which indicates + * that no caching will happen. + */ + public function getCacheDuration() : int { + return $this->cacheDuration; + } + /** + * Sets the duration of URI cache. + * + * @param int $val A positive value that represent cache duration in seconds. + * If 0 is given, it indicates that no caching will happen. + */ + public function setCacheDuration(int $val) { + if ($val >= 0) { + $this->cacheDuration = $val; + } } /** * Adds a language to the set of languages at which the resource that the URI From 53288a9063a672bb37da06e6d6e15a492d57b45b Mon Sep 17 00:00:00 2001 From: Ibrahim Date: Wed, 20 Nov 2024 15:07:08 +0300 Subject: [PATCH 2/2] chore: Remove Unused Imports --- tests/webfiori/framework/test/cli/HelpCommandTest.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/webfiori/framework/test/cli/HelpCommandTest.php b/tests/webfiori/framework/test/cli/HelpCommandTest.php index 9d516645a..75bc9b81e 100644 --- a/tests/webfiori/framework/test/cli/HelpCommandTest.php +++ b/tests/webfiori/framework/test/cli/HelpCommandTest.php @@ -2,9 +2,6 @@ namespace webfiori\framework\test\cli; use webfiori\framework\cli\CLITestCase; -use webfiori\framework\cli\commands\CreateCommand; -use webfiori\framework\scheduler\webServices\TasksServicesManager; -use webfiori\http\WebServicesManager; /** * @author Ibrahim */