diff --git a/.github/workflows/php83.yml b/.github/workflows/php83.yml
index 20b2f47e2..24b9d47c7 100644
--- a/.github/workflows/php83.yml
+++ b/.github/workflows/php83.yml
@@ -4,7 +4,7 @@ on:
push:
branches: [ master, dev ]
pull_request:
- branches: [ master ]
+ branches: [ master, dev ]
jobs:
diff --git a/phpunit.xml b/phpunit.xml
index cf553cdec..0ed2e9860 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -28,6 +28,10 @@
./webfiori/framework/router/RouterUri.php
./webfiori/framework/router/Router.php
+ ./webfiori/framework/cache/AbstractCacheStore.php
+ ./webfiori/framework/cache/FileCacheStore.php
+ ./webfiori/framework/cache/Cache.php
+
./webfiori/framework/session/Session.php
./webfiori/framework/session/SessionsManager.php
./webfiori/framework/session/DefaultSessionStorage.php
@@ -109,6 +113,8 @@
./tests/webfiori/framework/test/session
-
+
+ ./tests/webfiori/framework/test/cache
+
\ No newline at end of file
diff --git a/tests/webfiori/framework/test/cache/CacheTest.php b/tests/webfiori/framework/test/cache/CacheTest.php
new file mode 100644
index 000000000..f65ef6645
--- /dev/null
+++ b/tests/webfiori/framework/test/cache/CacheTest.php
@@ -0,0 +1,87 @@
+assertEquals('This is a test.', $data);
+ $this->assertEquals('This is a test.', Cache::get($key));
+ $this->assertNull(Cache::get('not_cached'));
+ }
+ /**
+ * @test
+ */
+ public function test01() {
+ $key = 'test_2';
+ $this->assertFalse(Cache::has($key));
+ $data = Cache::get($key, function () {
+ return 'This is a test.';
+ }, 5);
+ $this->assertEquals('This is a test.', $data);
+ $this->assertTrue(Cache::has($key));
+ sleep(6);
+ $this->assertFalse(Cache::has($key));
+ $this->assertNull(Cache::get($key));
+ }
+ /**
+ * @test
+ */
+ public function test03() {
+ $key = 'ok_test';
+ $this->assertFalse(Cache::has($key));
+ $data = Cache::get($key, function () {
+ return 'This is a test.';
+ }, 600);
+ $this->assertEquals('This is a test.', $data);
+ $this->assertTrue(Cache::has($key));
+ Cache::delete($key);
+ $this->assertFalse(Cache::has($key));
+ $this->assertNull(Cache::get($key));
+ }
+ /**
+ * @test
+ */
+ public function test04() {
+ $key = 'test_3';
+ $this->assertFalse(Cache::has($key));
+ $data = Cache::get($key, function () {
+ return 'This is a test.';
+ }, 600);
+ $this->assertEquals('This is a test.', $data);
+ $item = Cache::getItem($key);
+ $this->assertNotNull($item);
+ $this->assertEquals(600, $item->getTTL());
+ Cache::setTTL($key, 1000);
+ $item = Cache::getItem($key);
+ $this->assertEquals(1000, $item->getTTL());
+ Cache::delete($key);
+ $this->assertNull(Cache::getItem($key));
+ }
+ public function test05() {
+ $keys = [];
+ for ($x = 0 ; $x < 10 ; $x++) {
+ $key = 'item_'.$x;
+ Cache::get($key, function () {
+ return 'This is a test.';
+ }, 600);
+ $keys[] = $key;
+ }
+ foreach ($keys as $key) {
+ $this->assertTrue(Cache::has($key));
+ }
+ Cache::flush();
+ foreach ($keys as $key) {
+ $this->assertFalse(Cache::has($key));
+ }
+ }
+}
diff --git a/tests/webfiori/framework/test/cli/HelpCommandTest.php b/tests/webfiori/framework/test/cli/HelpCommandTest.php
new file mode 100644
index 000000000..9d516645a
--- /dev/null
+++ b/tests/webfiori/framework/test/cli/HelpCommandTest.php
@@ -0,0 +1,39 @@
+assertEquals([
+ "WebFiori Framework (c) Version ". WF_VERSION." ".WF_VERSION_TYPE."\n\n\n",
+ "Usage:\n",
+ " command [arg1 arg2=\"val\" arg3...]\n\n",
+ "Global Arguments:\n",
+ " --ansi:[Optional] Force the use of ANSI output.\n",
+ "Available Commands:\n",
+ " help: Display CLI Help. To display help for specific command, use the argument \"--command-name\" with this command.\n",
+ " v: Display framework version info.\n",
+ " show-settings: Display application configuration.\n",
+ " scheduler: Run tasks scheduler.\n",
+ " create: Creates a system entity (middleware, web service, background process ...).\n",
+ " add: Add a database connection or SMTP account.\n",
+ " list-routes: List all created routes and which resource they point to.\n",
+ " list-themes: List all registered themes.\n",
+ " run-query: Execute SQL query on specific database.\n",
+ " update-settings: Update application settings which are stored in specific configuration driver.\n",
+ " update-table: Update a database table.\n",
+ ], $this->executeMultiCommand([
+ 'help',
+ ]));
+ $this->assertEquals(0, $this->getExitCode());
+ }
+}
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/Cache.php b/webfiori/framework/cache/Cache.php
new file mode 100644
index 000000000..f5d1bf861
--- /dev/null
+++ b/webfiori/framework/cache/Cache.php
@@ -0,0 +1,172 @@
+delete($key);
+ }
+ /**
+ * Removes all items from the cache.
+ */
+ public static function flush() {
+ self::getDriver()->flush();
+ }
+ /**
+ * Returns or creates a cache item given its key.
+ *
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @param callable $generator A callback which is used as a fallback to
+ * create new cache entry or re-create an existing one if it was expired.
+ * This callback must return the data that will be cached.
+ *
+ * @param int $ttl Time to live of the item in seconds.
+ *
+ * @param array $params Any additional parameters to be passed to the callback
+ * which is used to generate cache data.
+ * @return null
+ */
+ public static function get(string $key, callable $generator = null, int $ttl = 60, array $params = []) {
+ $data = self::getDriver()->read($key);
+
+ if ($data !== null && $data !== false) {
+ return $data;
+ }
+
+ if (!is_callable($generator)) {
+ return null;
+ }
+ $newData = call_user_func_array($generator, $params);
+ $item = new Item($key, $newData, $ttl, defined('CACHE_SECRET') ? CACHE_SECRET : '');
+ self::getDriver()->cache($item);
+
+ return $newData;
+ }
+ /**
+ * Returns storage engine which is used to store, read, update and delete items
+ * from the cache.
+ *
+ * @return Storage
+ */
+ public static function getDriver() : Storage {
+ return self::getInst()->driver;
+ }
+ /**
+ * Reads an item from the cache and return its information.
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @return Item|null If such item exist and not yet expired, an object
+ * of type 'Item' is returned which has all cached item information. Other
+ * than that, null is returned.
+ */
+ public static function getItem(string $key) {
+ return self::getDriver()->readItem($key);
+ }
+ /**
+ * Checks if the cache has in item given its unique identifier.
+ *
+ * @param string $key
+ *
+ * @return bool If the item exist and is not yet expired, true is returned.
+ * Other than that, false is returned.
+ */
+ public static function has(string $key) : bool {
+ return self::getDriver()->has($key);
+ }
+ /**
+ * Creates new item in the cache.
+ *
+ * Note that the item will only be added if it does not exist or already
+ * expired or the override option is set to true in case it was already
+ * created and not expired.
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @param mixed $data The data that will be cached.
+ *
+ * @param int $ttl The time at which the data will be kept in the cache (in seconds).
+ *
+ * @param bool $override If cache item already exist which has given key and not yet
+ * expired and this one is set to true, the existing item will be overridden by
+ * provided data and ttl.
+ *
+ * @return bool If successfully added, the method will return true. False
+ * otherwise.
+ */
+ public static function set(string $key, $data, int $ttl = 60, bool $override = false) : bool {
+ if (!self::has($key) || $override === true) {
+ $item = new Item($key, $data, $ttl, defined('CACHE_SECRET') ? CACHE_SECRET : '');
+ self::getDriver()->cache($item);
+ }
+
+ return false;
+ }
+ /**
+ * Sets storage engine which is used to store, read, update and delete items
+ * from the cache.
+ *
+ * @param Storage $driver
+ */
+ public static function setDriver(Storage $driver) {
+ self::getInst()->driver = $driver;
+ }
+ /**
+ * Updates TTL of specific cache item.
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @param int $ttl The new value for TTL.
+ *
+ * @return bool If item is updated, true is returned. Other than that, false
+ * is returned.
+ */
+ public static function setTTL(string $key, int $ttl) {
+ $item = self::getItem($key);
+
+ if ($item === null) {
+ return false;
+ }
+ $item->setTTL($ttl);
+ self::getDriver()->cache($item);
+
+ return true;
+ }
+ /**
+ * Creates and returns a single instance of the class.
+ *
+ * @return Cache
+ */
+ private static function getInst() : Cache {
+ if (self::$inst === null) {
+ self::$inst = new Cache();
+ self::setDriver(new FileStorage());
+ }
+
+ return self::$inst;
+ }
+}
diff --git a/webfiori/framework/cache/FileStorage.php b/webfiori/framework/cache/FileStorage.php
new file mode 100644
index 000000000..500a39ee5
--- /dev/null
+++ b/webfiori/framework/cache/FileStorage.php
@@ -0,0 +1,160 @@
+setPath($path);
+ }
+ /**
+ * Store an item into the cache.
+ *
+ * @param Item $item An item that will be added to the cache.
+ */
+ public function cache(Item $item) {
+ 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);
+ }
+ 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.
+ *
+ * @param string $key The key of the item.
+ */
+ public function delete(string $key) {
+ $filePath = $this->getPath().md5($key).'.cache';
+
+ if (file_exists($filePath)) {
+ unlink($filePath);
+ }
+ }
+ /**
+ * Removes all cached items.
+ *
+ */
+ public function flush() {
+ $files = glob($this->cacheDir.'*.cache');
+
+ foreach ($files as $file) {
+ unlink($file);
+ }
+ }
+ /**
+ * Returns a string that represents the path to the folder which is used to
+ * create cache files.
+ *
+ * @return string A string that represents the path to the folder which is used to
+ * create cache files.
+ */
+ public function getPath() : string {
+ return $this->cacheDir;
+ }
+ /**
+ * Checks if an item exist in the cache.
+ * @param string $key The value of item key.
+ *
+ * @return bool Returns true if given
+ * key exist in the cache and not yet expired.
+ */
+ public function has(string $key): bool {
+ return $this->read($key) !== null;
+ }
+ /**
+ * Reads and returns the data stored in cache item given its key.
+ *
+ * @param string $key The key of the item.
+ *
+ * @return mixed|null If cache item is not expired, its data is returned. Other than
+ * that, null is returned.
+ */
+ public function read(string $key) {
+ $item = $this->readItem($key);
+
+ if ($item !== null) {
+ return $item->getDataDecrypted();
+ }
+
+ return null;
+ }
+ /**
+ * Reads cache item as an object given its key.
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @return Item|null If cache item exist and is not expired,
+ * an object of type 'Item' is returned. Other than
+ * that, null is returned.
+ */
+ public function readItem(string $key) {
+ $this->initData($key);
+ $now = time();
+
+ if ($now > $this->data['expires']) {
+ $this->delete($key);
+
+ return null;
+ }
+ $item = new Item($key, $this->data['data'], $this->data['ttl'], defined('CACHE_SECRET') ? CACHE_SECRET : '');
+ $item->setCreatedAt($this->data['created_at']);
+
+ return $item;
+ }
+ /**
+ * Sets the path to the folder which is used to create cache files.
+ *
+ * @param string $path
+ */
+ public function setPath(string $path) {
+ $this->cacheDir = $path;
+ }
+ private function initData(string $key) {
+ $filePath = $this->cacheDir.md5($key).'.cache';
+
+ if (!file_exists($filePath)) {
+ $this->data = [
+ 'expires' => 0,
+ 'ttl' => 0,
+ 'data' => null,
+ 'created_at' => 0,
+ 'key' => ''
+ ];
+
+ return ;
+ }
+
+ $this->data = unserialize(file_get_contents($filePath));
+ }
+}
diff --git a/webfiori/framework/cache/Item.php b/webfiori/framework/cache/Item.php
new file mode 100644
index 000000000..c8c206671
--- /dev/null
+++ b/webfiori/framework/cache/Item.php
@@ -0,0 +1,196 @@
+setKey($key);
+ $this->setTTL($ttl);
+ $this->setData($data);
+ $this->setSecret($secretKey);
+ $this->setCreatedAt(time());
+ }
+ /**
+ * Generates a cryptographic secure key.
+ *
+ * The generated key can be used to encrypt sensitive data.
+ *
+ * @return string
+ */
+ public static function generateKey() : string {
+ return bin2hex(random_bytes(32));
+ }
+ /**
+ * Returns the time at which the item was created at.
+ *
+ * The value returned by the method is Unix timestamp.
+ *
+ * @return int An integer that represents Unix timestamp in seconds.
+ */
+ public function getCreatedAt() : int {
+ return $this->createdAt;
+ }
+ /**
+ * Returns the data of cache item.
+ *
+ * @return mixed
+ */
+ public function getData() {
+ return $this->data;
+ }
+ /**
+ * Returns cache item data after performing decryption on it.
+ *
+ * @return mixed
+ */
+ public function getDataDecrypted() {
+ return unserialize($this->decrypt($this->getData()));
+ }
+ /**
+ * Returns cache data after performing encryption on it.
+ *
+ * Note that the raw data must be
+ *
+ * @return string
+ */
+ public function getDataEncrypted() : string {
+ return $this->encrypt(serialize($this->getData()));
+ }
+ /**
+ * Returns the time at which cache item will expire as Unix timestamp.
+ *
+ * The method will add the time at which the item was created at to TTL and
+ * return the value.
+ *
+ * @return int The time at which cache item will expire as Unix timestamp.
+ */
+ public function getExpiryTime() : int {
+ return $this->getCreatedAt() + $this->getTTL();
+ }
+ /**
+ * Gets the key of the item.
+ *
+ * The key acts as a unique identifier for cache items.
+ *
+ * @return string A string that represents the key.
+ */
+ public function getKey() : string {
+ return $this->key;
+ }
+ /**
+ * Returns the value of the key which is used in encrypting cache data.
+ *
+ * @return string The value of the key which is used in encrypting cache data.
+ * Default return value is empty string.
+ */
+ public function getSecret() : string {
+ return $this->secretKey;
+ }
+ /**
+ * Returns the duration at which the item will be kept in cache in seconds.
+ *
+ * @return int The duration at which the item will be kept in cache in seconds.
+ */
+ public function getTTL() : int {
+ return $this->timeToLive;
+ }
+ /**
+ * Sets the time at which the item was created at.
+ *
+ * @param int $time An integer that represents Unix timestamp in seconds.
+ * Must be a positive value.
+ */
+ public function setCreatedAt(int $time) {
+ if ($time > 0) {
+ $this->createdAt = $time;
+ }
+ }
+ /**
+ * Sets the data of the item.
+ *
+ * This represents the data that will be stored or retrieved.
+ *
+ * @param mixed $data
+ */
+ public function setData($data) {
+ $this->data = $data;
+ }
+ /**
+ * Sets the key of the item.
+ *
+ * The key acts as a unique identifier for cache items.
+ *
+ * @param string $key A string that represents the key.
+ */
+ public function setKey(string $key) {
+ $this->key = $key;
+ }
+ /**
+ * Sets the value of the key which is used in encrypting cache data.
+ *
+ * @param string $secret A cryptographic key which is used to encrypt
+ * cache data. To generate one, the method Item::generateKey() can be used.
+ */
+ public function setSecret(string $secret) {
+ $this->secretKey = $secret;
+ }
+ /**
+ * Sets the duration at which the item will be kept in cache in seconds.
+ *
+ * @param int $ttl Time-to-live of the item in cache.
+ */
+ public function setTTL(int $ttl) {
+ if ($ttl >= 0) {
+ $this->timeToLive = $ttl;
+ }
+ }
+
+
+ private function decrypt($data) {
+ // decode > extract iv > decrypt
+ $decodedData = base64_decode($data);
+ $ivLength = openssl_cipher_iv_length('aes-256-cbc');
+ $iv = substr($decodedData, 0, $ivLength);
+ $encryptedData = substr($decodedData, $ivLength);
+ $decrypted = openssl_decrypt($encryptedData, 'aes-256-cbc', $this->getSecret(), 0, $iv);
+
+ return $decrypted;
+ }
+ private function encrypt($data) {
+ // iv > encrypt > append iv > encode
+ $iv = random_bytes(openssl_cipher_iv_length('aes-256-cbc'));
+ $encryptedData = openssl_encrypt($data, 'aes-256-cbc', $this->getSecret(), 0, $iv);
+ $encoded = base64_encode($iv.$encryptedData);
+
+ return $encoded;
+ }
+}
diff --git a/webfiori/framework/cache/Storage.php b/webfiori/framework/cache/Storage.php
new file mode 100644
index 000000000..e9a870632
--- /dev/null
+++ b/webfiori/framework/cache/Storage.php
@@ -0,0 +1,81 @@
+
+ *
key
+ * data
+ * time to live
+ * creation time
+ *
+ *
+ * @param Item $item An item that will be added to the cache.
+ */
+ public function cache(Item $item);
+ /**
+ * Removes an item from the cache.
+ *
+ * @param string $key The key of the item.
+ */
+ public function delete(string $key);
+ /**
+ * Removes all cached items.
+ *
+ * This method must be implemented in a way that it removes all cache items
+ * regardless of expiry time.
+ */
+ public function flush();
+ /**
+ * Checks if an item exist in the cache.
+ *
+ * This method must be implemented in a way that it returns true if given
+ * key exist in the cache and not yet expired.
+ *
+ * @param string $key The value of item key.
+ *
+ * @return bool Returns true if given
+ * key exist in the cache and not yet expired.
+ */
+ public function has(string $key) : bool;
+ /**
+ * Reads and returns the data stored in cache item given its key.
+ *
+ * This method should be implemented in a way that it reads cache item
+ * as an object of type 'Item'. Then it should do a check if the cached
+ * item is expired or not. If not expired, its data is returned. Other than
+ * that, null should be returned.
+ *
+ * @param string $key The key of the item.
+ *
+ * @return mixed|null If cache item is not expired, its data is returned. Other than
+ * that, null is returned.
+ */
+ public function read(string $key);
+ /**
+ * Reads cache item as an object given its key.
+ *
+ * @param string $key The unique identifier of the item.
+ *
+ * @return Item|null If cache item exist and is not expired,
+ * an object of type 'Item' should be returned. Other than
+ * that, null is returned.
+ */
+ public function readItem(string $key);
+}
diff --git a/webfiori/framework/cli/commands/UpdateSettingsCommand.php b/webfiori/framework/cli/commands/UpdateSettingsCommand.php
index be850b020..b1345151d 100644
--- a/webfiori/framework/cli/commands/UpdateSettingsCommand.php
+++ b/webfiori/framework/cli/commands/UpdateSettingsCommand.php
@@ -33,7 +33,7 @@ public function __construct() {
.'Possible values are: version, app-name, scheduler-pass, page-title, '
.'page-description, primary-lang, title-sep, home-page, theme,'
.'admin-theme.', true),
- ], 'Update application settings which are stored in the class "AppConfig".');
+ ], 'Update application settings which are stored in specific configuration driver.');
}
public function exec() : int {
$options = [];
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