|
@@ -3,13 +3,15 @@
|
|
/**
|
|
/**
|
|
* @package Grav\Common\File
|
|
* @package Grav\Common\File
|
|
*
|
|
*
|
|
- * @copyright Copyright (c) 2015 - 2022 Trilby Media, LLC. All rights reserved.
|
|
|
|
|
|
+ * @copyright Copyright (c) 2015 - 2023 Trilby Media, LLC. All rights reserved.
|
|
* @license MIT License; see LICENSE file for details.
|
|
* @license MIT License; see LICENSE file for details.
|
|
*/
|
|
*/
|
|
|
|
|
|
namespace Grav\Common\File;
|
|
namespace Grav\Common\File;
|
|
|
|
|
|
use Exception;
|
|
use Exception;
|
|
|
|
+use Grav\Common\Debugger;
|
|
|
|
+use Grav\Common\Grav;
|
|
use Grav\Common\Utils;
|
|
use Grav\Common\Utils;
|
|
use RocketTheme\Toolbox\File\PhpFile;
|
|
use RocketTheme\Toolbox\File\PhpFile;
|
|
use RuntimeException;
|
|
use RuntimeException;
|
|
@@ -32,9 +34,10 @@ trait CompiledFile
|
|
public function content($var = null)
|
|
public function content($var = null)
|
|
{
|
|
{
|
|
try {
|
|
try {
|
|
|
|
+ $filename = $this->filename;
|
|
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
|
// If nothing has been loaded, attempt to get pre-compiled version of the file first.
|
|
if ($var === null && $this->raw === null && $this->content === null) {
|
|
if ($var === null && $this->raw === null && $this->content === null) {
|
|
- $key = md5($this->filename);
|
|
|
|
|
|
+ $key = md5($filename);
|
|
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
|
$file = PhpFile::instance(CACHE_DIR . "compiled/files/{$key}{$this->extension}.php");
|
|
|
|
|
|
$modified = $this->modified();
|
|
$modified = $this->modified();
|
|
@@ -48,39 +51,49 @@ trait CompiledFile
|
|
|
|
|
|
$class = get_class($this);
|
|
$class = get_class($this);
|
|
|
|
|
|
|
|
+ $size = filesize($filename);
|
|
$cache = $file->exists() ? $file->content() : null;
|
|
$cache = $file->exists() ? $file->content() : null;
|
|
|
|
|
|
// Load real file if cache isn't up to date (or is invalid).
|
|
// Load real file if cache isn't up to date (or is invalid).
|
|
if (!isset($cache['@class'])
|
|
if (!isset($cache['@class'])
|
|
|| $cache['@class'] !== $class
|
|
|| $cache['@class'] !== $class
|
|
|| $cache['modified'] !== $modified
|
|
|| $cache['modified'] !== $modified
|
|
- || $cache['filename'] !== $this->filename
|
|
|
|
|
|
+ || ($cache['size'] ?? null) !== $size
|
|
|
|
+ || $cache['filename'] !== $filename
|
|
) {
|
|
) {
|
|
// Attempt to lock the file for writing.
|
|
// Attempt to lock the file for writing.
|
|
try {
|
|
try {
|
|
- $file->lock(false);
|
|
|
|
|
|
+ $locked = $file->lock(false);
|
|
} catch (Exception $e) {
|
|
} catch (Exception $e) {
|
|
- // Another process has locked the file; we will check this in a bit.
|
|
|
|
|
|
+ $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.
|
|
// Decode RAW file into compiled array.
|
|
$data = (array)$this->decode($this->raw());
|
|
$data = (array)$this->decode($this->raw());
|
|
$cache = [
|
|
$cache = [
|
|
'@class' => $class,
|
|
'@class' => $class,
|
|
- 'filename' => $this->filename,
|
|
|
|
|
|
+ 'filename' => $filename,
|
|
'modified' => $modified,
|
|
'modified' => $modified,
|
|
|
|
+ 'size' => $size,
|
|
'data' => $data
|
|
'data' => $data
|
|
];
|
|
];
|
|
|
|
|
|
// If compiled file wasn't already locked by another process, save it.
|
|
// If compiled file wasn't already locked by another process, save it.
|
|
- if ($file->locked() !== false) {
|
|
|
|
|
|
+ if ($locked) {
|
|
$file->save($cache);
|
|
$file->save($cache);
|
|
$file->unlock();
|
|
$file->unlock();
|
|
|
|
|
|
// Compile cached file into bytecode cache
|
|
// Compile cached file into bytecode cache
|
|
- if (function_exists('opcache_invalidate')) {
|
|
|
|
|
|
+ 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.
|
|
// Silence error if function exists, but is restricted.
|
|
- @opcache_invalidate($file->filename(), true);
|
|
|
|
|
|
+ @opcache_invalidate($lockName, true);
|
|
|
|
+ @opcache_compile_file($lockName);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
@@ -89,12 +102,71 @@ trait CompiledFile
|
|
$this->content = $cache['data'];
|
|
$this->content = $cache['data'];
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
} catch (Exception $e) {
|
|
- throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($this->filename), $e->getMessage()), 500, $e);
|
|
|
|
|
|
+ throw new RuntimeException(sprintf('Failed to read %s: %s', Utils::basename($filename), $e->getMessage()), 500, $e);
|
|
}
|
|
}
|
|
|
|
|
|
return parent::content($var);
|
|
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.
|
|
* Serialize file.
|
|
*
|
|
*
|