BladeOne.php
4 days ago
BladeOneCache.php
4 days ago
BladeOneCacheRedis.php
2 years ago
BladeOneCustom.php
4 days ago
bladeonecli
2 years ago
BladeOneCache.php
317 lines
| 1 | <?php |
| 2 | |
| 3 | /** @noinspection UnknownInspectionInspection */ |
| 4 | /** @noinspection TypeUnsafeComparisonInspection */ |
| 5 | namespace IAWPSCOPED\eftec\bladeone; |
| 6 | |
| 7 | use Exception; |
| 8 | use JsonException; |
| 9 | use function fclose; |
| 10 | use function file_put_contents; |
| 11 | use function filemtime; |
| 12 | use function filesize; |
| 13 | use function fopen; |
| 14 | use function fwrite; |
| 15 | use function is_array; |
| 16 | use function is_object; |
| 17 | use function ob_get_contents; |
| 18 | use function print_r; |
| 19 | use function strlen; |
| 20 | use function substr; |
| 21 | use function time; |
| 22 | /** |
| 23 | * trait BladeOneCache |
| 24 | * Copyright (c) 2016 Jorge Patricio Castro Castillo MIT License. Don't delete this comment, its part of the license. |
| 25 | * Extends the tags of the class BladeOne. Its optional |
| 26 | * It adds the next tags to the template |
| 27 | * <code> |
| 28 | * @ cache([cacheid],[duration=86400]). The id is optional. The duration of the cache is in seconds |
| 29 | * // content here |
| 30 | * @ endcache() |
| 31 | * </code> |
| 32 | * It also adds a new function (optional) to the business or logic layer |
| 33 | * <code> |
| 34 | * if ($blade->cacheExpired('hellocache',1,5)) { //'helloonecache' =template, =1 id cache, 5=duration (seconds) |
| 35 | * // cache expired, so we should do some stuff (such as read from the database) |
| 36 | * } |
| 37 | * </code> |
| 38 | * |
| 39 | * @package BladeOneCache |
| 40 | * @version 3.43.1 2025-09-03 |
| 41 | * @link https://github.com/EFTEC/BladeOne |
| 42 | * @author Jorge Patricio Castro Castillo <jcastro arroba eftec dot cl> |
| 43 | * @internal |
| 44 | */ |
| 45 | trait BladeOneCache |
| 46 | { |
| 47 | protected int $curCacheId = 0; |
| 48 | protected int $curCacheDuration = 0; |
| 49 | protected int $curCachePosition = 0; |
| 50 | protected bool $cacheRunning = \false; |
| 51 | protected bool $cachePageRunning = \false; |
| 52 | /** @var string|null where the log file will be stored */ |
| 53 | protected ?string $cacheLog; |
| 54 | /** |
| 55 | * @var array avoids comparing the file different times. It also avoids race conditions. |
| 56 | */ |
| 57 | private array $cacheExpired = []; |
| 58 | /** |
| 59 | * @var string=['get','post','getpost','request',null][$i] |
| 60 | */ |
| 61 | private string $cacheStrategy; |
| 62 | /** @var array|null */ |
| 63 | private ?array $cacheStrategyIndex; |
| 64 | /** |
| 65 | * @return null|string $cacheStrategy=['get','post','getpost','request',null][$i] |
| 66 | */ |
| 67 | public function getCacheStrategy() : ?string |
| 68 | { |
| 69 | return $this->cacheStrategy; |
| 70 | } |
| 71 | /** |
| 72 | * It sets the cache log. If not cache log then it does not generate a log file<br> |
| 73 | * The cache log stores each time a template is created or expired.<br> |
| 74 | * |
| 75 | * @param string $file |
| 76 | */ |
| 77 | public function setCacheLog($file) : void |
| 78 | { |
| 79 | $this->cacheLog = $file; |
| 80 | } |
| 81 | /** |
| 82 | * @throws JsonException |
| 83 | */ |
| 84 | public function writeCacheLog($txt, $nivel) : void |
| 85 | { |
| 86 | if (!$this->cacheLog) { |
| 87 | return; |
| 88 | // if there is not a file assigned then it skips saving. |
| 89 | } |
| 90 | $fz = @filesize($this->cacheLog); |
| 91 | if (is_object($txt) || is_array($txt)) { |
| 92 | $txt = print_r($txt, \true); |
| 93 | } |
| 94 | // Rewrite file if more than 100000 bytes |
| 95 | $mode = $fz > 100000 ? 'w' : 'a'; |
| 96 | $fp = fopen($this->cacheLog, $mode); |
| 97 | if ($fp === \false) { |
| 98 | return; |
| 99 | } |
| 100 | switch ($nivel) { |
| 101 | case 1: |
| 102 | $txtNivel = 'expired'; |
| 103 | break; |
| 104 | case 2: |
| 105 | $txtNivel = 'new'; |
| 106 | break; |
| 107 | default: |
| 108 | $txtNivel = 'other'; |
| 109 | } |
| 110 | $txtarg = \json_encode($this->cacheUniqueGUID(\false), \JSON_THROW_ON_ERROR); |
| 111 | fwrite($fp, \date('c') . "\t{$txt}\t{$txtNivel}\t{$txtarg}\n"); |
| 112 | fclose($fp); |
| 113 | } |
| 114 | /** |
| 115 | * It sets the strategy of the cache page. |
| 116 | * |
| 117 | * @param null|string $cacheStrategy =['get','post','getpost','request',null][$i] |
| 118 | * @param array|null $index if null then it reads all indexes. If not, it reads an indexes. |
| 119 | */ |
| 120 | public function setCacheStrategy($cacheStrategy, $index = null) : void |
| 121 | { |
| 122 | $this->cacheStrategy = $cacheStrategy; |
| 123 | $this->cacheStrategyIndex = $index; |
| 124 | } |
| 125 | /** |
| 126 | * It obtains a unique GUID based in:<br> |
| 127 | * **get**= parameters from the url<br> |
| 128 | * **post**= parameters sends via post<br> |
| 129 | * **getpost** = a mix between get and post<br> |
| 130 | * **request** = get, post and cookies (including sessions)<br> |
| 131 | * MD5 could generate colisions (2^64 = 18,446,744,073,709,551,616) but the end hash is the sum of the hash of |
| 132 | * the page + this GUID. |
| 133 | * |
| 134 | * @param bool $serialize if true then it serializes using md5 |
| 135 | * @return string|null |
| 136 | */ |
| 137 | private function cacheUniqueGUID($serialize = \true) : ?string |
| 138 | { |
| 139 | switch ($this->cacheStrategy) { |
| 140 | case 'get': |
| 141 | $r = $_GET; |
| 142 | break; |
| 143 | case 'post': |
| 144 | $r = $_POST; |
| 145 | break; |
| 146 | case 'getpost': |
| 147 | $arr = \array_merge($_GET, $_POST); |
| 148 | $r = $arr; |
| 149 | break; |
| 150 | case 'request': |
| 151 | $r = $_REQUEST; |
| 152 | break; |
| 153 | default: |
| 154 | $r = null; |
| 155 | } |
| 156 | if ($this->cacheStrategyIndex === null || !is_array($r)) { |
| 157 | $r = \serialize($r); |
| 158 | } else { |
| 159 | $copy = \array_filter($r, function ($key) { |
| 160 | return \in_array($key, $this->cacheStrategyIndex, \true); |
| 161 | }, \ARRAY_FILTER_USE_KEY); |
| 162 | $r = \serialize($copy); |
| 163 | } |
| 164 | return $serialize === \true ? \md5($r) : $r; |
| 165 | } |
| 166 | public function compileCache($expression) : string |
| 167 | { |
| 168 | // get id of template |
| 169 | // if the file exists then |
| 170 | // compare date. |
| 171 | // if the date is too old then re-save. |
| 172 | // else |
| 173 | // save for the first time. |
| 174 | return $this->phpTag . "echo \$this->cacheStart{$expression}; if(!\$this->cacheRunning) { ?>"; |
| 175 | } |
| 176 | public function compileEndCache($expression) : string |
| 177 | { |
| 178 | return $this->phpTag . "} // if cacheRunning\necho \$this->cacheEnd{$expression}; ?>"; |
| 179 | } |
| 180 | /** |
| 181 | * It gets the filename of the compiled file (cached). If cache is not enabled, then it |
| 182 | * returns the regular file. |
| 183 | * |
| 184 | * @param string $view |
| 185 | * @return string The full filename |
| 186 | */ |
| 187 | private function getCompiledFileCache($view) : string |
| 188 | { |
| 189 | $id = $this->cacheUniqueGUID(); |
| 190 | if ($id !== null) { |
| 191 | return \str_replace($this->compileExtension, '_cache' . $id . $this->compileExtension, $this->getCompiledFile($view)); |
| 192 | } |
| 193 | return $this->getCompiledFile($view); |
| 194 | } |
| 195 | /** |
| 196 | * run the blade engine. It returns the result of the code. |
| 197 | * |
| 198 | * @param string $view The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.blade") |
| 199 | * @param array $variables An associative arrays with the values to display. |
| 200 | * @param int $ttl time to live (in second). |
| 201 | * @return string |
| 202 | * @throws Exception |
| 203 | */ |
| 204 | public function runCache($view, $variables = [], $ttl = 86400) : string |
| 205 | { |
| 206 | $this->cachePageRunning = \true; |
| 207 | $cacheStatus = $this->cachePageExpired($view, $ttl); |
| 208 | if ($cacheStatus !== 0) { |
| 209 | $this->writeCacheLog($view, $cacheStatus); |
| 210 | $this->cacheStart('_page_', $ttl); |
| 211 | $content = $this->run($view, $variables); |
| 212 | // if no cache, then it runs normally. |
| 213 | $this->fileName = $view; |
| 214 | // sometimes the filename is replaced (using the tag include), so we restore it |
| 215 | $this->cacheEnd($content); |
| 216 | // and it stores as a cache paged. |
| 217 | } else { |
| 218 | $content = $this->getFile($this->getCompiledFileCache($view)); |
| 219 | } |
| 220 | $this->cachePageRunning = \false; |
| 221 | return $content; |
| 222 | } |
| 223 | /** |
| 224 | * Returns true if the block cache expired (or doesn't exist), otherwise false. |
| 225 | * |
| 226 | * @param string $templateName name of the template to use (such hello for template hello.blade.php) |
| 227 | * @param string $id (id of cache, optional, if not id then it adds automatically a number) |
| 228 | * @param int $cacheDuration (duration of the cache in seconds) |
| 229 | * @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache file (if any) |
| 230 | */ |
| 231 | public function cacheExpired($templateName, $id, $cacheDuration) : int |
| 232 | { |
| 233 | if ($this->getMode() & 1) { |
| 234 | return 2; |
| 235 | // forced mode, hence it always expires. (fast mode is ignored). |
| 236 | } |
| 237 | $compiledFile = $this->getCompiledFile($templateName) . '_cache' . $id; |
| 238 | return $this->cacheExpiredInt($compiledFile, $cacheDuration); |
| 239 | } |
| 240 | /** |
| 241 | * It returns true if the whole page expired. |
| 242 | * |
| 243 | * @param string $templateName |
| 244 | * @param int $cacheDuration is seconds. |
| 245 | * @return int 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any) |
| 246 | */ |
| 247 | public function cachePageExpired($templateName, $cacheDuration) : int |
| 248 | { |
| 249 | if ($this->getMode() & 1) { |
| 250 | return 2; |
| 251 | // forced mode, hence it always expires. (fast mode is ignored). |
| 252 | } |
| 253 | $compiledFile = $this->getCompiledFileCache($templateName); |
| 254 | return $this->CacheExpiredInt($compiledFile, $cacheDuration); |
| 255 | } |
| 256 | /** |
| 257 | * This method is used by cacheExpired() and cachePageExpired() |
| 258 | * |
| 259 | * @param string $compiledFile |
| 260 | * @param int $cacheDuration is seconds. |
| 261 | * @return int|mixed 0=cache exists, 1= cache expired, 2=not exists, string= the cache content (if any) |
| 262 | */ |
| 263 | private function cacheExpiredInt($compiledFile, $cacheDuration) |
| 264 | { |
| 265 | if (isset($this->cacheExpired[$compiledFile])) { |
| 266 | // if the information is already in the array then returns it. |
| 267 | return $this->cacheExpired[$compiledFile]; |
| 268 | } |
| 269 | $date = @filemtime($compiledFile); |
| 270 | if ($date) { |
| 271 | if ($date + $cacheDuration < time()) { |
| 272 | $this->cacheExpired[$compiledFile] = 1; |
| 273 | return 2; |
| 274 | // time-out. |
| 275 | } |
| 276 | } else { |
| 277 | $this->cacheExpired[$compiledFile] = 2; |
| 278 | return 1; |
| 279 | // no file |
| 280 | } |
| 281 | $this->cacheExpired[$compiledFile] = 0; |
| 282 | return 0; |
| 283 | // cache active. |
| 284 | } |
| 285 | public function cacheStart($id = '', $cacheDuration = 86400) : void |
| 286 | { |
| 287 | $this->curCacheId = $id == '' ? $this->curCacheId + 1 : $id; |
| 288 | $this->curCacheDuration = $cacheDuration; |
| 289 | $this->curCachePosition = strlen(ob_get_contents()); |
| 290 | if ($this->cachePageRunning) { |
| 291 | $compiledFile = $this->getCompiledFileCache($this->fileName); |
| 292 | } else { |
| 293 | $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; |
| 294 | } |
| 295 | if ($this->cacheExpired('', $id, $cacheDuration) !== 0) { |
| 296 | $this->cacheRunning = \false; |
| 297 | } else { |
| 298 | $this->cacheRunning = \true; |
| 299 | $content = $this->getFile($compiledFile); |
| 300 | echo $content; |
| 301 | } |
| 302 | } |
| 303 | public function cacheEnd($txt = null) : void |
| 304 | { |
| 305 | if (!$this->cacheRunning) { |
| 306 | $txt = $txt ?? substr(ob_get_contents(), $this->curCachePosition); |
| 307 | if ($this->cachePageRunning) { |
| 308 | $compiledFile = $this->getCompiledFileCache($this->fileName); |
| 309 | } else { |
| 310 | $compiledFile = $this->getCompiledFile() . '_cache' . $this->curCacheId; |
| 311 | } |
| 312 | file_put_contents($compiledFile, $txt); |
| 313 | } |
| 314 | $this->cacheRunning = \false; |
| 315 | } |
| 316 | } |
| 317 |