123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195 |
- <?php
- /**
- * @package Grav\Common\File
- *
- * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Common\File;
- use Exception;
- use Grav\Common\Debugger;
- use Grav\Common\Grav;
- use Grav\Common\Utils;
- use RocketTheme\Toolbox\File\PhpFile;
- use RuntimeException;
- use Throwable;
- use function function_exists;
- use function get_class;
- /**
- * Trait CompiledFile
- * @package Grav\Common\File
- */
- trait CompiledFile
- {
- /**
- * Get/set parsed file contents.
- *
- * @param mixed $var
- * @return array
- */
- public function content($var = null)
- {
- try {
- $filename = $this->filename;
- // If nothing has been loaded, attempt to get pre-compiled version of the file first.
- if ($var === null && $this->raw === null && $this->content === null) {
- $key = md5($filename);
- $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
- $modified = $this->modified();
- if (!$modified) {
- try {
- return $this->decode($this->raw());
- } catch (Throwable $e) {
- // If the compiled file is broken, we can safely ignore the error and continue.
- }
- }
- $class = get_class($this);
- $size = filesize($filename);
- $cache = $file->exists() ? $file->content() : null;
- // Load real file if cache isn't up to date (or is invalid).
- if (!isset($cache['@class'])
- || $cache['@class'] !== $class
- || $cache['modified'] !== $modified
- || ($cache['size'] ?? null) !== $size
- || $cache['filename'] !== $filename
- ) {
- // Attempt to lock the file for writing.
- try {
- $locked = $file->lock(false);
- } catch (Exception $e) {
- $locked = false;
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
- }
- // Decode RAW file into compiled array.
- $data = (array)$this->decode($this->raw());
- $cache = [
- '@class' => $class,
- 'filename' => $filename,
- 'modified' => $modified,
- 'size' => $size,
- 'data' => $data
- ];
- // If compiled file wasn't already locked by another process, save it.
- if ($locked) {
- $file->save($cache);
- $file->unlock();
- // Compile cached file into bytecode cache
- if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
- $lockName = $file->filename();
- // Silence error if function exists, but is restricted.
- @opcache_invalidate($lockName, true);
- @opcache_compile_file($lockName);
- }
- }
- }
- $file->free();
- $this->content = $cache['data'];
- }
- } catch (Exception $e) {
- throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($filename), $e->getMessage()), 500, $e);
- }
- return parent::content($var);
- }
- /**
- * Save file.
- *
- * @param mixed $data Optional data to be saved, usually array.
- * @return void
- * @throws RuntimeException
- */
- public function save($data = null)
- {
- // Make sure that the cache file is always up to date!
- $key = md5($this->filename);
- $file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
- try {
- $locked = $file->lock();
- } catch (Exception $e) {
- $locked = false;
- /** @var Debugger $debugger */
- $debugger = Grav::instance()['debugger'];
- $debugger->addMessage(sprintf('%s(): Cannot obtain a lock for compiling cache file for %s: %s', __METHOD__, $this->filename, $e->getMessage()), 'warning');
- }
- parent::save($data);
- if ($locked) {
- $modified = $this->modified();
- $filename = $this->filename;
- $class = get_class($this);
- $size = filesize($filename);
- // windows doesn't play nicely with this as it can't read when locked
- if (!Utils::isWindows()) {
- // Reload data from the filesystem. This ensures that we always cache the correct data (see issue #2282).
- $this->raw = $this->content = null;
- $data = (array)$this->decode($this->raw());
- }
- // Decode data into compiled array.
- $cache = [
- '@class' => $class,
- 'filename' => $filename,
- 'modified' => $modified,
- 'size' => $size,
- 'data' => $data
- ];
- $file->save($cache);
- $file->unlock();
- // Compile cached file into bytecode cache
- if (function_exists('opcache_invalidate') && filter_var(ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
- $lockName = $file->filename();
- // Silence error if function exists, but is restricted.
- @opcache_invalidate($lockName, true);
- @opcache_compile_file($lockName);
- }
- }
- }
- /**
- * Serialize file.
- *
- * @return array
- */
- public function __sleep()
- {
- return [
- 'filename',
- 'extension',
- 'raw',
- 'content',
- 'settings'
- ];
- }
- /**
- * Unserialize file.
- */
- public function __wakeup()
- {
- if (!isset(static::$instances[$this->filename])) {
- static::$instances[$this->filename] = $this;
- }
- }
- }
|