2018-02-19 06:27:39 +01:00
|
|
|
<?php
|
2021-12-15 04:58:32 +01:00
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
namespace Psalm\Internal\Provider;
|
2018-02-19 06:27:39 +01:00
|
|
|
|
2019-07-19 05:08:54 +02:00
|
|
|
use Psalm\Config;
|
2022-08-05 11:05:08 +02:00
|
|
|
use Psalm\Internal\Provider\Providers;
|
2019-07-19 05:08:54 +02:00
|
|
|
use Psalm\Storage\ClassLikeStorage;
|
2022-08-18 13:39:46 +02:00
|
|
|
use RuntimeException;
|
2021-12-03 21:40:18 +01:00
|
|
|
use UnexpectedValueException;
|
2021-06-08 04:55:21 +02:00
|
|
|
|
2019-07-19 05:08:54 +02:00
|
|
|
use function array_merge;
|
2021-06-08 04:55:21 +02:00
|
|
|
use function dirname;
|
2019-06-26 22:52:29 +02:00
|
|
|
use function file_exists;
|
2019-07-19 05:08:54 +02:00
|
|
|
use function file_put_contents;
|
2021-06-08 04:55:21 +02:00
|
|
|
use function filemtime;
|
2019-07-19 05:08:54 +02:00
|
|
|
use function get_class;
|
2022-05-22 23:01:08 +02:00
|
|
|
use function hash;
|
2021-06-08 04:55:21 +02:00
|
|
|
use function igbinary_serialize;
|
2019-07-19 05:08:54 +02:00
|
|
|
use function igbinary_unserialize;
|
|
|
|
use function is_dir;
|
2022-06-28 20:46:23 +02:00
|
|
|
use function is_null;
|
2019-07-19 05:08:54 +02:00
|
|
|
use function mkdir;
|
2021-06-08 04:55:21 +02:00
|
|
|
use function serialize;
|
|
|
|
use function strtolower;
|
|
|
|
use function unlink;
|
|
|
|
use function unserialize;
|
|
|
|
|
|
|
|
use const DIRECTORY_SEPARATOR;
|
2022-08-05 11:05:08 +02:00
|
|
|
use const LOCK_EX;
|
2022-05-25 11:17:22 +02:00
|
|
|
use const PHP_VERSION_ID;
|
2018-02-19 06:27:39 +01:00
|
|
|
|
2018-12-02 00:37:49 +01:00
|
|
|
/**
|
|
|
|
* @internal
|
|
|
|
*/
|
2018-02-19 06:27:39 +01:00
|
|
|
class ClassLikeStorageCacheProvider
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* @var Config
|
|
|
|
*/
|
|
|
|
private $config;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
private $modified_timestamps = '';
|
|
|
|
|
2020-09-20 18:54:46 +02:00
|
|
|
private const CLASS_CACHE_DIRECTORY = 'class_cache';
|
2018-02-19 06:27:39 +01:00
|
|
|
|
|
|
|
public function __construct(Config $config)
|
|
|
|
{
|
|
|
|
$this->config = $config;
|
|
|
|
|
2018-11-06 03:57:36 +01:00
|
|
|
$storage_dir = dirname(__DIR__, 2) . DIRECTORY_SEPARATOR . 'Storage' . DIRECTORY_SEPARATOR;
|
2018-02-19 06:27:39 +01:00
|
|
|
|
|
|
|
$dependent_files = [
|
2018-03-27 04:13:10 +02:00
|
|
|
$storage_dir . 'FileStorage.php',
|
2018-02-19 06:27:39 +01:00
|
|
|
$storage_dir . 'FunctionLikeStorage.php',
|
2018-03-27 04:13:10 +02:00
|
|
|
$storage_dir . 'ClassLikeStorage.php',
|
2018-02-19 06:27:39 +01:00
|
|
|
$storage_dir . 'MethodStorage.php',
|
|
|
|
];
|
|
|
|
|
2021-01-09 01:51:26 +01:00
|
|
|
if ($config->eventDispatcher->hasAfterClassLikeVisitHandlers()) {
|
2018-09-04 21:28:03 +02:00
|
|
|
$dependent_files = array_merge($dependent_files, $config->plugin_paths);
|
|
|
|
}
|
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
foreach ($dependent_files as $dependent_file_path) {
|
|
|
|
if (!file_exists($dependent_file_path)) {
|
2021-12-03 21:40:18 +01:00
|
|
|
throw new UnexpectedValueException($dependent_file_path . ' must exist');
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->modified_timestamps .= ' ' . filemtime($dependent_file_path);
|
|
|
|
}
|
2018-02-25 17:35:43 +01:00
|
|
|
|
2021-08-06 17:21:55 +02:00
|
|
|
$this->modified_timestamps .= $this->config->computeHash();
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
2021-09-25 16:05:47 +02:00
|
|
|
public function writeToCache(ClassLikeStorage $storage, string $file_path, string $file_contents): void
|
2018-02-19 06:27:39 +01:00
|
|
|
{
|
|
|
|
$fq_classlike_name_lc = strtolower($storage->name);
|
2019-07-19 05:08:54 +02:00
|
|
|
|
2018-02-19 06:27:39 +01:00
|
|
|
$storage->hash = $this->getCacheHash($file_path, $file_contents);
|
|
|
|
|
2022-06-28 20:46:23 +02:00
|
|
|
// check if we have it in cache already
|
|
|
|
$cached_value = $this->loadFromCache($fq_classlike_name_lc, $file_path);
|
|
|
|
if (!is_null($cached_value) && $cached_value->hash === $storage->hash) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path, true);
|
2018-02-19 17:14:07 +01:00
|
|
|
if ($this->config->use_igbinary) {
|
2022-08-05 11:05:08 +02:00
|
|
|
file_put_contents($cache_location, igbinary_serialize($storage), LOCK_EX);
|
2018-02-19 17:14:07 +01:00
|
|
|
} else {
|
2022-08-05 11:05:08 +02:00
|
|
|
file_put_contents($cache_location, serialize($storage), LOCK_EX);
|
2018-02-19 17:14:07 +01:00
|
|
|
}
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
2020-09-12 17:24:05 +02:00
|
|
|
public function getLatestFromCache(
|
|
|
|
string $fq_classlike_name_lc,
|
|
|
|
?string $file_path,
|
|
|
|
?string $file_contents
|
|
|
|
): ClassLikeStorage {
|
2018-02-20 16:56:18 +01:00
|
|
|
$cached_value = $this->loadFromCache($fq_classlike_name_lc, $file_path);
|
2018-02-19 06:27:39 +01:00
|
|
|
|
|
|
|
if (!$cached_value) {
|
2021-12-03 21:40:18 +01:00
|
|
|
throw new UnexpectedValueException($fq_classlike_name_lc . ' should be in cache');
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$cache_hash = $this->getCacheHash($file_path, $file_contents);
|
|
|
|
|
2018-09-04 20:37:33 +02:00
|
|
|
/** @psalm-suppress TypeDoesNotContainType */
|
2018-03-12 05:01:21 +01:00
|
|
|
if (@get_class($cached_value) === '__PHP_Incomplete_Class'
|
|
|
|
|| $cache_hash !== $cached_value->hash
|
|
|
|
) {
|
2018-02-20 16:56:18 +01:00
|
|
|
unlink($this->getCacheLocationForClass($fq_classlike_name_lc, $file_path));
|
2018-02-19 06:27:39 +01:00
|
|
|
|
2021-12-03 21:40:18 +01:00
|
|
|
throw new UnexpectedValueException($fq_classlike_name_lc . ' should not be outdated');
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $cached_value;
|
|
|
|
}
|
|
|
|
|
2022-06-28 15:27:58 +02:00
|
|
|
private function getCacheHash(?string $_unused_file_path, ?string $file_contents): string
|
2018-02-19 06:27:39 +01:00
|
|
|
{
|
2022-06-28 15:27:58 +02:00
|
|
|
$data = $file_contents ? $file_contents : $this->modified_timestamps;
|
2022-05-28 22:26:08 +02:00
|
|
|
return PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data);
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-21 04:21:00 +01:00
|
|
|
* @psalm-suppress MixedAssignment
|
2018-02-19 06:27:39 +01:00
|
|
|
*/
|
2020-09-07 01:36:47 +02:00
|
|
|
private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path): ?ClassLikeStorage
|
2018-02-19 06:27:39 +01:00
|
|
|
{
|
2018-02-20 16:56:18 +01:00
|
|
|
$cache_location = $this->getCacheLocationForClass($fq_classlike_name_lc, $file_path);
|
2018-02-19 06:27:39 +01:00
|
|
|
|
|
|
|
if (file_exists($cache_location)) {
|
2018-02-19 17:14:07 +01:00
|
|
|
if ($this->config->use_igbinary) {
|
2022-08-05 11:05:08 +02:00
|
|
|
$storage = igbinary_unserialize(Providers::safeFileGetContents($cache_location));
|
2018-11-21 04:21:00 +01:00
|
|
|
|
|
|
|
if ($storage instanceof ClassLikeStorage) {
|
|
|
|
return $storage;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2022-08-05 11:05:08 +02:00
|
|
|
$storage = unserialize(Providers::safeFileGetContents($cache_location));
|
2018-11-21 04:21:00 +01:00
|
|
|
|
|
|
|
if ($storage instanceof ClassLikeStorage) {
|
|
|
|
return $storage;
|
2018-02-19 17:14:07 +01:00
|
|
|
}
|
2018-11-21 04:21:00 +01:00
|
|
|
|
|
|
|
return null;
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2020-09-07 01:36:47 +02:00
|
|
|
private function getCacheLocationForClass(
|
|
|
|
string $fq_classlike_name_lc,
|
|
|
|
?string $file_path,
|
|
|
|
bool $create_directory = false
|
|
|
|
): string {
|
2018-02-19 06:27:39 +01:00
|
|
|
$root_cache_directory = $this->config->getCacheDirectory();
|
|
|
|
|
|
|
|
if (!$root_cache_directory) {
|
2021-12-03 21:40:18 +01:00
|
|
|
throw new UnexpectedValueException('No cache directory defined');
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$parser_cache_directory = $root_cache_directory . DIRECTORY_SEPARATOR . self::CLASS_CACHE_DIRECTORY;
|
|
|
|
|
|
|
|
if ($create_directory && !is_dir($parser_cache_directory)) {
|
2022-08-18 13:39:46 +02:00
|
|
|
try {
|
|
|
|
if (mkdir($parser_cache_directory, 0777, true) === false) {
|
|
|
|
// any other error than directory already exists/permissions issue
|
2022-09-13 18:33:47 +02:00
|
|
|
throw new RuntimeException(
|
|
|
|
'Failed to create ' . $parser_cache_directory . ' cache directory for unknown reasons'
|
|
|
|
);
|
2022-08-18 13:39:46 +02:00
|
|
|
}
|
|
|
|
} catch (RuntimeException $e) {
|
|
|
|
// Race condition (#4483)
|
|
|
|
if (!is_dir($parser_cache_directory)) {
|
|
|
|
// rethrow the error with default message
|
|
|
|
// it contains the reason why creation failed
|
|
|
|
throw $e;
|
|
|
|
}
|
|
|
|
}
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
|
2022-05-22 23:01:08 +02:00
|
|
|
$data = $file_path ? strtolower($file_path) . ' ' : '';
|
|
|
|
$data .= $fq_classlike_name_lc;
|
2022-05-28 22:26:08 +02:00
|
|
|
$file_path_sha = PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data);
|
2022-05-22 23:01:08 +02:00
|
|
|
|
2018-02-19 17:14:07 +01:00
|
|
|
return $parser_cache_directory
|
|
|
|
. DIRECTORY_SEPARATOR
|
2022-05-22 23:01:08 +02:00
|
|
|
. $file_path_sha
|
2018-02-19 17:14:07 +01:00
|
|
|
. ($this->config->use_igbinary ? '-igbinary' : '');
|
2018-02-19 06:27:39 +01:00
|
|
|
}
|
|
|
|
}
|