CompiledBase.php 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. <?php
  2. /**
  3. * @package Grav\Common\Config
  4. *
  5. * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Config;
  9. use BadMethodCallException;
  10. use Exception;
  11. use RocketTheme\Toolbox\File\PhpFile;
  12. use RuntimeException;
  13. use function get_class;
  14. use function is_array;
  15. /**
  16. * Class CompiledBase
  17. * @package Grav\Common\Config
  18. */
  19. abstract class CompiledBase
  20. {
  21. /** @var int Version number for the compiled file. */
  22. public $version = 1;
  23. /** @var string Filename (base name) of the compiled configuration. */
  24. public $name;
  25. /** @var string|bool Configuration checksum. */
  26. public $checksum;
  27. /** @var int Timestamp of compiled configuration */
  28. public $timestamp = 0;
  29. /** @var string Cache folder to be used. */
  30. protected $cacheFolder;
  31. /** @var array List of files to load. */
  32. protected $files;
  33. /** @var string */
  34. protected $path;
  35. /** @var mixed Configuration object. */
  36. protected $object;
  37. /**
  38. * @param string $cacheFolder Cache folder to be used.
  39. * @param array $files List of files as returned from ConfigFileFinder class.
  40. * @param string $path Base path for the file list.
  41. * @throws BadMethodCallException
  42. */
  43. public function __construct($cacheFolder, array $files, $path)
  44. {
  45. if (!$cacheFolder) {
  46. throw new BadMethodCallException('Cache folder not defined.');
  47. }
  48. $this->path = $path ? rtrim($path, '\\/') . '/' : '';
  49. $this->cacheFolder = $cacheFolder;
  50. $this->files = $files;
  51. }
  52. /**
  53. * Get filename for the compiled PHP file.
  54. *
  55. * @param string|null $name
  56. * @return $this
  57. */
  58. public function name($name = null)
  59. {
  60. if (!$this->name) {
  61. $this->name = $name ?: md5(json_encode(array_keys($this->files)));
  62. }
  63. return $this;
  64. }
  65. /**
  66. * Function gets called when cached configuration is saved.
  67. *
  68. * @return void
  69. */
  70. public function modified()
  71. {
  72. }
  73. /**
  74. * Get timestamp of compiled configuration
  75. *
  76. * @return int Timestamp of compiled configuration
  77. */
  78. public function timestamp()
  79. {
  80. return $this->timestamp ?: time();
  81. }
  82. /**
  83. * Load the configuration.
  84. *
  85. * @return mixed
  86. */
  87. public function load()
  88. {
  89. if ($this->object) {
  90. return $this->object;
  91. }
  92. $filename = $this->createFilename();
  93. if (!$this->loadCompiledFile($filename) && $this->loadFiles()) {
  94. $this->saveCompiledFile($filename);
  95. }
  96. return $this->object;
  97. }
  98. /**
  99. * Returns checksum from the configuration files.
  100. *
  101. * You can set $this->checksum = false to disable this check.
  102. *
  103. * @return bool|string
  104. */
  105. public function checksum()
  106. {
  107. if (null === $this->checksum) {
  108. $this->checksum = md5(json_encode($this->files) . $this->version);
  109. }
  110. return $this->checksum;
  111. }
  112. /**
  113. * @return string
  114. */
  115. protected function createFilename()
  116. {
  117. return "{$this->cacheFolder}/{$this->name()->name}.php";
  118. }
  119. /**
  120. * Create configuration object.
  121. *
  122. * @param array $data
  123. * @return void
  124. */
  125. abstract protected function createObject(array $data = []);
  126. /**
  127. * Finalize configuration object.
  128. *
  129. * @return void
  130. */
  131. abstract protected function finalizeObject();
  132. /**
  133. * Load single configuration file and append it to the correct position.
  134. *
  135. * @param string $name Name of the position.
  136. * @param string|string[] $filename File(s) to be loaded.
  137. * @return void
  138. */
  139. abstract protected function loadFile($name, $filename);
  140. /**
  141. * Load and join all configuration files.
  142. *
  143. * @return bool
  144. * @internal
  145. */
  146. protected function loadFiles()
  147. {
  148. $this->createObject();
  149. $list = array_reverse($this->files);
  150. foreach ($list as $files) {
  151. foreach ($files as $name => $item) {
  152. $this->loadFile($name, $this->path . $item['file']);
  153. }
  154. }
  155. $this->finalizeObject();
  156. return true;
  157. }
  158. /**
  159. * Load compiled file.
  160. *
  161. * @param string $filename
  162. * @return bool
  163. * @internal
  164. */
  165. protected function loadCompiledFile($filename)
  166. {
  167. if (!file_exists($filename)) {
  168. return false;
  169. }
  170. $cache = include $filename;
  171. if (!is_array($cache)
  172. || !isset($cache['checksum'], $cache['data'], $cache['@class'])
  173. || $cache['@class'] !== get_class($this)
  174. ) {
  175. return false;
  176. }
  177. // Load real file if cache isn't up to date (or is invalid).
  178. if ($cache['checksum'] !== $this->checksum()) {
  179. return false;
  180. }
  181. $this->createObject($cache['data']);
  182. $this->timestamp = $cache['timestamp'] ?? 0;
  183. $this->finalizeObject();
  184. return true;
  185. }
  186. /**
  187. * Save compiled file.
  188. *
  189. * @param string $filename
  190. * @return void
  191. * @throws RuntimeException
  192. * @internal
  193. */
  194. protected function saveCompiledFile($filename)
  195. {
  196. $file = PhpFile::instance($filename);
  197. // Attempt to lock the file for writing.
  198. try {
  199. $file->lock(false);
  200. } catch (Exception $e) {
  201. // Another process has locked the file; we will check this in a bit.
  202. }
  203. if ($file->locked() === false) {
  204. // File was already locked by another process.
  205. return;
  206. }
  207. $cache = [
  208. '@class' => get_class($this),
  209. 'timestamp' => time(),
  210. 'checksum' => $this->checksum(),
  211. 'files' => $this->files,
  212. 'data' => $this->getState()
  213. ];
  214. $file->save($cache);
  215. $file->unlock();
  216. $file->free();
  217. $this->modified();
  218. }
  219. /**
  220. * @return array
  221. */
  222. protected function getState()
  223. {
  224. return $this->object->toArray();
  225. }
  226. }