update lock clucknut
All checks were successful
All checks were successful
This commit is contained in:
8
vendor/cuyz/valinor/src/Cache/CacheEntry.php
vendored
8
vendor/cuyz/valinor/src/Cache/CacheEntry.php
vendored
@@ -5,11 +5,11 @@
|
||||
namespace CuyZ\Valinor\Cache;
|
||||
|
||||
/** @internal */
|
||||
final class CacheEntry
|
||||
final readonly class CacheEntry
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $code,
|
||||
/** @var list<non-empty-string> */
|
||||
public readonly array $filesToWatch = [],
|
||||
public string $code,
|
||||
/** @var null|callable(): list<non-empty-string> */
|
||||
public mixed $filesToWatch = null,
|
||||
) {}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
use FilesystemIterator;
|
||||
|
||||
use function bin2hex;
|
||||
use function chmod;
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function is_dir;
|
||||
@@ -59,7 +60,14 @@ public function set(string $key, CacheEntry $entry): void
|
||||
$tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
|
||||
$filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
|
||||
|
||||
if (! is_dir($tmpDir) && ! @mkdir($tmpDir, 0777, true)) {
|
||||
/**
|
||||
* The third `is_dir()` check confirms the directory truly doesn't exist
|
||||
* after `mkdir()` failure, distinguishing a genuine error (permissions,
|
||||
* disk full) from a concurrent creation by another process.
|
||||
*
|
||||
* @infection-ignore-all
|
||||
*/
|
||||
if (! is_dir($tmpDir) && ! @mkdir($tmpDir, 0777, true) && ! is_dir($tmpDir)) {
|
||||
throw new CacheDirectoryNotWritable($this->cacheDir);
|
||||
}
|
||||
|
||||
@@ -79,7 +87,9 @@ public function set(string $key, CacheEntry $entry): void
|
||||
|
||||
@chmod($filename, 0666 & ~umask());
|
||||
} finally {
|
||||
@unlink($tmpFilename);
|
||||
if (file_exists($tmpFilename)) {
|
||||
unlink($tmpFilename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@
|
||||
|
||||
namespace CuyZ\Valinor\Cache;
|
||||
|
||||
use function assert;
|
||||
use function file_exists;
|
||||
use function filemtime;
|
||||
use function is_array;
|
||||
use function var_export;
|
||||
|
||||
/**
|
||||
@@ -18,7 +20,7 @@
|
||||
* environment, where source files are often modified by developers.
|
||||
*
|
||||
* It should decorate the original cache implementation and should be given to
|
||||
* the mapper builder: @see \CuyZ\Valinor\MapperBuilder::withCache
|
||||
* the mapper builder: {@see \CuyZ\Valinor\MapperBuilder::withCache()}
|
||||
*
|
||||
* @api
|
||||
*
|
||||
@@ -66,7 +68,11 @@ public function set(string $key, CacheEntry $entry): void
|
||||
|
||||
$this->timestamps[$key] = [];
|
||||
|
||||
foreach ($entry->filesToWatch as $fileName) {
|
||||
if ($entry->filesToWatch === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (($entry->filesToWatch)() as $fileName) {
|
||||
$time = @filemtime($fileName);
|
||||
|
||||
if (false === $time) {
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
*/
|
||||
final class KeySanitizerCache implements Cache
|
||||
{
|
||||
private static string $version;
|
||||
|
||||
public function __construct(
|
||||
/** @var Cache<EntryType> */
|
||||
private Cache $delegate,
|
||||
@@ -46,12 +44,10 @@ public function clear(): void
|
||||
*/
|
||||
private function sanitize(string $key): string
|
||||
{
|
||||
// @infection-ignore-all
|
||||
self::$version ??= PHP_VERSION . '/' . Package::version();
|
||||
|
||||
$firstPart = strstr($key, "\0", before_needle: true);
|
||||
|
||||
// @infection-ignore-all
|
||||
return $firstPart . hash('xxh128', $key . $this->settings->hash() . self::$version);
|
||||
$hash = hash('xxh128', $key . $this->settings->hash() . PHP_VERSION . Package::VERSION);
|
||||
|
||||
return $firstPart . $hash;
|
||||
}
|
||||
}
|
||||
|
||||
177
vendor/cuyz/valinor/src/Cache/TypeFilesWatcher.php
vendored
177
vendor/cuyz/valinor/src/Cache/TypeFilesWatcher.php
vendored
@@ -4,18 +4,22 @@
|
||||
|
||||
namespace CuyZ\Valinor\Cache;
|
||||
|
||||
use Closure;
|
||||
use CuyZ\Valinor\Definition\Attributes;
|
||||
use CuyZ\Valinor\Definition\ClassDefinition;
|
||||
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
|
||||
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
|
||||
use CuyZ\Valinor\Library\Settings;
|
||||
use CuyZ\Valinor\Type\ObjectType;
|
||||
use CuyZ\Valinor\Type\Type;
|
||||
use CuyZ\Valinor\Utility\Reflection\Reflection;
|
||||
use CuyZ\Valinor\Utility\TypeHelper;
|
||||
use ReflectionFunction;
|
||||
|
||||
use function array_filter;
|
||||
use function array_map;
|
||||
use function array_merge;
|
||||
use function array_unique;
|
||||
use function array_values;
|
||||
use function is_string;
|
||||
|
||||
/** @internal */
|
||||
final class TypeFilesWatcher
|
||||
@@ -23,23 +27,33 @@ final class TypeFilesWatcher
|
||||
public function __construct(
|
||||
private Settings $settings,
|
||||
private ClassDefinitionRepository $classDefinitionRepository,
|
||||
private FunctionDefinitionRepository $functionDefinitionRepository,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* This method returns a list of files in which are declared all types that
|
||||
* compose the given type. If the given type is composed of other types,
|
||||
* they are recursively resolved as well. If the type is a class, all
|
||||
* properties are also resolved.
|
||||
*
|
||||
* In addition, all callable that were given to the global settings are
|
||||
* checked, and their file names are added to the list.
|
||||
* they are recursively resolved as well.
|
||||
*
|
||||
* This file list can then be used by the `FileWatchingCache` to detect
|
||||
* changes to these files and invalidate the cache entries if needed.
|
||||
*
|
||||
* Example of returned value:
|
||||
* If the type is a class, will be resolved:
|
||||
* - The class attributes
|
||||
* - The properties attributes and types
|
||||
* - The methods attributes and return types
|
||||
* - The method parameters attributes and types
|
||||
*
|
||||
* ```php
|
||||
* If the type is a callable, will be resolved:
|
||||
* - The callable attributes and return type
|
||||
* - The callable parameters attributes and types
|
||||
*
|
||||
* In addition, all callables that were given to the global settings are
|
||||
* checked, and their file names are added to the list.
|
||||
*
|
||||
* Example of a returned value:
|
||||
*
|
||||
* ```
|
||||
* [
|
||||
* // An entity representing a user
|
||||
* '/root/path/src/Domain/User/User.php',
|
||||
@@ -60,59 +74,156 @@ public function __construct(
|
||||
*
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
public function for(Type $type): array
|
||||
public function for(Type|callable $item): array
|
||||
{
|
||||
// Merging the files bound to settings and the files bound to the type
|
||||
$files = [
|
||||
...array_map(
|
||||
fn (callable $callable) => (new ReflectionFunction(Closure::fromCallable($callable)))->getFileName(),
|
||||
$this->settings->callables(),
|
||||
),
|
||||
...$this->filesToWatch($type),
|
||||
];
|
||||
$files = array_merge(...array_map(
|
||||
$this->forCallable(...),
|
||||
$this->settings->callables(),
|
||||
));
|
||||
|
||||
if ($item instanceof Type) {
|
||||
$files = [...$files, ...$this->forType($item)];
|
||||
} else {
|
||||
$files = [...$files, ...$this->forCallable($item)];
|
||||
}
|
||||
|
||||
// Removing the duplicates
|
||||
$files = array_unique($files);
|
||||
|
||||
// Filtering the empty/invalid file names
|
||||
$files = array_filter($files, fn ($value) => is_string($value));
|
||||
$files = array_filter($files, is_string(...));
|
||||
|
||||
/** @var list<non-empty-string> */
|
||||
return array_values($files);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<non-empty-string> $files
|
||||
* @return array<non-empty-string>
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private function filesToWatch(Type $type, array $files = []): array
|
||||
private function forCallable(callable $callable): array
|
||||
{
|
||||
if (isset($files[$type->toString()])) {
|
||||
$definition = $this->functionDefinitionRepository->for($callable);
|
||||
$visited = [];
|
||||
$files = [];
|
||||
|
||||
if ($definition->fileName && $definition->class !== Settings::class) {
|
||||
$files = [$definition->fileName];
|
||||
}
|
||||
|
||||
$returnFiles = $this->forType($definition->returnType, $visited);
|
||||
$attributeFiles = $this->forAttributes($definition->attributes, $visited);
|
||||
|
||||
$files = [...$files, ...$returnFiles, ...$attributeFiles];
|
||||
|
||||
foreach ($definition->parameters as $parameter) {
|
||||
$parameterFiles = $this->forType($parameter->type, $visited);
|
||||
$attributeFiles = $this->forAttributes($parameter->attributes, $visited);
|
||||
|
||||
$files = [...$files, ...$parameterFiles, ...$attributeFiles];
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, true> $visited
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private function forType(Type $type, array &$visited = []): array
|
||||
{
|
||||
if (isset($visited[$type->toString()])) {
|
||||
// Prevents infinite loop in case of circular references
|
||||
return [];
|
||||
}
|
||||
|
||||
$visited[$type->toString()] = true;
|
||||
|
||||
$files = [];
|
||||
|
||||
foreach (TypeHelper::traverseRecursively($type) as $subType) {
|
||||
$files = [...$files, ...$this->filesToWatch($subType, $files)];
|
||||
$files = [...$files, ...$this->forType($subType, $visited)];
|
||||
}
|
||||
|
||||
if ($type instanceof ObjectType) {
|
||||
$fileName = Reflection::class($type->className())->getFileName();
|
||||
|
||||
if (! $fileName) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$files[$type->toString()] = $fileName;
|
||||
|
||||
$class = $this->classDefinitionRepository->for($type);
|
||||
$classFiles = $this->forClass($class, $visited);
|
||||
|
||||
foreach ($class->properties as $property) {
|
||||
$files = [...$files, ...$this->filesToWatch($property->type, $files)];
|
||||
$files = [...$files, ...$classFiles];
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, true> $visited
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private function forClass(ClassDefinition $class, array &$visited): array
|
||||
{
|
||||
$objectFiles = $this->traverseObjectInheritanceFileNames($class->name);
|
||||
|
||||
$attributeFiles = $this->forAttributes($class->attributes, $visited);
|
||||
|
||||
$files = [...$objectFiles, ...$attributeFiles];
|
||||
|
||||
foreach ($class->properties as $property) {
|
||||
$propertyFiles = $this->forType($property->type, $visited);
|
||||
$attributeFiles = $this->forAttributes($property->attributes, $visited);
|
||||
|
||||
$files = [...$files, ...$propertyFiles, ...$attributeFiles];
|
||||
}
|
||||
|
||||
foreach ($class->methods as $method) {
|
||||
$returnFiles = $this->forType($method->returnType, $visited);
|
||||
$attributeFiles = $this->forAttributes($method->attributes, $visited);
|
||||
|
||||
$files = [...$files, ...$returnFiles, ...$attributeFiles];
|
||||
|
||||
foreach ($method->parameters as $parameter) {
|
||||
$parameterFiles = $this->forType($parameter->type, $visited);
|
||||
$attributeFiles = $this->forAttributes($parameter->attributes, $visited);
|
||||
|
||||
$files = [...$files, ...$parameterFiles, ...$attributeFiles];
|
||||
}
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, true> $visited
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private function forAttributes(Attributes $attributes, array &$visited): array
|
||||
{
|
||||
$files = [];
|
||||
|
||||
foreach ($attributes as $attribute) {
|
||||
$files = [...$files, ...$this->forType($attribute->class->type, $visited)];
|
||||
}
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string $className
|
||||
* @return list<non-empty-string>
|
||||
*/
|
||||
private function traverseObjectInheritanceFileNames(string $className): array
|
||||
{
|
||||
$reflection = Reflection::class($className);
|
||||
|
||||
$fileNames = [];
|
||||
|
||||
do {
|
||||
$fileName = $reflection->getFileName();
|
||||
|
||||
if (is_string($fileName)) {
|
||||
$fileNames[] = $fileName;
|
||||
}
|
||||
} while ($reflection = $reflection->getParentClass());
|
||||
|
||||
return $fileNames;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user