CompiledBase.php 5.8 KB

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