diff --git a/com.woltlab.wcf/fileDelete.xml b/com.woltlab.wcf/fileDelete.xml
index 10520b1ddc4..9cd35e89423 100644
--- a/com.woltlab.wcf/fileDelete.xml
+++ b/com.woltlab.wcf/fileDelete.xml
@@ -9,15 +9,29 @@
js/WoltLabSuite/Core/Ui/User/CoverPhoto/Upload.js
lib/form/AvatarEditForm.class.php
lib/system/api/cuyz/valinor/qa/PHPStan/Stubs/Psr/SimpleCache/CacheInterface.stub
+ lib/system/api/cuyz/valinor/src/Cache/ChainCache.php
+ lib/system/api/cuyz/valinor/src/Cache/WarmupCache.php
+ lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php
+ lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php
+ lib/system/api/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php
lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/NoCasterForType.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/Node.php
+ lib/system/api/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php
lib/system/api/cuyz/valinor/src/Normalizer/Formatter/StreamFormatter.php
+ lib/system/api/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php
lib/system/api/cuyz/valinor/src/Normalizer/Transformer/KeyTransformersHandler.php
lib/system/api/cuyz/valinor/src/Normalizer/Transformer/ValueTransformersHandler.php
+ lib/system/api/cuyz/valinor/src/Utility/Priority/HasPriority.php
+ lib/system/api/cuyz/valinor/src/Utility/Priority/PrioritizedList.php
lib/system/api/cuyz/valinor/src/Utility/PermissiveTypeFound.php
lib/system/api/bin/pscss
lib/system/api/scssphp/scssphp/bin/pscss
diff --git a/wcfsetup/install/files/lib/action/AbstractOauth2AuthAction.class.php b/wcfsetup/install/files/lib/action/AbstractOauth2AuthAction.class.php
index 6649007b3d0..484946f7e41 100644
--- a/wcfsetup/install/files/lib/action/AbstractOauth2AuthAction.class.php
+++ b/wcfsetup/install/files/lib/action/AbstractOauth2AuthAction.class.php
@@ -111,7 +111,6 @@ protected function mapParameters(ServerRequestInterface $request): OAuth2Success
try {
$mapper = (new MapperBuilder())
->allowSuperfluousKeys()
- ->enableFlexibleCasting()
->mapper();
return $mapper->map(
diff --git a/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php b/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php
index 7b6287e1dce..c0512bc43eb 100644
--- a/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php
+++ b/wcfsetup/install/files/lib/action/TwitterAuthAction.class.php
@@ -86,7 +86,6 @@ protected function mapParameters(ServerRequestInterface $request): OAuth2Success
try {
$mapper = (new MapperBuilder())
->allowSuperfluousKeys()
- ->enableFlexibleCasting()
->mapper();
return $mapper->map(
diff --git a/wcfsetup/install/files/lib/http/Helper.class.php b/wcfsetup/install/files/lib/http/Helper.class.php
index 44dd0d85dfd..b64554e8b0f 100644
--- a/wcfsetup/install/files/lib/http/Helper.class.php
+++ b/wcfsetup/install/files/lib/http/Helper.class.php
@@ -115,7 +115,7 @@ public static function mapQueryParameters(array $queryParameters, string $schema
{
$mapper = (new MapperBuilder())
->allowSuperfluousKeys()
- ->enableFlexibleCasting()
+ ->allowScalarValueCasting()
->mapper();
return $mapper->map(
diff --git a/wcfsetup/install/files/lib/http/middleware/HandleValinorMappingErrors.class.php b/wcfsetup/install/files/lib/http/middleware/HandleValinorMappingErrors.class.php
index 27f4b06d68e..500fad07a6d 100644
--- a/wcfsetup/install/files/lib/http/middleware/HandleValinorMappingErrors.class.php
+++ b/wcfsetup/install/files/lib/http/middleware/HandleValinorMappingErrors.class.php
@@ -3,7 +3,6 @@
namespace wcf\http\middleware;
use CuyZ\Valinor\Mapper\MappingError;
-use CuyZ\Valinor\Mapper\Tree\Message\Messages;
use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
use Laminas\Diactoros\Response\HtmlResponse;
use Laminas\Diactoros\Response\JsonResponse;
@@ -34,8 +33,8 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
try {
return $handler->handle($request);
} catch (MappingError $e) {
- $message = "Could not map type '{$e->node()->type()}'.";
- $errors = Messages::flattenFromNode($e->node())
+ $message = "Could not map type '{$e->type()}'.";
+ $errors = $e->messages()
->formatWith(new PrependPath());
$preferredType = Helper::getPreferredContentType($request, [
@@ -49,7 +48,7 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface
'message' => $message,
'exception' => \ENABLE_DEBUG_MODE ? $e->__toString() : null,
'errors' => \array_map(
- static fn (NodeMessage $m) => $m->toString(),
+ static fn(NodeMessage $m) => $m->toString(),
\iterator_to_array($errors, false)
),
],
diff --git a/wcfsetup/install/files/lib/system/api/composer.json b/wcfsetup/install/files/lib/system/api/composer.json
index e4d1be4d0c9..cc5efcca12a 100644
--- a/wcfsetup/install/files/lib/system/api/composer.json
+++ b/wcfsetup/install/files/lib/system/api/composer.json
@@ -10,7 +10,7 @@
}
},
"require": {
- "cuyz/valinor": "^1.17.0",
+ "cuyz/valinor": "^2.1.1",
"dragonmantank/cron-expression": "^3.4.0",
"erusev/parsedown": "^1.7.4",
"ezyang/htmlpurifier": "^4.18",
diff --git a/wcfsetup/install/files/lib/system/api/composer.lock b/wcfsetup/install/files/lib/system/api/composer.lock
index 315a58c6eda..3bcfee035d9 100644
--- a/wcfsetup/install/files/lib/system/api/composer.lock
+++ b/wcfsetup/install/files/lib/system/api/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "7bda0b2a726b2865b120f6a816d53491",
+ "content-hash": "64dbf8f9a744d244356c93f12b98115b",
"packages": [
{
"name": "brick/math",
@@ -68,22 +68,21 @@
},
{
"name": "cuyz/valinor",
- "version": "1.17.0",
+ "version": "2.1.1",
"source": {
"type": "git",
"url": "https://github.com/CuyZ/Valinor.git",
- "reference": "9eb2802797e36f3af1fd6e13ff23e4e3d0058022"
+ "reference": "f51e9beebdefc968cbfcd4a2c561b40575288610"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/9eb2802797e36f3af1fd6e13ff23e4e3d0058022",
- "reference": "9eb2802797e36f3af1fd6e13ff23e4e3d0058022",
+ "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/f51e9beebdefc968cbfcd4a2c561b40575288610",
+ "reference": "f51e9beebdefc968cbfcd4a2c561b40575288610",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.0",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
- "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"phpstan/phpstan": "<1.0 || >= 3.0",
@@ -134,7 +133,7 @@
],
"support": {
"issues": "https://github.com/CuyZ/Valinor/issues",
- "source": "https://github.com/CuyZ/Valinor/tree/1.17.0"
+ "source": "https://github.com/CuyZ/Valinor/tree/2.1.1"
},
"funding": [
{
@@ -142,7 +141,7 @@
"type": "github"
}
],
- "time": "2025-06-20T06:40:38+00:00"
+ "time": "2025-07-23T20:32:01+00:00"
},
{
"name": "dragonmantank/cron-expression",
diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php
index 616e7a7fec1..1fbede304f5 100644
--- a/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php
+++ b/wcfsetup/install/files/lib/system/api/composer/autoload_classmap.php
@@ -34,7 +34,8 @@
'Cron\\HoursField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/HoursField.php',
'Cron\\MinutesField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
'Cron\\MonthField' => $vendorDir . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
- 'CuyZ\\Valinor\\Cache\\ChainCache' => $vendorDir . '/cuyz/valinor/src/Cache/ChainCache.php',
+ 'CuyZ\\Valinor\\Cache\\Cache' => $vendorDir . '/cuyz/valinor/src/Cache/Cache.php',
+ 'CuyZ\\Valinor\\Cache\\CacheEntry' => $vendorDir . '/cuyz/valinor/src/Cache/CacheEntry.php',
'CuyZ\\Valinor\\Cache\\Exception\\CacheDirectoryNotWritable' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CacheDirectoryNotWritable.php',
'CuyZ\\Valinor\\Cache\\Exception\\CompiledPhpCacheFileNotWritten' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CompiledPhpCacheFileNotWritten.php',
'CuyZ\\Valinor\\Cache\\Exception\\CorruptedCompiledPhpCacheFile' => $vendorDir . '/cuyz/valinor/src/Cache/Exception/CorruptedCompiledPhpCacheFile.php',
@@ -43,7 +44,7 @@
'CuyZ\\Valinor\\Cache\\FileWatchingCache' => $vendorDir . '/cuyz/valinor/src/Cache/FileWatchingCache.php',
'CuyZ\\Valinor\\Cache\\KeySanitizerCache' => $vendorDir . '/cuyz/valinor/src/Cache/KeySanitizerCache.php',
'CuyZ\\Valinor\\Cache\\RuntimeCache' => $vendorDir . '/cuyz/valinor/src/Cache/RuntimeCache.php',
- 'CuyZ\\Valinor\\Cache\\WarmupCache' => $vendorDir . '/cuyz/valinor/src/Cache/WarmupCache.php',
+ 'CuyZ\\Valinor\\Cache\\TypeFilesWatcher' => $vendorDir . '/cuyz/valinor/src/Cache/TypeFilesWatcher.php',
'CuyZ\\Valinor\\Cache\\Warmup\\RecursiveCacheWarmupService' => $vendorDir . '/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php',
'CuyZ\\Valinor\\Compiler\\Compiler' => $vendorDir . '/cuyz/valinor/src/Compiler/Compiler.php',
'CuyZ\\Valinor\\Compiler\\Library\\NewAttributeNode' => $vendorDir . '/cuyz/valinor/src/Compiler/Library/NewAttributeNode.php',
@@ -114,8 +115,8 @@
'CuyZ\\Valinor\\Definition\\Properties' => $vendorDir . '/cuyz/valinor/src/Definition/Properties.php',
'CuyZ\\Valinor\\Definition\\PropertyDefinition' => $vendorDir . '/cuyz/valinor/src/Definition/PropertyDefinition.php',
'CuyZ\\Valinor\\Definition\\Repository\\AttributesRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/AttributesRepository.php',
- 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CacheClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php',
- 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CacheFunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CompiledClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/CompiledClassDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CompiledFunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/CompiledFunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\AttributesCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\ClassDefinitionCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\Exception\\TypeCannotBeCompiled' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/Exception/TypeCannotBeCompiled.php',
@@ -124,6 +125,8 @@
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\ParameterDefinitionCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ParameterDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\PropertyDefinitionCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/PropertyDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\TypeCompiler' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\InMemoryClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/InMemoryClassDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\InMemoryFunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Cache/InMemoryFunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\ClassDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/ClassDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\FunctionDefinitionRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/FunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionAttributesRepository' => $vendorDir . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php',
@@ -145,6 +148,7 @@
'CuyZ\\Valinor\\MapperBuilder' => $vendorDir . '/cuyz/valinor/src/MapperBuilder.php',
'CuyZ\\Valinor\\Mapper\\ArgumentsMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/ArgumentsMapper.php',
'CuyZ\\Valinor\\Mapper\\ArgumentsMapperError' => $vendorDir . '/cuyz/valinor/src/Mapper/ArgumentsMapperError.php',
+ 'CuyZ\\Valinor\\Mapper\\AsConverter' => $vendorDir . '/cuyz/valinor/src/Mapper/AsConverter.php',
'CuyZ\\Valinor\\Mapper\\Exception\\InvalidMappingTypeSignature' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/InvalidMappingTypeSignature.php',
'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringArgumentsMapping' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php',
'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringMapping' => $vendorDir . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php',
@@ -165,10 +169,10 @@
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\MissingConstructorClassTypeParameter' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/MissingConstructorClassTypeParameter.php',
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\ObjectBuildersCollision' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/ObjectBuildersCollision.php',
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\PermissiveTypeNotAllowed' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Exception/PermissiveTypeNotAllowed.php',
- 'CuyZ\\Valinor\\Mapper\\Object\\Factory\\CacheObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ConstructorObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\DateTimeObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\DateTimeZoneObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php',
+ 'CuyZ\\Valinor\\Mapper\\Object\\Factory\\InMemoryObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/InMemoryObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/ObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ReflectionObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\SortingObjectBuilderFactory' => $vendorDir . '/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php',
@@ -188,7 +192,6 @@
'CuyZ\\Valinor\\Mapper\\Source\\Exception\\UnableToReadFile' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/Exception/UnableToReadFile.php',
'CuyZ\\Valinor\\Mapper\\Source\\Exception\\YamlExtensionNotEnabled' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/Exception/YamlExtensionNotEnabled.php',
'CuyZ\\Valinor\\Mapper\\Source\\FileSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/FileSource.php',
- 'CuyZ\\Valinor\\Mapper\\Source\\IdentifiableSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php',
'CuyZ\\Valinor\\Mapper\\Source\\JsonSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/JsonSource.php',
'CuyZ\\Valinor\\Mapper\\Source\\Modifier\\CamelCaseKeys' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/Modifier/CamelCaseKeys.php',
'CuyZ\\Valinor\\Mapper\\Source\\Modifier\\Mapping' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/Modifier/Mapping.php',
@@ -197,9 +200,11 @@
'CuyZ\\Valinor\\Mapper\\Source\\YamlSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Source/YamlSource.php',
'CuyZ\\Valinor\\Mapper\\TreeMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/TreeMapper.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ArrayNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ArrayNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ConverterContainer' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ConverterContainer.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\InterfaceNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ListNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\MixedNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\Node' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/Node.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NullNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectImplementations' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectImplementations.php',
@@ -207,32 +212,35 @@
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\RootNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ScalarNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ShapedArrayNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\TreeNode' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\TypeNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\UndefinedObjectNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\UnionNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ValueAlteringNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ValueConverterNodeBuilder' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Builder/ValueConverterNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotInferFinalClass' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotInferFinalClass.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotMapToPermissiveType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotMapToPermissiveType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveObjectType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveObjectType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveTypeFromUnion' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CircularDependencyDetected' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/CircularDependencyDetected.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasInvalidCallableParameter' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasInvalidCallableParameter.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasNoParameter' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasNoParameter.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasTooManyParameters' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasTooManyParameters.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InterfaceHasBothConstructorAndInfer' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidAbstractObjectName' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidAbstractObjectName.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidArrayKey' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidArrayKey.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidIterableKeyType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidIterableKeyType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidListKey' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeHasNoMappedValue' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeDuringValueConversion' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeDuringValueConversion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeValue' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidResolvedImplementationValue' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidResolvedImplementationValue.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidTraversableKey' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidTraversableKey.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\MissingNodeValue' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\MissingObjectImplementationRegistration' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/MissingObjectImplementationRegistration.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationCallbackError' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationCallbackError.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationNotRegistered' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationNotRegistered.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ResolvedImplementationIsNotAccepted' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/ResolvedImplementationIsNotAccepted.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsEmptyArray' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyArray.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsEmptyList' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyList.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsNotNull' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceMustBeIterable' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceValueWasNotFilled' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\TooManyResolvedTypesFromUnion' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedKeysInSource' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnresolvableShellType' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php',
@@ -251,12 +259,11 @@
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\Messages' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/Messages.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\NodeMessage' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\UserlandError' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Node' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Node.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\NodeTraverser' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Shell' => $vendorDir . '/cuyz/valinor/src/Mapper/Tree/Shell.php',
'CuyZ\\Valinor\\Mapper\\TypeArgumentsMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php',
'CuyZ\\Valinor\\Mapper\\TypeTreeMapper' => $vendorDir . '/cuyz/valinor/src/Mapper/TypeTreeMapper.php',
'CuyZ\\Valinor\\Mapper\\TypeTreeMapperError' => $vendorDir . '/cuyz/valinor/src/Mapper/TypeTreeMapperError.php',
+ 'CuyZ\\Valinor\\NormalizerBuilder' => $vendorDir . '/cuyz/valinor/src/NormalizerBuilder.php',
'CuyZ\\Valinor\\Normalizer\\ArrayNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/ArrayNormalizer.php',
'CuyZ\\Valinor\\Normalizer\\AsTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/AsTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Exception\\CircularReferenceFoundDuringNormalization' => $vendorDir . '/cuyz/valinor/src/Normalizer/Exception/CircularReferenceFoundDuringNormalization.php',
@@ -272,7 +279,7 @@
'CuyZ\\Valinor\\Normalizer\\JsonNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/JsonNormalizer.php',
'CuyZ\\Valinor\\Normalizer\\Normalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Normalizer.php',
'CuyZ\\Valinor\\Normalizer\\StreamNormalizer' => $vendorDir . '/cuyz/valinor/src/Normalizer/StreamNormalizer.php',
- 'CuyZ\\Valinor\\Normalizer\\Transformer\\CacheTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/CacheTransformer.php',
+ 'CuyZ\\Valinor\\Normalizer\\Transformer\\CompiledTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerDefinition' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerDefinition.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerDefinitionBuilder' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerDefinitionBuilder.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerRootNode' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerRootNode.php',
@@ -293,7 +300,6 @@
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TypeFormatter\\UnitEnumFormatter' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TypeFormatter/UnitEnumFormatter.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TypeFormatter\\UnsureTypeFormatter' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TypeFormatter/UnsureTypeFormatter.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\EmptyObject' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/EmptyObject.php',
- 'CuyZ\\Valinor\\Normalizer\\Transformer\\EvaluatedTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\RecursiveTransformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/RecursiveTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Transformer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/Transformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\TransformerContainer' => $vendorDir . '/cuyz/valinor/src/Normalizer/Transformer/TransformerContainer.php',
@@ -450,8 +456,6 @@
'CuyZ\\Valinor\\Utility\\IsSingleton' => $vendorDir . '/cuyz/valinor/src/Utility/IsSingleton.php',
'CuyZ\\Valinor\\Utility\\Package' => $vendorDir . '/cuyz/valinor/src/Utility/Package.php',
'CuyZ\\Valinor\\Utility\\Polyfill' => $vendorDir . '/cuyz/valinor/src/Utility/Polyfill.php',
- 'CuyZ\\Valinor\\Utility\\Priority\\HasPriority' => $vendorDir . '/cuyz/valinor/src/Utility/Priority/HasPriority.php',
- 'CuyZ\\Valinor\\Utility\\Priority\\PrioritizedList' => $vendorDir . '/cuyz/valinor/src/Utility/Priority/PrioritizedList.php',
'CuyZ\\Valinor\\Utility\\Reflection\\NamespaceFinder' => $vendorDir . '/cuyz/valinor/src/Utility/Reflection/NamespaceFinder.php',
'CuyZ\\Valinor\\Utility\\Reflection\\PhpParser' => $vendorDir . '/cuyz/valinor/src/Utility/Reflection/PhpParser.php',
'CuyZ\\Valinor\\Utility\\Reflection\\Reflection' => $vendorDir . '/cuyz/valinor/src/Utility/Reflection/Reflection.php',
diff --git a/wcfsetup/install/files/lib/system/api/composer/autoload_static.php b/wcfsetup/install/files/lib/system/api/composer/autoload_static.php
index 84cc2125763..6a86c841013 100644
--- a/wcfsetup/install/files/lib/system/api/composer/autoload_static.php
+++ b/wcfsetup/install/files/lib/system/api/composer/autoload_static.php
@@ -315,7 +315,8 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'Cron\\HoursField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/HoursField.php',
'Cron\\MinutesField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MinutesField.php',
'Cron\\MonthField' => __DIR__ . '/..' . '/dragonmantank/cron-expression/src/Cron/MonthField.php',
- 'CuyZ\\Valinor\\Cache\\ChainCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/ChainCache.php',
+ 'CuyZ\\Valinor\\Cache\\Cache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Cache.php',
+ 'CuyZ\\Valinor\\Cache\\CacheEntry' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/CacheEntry.php',
'CuyZ\\Valinor\\Cache\\Exception\\CacheDirectoryNotWritable' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CacheDirectoryNotWritable.php',
'CuyZ\\Valinor\\Cache\\Exception\\CompiledPhpCacheFileNotWritten' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CompiledPhpCacheFileNotWritten.php',
'CuyZ\\Valinor\\Cache\\Exception\\CorruptedCompiledPhpCacheFile' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Exception/CorruptedCompiledPhpCacheFile.php',
@@ -324,7 +325,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Cache\\FileWatchingCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/FileWatchingCache.php',
'CuyZ\\Valinor\\Cache\\KeySanitizerCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/KeySanitizerCache.php',
'CuyZ\\Valinor\\Cache\\RuntimeCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/RuntimeCache.php',
- 'CuyZ\\Valinor\\Cache\\WarmupCache' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/WarmupCache.php',
+ 'CuyZ\\Valinor\\Cache\\TypeFilesWatcher' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/TypeFilesWatcher.php',
'CuyZ\\Valinor\\Cache\\Warmup\\RecursiveCacheWarmupService' => __DIR__ . '/..' . '/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php',
'CuyZ\\Valinor\\Compiler\\Compiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Compiler/Compiler.php',
'CuyZ\\Valinor\\Compiler\\Library\\NewAttributeNode' => __DIR__ . '/..' . '/cuyz/valinor/src/Compiler/Library/NewAttributeNode.php',
@@ -395,8 +396,8 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Definition\\Properties' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Properties.php',
'CuyZ\\Valinor\\Definition\\PropertyDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/PropertyDefinition.php',
'CuyZ\\Valinor\\Definition\\Repository\\AttributesRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/AttributesRepository.php',
- 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CacheClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php',
- 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CacheFunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CompiledClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/CompiledClassDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\CompiledFunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/CompiledFunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\AttributesCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/AttributesCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\ClassDefinitionCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\Exception\\TypeCannotBeCompiled' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/Exception/TypeCannotBeCompiled.php',
@@ -405,6 +406,8 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\ParameterDefinitionCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ParameterDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\PropertyDefinitionCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/PropertyDefinitionCompiler.php',
'CuyZ\\Valinor\\Definition\\Repository\\Cache\\Compiler\\TypeCompiler' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\InMemoryClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/InMemoryClassDefinitionRepository.php',
+ 'CuyZ\\Valinor\\Definition\\Repository\\Cache\\InMemoryFunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Cache/InMemoryFunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\ClassDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/ClassDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\FunctionDefinitionRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/FunctionDefinitionRepository.php',
'CuyZ\\Valinor\\Definition\\Repository\\Reflection\\ReflectionAttributesRepository' => __DIR__ . '/..' . '/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php',
@@ -426,6 +429,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\MapperBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/MapperBuilder.php',
'CuyZ\\Valinor\\Mapper\\ArgumentsMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/ArgumentsMapper.php',
'CuyZ\\Valinor\\Mapper\\ArgumentsMapperError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/ArgumentsMapperError.php',
+ 'CuyZ\\Valinor\\Mapper\\AsConverter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/AsConverter.php',
'CuyZ\\Valinor\\Mapper\\Exception\\InvalidMappingTypeSignature' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/InvalidMappingTypeSignature.php',
'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringArgumentsMapping' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringArgumentsMapping.php',
'CuyZ\\Valinor\\Mapper\\Exception\\TypeErrorDuringMapping' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Exception/TypeErrorDuringMapping.php',
@@ -446,10 +450,10 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\MissingConstructorClassTypeParameter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/MissingConstructorClassTypeParameter.php',
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\ObjectBuildersCollision' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/ObjectBuildersCollision.php',
'CuyZ\\Valinor\\Mapper\\Object\\Exception\\PermissiveTypeNotAllowed' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Exception/PermissiveTypeNotAllowed.php',
- 'CuyZ\\Valinor\\Mapper\\Object\\Factory\\CacheObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ConstructorObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\DateTimeObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\DateTimeZoneObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php',
+ 'CuyZ\\Valinor\\Mapper\\Object\\Factory\\InMemoryObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/InMemoryObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/ObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\ReflectionObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/ReflectionObjectBuilderFactory.php',
'CuyZ\\Valinor\\Mapper\\Object\\Factory\\SortingObjectBuilderFactory' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php',
@@ -469,7 +473,6 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Mapper\\Source\\Exception\\UnableToReadFile' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/Exception/UnableToReadFile.php',
'CuyZ\\Valinor\\Mapper\\Source\\Exception\\YamlExtensionNotEnabled' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/Exception/YamlExtensionNotEnabled.php',
'CuyZ\\Valinor\\Mapper\\Source\\FileSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/FileSource.php',
- 'CuyZ\\Valinor\\Mapper\\Source\\IdentifiableSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php',
'CuyZ\\Valinor\\Mapper\\Source\\JsonSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/JsonSource.php',
'CuyZ\\Valinor\\Mapper\\Source\\Modifier\\CamelCaseKeys' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/Modifier/CamelCaseKeys.php',
'CuyZ\\Valinor\\Mapper\\Source\\Modifier\\Mapping' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/Modifier/Mapping.php',
@@ -478,9 +481,11 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Mapper\\Source\\YamlSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Source/YamlSource.php',
'CuyZ\\Valinor\\Mapper\\TreeMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TreeMapper.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ArrayNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ArrayNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ConverterContainer' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ConverterContainer.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\InterfaceNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ListNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\MixedNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\Node' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/Node.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\NullNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ObjectImplementations' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ObjectImplementations.php',
@@ -488,32 +493,35 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\RootNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ScalarNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ShapedArrayNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\TreeNode' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\TypeNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\UndefinedObjectNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\UnionNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ValueAlteringNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Builder\\ValueConverterNodeBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Builder/ValueConverterNodeBuilder.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotInferFinalClass' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotInferFinalClass.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotMapToPermissiveType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotMapToPermissiveType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveObjectType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveObjectType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CannotResolveTypeFromUnion' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\CircularDependencyDetected' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/CircularDependencyDetected.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasInvalidCallableParameter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasInvalidCallableParameter.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasNoParameter' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasNoParameter.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ConverterHasTooManyParameters' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasTooManyParameters.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InterfaceHasBothConstructorAndInfer' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InterfaceHasBothConstructorAndInfer.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidAbstractObjectName' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidAbstractObjectName.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidArrayKey' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidArrayKey.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidIterableKeyType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidIterableKeyType.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidListKey' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeHasNoMappedValue' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeHasNoMappedValue.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeDuringValueConversion' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeDuringValueConversion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidNodeValue' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidResolvedImplementationValue' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidResolvedImplementationValue.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\InvalidTraversableKey' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/InvalidTraversableKey.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\MissingNodeValue' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\MissingObjectImplementationRegistration' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/MissingObjectImplementationRegistration.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationCallbackError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationCallbackError.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ObjectImplementationNotRegistered' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ObjectImplementationNotRegistered.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\ResolvedImplementationIsNotAccepted' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/ResolvedImplementationIsNotAccepted.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsEmptyArray' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyArray.php',
+ 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsEmptyList' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyList.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceIsNotNull' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceMustBeIterable' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\SourceValueWasNotFilled' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\TooManyResolvedTypesFromUnion' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnexpectedKeysInSource' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Exception\\UnresolvableShellType' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Exception/UnresolvableShellType.php',
@@ -532,12 +540,11 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\Messages' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/Messages.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\NodeMessage' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Message\\UserlandError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\Node' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Node.php',
- 'CuyZ\\Valinor\\Mapper\\Tree\\NodeTraverser' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php',
'CuyZ\\Valinor\\Mapper\\Tree\\Shell' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/Tree/Shell.php',
'CuyZ\\Valinor\\Mapper\\TypeArgumentsMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php',
'CuyZ\\Valinor\\Mapper\\TypeTreeMapper' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TypeTreeMapper.php',
'CuyZ\\Valinor\\Mapper\\TypeTreeMapperError' => __DIR__ . '/..' . '/cuyz/valinor/src/Mapper/TypeTreeMapperError.php',
+ 'CuyZ\\Valinor\\NormalizerBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/NormalizerBuilder.php',
'CuyZ\\Valinor\\Normalizer\\ArrayNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/ArrayNormalizer.php',
'CuyZ\\Valinor\\Normalizer\\AsTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/AsTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Exception\\CircularReferenceFoundDuringNormalization' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Exception/CircularReferenceFoundDuringNormalization.php',
@@ -553,7 +560,7 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Normalizer\\JsonNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/JsonNormalizer.php',
'CuyZ\\Valinor\\Normalizer\\Normalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Normalizer.php',
'CuyZ\\Valinor\\Normalizer\\StreamNormalizer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/StreamNormalizer.php',
- 'CuyZ\\Valinor\\Normalizer\\Transformer\\CacheTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/CacheTransformer.php',
+ 'CuyZ\\Valinor\\Normalizer\\Transformer\\CompiledTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerDefinition' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerDefinition.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerDefinitionBuilder' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerDefinitionBuilder.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TransformerRootNode' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TransformerRootNode.php',
@@ -574,7 +581,6 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TypeFormatter\\UnitEnumFormatter' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TypeFormatter/UnitEnumFormatter.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Compiler\\TypeFormatter\\UnsureTypeFormatter' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Compiler/TypeFormatter/UnsureTypeFormatter.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\EmptyObject' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/EmptyObject.php',
- 'CuyZ\\Valinor\\Normalizer\\Transformer\\EvaluatedTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\RecursiveTransformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/RecursiveTransformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\Transformer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/Transformer.php',
'CuyZ\\Valinor\\Normalizer\\Transformer\\TransformerContainer' => __DIR__ . '/..' . '/cuyz/valinor/src/Normalizer/Transformer/TransformerContainer.php',
@@ -731,8 +737,6 @@ class ComposerStaticInita1f5f7c74275d47a45049a2936db1d0d
'CuyZ\\Valinor\\Utility\\IsSingleton' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/IsSingleton.php',
'CuyZ\\Valinor\\Utility\\Package' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Package.php',
'CuyZ\\Valinor\\Utility\\Polyfill' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Polyfill.php',
- 'CuyZ\\Valinor\\Utility\\Priority\\HasPriority' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Priority/HasPriority.php',
- 'CuyZ\\Valinor\\Utility\\Priority\\PrioritizedList' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Priority/PrioritizedList.php',
'CuyZ\\Valinor\\Utility\\Reflection\\NamespaceFinder' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Reflection/NamespaceFinder.php',
'CuyZ\\Valinor\\Utility\\Reflection\\PhpParser' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Reflection/PhpParser.php',
'CuyZ\\Valinor\\Utility\\Reflection\\Reflection' => __DIR__ . '/..' . '/cuyz/valinor/src/Utility/Reflection/Reflection.php',
diff --git a/wcfsetup/install/files/lib/system/api/composer/installed.json b/wcfsetup/install/files/lib/system/api/composer/installed.json
index 51b764f7f0d..ae893713ed4 100644
--- a/wcfsetup/install/files/lib/system/api/composer/installed.json
+++ b/wcfsetup/install/files/lib/system/api/composer/installed.json
@@ -65,23 +65,22 @@
},
{
"name": "cuyz/valinor",
- "version": "1.17.0",
- "version_normalized": "1.17.0.0",
+ "version": "2.1.1",
+ "version_normalized": "2.1.1.0",
"source": {
"type": "git",
"url": "https://github.com/CuyZ/Valinor.git",
- "reference": "9eb2802797e36f3af1fd6e13ff23e4e3d0058022"
+ "reference": "f51e9beebdefc968cbfcd4a2c561b40575288610"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/9eb2802797e36f3af1fd6e13ff23e4e3d0058022",
- "reference": "9eb2802797e36f3af1fd6e13ff23e4e3d0058022",
+ "url": "https://api.github.com/repos/CuyZ/Valinor/zipball/f51e9beebdefc968cbfcd4a2c561b40575288610",
+ "reference": "f51e9beebdefc968cbfcd4a2c561b40575288610",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2.0",
- "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
- "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
+ "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0"
},
"conflict": {
"phpstan/phpstan": "<1.0 || >= 3.0",
@@ -100,7 +99,7 @@
"rector/rector": "^2.0",
"vimeo/psalm": "^6.0"
},
- "time": "2025-06-20T06:40:38+00:00",
+ "time": "2025-07-23T20:32:01+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -134,7 +133,7 @@
],
"support": {
"issues": "https://github.com/CuyZ/Valinor/issues",
- "source": "https://github.com/CuyZ/Valinor/tree/1.17.0"
+ "source": "https://github.com/CuyZ/Valinor/tree/2.1.1"
},
"funding": [
{
diff --git a/wcfsetup/install/files/lib/system/api/composer/installed.php b/wcfsetup/install/files/lib/system/api/composer/installed.php
index 6ec37435072..84d6db16fa1 100644
--- a/wcfsetup/install/files/lib/system/api/composer/installed.php
+++ b/wcfsetup/install/files/lib/system/api/composer/installed.php
@@ -3,7 +3,7 @@
'name' => '__root__',
'pretty_version' => '6.2.x-dev',
'version' => '6.2.9999999.9999999-dev',
- 'reference' => 'f6d3061d5091741d703ba7ca3e55e2a1339c1a2d',
+ 'reference' => '324074156c23476a60156eaceaaa12a890aacffb',
'type' => 'project',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -13,7 +13,7 @@
'__root__' => array(
'pretty_version' => '6.2.x-dev',
'version' => '6.2.9999999.9999999-dev',
- 'reference' => 'f6d3061d5091741d703ba7ca3e55e2a1339c1a2d',
+ 'reference' => '324074156c23476a60156eaceaaa12a890aacffb',
'type' => 'project',
'install_path' => __DIR__ . '/../',
'aliases' => array(),
@@ -29,9 +29,9 @@
'dev_requirement' => false,
),
'cuyz/valinor' => array(
- 'pretty_version' => '1.17.0',
- 'version' => '1.17.0.0',
- 'reference' => '9eb2802797e36f3af1fd6e13ff23e4e3d0058022',
+ 'pretty_version' => '2.1.1',
+ 'version' => '2.1.1.0',
+ 'reference' => 'f51e9beebdefc968cbfcd4a2c561b40575288610',
'type' => 'library',
'install_path' => __DIR__ . '/../cuyz/valinor',
'aliases' => array(),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/composer.json b/wcfsetup/install/files/lib/system/api/cuyz/valinor/composer.json
index f4937a193ba..ceefe270d2e 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/composer.json
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/composer.json
@@ -16,8 +16,7 @@
],
"require": {
"php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0",
- "composer-runtime-api": "^2.0",
- "psr/simple-cache": "^1.0 || ^2.0 || ^3.0"
+ "composer-runtime-api": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^10.5",
@@ -64,6 +63,10 @@
"php-cs-fixer fix",
"rector"
],
+ "test": [
+ "@putenv XDEBUG_MODE=off",
+ "phpunit"
+ ],
"mutation": [
"infection --threads=max --git-diff-lines"
],
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/Extension/SuppressPureErrors.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/Extension/SuppressPureErrors.php
new file mode 100644
index 00000000000..28ff61e6936
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/Extension/SuppressPureErrors.php
@@ -0,0 +1,91 @@
+getIdentifier() !== 'argument.type') {
+ return false;
+ }
+
+ if (! $node instanceof Node\Expr\MethodCall) {
+ return false;
+ }
+
+ $type = $scope->getType($node->var);
+
+ if (! $type->isObject()->yes()) {
+ return false;
+ }
+
+ if (! Polyfill::array_find($type->getObjectClassNames(), fn (string $className) => $className === MapperBuilder::class || $className === NormalizerBuilder::class)) {
+ return false;
+ }
+
+ if (! $node->name instanceof Node\Identifier) {
+ return false;
+ }
+
+ $methodName = $node->name->toString();
+
+ if (! in_array($methodName, [
+ 'infer',
+ 'registerConstructor',
+ 'registerConverter',
+ 'registerTransformer',
+ ], true)) {
+ return false;
+ }
+
+ if ($node->args === []) {
+ return false;
+ }
+
+ $callableArgumentIndex = [
+ 'infer' => 1,
+ 'registerConstructor' => 0,
+ 'registerConverter' => 0,
+ 'registerTransformer' => 0,
+ ][$methodName];
+
+ $argument = $node->args[$callableArgumentIndex];
+
+ if (! $argument instanceof Node\Arg) {
+ return false;
+ }
+
+ $argumentType = $scope->getType($argument->value);
+
+ if ($argumentType->isCallable()->yes()) {
+ return true;
+ }
+
+ if ($argumentType->isArray()->yes() && $argumentType->getIterableValueType()->isCallable()->yes()) {
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/valinor-phpstan-suppress-pure-errors.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/valinor-phpstan-suppress-pure-errors.php
new file mode 100644
index 00000000000..59981b50b3a
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/PHPStan/valinor-phpstan-suppress-pure-errors.php
@@ -0,0 +1,14 @@
+ [
+ [
+ 'class' => SuppressPureErrors::class,
+ 'tags' => ['phpstan.ignoreErrorExtension'],
+ ],
+ ],
+];
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/Psalm/valinor-psalm-suppress-pure-errors.xml b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/Psalm/valinor-psalm-suppress-pure-errors.xml
new file mode 100644
index 00000000000..2b7aaf5e270
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/qa/Psalm/valinor-psalm-suppress-pure-errors.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Cache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Cache.php
new file mode 100644
index 00000000000..6b08c940fb1
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Cache.php
@@ -0,0 +1,26 @@
+ */
+ public readonly array $filesToWatch = [],
+ ) {}
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/ChainCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/ChainCache.php
deleted file mode 100644
index 57e609845d9..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/ChainCache.php
+++ /dev/null
@@ -1,136 +0,0 @@
-
- */
-final class ChainCache implements WarmupCache
-{
- /** @var array> */
- private array $delegates;
-
- private int $count;
-
- /**
- * @param CacheInterface ...$delegates
- */
- public function __construct(CacheInterface ...$delegates)
- {
- $this->delegates = $delegates;
- $this->count = count($delegates);
- }
-
- public function warmup(): void
- {
- foreach ($this->delegates as $delegate) {
- if ($delegate instanceof WarmupCache) {
- $delegate->warmup();
- }
- }
- }
-
- public function get($key, $default = null): mixed
- {
- foreach ($this->delegates as $i => $delegate) {
- $value = $delegate->get($key, $default);
-
- if (null !== $value) {
- while (--$i >= 0) {
- $this->delegates[$i]->set($key, $value);
- }
-
- return $value;
- }
- }
-
- return $default;
- }
-
- public function set($key, $value, $ttl = null): bool
- {
- $saved = true;
- $i = $this->count;
-
- while ($i--) {
- $saved = $this->delegates[$i]->set($key, $value, $ttl) && $saved;
- }
-
- return $saved;
- }
-
- public function delete($key): bool
- {
- $deleted = true;
- $i = $this->count;
-
- while ($i--) {
- $deleted = $this->delegates[$i]->delete($key) && $deleted;
- }
-
- return $deleted;
- }
-
- public function clear(): bool
- {
- $cleared = true;
- $i = $this->count;
-
- while ($i--) {
- $cleared = $this->delegates[$i]->clear() && $cleared;
- }
-
- return $cleared;
- }
-
- /**
- * @return Traversable
- */
- public function getMultiple($keys, $default = null): Traversable
- {
- foreach ($keys as $key) {
- yield $key => $this->get($key, $default);
- }
- }
-
- public function setMultiple($values, $ttl = null): bool
- {
- $saved = true;
-
- foreach ($values as $key => $value) {
- $saved = $this->set($key, $value, $ttl) && $saved;
- }
-
- return $saved;
- }
-
- public function deleteMultiple($keys): bool
- {
- $deleted = true;
-
- foreach ($keys as $key) {
- $deleted = $this->delete($key) && $deleted;
- }
-
- return $deleted;
- }
-
- public function has($key): bool
- {
- foreach ($this->delegates as $cache) {
- if ($cache->has($key)) {
- return true;
- }
- }
-
- return false;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileSystemCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileSystemCache.php
index 159467364c4..7d0cd6b4124 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileSystemCache.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileSystemCache.php
@@ -7,14 +7,9 @@
use CuyZ\Valinor\Cache\Exception\CacheDirectoryNotWritable;
use CuyZ\Valinor\Cache\Exception\CompiledPhpCacheFileNotWritten;
use CuyZ\Valinor\Cache\Exception\CorruptedCompiledPhpCacheFile;
-use CuyZ\Valinor\Definition\ClassDefinition;
-use CuyZ\Valinor\Definition\FunctionDefinition;
-use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
-use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
-use CuyZ\Valinor\Normalizer\Transformer\EvaluatedTransformer;
use Error;
+
use FilesystemIterator;
-use Traversable;
use function bin2hex;
use function file_exists;
@@ -25,113 +20,81 @@
use function rename;
use function rmdir;
use function str_contains;
+use function umask;
use function unlink;
-use function var_export;
/**
* @api
*
* @template EntryType
- * @implements WarmupCache
+ * @implements Cache
*/
-final class FileSystemCache implements WarmupCache
+final class FileSystemCache implements Cache
{
- private const TEMPORARY_DIR_PERMISSION = 510;
-
private const GENERATED_MESSAGE = 'Generated by ' . self::class;
- private string $cacheDir;
-
- private ClassDefinitionCompiler $classDefinitionCompiler;
-
- private FunctionDefinitionCompiler $functionDefinitionCompiler;
+ public function __construct(
+ private string $cacheDir,
+ ) {}
- public function __construct(string $cacheDir)
+ /** @internal */
+ public function get(string $key, mixed ...$arguments): mixed
{
- $this->cacheDir = $cacheDir;
- $this->classDefinitionCompiler = new ClassDefinitionCompiler();
- $this->functionDefinitionCompiler = new FunctionDefinitionCompiler();
- }
-
- public function warmup(): void
- {
- $this->createTemporaryDir();
- }
-
- public function has($key): bool
- {
- $filename = $this->path($key);
-
- return file_exists($filename);
- }
-
- public function get($key, $default = null): mixed
- {
- $filename = $this->path($key);
+ $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
if (! file_exists($filename)) {
- return $default;
+ return null;
}
try {
- return include $filename;
+ return (require $filename)(...$arguments); // @phpstan-ignore callable.nonCallable
} catch (Error) {
throw new CorruptedCompiledPhpCacheFile($filename);
}
}
- public function set($key, $value, $ttl = null): bool
+ /** @internal */
+ public function set(string $key, CacheEntry $entry): void
{
- $filename = $this->path($key);
-
- $code = $this->compile($value);
+ $tmpDir = $this->cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
+ $filename = $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
- $tmpDir = $this->createTemporaryDir();
+ if (! is_dir($tmpDir) && ! @mkdir($tmpDir, 0777, true)) {
+ throw new CacheDirectoryNotWritable($this->cacheDir);
+ }
/** @infection-ignore-all */
$tmpFilename = $tmpDir . DIRECTORY_SEPARATOR . bin2hex(random_bytes(16));
try {
+ $code = 'code;" . PHP_EOL;
+
if (! @file_put_contents($tmpFilename, $code)) {
throw new CompiledPhpCacheFileNotWritten($tmpFilename);
}
- if (! file_exists($filename) && ! @rename($tmpFilename, $filename)) {
+ if (! @rename($tmpFilename, $filename)) {
throw new CompiledPhpCacheFileNotWritten($filename);
}
- } finally {
- if (file_exists($tmpFilename)) {
- unlink($tmpFilename);
- }
- }
- return true;
- }
-
- public function delete($key): bool
- {
- $filename = $this->path($key);
-
- if (file_exists($filename)) {
- return @unlink($filename);
+ @chmod($filename, 0666 & ~umask());
+ } finally {
+ @unlink($tmpFilename);
}
-
- return true;
}
- public function clear(): bool
+ public function clear(): void
{
if (! is_dir($this->cacheDir)) {
- return true;
+ return;
}
- $success = true;
$shouldDeleteRootDir = true;
/** @var FilesystemIterator $file */
foreach (new FilesystemIterator($this->cacheDir) as $file) {
if ($file->getFilename() === '.valinor.tmp') {
- $success = @rmdir($this->cacheDir . DIRECTORY_SEPARATOR . $file->getFilename()) && $success;
+ @rmdir($file->getPathname());
continue;
}
@@ -147,77 +110,11 @@ public function clear(): bool
continue;
}
- $success = @unlink($this->cacheDir . DIRECTORY_SEPARATOR . $file->getFilename()) && $success;
+ @unlink($file->getPathname());
}
if ($shouldDeleteRootDir) {
- $success = @rmdir($this->cacheDir) && $success;
+ @rmdir($this->cacheDir);
}
-
- return $success;
- }
-
- /**
- * @return Traversable
- */
- public function getMultiple($keys, $default = null): Traversable
- {
- foreach ($keys as $key) {
- yield $key => $this->get($key, $default);
- }
- }
-
- public function setMultiple($values, $ttl = null): bool
- {
- foreach ($values as $key => $value) {
- $this->set($key, $value, $ttl);
- }
-
- return true;
- }
-
- public function deleteMultiple($keys): bool
- {
- $deleted = true;
-
- foreach ($keys as $key) {
- $deleted = $this->delete($key) && $deleted;
- }
-
- return $deleted;
- }
-
- private function compile(mixed $value): string
- {
- $generatedMessage = self::GENERATED_MESSAGE;
-
- $code = match (true) {
- $value instanceof ClassDefinition => $this->classDefinitionCompiler->compile($value),
- $value instanceof FunctionDefinition => $this->functionDefinitionCompiler->compile($value),
- $value instanceof EvaluatedTransformer => $value->code,
- default => var_export($value, true),
- };
-
- return <<cacheDir . DIRECTORY_SEPARATOR . '.valinor.tmp';
-
- if (! is_dir($tmpDir) && ! @mkdir($tmpDir, self::TEMPORARY_DIR_PERMISSION, true)) {
- throw new CacheDirectoryNotWritable($this->cacheDir);
- }
-
- return $tmpDir;
- }
-
- private function path(string $key): string
- {
- /** @infection-ignore-all */
- return $this->cacheDir . DIRECTORY_SEPARATOR . $key . '.php';
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileWatchingCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileWatchingCache.php
index 2ec5fb52aaf..3956e140d9f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileWatchingCache.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/FileWatchingCache.php
@@ -4,14 +4,9 @@
namespace CuyZ\Valinor\Cache;
-use CuyZ\Valinor\Definition\ClassDefinition;
-use CuyZ\Valinor\Definition\FunctionDefinition;
-use CuyZ\Valinor\Utility\Reflection\Reflection;
-use Psr\SimpleCache\CacheInterface;
-
use function file_exists;
use function filemtime;
-use function is_string;
+use function var_export;
/**
* This cache implementation will watch the files of the application and
@@ -27,124 +22,53 @@
*
* @api
*
- * @phpstan-type TimestampsArray = array
* @template EntryType
- * @implements WarmupCache
+ * @implements Cache
*/
-final class FileWatchingCache implements WarmupCache
+final class FileWatchingCache implements Cache
{
- /** @var array */
+ /** @var array> */
private array $timestamps = [];
public function __construct(
- /** @var CacheInterface */
- private CacheInterface $delegate
+ /** @var Cache */
+ private Cache $delegate,
) {}
- public function warmup(): void
+ /** @internal */
+ public function get(string $key, mixed ...$arguments): mixed
{
- if ($this->delegate instanceof WarmupCache) {
- $this->delegate->warmup();
+ $this->timestamps[$key] ??= $this->delegate->get("$key.timestamps"); // @phpstan-ignore assign.propertyType
+
+ if ($this->timestamps[$key] === null) {
+ return null;
}
- }
- public function has($key): bool
- {
- foreach ($this->timestamps($key) as $fileName => $timestamp) {
+ assert(is_array($this->timestamps[$key]));
+
+ foreach ($this->timestamps[$key] as $fileName => $timestamp) {
if (! file_exists($fileName)) {
- return false;
+ return null;
}
if (filemtime($fileName) !== $timestamp) {
- return false;
+ return null;
}
}
- return $this->delegate->has($key);
- }
-
- public function get($key, $default = null): mixed
- {
- if (! $this->has($key)) {
- return $default;
- }
-
- return $this->delegate->get($key, $default);
- }
-
- public function set($key, $value, $ttl = null): bool
- {
- $this->saveTimestamps($key, $value);
-
- return $this->delegate->set($key, $value, $ttl);
- }
-
- public function delete($key): bool
- {
- return $this->delegate->delete($key);
+ return $this->delegate->get($key, ...$arguments);
}
- public function clear(): bool
+ /** @internal */
+ public function set(string $key, CacheEntry $entry): void
{
- $this->timestamps = [];
-
- return $this->delegate->clear();
- }
-
- public function getMultiple($keys, $default = null): iterable
- {
- return $this->delegate->getMultiple($keys, $default);
- }
-
- public function setMultiple($values, $ttl = null): bool
- {
- foreach ($values as $key => $value) {
- $this->saveTimestamps($key, $value);
- }
+ $this->delegate->set($key, $entry);
- return $this->delegate->setMultiple($values, $ttl);
- }
-
- public function deleteMultiple($keys): bool
- {
- return $this->delegate->deleteMultiple($keys);
- }
-
- /**
- * @return TimestampsArray
- */
- private function timestamps(string $key): array
- {
- return $this->timestamps[$key] ??= $this->delegate->get("$key.timestamps", []); // @phpstan-ignore-line
- }
-
- private function saveTimestamps(string $key, mixed $value): void
- {
$this->timestamps[$key] = [];
- $fileNames = [];
-
- if ($value instanceof ClassDefinition) {
- $reflection = Reflection::class($value->name);
-
- do {
- $fileNames[] = $reflection->getFileName();
- } while ($reflection = $reflection->getParentClass());
- }
-
- if ($value instanceof FunctionDefinition) {
- $fileNames[] = $value->fileName;
- }
-
- foreach ($fileNames as $fileName) {
- if (! is_string($fileName)) {
- // @infection-ignore-all
- continue;
- }
-
+ foreach ($entry->filesToWatch as $fileName) {
$time = @filemtime($fileName);
- // @infection-ignore-all
if (false === $time) {
continue;
}
@@ -152,8 +76,15 @@ private function saveTimestamps(string $key, mixed $value): void
$this->timestamps[$key][$fileName] = $time;
}
- if (! empty($this->timestamps[$key])) {
- $this->delegate->set("$key.timestamps", $this->timestamps[$key]);
- }
+ $code = 'fn () => ' . var_export($this->timestamps[$key], true);
+
+ $this->delegate->set("$key.timestamps", new CacheEntry($code));
+ }
+
+ public function clear(): void
+ {
+ $this->timestamps = [];
+
+ $this->delegate->clear();
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/KeySanitizerCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/KeySanitizerCache.php
index d2445c8714d..61edb56c0a7 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/KeySanitizerCache.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/KeySanitizerCache.php
@@ -6,8 +6,6 @@
use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\Utility\Package;
-use Psr\SimpleCache\CacheInterface;
-use Traversable;
use function hash;
use function strstr;
@@ -16,98 +14,44 @@
* @internal
*
* @template EntryType
- * @implements WarmupCache
+ * @implements Cache
*/
-final class KeySanitizerCache implements WarmupCache
+final class KeySanitizerCache implements Cache
{
private static string $version;
public function __construct(
- /** @var CacheInterface */
- private CacheInterface $delegate,
+ /** @var Cache */
+ private Cache $delegate,
private Settings $settings,
) {}
- /**
- * Two things:
- * 1. We append the current version of the package to the cache key in order
- * to avoid collisions between entries from different versions of the
- * library.
- * 2. The key is hashed so that it does not contain illegal characters.
- * @see https://www.php-fig.org/psr/psr-16/#12-definitions
- *
- * @infection-ignore-all
- */
- private function sanitize(string $key): string
- {
- self::$version ??= PHP_VERSION . '/' . Package::version();
-
- $firstPart = strstr($key, "\0", before_needle: true);
-
- return $firstPart . hash('xxh128', $key . $this->settings->hash() . self::$version);
- }
-
- public function warmup(): void
- {
- if ($this->delegate instanceof WarmupCache) {
- $this->delegate->warmup();
- }
- }
-
- public function get($key, $default = null): mixed
- {
- return $this->delegate->get($this->sanitize($key), $default);
- }
-
- public function set($key, $value, $ttl = null): bool
- {
- return $this->delegate->set($this->sanitize($key), $value, $ttl);
- }
-
- public function delete($key): bool
+ public function get(string $key, mixed ...$arguments): mixed
{
- return $this->delegate->delete($this->sanitize($key));
+ return $this->delegate->get($this->sanitize($key), ...$arguments);
}
- public function clear(): bool
+ public function set(string $key, CacheEntry $entry): void
{
- return $this->delegate->clear();
+ $this->delegate->set($this->sanitize($key), $entry);
}
- public function has($key): bool
+ public function clear(): void
{
- return $this->delegate->has($this->sanitize($key));
+ $this->delegate->clear();
}
/**
- * @return Traversable
+ * @return non-empty-string
*/
- public function getMultiple($keys, $default = null): Traversable
- {
- foreach ($keys as $key) {
- yield $key => $this->delegate->get($this->sanitize($key), $default);
- }
- }
-
- public function setMultiple($values, $ttl = null): bool
- {
- $versionedValues = [];
-
- foreach ($values as $key => $value) {
- $versionedValues[$this->sanitize($key)] = $value;
- }
-
- return $this->delegate->setMultiple($versionedValues, $ttl);
- }
-
- public function deleteMultiple($keys): bool
+ private function sanitize(string $key): string
{
- $transformedKeys = [];
+ // @infection-ignore-all
+ self::$version ??= PHP_VERSION . '/' . Package::version();
- foreach ($keys as $key) {
- $transformedKeys[] = $this->sanitize($key);
- }
+ $firstPart = strstr($key, "\0", before_needle: true);
- return $this->delegate->deleteMultiple($transformedKeys);
+ // @infection-ignore-all
+ return $firstPart . hash('xxh128', $key . $this->settings->hash() . self::$version);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/RuntimeCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/RuntimeCache.php
index 90eeef5a815..a6e35111eeb 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/RuntimeCache.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/RuntimeCache.php
@@ -4,88 +4,36 @@
namespace CuyZ\Valinor\Cache;
-use CuyZ\Valinor\Normalizer\Transformer\EvaluatedTransformer;
-use Psr\SimpleCache\CacheInterface;
-
/**
- * Simple PSR-16-compatible runtime cache implementation.
- *
- * Used by default by the library so that entries can be cached in memory during
- * runtime.
- *
- * @link http://www.php-fig.org/psr/psr-16/
- *
* @internal
*
* @template EntryType
- * @implements CacheInterface
+ * @implements Cache
*/
-final class RuntimeCache implements CacheInterface
+final class RuntimeCache implements Cache
{
- /** @var array */
+ /** @var array */
private array $entries = [];
- public function get($key, $default = null): mixed
- {
- return $this->entries[$key] ?? $default;
- }
+ public function __construct(
+ /** @var Cache */
+ private Cache $delegate,
+ ) {}
- public function set($key, $value, $ttl = null): bool
+ public function get(string $key, mixed ...$arguments): mixed
{
- if ($value instanceof EvaluatedTransformer) {
- return false;
- }
-
- $this->entries[$key] = $value;
-
- return true;
+ return $this->entries[$key] ??= $this->delegate->get($key, ...$arguments);
}
- public function delete($key): bool
+ public function set(string $key, CacheEntry $entry): void
{
- unset($this->entries[$key]);
-
- return true;
+ $this->delegate->set($key, $entry);
}
- public function clear(): bool
+ public function clear(): void
{
$this->entries = [];
- return true;
- }
-
- public function getMultiple($keys, $default = null): iterable
- {
- $entries = [];
-
- foreach ($keys as $key) {
- $entries[$key] = $this->get($key, $default);
- }
-
- return $entries;
- }
-
- public function setMultiple($values, $ttl = null): bool
- {
- foreach ($values as $key => $value) {
- $this->set($key, $value, $ttl);
- }
-
- return true;
- }
-
- public function deleteMultiple($keys): bool
- {
- foreach ($keys as $key) {
- $this->delete($key);
- }
-
- return true;
- }
-
- public function has($key): bool
- {
- return isset($this->entries[$key]);
+ $this->delegate->clear();
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/TypeFilesWatcher.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/TypeFilesWatcher.php
new file mode 100644
index 00000000000..eeb0189d9a6
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/TypeFilesWatcher.php
@@ -0,0 +1,120 @@
+
+ */
+ public function for(Type $type): 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),
+ ];
+
+ // Removing the duplicates
+ $files = array_unique($files);
+
+ // Filtering the empty/invalid file names
+ $files = array_filter($files, fn ($value) => is_string($value));
+
+ /** @var list */
+ return array_values($files);
+ }
+
+ /**
+ * @param array $files
+ * @return array
+ */
+ private function filesToWatch(Type $type, array $files = []): array
+ {
+ if (isset($files[$type->toString()])) {
+ // Prevents infinite loop in case of circular references
+ return [];
+ }
+
+ if ($type instanceof CompositeType) {
+ foreach ($type->traverse() as $subType) {
+ $files = [...$files, ...$this->filesToWatch($subType, $files)];
+ }
+ }
+
+ if ($type instanceof ObjectType) {
+ $fileName = Reflection::class($type->className())->getFileName();
+
+ if (! $fileName) {
+ return [];
+ }
+
+ $files[$type->toString()] = $fileName;
+
+ $class = $this->classDefinitionRepository->for($type);
+
+ foreach ($class->properties as $property) {
+ $files = [...$files, ...$this->filesToWatch($property->type, $files)];
+ }
+ }
+
+ return $files;
+ }
+
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php
index 568ec4ef4f2..444d2693358 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/Warmup/RecursiveCacheWarmupService.php
@@ -5,7 +5,6 @@
namespace CuyZ\Valinor\Cache\Warmup;
use CuyZ\Valinor\Cache\Exception\InvalidSignatureToWarmup;
-use CuyZ\Valinor\Cache\WarmupCache;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Mapper\Object\Factory\ObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Tree\Builder\ObjectImplementations;
@@ -15,7 +14,6 @@
use CuyZ\Valinor\Type\Parser\TypeParser;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\InterfaceType;
-use Psr\SimpleCache\CacheInterface;
use function in_array;
@@ -25,12 +23,8 @@ final class RecursiveCacheWarmupService
/** @var list */
private array $classesWarmedUp = [];
- private bool $warmupWasDone = false;
-
public function __construct(
private TypeParser $parser,
- /** @var CacheInterface */
- private CacheInterface $cache,
private ObjectImplementations $implementations,
private ClassDefinitionRepository $classDefinitionRepository,
private ObjectBuilderFactory $objectBuilderFactory
@@ -38,14 +32,6 @@ public function __construct(
public function warmup(string ...$signatures): void
{
- if (! $this->warmupWasDone) {
- $this->warmupWasDone = true;
-
- if ($this->cache instanceof WarmupCache) {
- $this->cache->warmup();
- }
- }
-
foreach ($signatures as $signature) {
try {
$this->warmupType($this->parser->parse($signature));
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/WarmupCache.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/WarmupCache.php
deleted file mode 100644
index 97a46843007..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Cache/WarmupCache.php
+++ /dev/null
@@ -1,16 +0,0 @@
-
- */
-interface WarmupCache extends CacheInterface
-{
- public function warmup(): void;
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Attributes.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Attributes.php
index 7cda965ff51..a6e3476842f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Attributes.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Attributes.php
@@ -58,6 +58,14 @@ public function filter(callable $callback): self
);
}
+ public function merge(self $other): self
+ {
+ $clone = clone $this;
+ $clone->attributes = [...$this->attributes, ...$other->attributes];
+
+ return $clone;
+ }
+
public function count(): int
{
return count($this->attributes);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionsContainer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionsContainer.php
index 0381780f164..164e8cc2348 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionsContainer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/FunctionsContainer.php
@@ -11,6 +11,7 @@
use function array_keys;
use function count;
+use function iterator_to_array;
/**
* @internal
@@ -38,6 +39,14 @@ public function get(string|int $key): FunctionObject
return $this->function($key);
}
+ /**
+ * @return array
+ */
+ public function toArray(): array
+ {
+ return iterator_to_array($this);
+ }
+
public function getIterator(): Traversable
{
foreach (array_keys($this->callables) as $key) {
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php
deleted file mode 100644
index 89a720e4b72..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheClassDefinitionRepository.php
+++ /dev/null
@@ -1,38 +0,0 @@
- */
- private CacheInterface $cache
- ) {}
-
- public function for(ObjectType $type): ClassDefinition
- {
- // @infection-ignore-all
- $key = "class-definition-\0" . $type->toString();
-
- $entry = $this->cache->get($key);
-
- if ($entry) {
- return $entry;
- }
-
- $class = $this->delegate->for($type);
-
- $this->cache->set($key, $class);
-
- return $class;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledClassDefinitionRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledClassDefinitionRepository.php
new file mode 100644
index 00000000000..41205f924b8
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledClassDefinitionRepository.php
@@ -0,0 +1,68 @@
+ */
+ private Cache $cache,
+ private ClassDefinitionCompiler $compiler,
+ ) {}
+
+ public function for(ObjectType $type): ClassDefinition
+ {
+ // @infection-ignore-all
+ $key = "class-definition-\0" . $type->toString();
+
+ $entry = $this->cache->get($key);
+
+ if ($entry) {
+ return $entry;
+ }
+
+ $class = $this->delegate->for($type);
+
+ $code = 'fn () => ' . $this->compiler->compile($class);
+ $filesToWatch = $this->filesToWatch($type);
+
+ $this->cache->set($key, new CacheEntry($code, $filesToWatch));
+
+ /** @var ClassDefinition */
+ return $this->cache->get($key);
+ }
+
+ /**
+ * @return list
+ */
+ private function filesToWatch(ObjectType $type): array
+ {
+ $reflection = Reflection::class($type->className());
+
+ $fileNames = [];
+
+ do {
+ $fileName = $reflection->getFileName();
+
+ if (is_string($fileName)) {
+ $fileNames[] = $fileName;
+ }
+ } while ($reflection = $reflection->getParentClass());
+
+ return $fileNames;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledFunctionDefinitionRepository.php
similarity index 55%
rename from wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php
rename to wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledFunctionDefinitionRepository.php
index 265b8189366..e0cf4806b00 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CacheFunctionDefinitionRepository.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/CompiledFunctionDefinitionRepository.php
@@ -4,18 +4,21 @@
namespace CuyZ\Valinor\Definition\Repository\Cache;
+use CuyZ\Valinor\Cache\Cache;
+use CuyZ\Valinor\Cache\CacheEntry;
use CuyZ\Valinor\Definition\FunctionDefinition;
+use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Utility\Reflection\Reflection;
-use Psr\SimpleCache\CacheInterface;
/** @internal */
-final class CacheFunctionDefinitionRepository implements FunctionDefinitionRepository
+final class CompiledFunctionDefinitionRepository implements FunctionDefinitionRepository
{
public function __construct(
private FunctionDefinitionRepository $delegate,
- /** @var CacheInterface */
- private CacheInterface $cache
+ /** @var Cache */
+ private Cache $cache,
+ private FunctionDefinitionCompiler $compiler,
) {}
public function for(callable $function): FunctionDefinition
@@ -33,8 +36,12 @@ public function for(callable $function): FunctionDefinition
$definition = $this->delegate->for($function);
- $this->cache->set($key, $definition);
+ $code = 'fn () => ' . $this->compiler->compile($definition);
+ $filesToWatch = $definition->fileName ? [$definition->fileName] : [];
- return $definition;
+ $this->cache->set($key, new CacheEntry($code, $filesToWatch));
+
+ /** @var FunctionDefinition */
+ return $this->cache->get($key);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php
index 7616ee46867..40c4feca8d3 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php
@@ -9,7 +9,6 @@
use CuyZ\Valinor\Definition\PropertyDefinition;
use function array_map;
-use function assert;
use function implode;
use function iterator_to_array;
use function var_export;
@@ -27,17 +26,15 @@ final class ClassDefinitionCompiler
public function __construct()
{
- $this->typeCompiler = new TypeCompiler();
$this->attributesCompiler = new AttributesCompiler($this);
+ $this->typeCompiler = new TypeCompiler($this->attributesCompiler);
$this->methodCompiler = new MethodDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
$this->propertyCompiler = new PropertyDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
}
- public function compile(mixed $value): string
+ public function compile(ClassDefinition $value): string
{
- assert($value instanceof ClassDefinition);
-
$name = var_export($value->name, true);
$type = $this->typeCompiler->compile($value->type);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php
index 9235f686f2d..2f5f0dd4990 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/FunctionDefinitionCompiler.php
@@ -20,16 +20,14 @@ final class FunctionDefinitionCompiler
public function __construct()
{
- $this->typeCompiler = new TypeCompiler();
$this->attributesCompiler = new AttributesCompiler(new ClassDefinitionCompiler());
+ $this->typeCompiler = new TypeCompiler($this->attributesCompiler);
$this->parameterCompiler = new ParameterDefinitionCompiler($this->typeCompiler, $this->attributesCompiler);
}
- public function compile(mixed $value): string
+ public function compile(FunctionDefinition $value): string
{
- assert($value instanceof FunctionDefinition);
-
$parameters = array_map(
fn (ParameterDefinition $parameter) => $this->parameterCompiler->compile($parameter),
iterator_to_array($value->parameters)
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php
index e1f4dfe0516..d8d0225fc27 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/Compiler/TypeCompiler.php
@@ -51,6 +51,10 @@
/** @internal */
final class TypeCompiler
{
+ public function __construct(
+ private AttributesCompiler $attributesCompiler,
+ ) {}
+
public function compile(Type $type): string
{
$class = $type::class;
@@ -187,7 +191,8 @@ private function compileArrayShapeElement(ShapedArrayElement $element): string
$key = $this->compile($element->key());
$type = $this->compile($element->type());
$optional = var_export($element->isOptional(), true);
+ $attributes = $this->attributesCompiler->compile($element->attributes());
- return "new $class($key, $type, $optional)";
+ return "new $class($key, $type, $optional, $attributes)";
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryClassDefinitionRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryClassDefinitionRepository.php
new file mode 100644
index 00000000000..4ac39910877
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryClassDefinitionRepository.php
@@ -0,0 +1,25 @@
+ */
+ private array $classDefinitions = [];
+
+ public function __construct(
+ private ClassDefinitionRepository $delegate,
+ ) {}
+
+ public function for(ObjectType $type): ClassDefinition
+ {
+ return $this->classDefinitions[$type->toString()] ??= $this->delegate->for($type);
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryFunctionDefinitionRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryFunctionDefinitionRepository.php
new file mode 100644
index 00000000000..2a64e00b6e5
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Cache/InMemoryFunctionDefinitionRepository.php
@@ -0,0 +1,30 @@
+ */
+ private array $functionDefinitions = [];
+
+ public function __construct(
+ private FunctionDefinitionRepository $delegate,
+ ) {}
+
+ public function for(callable $function): FunctionDefinition
+ {
+ $reflection = Reflection::function($function);
+
+ // @infection-ignore-all
+ $key = $reflection->getFileName() . ':' . $reflection->getStartLine() . '-' . $reflection->getEndLine();
+
+ return $this->functionDefinitions[$key] ??= $this->delegate->for($function);
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php
index a56d6d59eff..d47139876c7 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Definition/Repository/Reflection/ReflectionAttributesRepository.php
@@ -7,6 +7,7 @@
use CuyZ\Valinor\Definition\AttributeDefinition;
use CuyZ\Valinor\Definition\Repository\AttributesRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
+use CuyZ\Valinor\Mapper\AsConverter;
use CuyZ\Valinor\Normalizer\AsTransformer;
use CuyZ\Valinor\Type\Types\NativeClassType;
use CuyZ\Valinor\Utility\Reflection\Reflection;
@@ -37,9 +38,8 @@ function (ReflectionAttribute $attribute) {
}
}
- $parentAttributes = Reflection::class($attribute->getName())->getAttributes(AsTransformer::class);
-
- return $parentAttributes !== [];
+ return Reflection::class($attribute->getName())->getAttributes(AsConverter::class) !== []
+ || Reflection::class($attribute->getName())->getAttributes(AsTransformer::class) !== [];
},
);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Container.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Container.php
index 060a1eb43fd..19def85cb27 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Container.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Container.php
@@ -4,20 +4,25 @@
namespace CuyZ\Valinor\Library;
-use CuyZ\Valinor\Cache\ChainCache;
+use CuyZ\Valinor\Cache\Cache;
use CuyZ\Valinor\Cache\KeySanitizerCache;
use CuyZ\Valinor\Cache\RuntimeCache;
+use CuyZ\Valinor\Cache\TypeFilesWatcher;
use CuyZ\Valinor\Cache\Warmup\RecursiveCacheWarmupService;
use CuyZ\Valinor\Definition\FunctionsContainer;
-use CuyZ\Valinor\Definition\Repository\Cache\CacheClassDefinitionRepository;
-use CuyZ\Valinor\Definition\Repository\Cache\CacheFunctionDefinitionRepository;
+use CuyZ\Valinor\Definition\Repository\Cache\CompiledClassDefinitionRepository;
+use CuyZ\Valinor\Definition\Repository\Cache\CompiledFunctionDefinitionRepository;
+use CuyZ\Valinor\Definition\Repository\Cache\Compiler\ClassDefinitionCompiler;
+use CuyZ\Valinor\Definition\Repository\Cache\Compiler\FunctionDefinitionCompiler;
+use CuyZ\Valinor\Definition\Repository\Cache\InMemoryClassDefinitionRepository;
+use CuyZ\Valinor\Definition\Repository\Cache\InMemoryFunctionDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\ClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\FunctionDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionAttributesRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionClassDefinitionRepository;
use CuyZ\Valinor\Definition\Repository\Reflection\ReflectionFunctionDefinitionRepository;
use CuyZ\Valinor\Mapper\ArgumentsMapper;
-use CuyZ\Valinor\Mapper\Object\Factory\CacheObjectBuilderFactory;
+use CuyZ\Valinor\Mapper\Object\Factory\InMemoryObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\DateTimeZoneObjectBuilderFactory;
@@ -25,8 +30,8 @@
use CuyZ\Valinor\Mapper\Object\Factory\ReflectionObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\SortingObjectBuilderFactory;
use CuyZ\Valinor\Mapper\Object\Factory\StrictTypesObjectBuilderFactory;
-use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use CuyZ\Valinor\Mapper\Tree\Builder\ArrayNodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Builder\ConverterContainer;
use CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder;
use CuyZ\Valinor\Mapper\Tree\Builder\ListNodeBuilder;
use CuyZ\Valinor\Mapper\Tree\Builder\MixedNodeBuilder;
@@ -40,7 +45,7 @@
use CuyZ\Valinor\Mapper\Tree\Builder\TypeNodeBuilder;
use CuyZ\Valinor\Mapper\Tree\Builder\UndefinedObjectNodeBuilder;
use CuyZ\Valinor\Mapper\Tree\Builder\UnionNodeBuilder;
-use CuyZ\Valinor\Mapper\Tree\Builder\ValueAlteringNodeBuilder;
+use CuyZ\Valinor\Mapper\Tree\Builder\ValueConverterNodeBuilder;
use CuyZ\Valinor\Mapper\TreeMapper;
use CuyZ\Valinor\Mapper\TypeArgumentsMapper;
use CuyZ\Valinor\Mapper\TypeTreeMapper;
@@ -48,7 +53,7 @@
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\Normalizer\JsonNormalizer;
use CuyZ\Valinor\Normalizer\Normalizer;
-use CuyZ\Valinor\Normalizer\Transformer\CacheTransformer;
+use CuyZ\Valinor\Normalizer\Transformer\CompiledTransformer;
use CuyZ\Valinor\Normalizer\Transformer\Compiler\TransformerDefinitionBuilder;
use CuyZ\Valinor\Normalizer\Transformer\RecursiveTransformer;
use CuyZ\Valinor\Normalizer\Transformer\Transformer;
@@ -56,10 +61,8 @@
use CuyZ\Valinor\Type\Parser\Factory\LexingTypeParserFactory;
use CuyZ\Valinor\Type\Parser\Factory\TypeParserFactory;
use CuyZ\Valinor\Type\Parser\TypeParser;
-use Psr\SimpleCache\CacheInterface;
use function call_user_func;
-use function count;
/** @internal */
final class Container
@@ -117,19 +120,20 @@ public function __construct(Settings $settings)
$settings->exceptionFilter,
);
- if (count($settings->valueModifier) > 0) {
- $builder = new ValueAlteringNodeBuilder(
- $builder,
- new FunctionsContainer(
- $this->get(FunctionDefinitionRepository::class),
- $settings->valueModifier,
- ),
- );
- }
-
- return $builder;
+ return new ValueConverterNodeBuilder(
+ $builder,
+ $this->get(ConverterContainer::class),
+ $this->get(ClassDefinitionRepository::class),
+ $this->get(FunctionDefinitionRepository::class),
+ $settings->exceptionFilter,
+ );
},
+ ConverterContainer::class => fn () => new ConverterContainer(
+ $this->get(FunctionDefinitionRepository::class),
+ $settings->convertersSortedByPriority(),
+ ),
+
ObjectImplementations::class => fn () => new ObjectImplementations(
new FunctionsContainer(
$this->get(FunctionDefinitionRepository::class),
@@ -154,17 +158,15 @@ public function __construct(Settings $settings)
$factory = new StrictTypesObjectBuilderFactory($factory);
}
- /** @var RuntimeCache> $cache */
- $cache = new RuntimeCache();
-
- return new CacheObjectBuilderFactory($factory, $cache);
+ return new InMemoryObjectBuilderFactory($factory);
},
Transformer::class => function () use ($settings) {
if (isset($settings->cache)) {
- return new CacheTransformer(
+ return new CompiledTransformer(
$this->get(TransformerDefinitionBuilder::class),
- $this->get(CacheInterface::class),
+ $this->get(TypeFilesWatcher::class),
+ new RuntimeCache($this->get(Cache::class)), // @phpstan-ignore argument.type
$settings->transformersSortedByPriority(),
);
}
@@ -195,24 +197,42 @@ public function __construct(Settings $settings)
$this->get(TransformerContainer::class),
),
- ClassDefinitionRepository::class => fn () => new CacheClassDefinitionRepository(
- new ReflectionClassDefinitionRepository(
+ ClassDefinitionRepository::class => function () use ($settings) {
+ $repository = new ReflectionClassDefinitionRepository(
$this->get(TypeParserFactory::class),
$settings->allowedAttributes(),
- ),
- $this->get(CacheInterface::class),
- ),
+ );
+
+ if (isset($settings->cache)) {
+ $repository = new CompiledClassDefinitionRepository(
+ $repository,
+ $this->get(Cache::class),
+ new ClassDefinitionCompiler(),
+ );
+ }
- FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository(
- new ReflectionFunctionDefinitionRepository(
+ return new InMemoryClassDefinitionRepository($repository);
+ },
+
+ FunctionDefinitionRepository::class => function () use ($settings) {
+ $repository = new ReflectionFunctionDefinitionRepository(
$this->get(TypeParserFactory::class),
new ReflectionAttributesRepository(
$this->get(ClassDefinitionRepository::class),
$settings->allowedAttributes(),
),
- ),
- $this->get(CacheInterface::class),
- ),
+ );
+
+ if (isset($settings->cache)) {
+ $repository = new CompiledFunctionDefinitionRepository(
+ $repository,
+ $this->get(Cache::class),
+ new FunctionDefinitionCompiler(),
+ );
+ }
+
+ return new InMemoryFunctionDefinitionRepository($repository);
+ },
TypeParserFactory::class => fn () => new LexingTypeParserFactory(),
@@ -220,21 +240,17 @@ public function __construct(Settings $settings)
RecursiveCacheWarmupService::class => fn () => new RecursiveCacheWarmupService(
$this->get(TypeParser::class),
- $this->get(CacheInterface::class),
$this->get(ObjectImplementations::class),
$this->get(ClassDefinitionRepository::class),
$this->get(ObjectBuilderFactory::class),
),
- CacheInterface::class => function () use ($settings) {
- $cache = new RuntimeCache();
-
- if (isset($settings->cache)) {
- $cache = new ChainCache($cache, new KeySanitizerCache($settings->cache, $settings));
- }
+ Cache::class => fn () => new KeySanitizerCache($settings->cache, $settings),
- return $cache;
- },
+ TypeFilesWatcher::class => fn () => new TypeFilesWatcher(
+ $settings,
+ $this->get(ClassDefinitionRepository::class),
+ ),
];
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Settings.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Settings.php
index 4cdb1cb61ff..494b958bbc5 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Settings.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Library/Settings.php
@@ -5,17 +5,17 @@
namespace CuyZ\Valinor\Library;
use Closure;
+use CuyZ\Valinor\Cache\Cache;
use CuyZ\Valinor\Mapper\Object\Constructor;
use CuyZ\Valinor\Mapper\Object\DynamicConstructor;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
-use CuyZ\Valinor\Normalizer\AsTransformer;
use DateTimeImmutable;
use DateTimeInterface;
-use Psr\SimpleCache\CacheInterface;
use ReflectionFunction;
use Throwable;
use function array_keys;
+use function array_values;
use function hash;
/** @internal */
@@ -38,11 +38,7 @@ final class Settings
/** @var list */
public array $customConstructors = [];
- /** @var list */
- public array $valueModifier = [];
-
- /** @var CacheInterface */
- public CacheInterface $cache;
+ public Cache $cache;
/** @var non-empty-list */
public array $supportedDateFormats = self::DEFAULT_SUPPORTED_DATETIME_FORMATS;
@@ -57,14 +53,20 @@ final class Settings
public bool $allowPermissiveTypes = false;
+ /** @var array> */
+ public array $mapperConverters = [];
+
+ /** @var array */
+ public array $mapperConverterAttributes = [];
+
/** @var callable(Throwable): ErrorMessage */
- public $exceptionFilter;
+ public mixed $exceptionFilter;
/** @var array> */
- public array $transformers = [];
+ public array $normalizerTransformers = [];
/** @var array */
- public array $transformerAttributes = [];
+ public array $normalizerTransformerAttributes = [];
private string $hash;
@@ -80,23 +82,39 @@ public function __construct()
public function allowedAttributes(): array
{
return [
- AsTransformer::class,
Constructor::class,
DynamicConstructor::class,
- ...array_keys($this->transformerAttributes),
+ ...array_keys($this->mapperConverterAttributes),
+ ...array_keys($this->normalizerTransformerAttributes),
];
}
+ /**
+ * @return list
+ */
+ public function convertersSortedByPriority(): array
+ {
+ krsort($this->mapperConverters);
+
+ $callables = [];
+
+ foreach ($this->mapperConverters as $list) {
+ $callables = [...$callables, ...$list];
+ }
+
+ return $callables;
+ }
+
/**
* @return list
*/
public function transformersSortedByPriority(): array
{
- krsort($this->transformers);
+ krsort($this->normalizerTransformers);
$callables = [];
- foreach ($this->transformers as $list) {
+ foreach ($this->normalizerTransformers as $list) {
$callables = [...$callables, ...$list];
}
@@ -110,29 +128,34 @@ public function transformersSortedByPriority(): array
public function hash(): string
{
return $this->hash ??= hash('xxh128', serialize([
- implode('', array_map($this->callableSignature(...), $this->inferredMapping)),
$this->nativeConstructors,
- implode('', array_map($this->callableSignature(...), $this->customConstructors)),
- implode('', array_map($this->callableSignature(...), $this->valueModifier)),
$this->supportedDateFormats,
$this->allowScalarValueCasting,
$this->allowNonSequentialList,
$this->allowUndefinedValues,
$this->allowSuperfluousKeys,
$this->allowPermissiveTypes,
- $this->callableSignature($this->exceptionFilter),
- array_map(
- fn (array $transformers) => implode('', array_map($this->callableSignature(...), $transformers)),
- $this->transformers,
- ),
- $this->transformerAttributes,
+ $this->mapperConverterAttributes,
+ $this->normalizerTransformerAttributes,
+ implode('', array_map(function (callable $callable) {
+ $reflection = new ReflectionFunction(Closure::fromCallable($callable));
+
+ return ($reflection->getClosureCalledClass()->name ?? $reflection->getFileName()) . $reflection->getStartLine() . $reflection->getEndLine();
+ }, $this->callables())),
]));
}
- private function callableSignature(callable $callable): string
+ /**
+ * @return non-empty-list
+ */
+ public function callables(): array
{
- $reflection = new ReflectionFunction(Closure::fromCallable($callable));
-
- return ($reflection->getClosureCalledClass()->name ?? $reflection->getFileName()) . $reflection->getStartLine() . $reflection->getEndLine();
+ return array_values([
+ $this->exceptionFilter,
+ ...$this->inferredMapping,
+ ...$this->customConstructors,
+ ...array_merge(...$this->mapperConverters),
+ ...array_merge(...$this->normalizerTransformers),
+ ]);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapper.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapper.php
index 8e1bde6f7d5..e12011bb38e 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapper.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapper.php
@@ -8,6 +8,8 @@
interface ArgumentsMapper
{
/**
+ * @pure
+ *
* @return array
*
* @throws MappingError
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapperError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapperError.php
index 0cfbf969f87..39b14624644 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapperError.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/ArgumentsMapperError.php
@@ -4,39 +4,55 @@
namespace CuyZ\Valinor\Mapper;
-use CuyZ\Valinor\Definition\FunctionDefinition;
use CuyZ\Valinor\Mapper\Tree\Message\Messages;
-use CuyZ\Valinor\Mapper\Tree\Node;
+use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
use CuyZ\Valinor\Utility\ValueDumper;
use RuntimeException;
/** @internal */
final class ArgumentsMapperError extends RuntimeException implements MappingError
{
- private Node $node;
+ private Messages $messages;
- public function __construct(FunctionDefinition $function, Node $node)
+ private string $type;
+
+ private mixed $source;
+
+ /**
+ * @param non-empty-list $messages
+ */
+ public function __construct(mixed $source, string $type, string $function, array $messages)
{
- $this->node = $node;
+ $this->messages = new Messages(...$messages);
+ $this->type = $type;
+ $this->source = $source;
- $errors = Messages::flattenFromNode($node)->errors();
- $errorsCount = count($errors);
+ $errorsCount = count($messages);
if ($errorsCount === 1) {
- $body = $errors
- ->toArray()[0]
- ->withBody("Could not map arguments of `$function->signature`. An error occurred at path {node_path}: {original_message}")
+ $body = $messages[0]
+ ->withBody("Could not map arguments of `$function`. An error occurred at path {node_path}: {original_message}")
->toString();
} else {
- $source = ValueDumper::dump($node->sourceValue());
- $body = "Could not map arguments of `$function->signature` with value $source. A total of $errorsCount errors were encountered.";
+ $source = ValueDumper::dump($source);
+ $body = "Could not map arguments of `$function` with value $source. A total of $errorsCount errors were encountered.";
}
parent::__construct($body, 1671115362);
}
- public function node(): Node
+ public function messages(): Messages
+ {
+ return $this->messages;
+ }
+
+ public function type(): string
+ {
+ return $this->type;
+ }
+
+ public function source(): mixed
{
- return $this->node;
+ return $this->source;
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/AsConverter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/AsConverter.php
new file mode 100644
index 00000000000..47dec3a49a7
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/AsConverter.php
@@ -0,0 +1,77 @@
+ true,
+ * 'no', 'off' => false,
+ * default => $value,
+ * };
+ *
+ * return $next($value);
+ * }
+ * }
+ *
+ * final class User
+ * {
+ * public string $name;
+ *
+ * #[\My\App\CastToBool]
+ * public bool $isActive;
+ * }
+ *
+ * $user = (new \CuyZ\Valinor\MapperBuilder())
+ * ->mapper()
+ * ->map(User::class, [
+ * 'name' => 'John Doe',
+ * 'isActive' => 'yes',
+ * ]);
+ *
+ * $user->name === 'John Doe';
+ * $user->isActive === true;
+ * ```
+ *
+ * Attribute converters can also be used on function parameters when mapping
+ * arguments:
+ *
+ * ```php
+ * function someFunction(string $name, #[\My\App\CastToBool] bool $isActive) {
+ * // …
+ * };
+ *
+ * $arguments = (new \CuyZ\Valinor\MapperBuilder())
+ * ->argumentsMapper()
+ * ->mapArguments(someFunction(...), [
+ * 'name' => 'John Doe',
+ * 'isActive' => 'yes',
+ * ]);
+ *
+ * $arguments['name'] === 'John Doe';
+ * $arguments['isActive'] === true;
+ * ```
+ * @api
+ */
+#[Attribute(Attribute::TARGET_CLASS)]
+final class AsConverter {}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/MappingError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/MappingError.php
index bc92ef7e26a..69690b6151a 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/MappingError.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/MappingError.php
@@ -4,11 +4,24 @@
namespace CuyZ\Valinor\Mapper;
-use CuyZ\Valinor\Mapper\Tree\Node;
+use CuyZ\Valinor\Mapper\Tree\Message\Messages;
use Throwable;
/** @api */
interface MappingError extends Throwable
{
- public function node(): Node;
+ /**
+ * Container for all messages that were caught during the mapping process.
+ */
+ public function messages(): Messages;
+
+ /**
+ * Returns the original type that the mapper was attempting to map to.
+ */
+ public function type(): string;
+
+ /**
+ * Returns the original source value given to the mapper.
+ */
+ public function source(): mixed;
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php
index d0026792afb..e091a2e828f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/DateTimeFormatConstructor.php
@@ -9,25 +9,7 @@
use DateTimeImmutable;
use DateTimeInterface;
-/**
- * Can be given to {@see MapperBuilder::registerConstructor()} to describe which
- * date formats should be allowed during mapping.
- *
- * By default, if this constructor is never registered, the dates will accept
- * any valid timestamp or RFC 3339-formatted value.
- *
- * Usage:
- *
- * ```php
- * (new \CuyZ\Valinor\MapperBuilder())
- * // Both `Cookie` and `ATOM` formats will be accepted
- * ->registerConstructor(new DateTimeFormatConstructor(DATE_COOKIE, DATE_ATOM))
- * ->mapper()
- * ->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
- * ```
- *
- * @internal
- */
+/** @internal */
final class DateTimeFormatConstructor
{
/** @var non-empty-list */
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php
index 1248722d17e..435b4288e7f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotFindObjectBuilder.php
@@ -6,20 +6,21 @@
use CuyZ\Valinor\Mapper\Object\ObjectBuilder;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
use function array_keys;
use function count;
use function ksort;
/** @internal */
-final class CannotFindObjectBuilder extends RuntimeException implements ErrorMessage, HasParameters
+final class CannotFindObjectBuilder implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Value {source_value} does not match any of {allowed_types}.';
+ private string $code = 'cannot_find_object_builder';
+
/** @var array */
private array $parameters;
@@ -52,8 +53,6 @@ public function __construct(array $builders)
return implode(', ', $sortedSignatures);
})(),
];
-
- parent::__construct(StringFormatter::for($this), 1642183169);
}
public function body(): string
@@ -61,6 +60,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php
index f945eaf4cff..2a13dbb1090 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/CannotParseToDateTime.php
@@ -5,12 +5,12 @@
namespace CuyZ\Valinor\Mapper\Object\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use RuntimeException;
/** @internal */
-final class CannotParseToDateTime extends RuntimeException implements ErrorMessage, HasParameters
+final class CannotParseToDateTime extends RuntimeException implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Value {source_value} does not match any of the following formats: {formats}.';
@@ -26,7 +26,8 @@ public function __construct(array $formats)
'formats' => '`' . implode('`, `', $formats) . '`',
];
- parent::__construct(StringFormatter::for($this), 1630686564);
+ // @infection-ignore-all
+ parent::__construct($this->body);
}
public function body(): string
@@ -34,6 +35,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return 'cannot_parse_datetime_format';
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php
index d7a96c4eff8..544c70ed77d 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Exception/InvalidSource.php
@@ -6,16 +6,17 @@
use CuyZ\Valinor\Mapper\Object\Arguments;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
/** @internal */
-final class InvalidSource extends RuntimeException implements ErrorMessage, HasParameters
+final class InvalidSource implements ErrorMessage, HasCode, HasParameters
{
private string $body;
+ private string $code = 'invalid_source';
+
/** @var array */
private array $parameters;
@@ -28,8 +29,6 @@ public function __construct(mixed $source, Arguments $arguments)
$this->body = $source === null
? 'Cannot be empty and must be filled with a value matching type {expected_type}.'
: 'Value {source_value} does not match type {expected_type}.';
-
- parent::__construct(StringFormatter::for($this), 1632903281);
}
public function body(): string
@@ -37,6 +36,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php
deleted file mode 100644
index c1e460dfdd5..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php
+++ /dev/null
@@ -1,36 +0,0 @@
-> */
- private CacheInterface $cache
- ) {}
-
- public function for(ClassDefinition $class): array
- {
- $signature = $class->type->toString();
-
- $entry = $this->cache->get($signature);
-
- if ($entry) {
- return $entry;
- }
-
- $builders = $this->delegate->for($class);
-
- $this->cache->set($signature, $builders);
-
- return $builders;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php
index 0e929d03709..c7d29c71a9d 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php
@@ -67,7 +67,9 @@ private function defaultBuilder(ObjectType $type): FunctionObjectBuilder
try {
return new DateTimeZone($timezone);
} catch (Exception) {
- throw MessageBuilder::newError('Value {source_value} is not a valid timezone.')->build();
+ throw MessageBuilder::newError('Value {source_value} is not a valid timezone.')
+ ->withCode('invalid_timezone')
+ ->build();
}
};
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/InMemoryObjectBuilderFactory.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/InMemoryObjectBuilderFactory.php
new file mode 100644
index 00000000000..449f3475e6b
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/InMemoryObjectBuilderFactory.php
@@ -0,0 +1,24 @@
+> */
+ private array $builders = [];
+
+ public function __construct(
+ private ObjectBuilderFactory $delegate,
+ ) {}
+
+ public function for(ClassDefinition $class): array
+ {
+ return $this->builders[$class->type->toString()] ??= $this->delegate->for($class);
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php
index e7dea8c85ec..6db8e339dc5 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Object/Factory/SortingObjectBuilderFactory.php
@@ -96,7 +96,7 @@ private function sortObjectBuilders(ObjectBuilder $builderA, ObjectBuilder $buil
private function sortTypes(Type $typeA, Type $typeB): int
{
if ($typeA instanceof ScalarType && $typeB instanceof ScalarType) {
- return TypeHelper::typePriority($typeB) <=> TypeHelper::typePriority($typeA);
+ return TypeHelper::scalarTypePriority($typeB) <=> TypeHelper::scalarTypePriority($typeA);
}
if (! $typeA instanceof ScalarType) {
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/FileSource.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/FileSource.php
index 2b9424dbc00..5206f764522 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/FileSource.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/FileSource.php
@@ -18,7 +18,7 @@
*
* @implements IteratorAggregate
*/
-final class FileSource implements IteratorAggregate, IdentifiableSource
+final class FileSource implements IteratorAggregate
{
private string $filePath;
@@ -43,11 +43,6 @@ public function __construct(SplFileObject $file)
};
}
- public function sourceName(): string
- {
- return $this->filePath;
- }
-
/**
* @return Iterator
*/
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php
deleted file mode 100644
index 8d9250bbcb9..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Source/IdentifiableSource.php
+++ /dev/null
@@ -1,11 +0,0 @@
-type();
$value = $shell->value();
@@ -29,32 +31,24 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof ArrayType || $type instanceof NonEmptyArrayType || $type instanceof IterableType);
if ($shell->allowUndefinedValues() && $value === null) {
- return TreeNode::branch($shell, [], []);
+ return Node::new([]);
}
if (! is_iterable($value)) {
- return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
+ return Node::error($shell, new SourceMustBeIterable($value, $type));
}
- $children = $this->children($type, $shell, $rootBuilder);
- $array = $this->buildArray($children);
-
- return TreeNode::branch($shell, $array, $children);
- }
+ if ($value === [] && $type instanceof NonEmptyArrayType) {
+ return Node::error($shell, new SourceIsEmptyArray($type));
+ }
- /**
- * @return array
- */
- private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
- {
- /** @var iterable $values */
- $values = $shell->value();
$keyType = $type->keyType();
$subType = $type->subType();
$children = [];
+ $errors = [];
- foreach ($values as $key => $value) {
+ foreach ($value as $key => $val) {
if (! is_string($key) && ! is_int($key)) {
throw new InvalidIterableKeyType($key, $shell->path());
}
@@ -62,31 +56,26 @@ private function children(CompositeTraversableType $type, Shell $shell, RootNode
$child = $shell->child((string)$key, $subType);
if (! $keyType->accepts($key)) {
- $children[$key] = TreeNode::error($child, new InvalidTraversableKey($key, $keyType));
+ $children[$key] = Node::error($child, new InvalidArrayKey($key, $keyType));
} else {
- $children[$key] = $rootBuilder->build($child->withValue($value));
+ $children[$key] = $rootBuilder->build($child->withValue($val));
}
- }
- return $children;
- }
-
- /**
- * @param array $children
- * @return mixed[]|null
- */
- private function buildArray(array $children): ?array
- {
- $array = [];
-
- foreach ($children as $key => $child) {
- if (! $child->isValid()) {
- return null;
+ if (! $children[$key]->isValid()) {
+ $errors[] = $children[$key];
}
+ }
- $array[$key] = $child->value();
+ if ($errors !== []) {
+ return Node::branchWithErrors($errors);
}
- return $array;
+ return Node::new(
+ value: array_map(
+ static fn (Node $child) => $child->value(),
+ $children,
+ ),
+ childrenCount: count($children),
+ );
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ConverterContainer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ConverterContainer.php
new file mode 100644
index 00000000000..d63eaca37ff
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ConverterContainer.php
@@ -0,0 +1,69 @@
+ */
+ private array $converters,
+ ) {}
+
+ /**
+ * @return list
+ */
+ public function converters(): array
+ {
+ if (! $this->convertersCallablesWereChecked) {
+ $this->convertersCallablesWereChecked = true;
+
+ foreach ($this->converters as $transformer) {
+ $function = $this->functionDefinitionRepository->for($transformer);
+
+ self::checkConverter($function);
+ }
+ }
+
+ return $this->converters;
+ }
+
+ public static function filterConverterAttributes(AttributeDefinition $attribute): bool
+ {
+ return $attribute->class->methods->has('map')
+ && self::checkConverter($attribute->class->methods->get('map'));
+ }
+
+ private static function checkConverter(MethodDefinition|FunctionDefinition $method): bool
+ {
+ $parameters = $method->parameters;
+
+ if ($parameters->count() === 0) {
+ throw new ConverterHasNoParameter($method);
+ }
+
+ if ($parameters->count() > 2) {
+ throw new ConverterHasTooManyParameters($method);
+ }
+
+ if ($parameters->count() > 1 && ! $parameters->at(1)->nativeType instanceof CallableType) {
+ throw new ConverterHasInvalidCallableParameter($method, $parameters->at(1)->nativeType);
+ }
+
+ return true;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php
index d36f53df902..8405634cbbf 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php
@@ -32,7 +32,7 @@ public function __construct(
private mixed $exceptionFilter,
) {}
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
@@ -41,7 +41,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
}
if ($type->accepts($shell->value())) {
- return TreeNode::leaf($shell, $shell->value());
+ return Node::new($shell->value());
}
if ($this->constructorRegisteredFor($type)) {
@@ -78,7 +78,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
$argumentsValues = ArgumentsValues::forInterface($arguments, $shell);
if ($argumentsValues->hasInvalidValue()) {
- return TreeNode::error($shell, new InvalidSource($shell->value(), $arguments));
+ return Node::error($shell, new InvalidSource($shell->value(), $arguments));
}
$children = $this->children($shell, $argumentsValues, $rootBuilder);
@@ -87,7 +87,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
foreach ($children as $child) {
if (! $child->isValid()) {
- return TreeNode::branch($shell, null, $children);
+ return Node::branchWithErrors($children);
}
$values[] = $child->value();
@@ -98,13 +98,13 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
} catch (ObjectImplementationCallbackError $exception) {
$exception = ($this->exceptionFilter)($exception->original());
- return TreeNode::error($shell, $exception);
+ return Node::error($shell, $exception);
}
$shell = $shell->withType($classType);
$shell = $shell->withAllowedSuperfluousKeys($arguments->names());
- return $this->delegate->build($shell, $rootBuilder);
+ return $rootBuilder->build($shell);
}
private function constructorRegisteredFor(Type $type): bool
@@ -119,7 +119,7 @@ private function constructorRegisteredFor(Type $type): bool
}
/**
- * @return array
+ * @return list
*/
private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
{
@@ -128,9 +128,9 @@ private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuil
foreach ($arguments as $argument) {
$name = $argument->name();
$type = $argument->type();
- $attributes = $argument->attributes();
- $child = $shell->child($name, $type, $attributes);
+ $child = $shell->child($name, $type);
+ $child = $child->withAttributes($argument->attributes());
if ($arguments->hasValue($name)) {
$child = $child->withValue($arguments->getValue($name));
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php
index 014c275ddb4..d3f3d7fc20b 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ListNodeBuilder.php
@@ -6,9 +6,9 @@
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidIterableKeyType;
use CuyZ\Valinor\Mapper\Tree\Exception\InvalidListKey;
+use CuyZ\Valinor\Mapper\Tree\Exception\SourceIsEmptyList;
use CuyZ\Valinor\Mapper\Tree\Exception\SourceMustBeIterable;
use CuyZ\Valinor\Mapper\Tree\Shell;
-use CuyZ\Valinor\Type\CompositeTraversableType;
use CuyZ\Valinor\Type\Types\ListType;
use CuyZ\Valinor\Type\Types\NonEmptyListType;
@@ -20,7 +20,7 @@
/** @internal */
final class ListNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
$value = $shell->value();
@@ -28,66 +28,53 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof ListType || $type instanceof NonEmptyListType);
if ($shell->allowUndefinedValues() && $value === null) {
- return TreeNode::branch($shell, [], []);
+ return Node::new(value: []);
}
if (! is_iterable($value)) {
- return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
+ return Node::error($shell, new SourceMustBeIterable($value, $type));
}
- $children = $this->children($type, $shell, $rootBuilder);
- $array = $this->buildArray($children);
-
- return TreeNode::branch($shell, $array, $children);
- }
+ if ($value === [] && $type instanceof NonEmptyListType) {
+ return Node::error($shell, new SourceIsEmptyList($type));
+ }
- /**
- * @return array
- */
- private function children(CompositeTraversableType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
- {
- /** @var iterable $values */
- $values = $shell->value();
$subType = $type->subType();
$expected = 0;
$children = [];
+ $errors = [];
- foreach ($values as $key => $value) {
+ foreach ($value as $key => $val) {
if (! is_string($key) && ! is_int($key)) {
throw new InvalidIterableKeyType($key, $shell->path());
}
if ($shell->allowNonSequentialList() || $key === $expected) {
$child = $shell->child((string)$expected, $subType);
- $children[$expected] = $rootBuilder->build($child->withValue($value));
+ $childNode = $children[$expected] = $rootBuilder->build($child->withValue($val));
} else {
$child = $shell->child((string)$key, $subType);
- $children[$key] = TreeNode::error($child, new InvalidListKey($key, $expected));
+ $childNode = $children[$key] = Node::error($child, new InvalidListKey($key, $expected));
+ }
+
+ if (! $childNode->isValid()) {
+ $errors[] = $childNode;
}
$expected++;
}
- return $children;
- }
-
- /**
- * @param array $children
- * @return mixed[]|null
- */
- private function buildArray(array $children): ?array
- {
- $array = [];
-
- foreach ($children as $key => $child) {
- if (! $child->isValid()) {
- return null;
- }
-
- $array[$key] = $child->value();
+ if ($errors !== []) {
+ return Node::branchWithErrors($errors);
}
- return $array;
+ return Node::new(
+ value: array_map(
+ static fn (Node $child) => $child->value(),
+ $children,
+ ),
+ childrenCount: count($children),
+ );
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php
index 328031e0642..d2fea564815 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/MixedNodeBuilder.php
@@ -11,7 +11,7 @@
/** @internal */
final class MixedNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
assert($shell->type() instanceof MixedType);
@@ -19,6 +19,6 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
throw new CannotMapToPermissiveType($shell);
}
- return TreeNode::leaf($shell, $shell->value());
+ return Node::new($shell->value());
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/Node.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/Node.php
new file mode 100644
index 00000000000..190a84e035a
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/Node.php
@@ -0,0 +1,131 @@
+ */
+ private array $messages = [],
+ /** @var non-negative-int */
+ private int $childrenCount = 0,
+ ) {}
+
+ /**
+ * @param non-negative-int $childrenCount
+ */
+ public static function new(mixed $value, int $childrenCount = 0): self
+ {
+ return new self(value: $value, childrenCount: $childrenCount);
+ }
+
+ public static function error(Shell $shell, Message $error): self
+ {
+ $nodeMessage = new NodeMessage(
+ $error,
+ $error->body(),
+ $shell->name(),
+ $shell->path(),
+ "`{$shell->type()->toString()}`",
+ $shell->hasValue() ? ValueDumper::dump($shell->value()) : '*missing*',
+ );
+
+ return new self(value: null, messages: [$nodeMessage]);
+ }
+
+ /**
+ * @param array $nodes
+ */
+ public static function branchWithErrors(array $nodes): self
+ {
+ $messages = [];
+
+ foreach ($nodes as $node) {
+ $messages = array_merge($messages, $node->messages);
+ }
+
+ return new self(value: null, messages: $messages);
+ }
+
+ /**
+ * @phpstan-assert-if-true ! non-empty-list $this->messages()
+ */
+ public function isValid(): bool
+ {
+ return $this->messages === [];
+ }
+
+ public function value(): mixed
+ {
+ assert($this->messages === [], 'Trying to get value of an invalid node.');
+
+ return $this->value;
+ }
+
+ /**
+ * @return list
+ */
+ public function messages(): array
+ {
+ return $this->messages;
+ }
+
+ /**
+ * @return non-negative-int
+ */
+ public function childrenCount(): int
+ {
+ return $this->childrenCount;
+ }
+
+ /**
+ * @param list $children
+ */
+ public function checkUnexpectedKeys(Shell $shell, array $children): self
+ {
+ $value = $shell->value();
+
+ if ($shell->allowSuperfluousKeys() || ! is_array($value)) {
+ return $this;
+ }
+
+ $diff = array_diff(array_keys($value), $children, $shell->allowedSuperfluousKeys());
+
+ if ($diff !== []) {
+ /** @var non-empty-list $children */
+ $error = new UnexpectedKeysInSource($value, $children);
+
+ $nodeMessage = new NodeMessage(
+ $error,
+ $error->body(),
+ $shell->name(),
+ $shell->path(),
+ "`{$shell->type()->toString()}`",
+ ValueDumper::dump($shell->value()),
+ );
+
+ return new self(
+ value: null,
+ messages: array_merge($this->messages, [$nodeMessage])
+ );
+ }
+
+ return $this;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php
index 52de275d169..68596e44061 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NodeBuilder.php
@@ -7,5 +7,5 @@
/** @internal */
interface NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode;
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node;
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php
index a6584d6166f..99c6ef6b4ed 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/NullNodeBuilder.php
@@ -13,7 +13,7 @@
/** @internal */
final class NullNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
$value = $shell->value();
@@ -21,9 +21,9 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof NullType);
if ($value !== null) {
- return TreeNode::error($shell, new SourceIsNotNull());
+ return Node::error($shell, new SourceIsNotNull());
}
- return TreeNode::leaf($shell, null);
+ return Node::new(null);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php
index 74784394554..69fd8745ab3 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ObjectNodeBuilder.php
@@ -19,6 +19,7 @@
use CuyZ\Valinor\Type\ObjectType;
use Throwable;
+use function array_keys;
use function assert;
use function count;
@@ -32,7 +33,7 @@ public function __construct(
private mixed $exceptionFilter,
) {}
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
@@ -40,7 +41,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof ObjectType);
if ($type->accepts($shell->value())) {
- return TreeNode::leaf($shell, $shell->value());
+ return Node::new($shell->value());
}
if ($shell->allowUndefinedValues() && $shell->value() === null) {
@@ -57,7 +58,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
if ($argumentsValues->hasInvalidValue()) {
if (count($builders) === 1) {
- return TreeNode::error($shell, new InvalidSource($shell->value(), $builder->describeArguments()));
+ return Node::error($shell, new InvalidSource($shell->value(), $builder->describeArguments()));
}
continue;
@@ -67,19 +68,27 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
try {
$object = $this->buildObject($builder, $children);
- } catch (Message $exception) {
+ } catch (UserlandError|Message $exception) {
if ($exception instanceof UserlandError) {
- $exception = ($this->exceptionFilter)($exception->previous());
+ // @phpstan-ignore argument.type (we know there always is a previous exception)
+ $exception = ($this->exceptionFilter)($exception->getPrevious());
}
- return TreeNode::error($shell, $exception);
+ return Node::error($shell, $exception);
}
- if ($argumentsValues->hadSingleArgument()) {
- $node = TreeNode::flattenedBranch($shell, $object, $children[0]);
+ if ($object === null) {
+ if (count($builders) > 1) {
+ continue;
+ }
+
+ $node = Node::branchWithErrors($children);
} else {
- $node = TreeNode::branch($shell, $object, $children);
- $node = $node->checkUnexpectedKeys();
+ $node = Node::new(value: $object, childrenCount: count($children));
+ }
+
+ if (! $argumentsValues->hadSingleArgument()) {
+ $node = $node->checkUnexpectedKeys($shell, array_keys($children));
}
if ($node->isValid() || count($builders) === 1) {
@@ -87,11 +96,11 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
}
}
- return TreeNode::error($shell, new CannotFindObjectBuilder($builders));
+ return Node::error($shell, new CannotFindObjectBuilder($builders));
}
/**
- * @return list
+ * @return array
*/
private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuilder $rootBuilder): array
{
@@ -100,9 +109,14 @@ private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuil
foreach ($arguments as $argument) {
$name = $argument->name();
$type = $argument->type();
- $attributes = $argument->attributes();
- $child = $shell->child($name, $type, $attributes);
+ if ($arguments->hadSingleArgument()) {
+ $child = $shell->withType($type);
+ } else {
+ $child = $shell->child($name, $type);
+ }
+
+ $child = $child->withAttributes($argument->attributes());
if ($arguments->hasValue($name)) {
$child = $child->withValue($arguments->getValue($name));
@@ -120,7 +134,7 @@ private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuil
throw new CircularDependencyDetected($argument);
}
- $children[] = TreeNode::error($shell, new InvalidNodeValue($type));
+ $children[$name] = Node::error($shell, new InvalidNodeValue($type));
} else {
$childBuilder = $rootBuilder;
@@ -128,7 +142,7 @@ private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuil
$childBuilder = $rootBuilder->withTypeAsCurrentRoot($type);
}
- $children[] = $childBuilder->build($child);
+ $children[$name] = $childBuilder->build($child);
}
}
@@ -136,18 +150,18 @@ private function children(Shell $shell, ArgumentsValues $arguments, RootNodeBuil
}
/**
- * @param list $children
+ * @param array $children
*/
private function buildObject(ObjectBuilder $builder, array $children): ?object
{
$arguments = [];
- foreach ($children as $child) {
+ foreach ($children as $name => $child) {
if (! $child->isValid()) {
return null;
}
- $arguments[$child->name()] = $child->value();
+ $arguments[$name] = $child->value();
}
return $builder->build($arguments);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php
index 211cef68eac..720ea2bca9a 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php
@@ -18,11 +18,11 @@ final class RootNodeBuilder
public function __construct(private NodeBuilder $root) {}
- public function build(Shell $shell): TreeNode
+ public function build(Shell $shell): Node
{
if (! $shell->hasValue()) {
if (! $shell->allowUndefinedValues()) {
- return TreeNode::error($shell, new MissingNodeValue($shell->type()));
+ return Node::error($shell, new MissingNodeValue($shell->type()));
}
$shell = $shell->withValue(null);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php
index 80c79645652..eb0342e9ec3 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ScalarNodeBuilder.php
@@ -12,7 +12,7 @@
/** @internal */
final class ScalarNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
$value = $shell->value();
@@ -20,13 +20,13 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof ScalarType);
if ($type->accepts($value)) {
- return TreeNode::leaf($shell, $value);
+ return Node::new($value);
}
if (! $shell->allowScalarValueCasting() || ! $type->canCast($value)) {
- return TreeNode::error($shell, $type->errorMessage());
+ return Node::error($shell, $type->errorMessage());
}
- return TreeNode::leaf($shell, $type->cast($value));
+ return Node::new($type->cast($value));
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php
index a2135ca6c43..49139c81e50 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ShapedArrayNodeBuilder.php
@@ -16,7 +16,7 @@
/** @internal */
final class ShapedArrayNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
$value = $shell->value();
@@ -24,37 +24,23 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
assert($type instanceof ShapedArrayType);
if (! is_iterable($value)) {
- return TreeNode::error($shell, new SourceMustBeIterable($value, $type));
+ return Node::error($shell, new SourceMustBeIterable($value, $type));
}
- $children = $this->children($type, $shell, $rootBuilder);
-
- $array = $this->buildArray($children);
-
- $node = TreeNode::branch($shell, $array, $children);
- $node = $node->checkUnexpectedKeys();
-
- return $node;
- }
-
- /**
- * @return array
- */
- private function children(ShapedArrayType $type, Shell $shell, RootNodeBuilder $rootBuilder): array
- {
- /** @var iterable $value */
- $value = $shell->value();
- $elements = $type->elements();
$children = [];
+ $childrenNames = [];
+ $errors = [];
if (! is_array($value)) {
$value = iterator_to_array($value);
}
- foreach ($elements as $element) {
+ foreach ($type->elements() as $element) {
+ $childrenNames[] = $element->key()->value();
$key = $element->key()->value();
$child = $shell->child((string)$key, $element->type());
+ $child = $child->withAttributes($element->attributes());
if (array_key_exists($key, $value)) {
$child = $child->withValue($value[$key]);
@@ -62,39 +48,39 @@ private function children(ShapedArrayType $type, Shell $shell, RootNodeBuilder $
continue;
}
- $children[$key] = $rootBuilder->build($child);
+ $child = $rootBuilder->build($child);
+
+ if (! $child->isValid()) {
+ $errors[] = $child;
+ } else {
+ $children[$key] = $child->value();
+ }
unset($value[$key]);
}
if ($type->isUnsealed()) {
+ $childrenNames = array_merge($childrenNames, array_keys($value));
+
$unsealedShell = $shell->withType($type->unsealedType())->withValue($value);
- $unsealedChildren = $rootBuilder->build($unsealedShell)->children();
+ $unsealedNode = $rootBuilder->build($unsealedShell);
- foreach ($unsealedChildren as $unsealedChild) {
- $children[$unsealedChild->name()] = $unsealedChild;
+ if (! $unsealedNode->isValid()) {
+ $errors[] = $unsealedNode;
+ } else {
+ // @phpstan-ignore assignOp.invalid (we know value is an array)
+ $children += $unsealedNode->value();
}
}
- return $children;
- }
-
- /**
- * @param array $children
- * @return mixed[]|null
- */
- private function buildArray(array $children): ?array
- {
- $array = [];
-
- foreach ($children as $key => $child) {
- if (! $child->isValid()) {
- return null;
- }
-
- $array[$key] = $child->value();
+ if ($errors === []) {
+ $node = Node::new(value: $children, childrenCount: count($children));
+ } else {
+ $node = Node::branchWithErrors($errors);
}
- return $array;
+ $node = $node->checkUnexpectedKeys($shell, $childrenNames);
+
+ return $node;
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php
deleted file mode 100644
index afc72587cab..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TreeNode.php
+++ /dev/null
@@ -1,184 +0,0 @@
- */
- private array $children = [];
-
- /** @var array */
- private array $messages = [];
-
- private bool $valid = true;
-
- private function __construct(Shell $shell, mixed $value)
- {
- $this->shell = $shell;
- $this->value = $value;
- }
-
- public static function leaf(Shell $shell, mixed $value): self
- {
- $instance = new self($shell, $value);
- $instance->check();
-
- return $instance;
- }
-
- /**
- * @param array $children
- */
- public static function branch(Shell $shell, mixed $value, array $children): self
- {
- $instance = new self($shell, $value);
-
- foreach ($children as $child) {
- $instance->children[$child->name()] = $child;
- }
-
- $instance->check();
-
- return $instance;
- }
-
- public static function flattenedBranch(Shell $shell, mixed $value, self $child): self
- {
- $instance = new self($shell, $value);
- $instance->messages = $child->messages;
- $instance->children = $child->children;
- $instance->valid = $child->valid;
-
- return $instance;
- }
-
- public static function error(Shell $shell, Throwable&Message $message): self
- {
- return (new self($shell, null))->withMessage($message);
- }
-
- public function name(): string
- {
- return $this->shell->name();
- }
-
- public function type(): Type
- {
- return $this->shell->type();
- }
-
- /**
- * @return array
- */
- public function children(): array
- {
- return $this->children;
- }
-
- public function isValid(): bool
- {
- return $this->valid;
- }
-
- public function withValue(mixed $value): self
- {
- $clone = clone $this;
- $clone->value = $value;
- $clone->check();
-
- return $clone;
- }
-
- public function value(): mixed
- {
- assert($this->valid, "Trying to get value of an invalid node at path `{$this->shell->path()}`.");
-
- return $this->value;
- }
-
- public function withMessage(Message $message): self
- {
- $clone = clone $this;
- $clone->messages[] = $message;
- $clone->valid = $clone->valid && ! $message instanceof Throwable;
-
- return $clone;
- }
-
- public function node(): Node
- {
- return $this->buildNode($this);
- }
-
- public function checkUnexpectedKeys(): self
- {
- $value = $this->shell->value();
-
- if ($this->shell->allowSuperfluousKeys() || ! is_array($value)) {
- return $this;
- }
-
- $diff = array_diff(array_keys($value), array_keys($this->children), $this->shell->allowedSuperfluousKeys());
-
- if ($diff !== []) {
- return $this->withMessage(new UnexpectedKeysInSource($value, $this->children));
- }
-
- return $this;
- }
-
- private function check(): void
- {
- foreach ($this->children as $child) {
- if (! $child->valid) {
- $this->valid = false;
- }
- }
-
- $type = $this->shell->type();
-
- if ($this->valid && ! $type->accepts($this->value)) {
- $this->valid = false;
- $this->messages[] = new InvalidNodeValue($type);
- }
- }
-
- private function buildNode(self $self): Node
- {
- return new Node(
- $self->shell->isRoot(),
- $self->shell->name(),
- $self->shell->path(),
- $self->shell->type()->toString(),
- $self->shell->hasValue(),
- $self->shell->hasValue() ? $self->shell->value() : null,
- $self->valid ? $self->value : null,
- $self->messages,
- array_map(
- fn (self $child) => $self->buildNode($child),
- $self->children
- )
- );
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php
index 889bfed8332..5fff7499c6d 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/TypeNodeBuilder.php
@@ -34,7 +34,7 @@ public function __construct(
private ObjectNodeBuilder $objectNodeBuilder,
) {}
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$builder = match ($shell->type()::class) {
// List
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php
index d78cb618f30..afac89641da 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UndefinedObjectNodeBuilder.php
@@ -5,15 +5,17 @@
namespace CuyZ\Valinor\Mapper\Tree\Builder;
use CuyZ\Valinor\Mapper\Tree\Exception\CannotMapToPermissiveType;
+use CuyZ\Valinor\Mapper\Tree\Exception\InvalidNodeValue;
use CuyZ\Valinor\Mapper\Tree\Shell;
use CuyZ\Valinor\Type\Types\UndefinedObjectType;
use function assert;
+use function is_object;
/** @internal */
final class UndefinedObjectNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
assert($shell->type() instanceof UndefinedObjectType);
@@ -21,6 +23,12 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
throw new CannotMapToPermissiveType($shell);
}
- return TreeNode::leaf($shell, $shell->value());
+ $value = $shell->value();
+
+ if (! is_object($value)) {
+ return Node::error($shell, new InvalidNodeValue($shell->type()));
+ }
+
+ return Node::new($value);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php
index bc934d10450..3cf3215f472 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php
@@ -24,7 +24,7 @@
/** @internal */
final class UnionNodeBuilder implements NodeBuilder
{
- public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
+ public function build(Shell $shell, RootNodeBuilder $rootBuilder): Node
{
$type = $shell->type();
@@ -33,12 +33,13 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
$structs = [];
$scalars = [];
$all = [];
+ $errors = [];
foreach ($type->types() as $subType) {
// @infection-ignore-all / This is a performance optimisation, so we
// cannot easily test this behavior.
if ($subType instanceof NullType && $shell->value() === null) {
- return TreeNode::leaf($shell, null);
+ return Node::new(null);
}
try {
@@ -51,6 +52,8 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
}
if (! $node->isValid()) {
+ $errors[TypeHelper::typePriority($subType)][] = $node;
+
continue;
}
@@ -59,12 +62,22 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
if ($subType instanceof InterfaceType || $subType instanceof ClassType || $subType instanceof ShapedArrayType) {
$structs[] = $node;
} elseif ($subType instanceof ScalarType) {
- $scalars[] = $node;
+ $scalars[] = [
+ 'type' => $subType,
+ 'node' => $node,
+ ];
}
}
if ($all === []) {
- return TreeNode::error($shell, new CannotResolveTypeFromUnion($shell->value(), $type));
+ /** @var non-empty-array> $errors */
+ krsort($errors);
+
+ if (count(reset($errors)) === 1) {
+ return reset($errors)[0];
+ }
+
+ return Node::error($shell, new CannotResolveTypeFromUnion($shell->value(), $type));
}
if (count($all) === 1) {
@@ -73,7 +86,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
// If there is only one scalar and one struct, the scalar has priority.
if (count($scalars) === 1 && count($structs) === 1) {
- return $scalars[0];
+ return $scalars[0]['node'];
}
if ($structs !== []) {
@@ -84,7 +97,7 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
$childrenCount = [];
foreach ($structs as $node) {
- $childrenCount[count($node->children())][] = $node;
+ $childrenCount[$node->childrenCount()][] = $node;
}
krsort($childrenCount);
@@ -97,12 +110,12 @@ public function build(Shell $shell, RootNodeBuilder $rootBuilder): TreeNode
} elseif ($scalars !== []) {
usort(
$scalars,
- fn (TreeNode $a, TreeNode $b): int => TypeHelper::typePriority($b->type()) <=> TypeHelper::typePriority($a->type()),
+ fn (array $a, array $b): int => TypeHelper::scalarTypePriority($b['type']) <=> TypeHelper::scalarTypePriority($a['type']),
);
- return $scalars[0];
+ return $scalars[0]['node'];
}
- return TreeNode::error($shell, new TooManyResolvedTypesFromUnion($type));
+ return Node::error($shell, new TooManyResolvedTypesFromUnion($type));
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php
deleted file mode 100644
index ed518d946e1..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueAlteringNodeBuilder.php
+++ /dev/null
@@ -1,47 +0,0 @@
-delegate->build($shell, $rootBuilder);
-
- if (! $node->isValid()) {
- return $node;
- }
-
- $value = $node->value();
-
- foreach ($this->functions as $function) {
- $parameters = $function->definition->parameters;
-
- if (count($parameters) === 0) {
- continue;
- }
-
- $firstParameterType = $parameters->at(0)->type;
-
- if (! $firstParameterType->accepts($value)) {
- continue;
- }
-
- $value = ($function->callback)($value);
- $node = $node->withValue($value);
- }
-
- return $node;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueConverterNodeBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueConverterNodeBuilder.php
new file mode 100644
index 00000000000..9dcd70669fe
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Builder/ValueConverterNodeBuilder.php
@@ -0,0 +1,126 @@
+type();
+ $attributes = $shell->attributes();
+
+ if ($type instanceof ObjectType) {
+ $class = $this->classDefinitionRepository->for($type);
+
+ $attributes = $attributes->merge($class->attributes);
+ }
+
+ // @infection-ignore-all (This is a performance optimization, we don't test this)
+ if ($attributes->count() === 0 && $this->converterContainer->converters() === []) {
+ return $this->delegate->build($shell, $rootBuilder);
+ }
+
+ $converterAttributes = $attributes->filter(ConverterContainer::filterConverterAttributes(...));
+
+ $stack = [
+ ...array_map(
+ // @phpstan-ignore method.notFound (we know the `map` method exists)
+ static fn (AttributeDefinition $attribute) => $attribute->instantiate()->map(...),
+ $converterAttributes->toArray(),
+ ),
+ ...$this->converterContainer->converters(),
+ ];
+
+ if ($stack === []) {
+ return $this->delegate->build($shell, $rootBuilder);
+ }
+
+ try {
+ $result = $this->unstack($stack, $shell, $rootBuilder);
+
+ $shell = $shell->withValue($result);
+
+ return $this->delegate->build($shell, $rootBuilder);
+ } catch (InvalidNodeDuringValueConversion $exception) {
+ return $exception->node;
+ }
+ }
+
+ /**
+ * @param array $stack
+ */
+ private function unstack(array $stack, Shell $shell, RootNodeBuilder $rootBuilder): mixed
+ {
+ while ($current = array_shift($stack)) {
+ $converter = $this->functionDefinitionRepository->for($current);
+
+ if (! $shell->type()->matches($converter->returnType)) {
+ continue;
+ }
+
+ if (! $converter->parameters->at(0)->type->accepts($shell->value())) {
+ continue;
+ }
+
+ $arguments = [$shell->value()];
+
+ if ($converter->parameters->count() > 1) {
+ $arguments[] = function (mixed $value = NAN) use ($stack, $shell, $rootBuilder) {
+ if (! is_float($value) || ! is_nan($value)) {
+ $shell = $shell->withValue($value);
+ }
+
+ return $this->unstack($stack, $shell, $rootBuilder);
+ };
+ }
+
+ try {
+ return $current(...$arguments);
+ } catch (Exception $exception) {
+ if (! $exception instanceof Message) {
+ $exception = ($this->exceptionFilter)($exception);
+ }
+
+ $error = Node::error($shell, $exception);
+
+ throw new InvalidNodeDuringValueConversion($error);
+ }
+ }
+
+ $node = $this->delegate->build($shell, $rootBuilder);
+
+ if (! $node->isValid()) {
+ throw new InvalidNodeDuringValueConversion($node);
+ }
+
+ return $node->value();
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php
index a8dacb2f6ce..195a9ff4527 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/CannotResolveTypeFromUnion.php
@@ -5,20 +5,21 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Types\UnionType;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
use function array_map;
use function implode;
/** @internal */
-final class CannotResolveTypeFromUnion extends RuntimeException implements ErrorMessage, HasParameters
+final class CannotResolveTypeFromUnion implements ErrorMessage, HasCode, HasParameters
{
private string $body;
+ private string $code = 'cannot_resolve_type_from_union';
+
/** @var array */
private array $parameters;
@@ -40,8 +41,6 @@ public function __construct(mixed $source, UnionType $unionType)
? 'Invalid value {source_value}.'
: 'Value {source_value} does not match any of {allowed_types}.';
}
-
- parent::__construct(StringFormatter::for($this), 1607027306);
}
public function body(): string
@@ -49,6 +48,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasInvalidCallableParameter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasInvalidCallableParameter.php
new file mode 100644
index 00000000000..e46a6e1c9de
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasInvalidCallableParameter.php
@@ -0,0 +1,21 @@
+toString()}` given for `$method->signature`.",
+ 1751296766,
+ );
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasNoParameter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasNoParameter.php
new file mode 100644
index 00000000000..838e40097cf
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasNoParameter.php
@@ -0,0 +1,21 @@
+signature` has no parameter to convert the value to, a typed parameter is required.",
+ 1746449489,
+ );
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasTooManyParameters.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasTooManyParameters.php
new file mode 100644
index 00000000000..207fa62d459
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/ConverterHasTooManyParameters.php
@@ -0,0 +1,21 @@
+parameters->count()} given for `$method->signature`.",
+ 1751296711,
+ );
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidTraversableKey.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidArrayKey.php
similarity index 77%
rename from wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidTraversableKey.php
rename to wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidArrayKey.php
index 13f517ff84c..ebc73228373 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidTraversableKey.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidArrayKey.php
@@ -5,18 +5,19 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Types\ArrayKeyType;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
use CuyZ\Valinor\Utility\ValueDumper;
-use RuntimeException;
/** @internal */
-final class InvalidTraversableKey extends RuntimeException implements ErrorMessage, HasParameters
+final class InvalidArrayKey implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Key {key} does not match type {expected_type}.';
+ private string $code = 'invalid_array_key';
+
/** @var array */
private array $parameters;
@@ -26,8 +27,6 @@ public function __construct(string|int $key, ArrayKeyType $type)
'key' => ValueDumper::dump($key),
'expected_type' => TypeHelper::dump($type),
];
-
- parent::__construct(StringFormatter::for($this), 1630946163);
}
public function body(): string
@@ -35,6 +34,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php
index 2e47b487a04..fe475dcaed8 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidListKey.php
@@ -5,16 +5,17 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\ValueDumper;
-use RuntimeException;
/** @internal */
-final class InvalidListKey extends RuntimeException implements ErrorMessage, HasParameters
+final class InvalidListKey implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Invalid sequential key {key}, expected {expected}.';
+ private string $code = 'invalid_list_key';
+
/** @var array */
private array $parameters;
@@ -24,8 +25,6 @@ public function __construct(int|string $key, int $expected)
'key' => ValueDumper::dump($key),
'expected' => (string)$expected,
];
-
- parent::__construct(StringFormatter::for($this), 1654273010);
}
public function body(): string
@@ -33,6 +32,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeDuringValueConversion.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeDuringValueConversion.php
new file mode 100644
index 00000000000..10a4a7a2120
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeDuringValueConversion.php
@@ -0,0 +1,19 @@
+isValid()`.",
- 1657466305
- );
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php
index adb2c8b866c..a4b0b66bae8 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/InvalidNodeValue.php
@@ -5,17 +5,18 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
/** @internal */
-final class InvalidNodeValue extends RuntimeException implements ErrorMessage, HasParameters
+final class InvalidNodeValue implements ErrorMessage, HasCode, HasParameters
{
private string $body;
+ private string $code = 'invalid_value';
+
/** @var array */
private array $parameters;
@@ -28,8 +29,6 @@ public function __construct(Type $type)
$this->body = TypeHelper::containsObject($type)
? 'Invalid value {source_value}.'
: 'Value {source_value} does not match type {expected_type}.';
-
- parent::__construct(StringFormatter::for($this), 1630678334);
}
public function body(): string
@@ -37,6 +36,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php
index 3b4dd5cca35..4439e528d13 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/MissingNodeValue.php
@@ -5,17 +5,18 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
/** @internal */
-final class MissingNodeValue extends RuntimeException implements ErrorMessage, HasParameters
+final class MissingNodeValue implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Cannot be empty and must be filled with a value matching type {expected_type}.';
+ private string $code = 'missing_value';
+
/** @var array */
private array $parameters;
@@ -24,8 +25,6 @@ public function __construct(Type $type)
$this->parameters = [
'expected_type' => TypeHelper::dump($type),
];
-
- parent::__construct(StringFormatter::for($this), 1655449641);
}
public function body(): string
@@ -33,6 +32,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyArray.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyArray.php
new file mode 100644
index 00000000000..0f4f5407cf1
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyArray.php
@@ -0,0 +1,44 @@
+ */
+ private array $parameters;
+
+ public function __construct(NonEmptyArrayType $type)
+ {
+ $this->parameters = [
+ 'expected_subtype' => TypeHelper::dump($type->subType()),
+ ];
+ }
+
+ public function body(): string
+ {
+ return $this->body;
+ }
+
+ public function code(): string
+ {
+ return $this->code;
+ }
+
+ public function parameters(): array
+ {
+ return $this->parameters;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyList.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyList.php
new file mode 100644
index 00000000000..ba5be05c6dc
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsEmptyList.php
@@ -0,0 +1,44 @@
+ */
+ private array $parameters;
+
+ public function __construct(NonEmptyListType $type)
+ {
+ $this->parameters = [
+ 'expected_subtype' => TypeHelper::dump($type->subType()),
+ ];
+ }
+
+ public function body(): string
+ {
+ return $this->body;
+ }
+
+ public function code(): string
+ {
+ return $this->code;
+ }
+
+ public function parameters(): array
+ {
+ return $this->parameters;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php
index a07f4368b25..67394b68164 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceIsNotNull.php
@@ -5,22 +5,22 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
-use RuntimeException;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
/** @internal */
-final class SourceIsNotNull extends RuntimeException implements ErrorMessage
+final class SourceIsNotNull implements ErrorMessage, HasCode
{
- private string $body;
+ private string $body = 'Value {source_value} is not null.';
- public function __construct()
- {
- $this->body = 'Value {source_value} is not null.';
-
- parent::__construct($this->body, 1710263908);
- }
+ private string $code = 'value_is_not_null';
public function body(): string
{
return $this->body;
}
+
+ public function code(): string
+ {
+ return $this->code;
+ }
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php
index 82a27f40f5e..ca2de7f0a40 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceMustBeIterable.php
@@ -5,17 +5,18 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Type;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
/** @internal */
-final class SourceMustBeIterable extends RuntimeException implements ErrorMessage, HasParameters
+final class SourceMustBeIterable implements ErrorMessage, HasCode, HasParameters
{
private string $body;
+ private string $code = 'value_is_not_iterable';
+
/** @var array */
private array $parameters;
@@ -34,8 +35,6 @@ public function __construct(mixed $value, Type $type)
? 'Invalid value {source_value}.'
: 'Value {source_value} does not match type {expected_type}.';
}
-
- parent::__construct(StringFormatter::for($this), 1618739163);
}
public function body(): string
@@ -43,6 +42,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php
deleted file mode 100644
index b3dbde2ebb8..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/SourceValueWasNotFilled.php
+++ /dev/null
@@ -1,19 +0,0 @@
-sourceFilled()`.",
- 1657466107
- );
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php
index 857eaae1aca..97b8e841453 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/TooManyResolvedTypesFromUnion.php
@@ -5,20 +5,21 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use CuyZ\Valinor\Type\Types\UnionType;
-use CuyZ\Valinor\Utility\String\StringFormatter;
use CuyZ\Valinor\Utility\TypeHelper;
-use RuntimeException;
use function array_map;
use function implode;
/** @internal */
-final class TooManyResolvedTypesFromUnion extends RuntimeException implements ErrorMessage, HasParameters
+final class TooManyResolvedTypesFromUnion implements ErrorMessage, HasCode, HasParameters
{
private string $body;
+ private string $code = 'too_many_resolved_types_from_union';
+
/** @var array */
private array $parameters;
@@ -27,15 +28,13 @@ public function __construct(UnionType $unionType)
$this->parameters = [
'allowed_types' => implode(
', ',
- array_map(TypeHelper::dump(...), $unionType->types())
+ array_map(TypeHelper::dump(...), $unionType->types()),
),
];
$this->body = TypeHelper::containsObject($unionType)
? 'Invalid value {source_value}, it matches two or more types from union: cannot take a decision.'
: 'Invalid value {source_value}, it matches two or more types from {allowed_types}: cannot take a decision.';
-
- parent::__construct(StringFormatter::for($this), 1710262975);
}
public function body(): string
@@ -43,6 +42,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php
index c32ff4d53c3..7fd126fb718 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Exception/UnexpectedKeysInSource.php
@@ -4,44 +4,40 @@
namespace CuyZ\Valinor\Mapper\Tree\Exception;
-use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
+use CuyZ\Valinor\Mapper\Tree\Message\HasCode;
use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
-use CuyZ\Valinor\Utility\String\StringFormatter;
-use RuntimeException;
use function array_filter;
use function array_keys;
-use function array_map;
use function implode;
use function in_array;
/** @internal */
-final class UnexpectedKeysInSource extends RuntimeException implements ErrorMessage, HasParameters
+final class UnexpectedKeysInSource implements ErrorMessage, HasCode, HasParameters
{
private string $body = 'Unexpected key(s) {keys}, expected {expected_keys}.';
+ private string $code = 'unexpected_keys';
+
/** @var array */
private array $parameters;
/**
* @param array $value
- * @param array $children
+ * @param non-empty-list $children
*/
public function __construct(array $value, array $children)
{
- $expected = array_map(fn (TreeNode $child) => $child->name(), $children);
$superfluous = array_filter(
array_keys($value),
- fn (string $key) => ! in_array($key, $expected, true)
+ fn (string $key) => ! in_array($key, $children, true),
);
$this->parameters = [
'keys' => '`' . implode('`, `', $superfluous) . '`',
- 'expected_keys' => '`' . implode('`, `', $expected) . '`',
+ 'expected_keys' => '`' . implode('`, `', $children) . '`',
];
-
- parent::__construct(StringFormatter::for($this), 1655117782);
}
public function body(): string
@@ -49,6 +45,11 @@ public function body(): string
return $this->body;
}
+ public function code(): string
+ {
+ return $this->code;
+ }
+
public function parameters(): array
{
return $this->parameters;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php
index f1c4c8fe1ab..f8f583f7dec 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/DefaultMessage.php
@@ -8,9 +8,6 @@
interface DefaultMessage
{
public const TRANSLATIONS = [
- 'Value {source_value} does not match any of {allowed_values}.' => [
- 'en' => 'Value {source_value} does not match any of {allowed_values}.',
- ],
'Value {source_value} does not match any of {allowed_types}.' => [
'en' => 'Value {source_value} does not match any of {allowed_types}.',
],
@@ -20,9 +17,6 @@ interface DefaultMessage
'Value {source_value} does not match type {expected_type}.' => [
'en' => 'Value {source_value} does not match type {expected_type}.',
],
- 'Value {source_value} does not match {expected_value}.' => [
- 'en' => 'Value {source_value} does not match {expected_value}.',
- ],
'Value {source_value} does not match boolean value {expected_value}.' => [
'en' => 'Value {source_value} does not match boolean value {expected_value}.',
],
@@ -74,6 +68,9 @@ interface DefaultMessage
'Value {source_value} is not a valid class string of `{expected_class_type}`.' => [
'en' => 'Value {source_value} is not a valid class string of `{expected_class_type}`.',
],
+ 'Value {source_value} is not a valid array key.' => [
+ 'en' => 'Value {source_value} is not a valid array key.',
+ ],
'Invalid value {source_value}.' => [
'en' => 'Invalid value {source_value}.',
],
@@ -95,11 +92,20 @@ interface DefaultMessage
'Key {key} does not match type {expected_type}.' => [
'en' => 'Key {key} does not match type {expected_type}.',
],
+ 'Array cannot be empty and must contain values of type {expected_subtype}.' => [
+ 'en' => 'Array cannot be empty and must contain values of type {expected_subtype}.',
+ ],
+ 'List cannot be empty and must contain values of type {expected_subtype}.' => [
+ 'en' => 'List cannot be empty and must contain values of type {expected_subtype}.',
+ ],
'Value {source_value} does not match a valid date format.' => [
'en' => 'Value {source_value} does not match a valid date format.',
],
'Value {source_value} does not match any of the following formats: {formats}.' => [
'en' => 'Value {source_value} does not match any of the following formats: {formats}.',
],
+ 'Unexpected key(s) {keys}, expected {expected_keys}.' => [
+ 'en' => 'Unexpected key(s) {keys}, expected {expected_keys}.',
+ ],
];
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php
index 1c53b35e5b4..a790e613046 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/ErrorMessage.php
@@ -4,7 +4,5 @@
namespace CuyZ\Valinor\Mapper\Tree\Message;
-use Throwable;
-
/** @api */
-interface ErrorMessage extends Message, Throwable {}
+interface ErrorMessage extends Message {}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/MessageBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/MessageBuilder.php
index e668aede69a..a648531ba1f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/MessageBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/MessageBuilder.php
@@ -4,7 +4,6 @@
namespace CuyZ\Valinor\Mapper\Tree\Message;
-use LanguageServerProtocol\MessageType;
use RuntimeException;
use Throwable;
@@ -42,11 +41,11 @@ public static function new(string $body): self
}
/**
- * @return self
+ * @return self
*/
public static function newError(string $body): self
{
- /** @var self $instance */
+ /** @var self $instance */
$instance = new self($body);
$instance->isError = true;
@@ -155,7 +154,7 @@ public function parameters(): array
};
}
- private function buildErrorMessage(): ErrorMessage&HasCode&HasParameters
+ private function buildErrorMessage(): ErrorMessage&Throwable&HasCode&HasParameters
{
return new class ($this->body, $this->code, $this->parameters) extends RuntimeException implements ErrorMessage, HasCode, HasParameters {
/**
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/Messages.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/Messages.php
index 33fa2addff8..837cda6cb95 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/Messages.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/Messages.php
@@ -6,18 +6,15 @@
use Countable;
use CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageFormatter;
-use CuyZ\Valinor\Mapper\Tree\Node;
-use CuyZ\Valinor\Mapper\Tree\NodeTraverser;
use Iterator;
use IteratorAggregate;
use function array_filter;
-use function array_values;
use function count;
+use function iterator_to_array;
/**
- * Contains instances of messages. Can be used to flatten all messages of a node
- * when a mapping error occurs.
+ * Container for messages representing errors detected during mapping.
*
* Message formatters can be added and will be applied on all messages.
*
@@ -27,10 +24,8 @@
* ->mapper()
* ->map(SomeClass::class, [/* … * /]);
* } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
- * // Get a flatten list of all messages through the whole nodes tree
- * $messages = \CuyZ\Valinor\Mapper\Tree\Message\Messages::flattenFromNode(
- * $error->node()
- * );
+ * // Get a flattened list of all messages detected during mapping
+ * $messages = $error->messages();
*
* // Formatters can be added and will be applied on all messages
* $messages = $messages->formatWith(
@@ -43,11 +38,8 @@
* ])
* );
*
- * // If only errors are wanted, they can be filtered
- * $errors = $messages->errors();
- *
- * foreach ($errors as $errorMessage) {
- * // …
+ * foreach ($messages as $message) {
+ * echo $message;
* }
* }
* ```
@@ -58,13 +50,14 @@
*/
final class Messages implements IteratorAggregate, Countable
{
- /** @var list */
+ /** @var array */
private array $messages;
/** @var array */
private array $formatters = [];
/**
+ * @internal
* @no-named-arguments
*/
public function __construct(NodeMessage ...$messages)
@@ -72,27 +65,10 @@ public function __construct(NodeMessage ...$messages)
$this->messages = $messages;
}
- public static function flattenFromNode(Node $node): self
- {
- $nodeMessages = (new NodeTraverser(
- fn (Node $node) => $node->messages()
- ))->traverse($node);
-
- $messages = [];
-
- foreach ($nodeMessages as $messagesGroup) {
- $messages = [...$messages, ...$messagesGroup];
- }
-
- return new self(...$messages);
- }
-
public function errors(): self
{
$clone = clone $this;
- $clone->messages = array_values(
- array_filter($clone->messages, fn (NodeMessage $message) => $message->isError())
- );
+ $clone->messages = array_filter($this->messages, fn (NodeMessage $message) => $message->isError());
return $clone;
}
@@ -110,7 +86,8 @@ public function formatWith(MessageFormatter ...$formatters): self
*/
public function toArray(): array
{
- return [...$this];
+ /** @var list */
+ return iterator_to_array($this);
}
public function count(): int
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php
index 83dd7287a3d..0c6d00d2833 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/NodeMessage.php
@@ -4,9 +4,7 @@
namespace CuyZ\Valinor\Mapper\Tree\Message;
-use CuyZ\Valinor\Mapper\Tree\Node;
use CuyZ\Valinor\Utility\String\StringFormatter;
-use CuyZ\Valinor\Utility\ValueDumper;
use Stringable;
use Throwable;
@@ -15,28 +13,22 @@
/** @api */
final class NodeMessage implements Message, HasCode, Stringable
{
- private Node $node;
-
- private Message $message;
-
- private string $body;
-
/** @var array */
private array $parameters = [];
private string $locale = StringFormatter::DEFAULT_LOCALE;
- public function __construct(Node $node, Message $message)
- {
- $this->node = $node;
- $this->message = $message;
- $this->body = $message->body();
- }
-
- public function node(): Node
- {
- return $this->node;
- }
+ /**
+ * @internal
+ */
+ public function __construct(
+ private Message $message,
+ private string $body,
+ private string $name,
+ private string $path,
+ private string $type,
+ private string $sourceValue,
+ ) {}
public function withLocale(string $locale): self
{
@@ -83,6 +75,26 @@ public function body(): string
return $this->body;
}
+ public function name(): string
+ {
+ return $this->name;
+ }
+
+ public function path(): string
+ {
+ return $this->path;
+ }
+
+ public function type(): string
+ {
+ return $this->type;
+ }
+
+ public function sourceValue(): string
+ {
+ return $this->sourceValue;
+ }
+
/**
* Adds a parameter that can replace a placeholder in the message body.
*
@@ -103,7 +115,7 @@ public function originalMessage(): Message
public function isError(): bool
{
- return $this->message instanceof Throwable;
+ return $this->message instanceof ErrorMessage;
}
public function code(): string
@@ -144,10 +156,10 @@ private function parameters(): array
{
$parameters = [
'message_code' => $this->code(),
- 'node_name' => $this->node->name(),
- 'node_path' => $this->node->path(),
- 'node_type' => "`{$this->node->type()}`",
- 'source_value' => $this->node->sourceFilled() ? ValueDumper::dump($this->node->sourceValue()) : '*missing*',
+ 'node_name' => $this->name,
+ 'node_path' => $this->path,
+ 'node_type' => $this->type,
+ 'source_value' => $this->sourceValue,
];
if ($this->message instanceof HasParameters) {
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php
index fd4fc4f41b8..301063dccad 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Message/UserlandError.php
@@ -8,23 +8,14 @@
use Throwable;
/** @internal */
-final class UserlandError extends RuntimeException implements ErrorMessage
+final class UserlandError extends RuntimeException
{
- public static function from(Throwable $message): Message&Throwable
+ public static function from(Throwable $message): Throwable
{
- // @infection-ignore-all
- return $message instanceof Message
- ? $message
- : new self('Invalid value.', 1657215570, $message);
- }
+ if ($message instanceof ErrorMessage) {
+ return $message;
+ }
- public function body(): string
- {
- return 'Invalid value.';
- }
-
- public function previous(): Throwable
- {
- return $this->getPrevious(); // @phpstan-ignore-line
+ return new self(previous: $message);
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Node.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Node.php
deleted file mode 100644
index d66c081866f..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Node.php
+++ /dev/null
@@ -1,136 +0,0 @@
- */
- private array $messages = [];
-
- /** @var array */
- private array $children;
-
- /**
- * @param array $messages
- * @param array $children
- */
- public function __construct(
- bool $isRoot,
- string $name,
- string $path,
- string $type,
- bool $sourceFilled,
- mixed $sourceValue,
- mixed $mappedValue,
- array $messages,
- array $children
- ) {
- $this->isRoot = $isRoot;
- $this->name = $name;
- $this->path = $path;
- $this->type = $type;
- $this->sourceFilled = $sourceFilled;
- $this->sourceValue = $sourceValue;
- $this->mappedValue = $mappedValue;
- $this->children = $children;
-
- foreach ($messages as $message) {
- $message = new NodeMessage($this, $message);
-
- $this->messages[] = $message;
- $this->isValid = $this->isValid && ! $message->isError();
- }
-
- foreach ($this->children as $child) {
- $this->isValid = $this->isValid && $child->isValid();
- }
- }
-
- public function isRoot(): bool
- {
- return $this->isRoot;
- }
-
- public function name(): string
- {
- return $this->name;
- }
-
- public function path(): string
- {
- return $this->path;
- }
-
- public function type(): string
- {
- return $this->type;
- }
-
- public function sourceFilled(): bool
- {
- return $this->sourceFilled;
- }
-
- public function sourceValue(): mixed
- {
- if (! $this->sourceFilled) {
- throw new SourceValueWasNotFilled($this->path);
- }
-
- return $this->sourceValue;
- }
-
- public function isValid(): bool
- {
- return $this->isValid;
- }
-
- public function mappedValue(): mixed
- {
- if (! $this->isValid) {
- throw new InvalidNodeHasNoMappedValue($this->path);
- }
-
- return $this->mappedValue;
- }
-
- /**
- * @return list
- */
- public function messages(): array
- {
- return $this->messages;
- }
-
- /**
- * @return array
- */
- public function children(): array
- {
- return $this->children;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php
deleted file mode 100644
index ea48df9f36b..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/NodeTraverser.php
+++ /dev/null
@@ -1,44 +0,0 @@
-callback = $callback;
- }
-
- /**
- * @return iterable
- */
- public function traverse(Node $node): iterable
- {
- return $this->recurse($node);
- }
-
- /**
- * @return iterable
- */
- private function recurse(Node $node): iterable
- {
- yield ($this->callback)($node);
-
- foreach ($node->children() as $child) {
- yield from $this->recurse($child);
- }
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Shell.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Shell.php
index 2724713f0dc..8331d8cfba8 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Shell.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/Tree/Shell.php
@@ -59,17 +59,13 @@ public static function root(
return (new self($settings, $type))->withValue($value);
}
- public function child(string $name, Type $type, ?Attributes $attributes = null): self
+ public function child(string $name, Type $type): self
{
$path = $this->path;
$path[] = $name;
$instance = new self($this->settings, $type, $path);
$instance->name = $name;
- if ($attributes) {
- $instance->attributes = $attributes;
- }
-
return $instance;
}
@@ -118,6 +114,14 @@ public function value(): mixed
return $this->value;
}
+ public function withAttributes(Attributes $attributes): self
+ {
+ $clone = clone $this;
+ $clone->attributes = $this->attributes()->merge($attributes);
+
+ return $clone;
+ }
+
public function allowScalarValueCasting(): bool
{
return $this->settings->allowScalarValueCasting;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TreeMapper.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TreeMapper.php
index 503de2d4ddd..8da44254844 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TreeMapper.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TreeMapper.php
@@ -8,6 +8,8 @@
interface TreeMapper
{
/**
+ * @pure
+ *
* @template T of object
*
* @param string|class-string $signature
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php
index 05f17ffdbcd..4f22a9ce04c 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeArgumentsMapper.php
@@ -25,6 +25,9 @@ public function __construct(
private Settings $settings,
) {}
+ /**
+ * @pure
+ */
public function mapArguments(callable $callable, mixed $source): array
{
$function = $this->functionDefinitionRepository->for($callable);
@@ -34,6 +37,7 @@ public function mapArguments(callable $callable, mixed $source): array
new StringValueType($parameter->name),
$parameter->type,
$parameter->isOptional,
+ $parameter->attributes,
),
$function->parameters->toList(),
);
@@ -41,6 +45,7 @@ public function mapArguments(callable $callable, mixed $source): array
$type = new ShapedArrayType(...$elements);
$shell = Shell::root($this->settings, $type, $source);
+ $shell = $shell->withAttributes($function->attributes);
try {
$node = $this->nodeBuilder->build($shell);
@@ -66,6 +71,6 @@ public function mapArguments(callable $callable, mixed $source): array
}
}
- throw new ArgumentsMapperError($function, $node->node());
+ throw new ArgumentsMapperError($shell->value(), $type->toString(), $function->signature, $node->messages());
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapper.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapper.php
index eb997694f46..2ba252efa7e 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapper.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapper.php
@@ -8,7 +8,6 @@
use CuyZ\Valinor\Mapper\Exception\InvalidMappingTypeSignature;
use CuyZ\Valinor\Mapper\Exception\TypeErrorDuringMapping;
use CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder;
-use CuyZ\Valinor\Mapper\Tree\Builder\TreeNode;
use CuyZ\Valinor\Mapper\Tree\Exception\UnresolvableShellType;
use CuyZ\Valinor\Mapper\Tree\Shell;
use CuyZ\Valinor\Type\Parser\Exception\InvalidType;
@@ -23,18 +22,10 @@ public function __construct(
private Settings $settings,
) {}
+ /**
+ * @pure
+ */
public function map(string $signature, mixed $source): mixed
- {
- $node = $this->node($signature, $source);
-
- if (! $node->isValid()) {
- throw new TypeTreeMapperError($node->node());
- }
-
- return $node->value();
- }
-
- private function node(string $signature, mixed $source): TreeNode
{
try {
$type = $this->typeParser->parse($signature);
@@ -45,9 +36,15 @@ private function node(string $signature, mixed $source): TreeNode
$shell = Shell::root($this->settings, $type, $source);
try {
- return $this->nodeBuilder->build($shell);
+ $node = $this->nodeBuilder->build($shell);
} catch (UnresolvableShellType $exception) {
throw new TypeErrorDuringMapping($type, $exception);
}
+
+ if (! $node->isValid()) {
+ throw new TypeTreeMapperError($source, $type->toString(), $node->messages());
+ }
+
+ return $node->value();
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapperError.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapperError.php
index 256ac4b80be..79a672b2bf2 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapperError.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Mapper/TypeTreeMapperError.php
@@ -5,34 +5,55 @@
namespace CuyZ\Valinor\Mapper;
use CuyZ\Valinor\Mapper\Tree\Message\Messages;
-use CuyZ\Valinor\Mapper\Tree\Node;
+use CuyZ\Valinor\Mapper\Tree\Message\NodeMessage;
use CuyZ\Valinor\Utility\ValueDumper;
use RuntimeException;
/** @internal */
final class TypeTreeMapperError extends RuntimeException implements MappingError
{
- public function __construct(private Node $node)
+ private Messages $messages;
+
+ private string $type;
+
+ private mixed $source;
+
+ /**
+ * @param non-empty-list $messages
+ */
+ public function __construct(mixed $source, string $type, array $messages)
{
- $errors = Messages::flattenFromNode($node)->errors();
- $errorsCount = count($errors);
+ $this->messages = new Messages(...$messages);
+ $this->type = $type;
+ $this->source = $source;
+
+ $errorsCount = count($messages);
if ($errorsCount === 1) {
- $body = $errors
- ->toArray()[0]
- ->withParameter('root_type', $node->type())
+ $body = $messages[0]
+ ->withParameter('root_type', $type)
->withBody("Could not map type `{root_type}`. An error occurred at path {node_path}: {original_message}")
->toString();
} else {
- $source = ValueDumper::dump($node->sourceValue());
- $body = "Could not map type `{$node->type()}` with value $source. A total of $errorsCount errors were encountered.";
+ $source = ValueDumper::dump($source);
+ $body = "Could not map type `$type` with value $source. A total of $errorsCount errors were encountered.";
}
parent::__construct($body, 1617193185);
}
- public function node(): Node
+ public function messages(): Messages
+ {
+ return $this->messages;
+ }
+
+ public function type(): string
+ {
+ return $this->type;
+ }
+
+ public function source(): mixed
{
- return $this->node;
+ return $this->source;
}
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/MapperBuilder.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/MapperBuilder.php
index a0b53bae3fc..e2065d25d4b 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/MapperBuilder.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/MapperBuilder.php
@@ -4,14 +4,12 @@
namespace CuyZ\Valinor;
+use CuyZ\Valinor\Cache\Cache;
use CuyZ\Valinor\Library\Container;
use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\Mapper\ArgumentsMapper;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Mapper\TreeMapper;
-use CuyZ\Valinor\Normalizer\Format;
-use CuyZ\Valinor\Normalizer\Normalizer;
-use Psr\SimpleCache\CacheInterface;
use Throwable;
use function array_unique;
@@ -37,6 +35,9 @@ public function __construct()
* using the given source. These arguments can then be used to decide which
* implementation should be used.
*
+ * The callback *must* be pure, its output must be deterministic.
+ * @see https://en.wikipedia.org/wiki/Pure_function
+ *
* Example:
*
* ```php
@@ -55,6 +56,7 @@ public function __construct()
* ```
*
* @param interface-string|class-string $name
+ * @param pure-callable $callback
*/
public function infer(string $name, callable $callback): self
{
@@ -66,7 +68,13 @@ public function infer(string $name, callable $callback): self
/**
* Registers a constructor that can be used by the mapper to create an
- * instance of an object. A constructor is a callable that can be either:
+ * instance of an object.
+ *
+ * Note that depending on your needs, a more straightforward way to register
+ * a constructor is to use the following attribute on a static method:
+ * @see \CuyZ\Valinor\Mapper\Object\Constructor
+ *
+ * A constructor is a callable that can be either:
*
* 1. A named constructor, also known as a static factory method
* 2. The method of a service — for instance a repository
@@ -194,7 +202,10 @@ public function infer(string $name, callable $callback): self
* ]);
* ```
*
- * @param callable|class-string ...$constructors
+ * The constructor *must* be pure, its output must be deterministic.
+ * @see https://en.wikipedia.org/wiki/Pure_function
+ *
+ * @param pure-callable|class-string ...$constructors
*/
public function registerConstructor(callable|string ...$constructors): self
{
@@ -251,20 +262,17 @@ public function supportedDateFormats(): array
/**
* Inject a cache implementation that will be in charge of caching heavy
- * data used by the mapper.
+ * data used by the mapper. It is *strongly* recommended to use it when the
+ * application runs in a production environment.
*
- * An implementation is provided by the library, which writes cache entries
- * in the file system; it is strongly recommended to use it when the
- * application runs in production environment.
- *
- * It is also possible to use any PSR-16 compliant implementation, as long
- * as it is capable of caching the entries handled by the library.
+ * An implementation is provided out of the box, which writes cache entries
+ * in the file system.
*
* When the application runs in a development environment, the cache
- * implementation should be decorated with `FileWatchingCache`, which will
- * watch the files of the application and invalidate cache entries when a
- * PHP file is modified by a developer — preventing the library not behaving
- * as expected when the signature of a property or a method changes.
+ * implementation should be decorated with `FileWatchingCache`. This service
+ * will watch the files of the application and invalidate cache entries when
+ * a PHP file is modified by a developer — preventing the library not
+ * behaving as expected when the signature of a property or a method changes.
*
* ```php
* $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir');
@@ -281,7 +289,7 @@ public function supportedDateFormats(): array
* ]);
* ```
*/
- public function withCache(CacheInterface $cache): self
+ public function withCache(Cache $cache): self
{
$clone = clone $this;
$clone->settings->cache = $cache;
@@ -289,34 +297,6 @@ public function withCache(CacheInterface $cache): self
return $clone;
}
- /**
- * @template T
- * @param callable(T): T $callback
- */
- public function alter(callable $callback): self
- {
- $clone = clone $this;
- $clone->settings->valueModifier[] = $callback;
-
- return $clone;
- }
-
- /**
- * This setting will be removed in a future major version, as a replacement
- * the following methods should be used:
- *
- * - @see allowScalarValueCasting()
- * - @see allowNonSequentialList()
- * - @see allowUndefinedValues()
- */
- public function enableFlexibleCasting(): self
- {
- return $this
- ->allowScalarValueCasting()
- ->allowNonSequentialList()
- ->allowUndefinedValues();
- }
-
/**
* With this setting enabled, scalar types will accept castable values:
*
@@ -449,6 +429,111 @@ public function allowPermissiveTypes(): self
return $clone;
}
+ /**
+ * A mapper converter allows users to hook into the mapping process and
+ * apply custom logic to the input, by defining a callable signature that
+ * properly describes when it should be called:
+ *
+ * - A first argument with a type matching the expected input being mapped
+ * - A return type representing the targeted mapped type
+ *
+ * These two types are enough for the library to know when to call the
+ * converter and can contain advanced type annotations for more specific
+ * use cases.
+ *
+ * Below is a basic example of a converter that converts string inputs to
+ * uppercase:
+ *
+ * ```php
+ * (new \CuyZ\Valinor\MapperBuilder())
+ * ->registerConverter(fn (string $value): string => strtoupper($value))
+ * ->mapper()
+ * ->map('string', 'hello world'); // 'HELLO WORLD'
+ * ```
+ *
+ * Converters can be chained, allowing multiple transformations to be
+ * applied to a value. A second `callable` parameter can be declared,
+ * allowing the current converter to call the next one in the chain.
+ *
+ * A priority can be given to a converter to control the order in which
+ * converters are applied. The higher the priority, the earlier the
+ * converter will be executed. The default priority is 0.
+ *
+ * An attribute on a property or a class can act as a converter if:
+ * 1. It defines a `map` method.
+ * 2. It is registered using either the `registerConverter()` method or
+ * the following attribute: @see \CuyZ\Valinor\Mapper\AsConverter
+ *
+ * ```php
+ * (new \CuyZ\Valinor\MapperBuilder())
+ *
+ * // The type of the first parameter of the converter will determine
+ * // when it will be used by the mapper.
+ * ->registerConverter(
+ * fn (string $value, callable $next): string => $next(strtoupper($value))
+ * )
+ *
+ * // Converters can be chained, the last registered one will take
+ * // precedence over the previous ones, which can be called using the
+ * // `$next` parameter.
+ * ->registerConverter(
+ * fn (string $value, callable $next): string => $next($value . '!')
+ * )
+ *
+ * // A priority can be given to a converter, to make sure it is called
+ * // before or after another one.
+ * ->registerConverter(
+ * fn (string $value, callable $next): string => $next($value . '?'),
+ * priority: -100 // Negative priority: converter is called early
+ * )
+ *
+ * // External converter attributes must be registered before they are
+ * // used by the mapper.
+ * ->registerConverter(\Some\External\ConverterAttribute::class)
+ *
+ * ->mapper()
+ * ->map('string', 'hello world'); // 'HELLO WORLD!?'
+ * ```
+ *
+ * It is also possible to register attributes that share a common interface
+ * by giving the interface name to the registration method.
+ *
+ * ```php
+ * namespace My\App;
+ *
+ * interface MyAttributeInterface {}
+ *
+ * #[\Attribute]
+ * final class SomeAttribute implements \My\App\MyAttributeInterface {}
+ *
+ * #[\Attribute]
+ * final class SomeOtherAttribute implements \My\App\MyAttributeInterface {}
+ *
+ * (new \CuyZ\Valinor\MapperBuilder())
+ * // Registers both `SomeAttribute` and `SomeOtherAttribute` attributes
+ * ->registerConverter(\My\App\MyAttributeInterface::class)
+ * ->mapper()
+ * ->map(…);
+ * ```
+ *
+ * The converter *must* be pure, its output must be deterministic.
+ * @see https://en.wikipedia.org/wiki/Pure_function
+ *
+ * @param pure-callable|class-string $converter
+ */
+ public function registerConverter(callable|string $converter, int $priority = 0): self
+ {
+ $clone = clone $this;
+
+ if (is_callable($converter)) {
+ $clone->settings->mapperConverters[$priority][] = $converter;
+ } else {
+ $clone->settings->mapperConverterAttributes[$converter] = null;
+ }
+
+ return $clone;
+ }
+
/**
* Filters which userland exceptions are allowed during the mapping.
*
@@ -493,86 +578,49 @@ public function filterExceptions(callable $filter): self
}
/**
- * A transformer is responsible for transforming specific values during a
- * normalization process.
- *
- * Transformers can be chained, the last registered one will take precedence
- * over the previous ones.
- *
- * By specifying the type of its first parameter, the given callable will
- * determine when the transformer is used. Advanced type annotations like
- * `non-empty-string` can be used to target a more specific type.
- *
- * A second `callable` parameter may be declared, allowing to call the next
- * transformer in the chain and get the modified value from it, before
- * applying its own transformations.
- *
- * A priority can be given to a transformer, to make sure it is called
- * before or after another one. The higher the priority, the sooner the
- * transformer will be called. Default priority is 0.
- *
- * An attribute on a property or a class can act as a transformer if:
- * 1. It defines a `normalize` or `normalizeKey` method.
- * 2. It is registered using either the `registerTransformer()` method or
- * the following attribute: @see \CuyZ\Valinor\Normalizer\AsTransformer
- *
- * Example:
+ * Warms up the injected cache implementation with the provided type
+ * signatures. This will improve the performance when the first call to the
+ * mapper is done for each of these types.
*
* ```php
- * (new \CuyZ\Valinor\MapperBuilder())
- *
- * // The type of the first parameter of the transformer will determine
- * // when it will be used by the normalizer.
- * ->registerTransformer(
- * fn (string $value, callable $next) => strtoupper($next())
- * )
- *
- * // Transformers can be chained, the last registered one will take
- * // precedence over the previous ones, which can be called using the
- * // `$next` parameter.
- * ->registerTransformer(
- * fn (string $value, callable $next) => $next() . '!'
- * )
- *
- * // A priority can be given to a transformer, to make sure it is
- * // called before or after another one.
- * ->registerTransformer(
- * fn (string $value, callable $next) => $next() . '?',
- * priority: -100 // Negative priority: transformer is called early
- * )
- *
- * // External transformer attributes must be registered before they are
- * // used by the normalizer.
- * ->registerTransformer(\Some\External\TransformerAttribute::class)
- *
- * ->normalizer()
- * ->normalize('Hello world'); // HELLO WORLD?!
+ * $mapperBuilder = (new \CuyZ\Valinor\MapperBuilder())
+ * ->withCache(new \CuyZ\Valinor\Cache\FileSystemCache('path/to/dir'));
+ *
+ * // During the build:
+ * $mapperBuilder->warmupCacheFor(
+ * // This will also recursively warm up the cache for the types of
+ * // the class properties.
+ * SomeClass::class,
+ *
+ * // Any valid type signature can be used.
+ * 'non-empty-list',
+ * 'array{name: string, age: int}',
+ * );
+ *
+ * // In the application:
+ * $mapperBuilder->mapper()->map(SomeClass::class, […]);
* ```
- *
- * @param callable|class-string $transformer
*/
- public function registerTransformer(callable|string $transformer, int $priority = 0): self
+ public function warmupCacheFor(string ...$signatures): void
{
- $clone = clone $this;
-
- if (is_callable($transformer)) {
- $clone->settings->transformers[$priority][] = $transformer;
- } else {
- $clone->settings->transformerAttributes[$transformer] = null;
+ if (! isset($this->settings->cache)) {
+ return;
}
- return $clone;
+ $this->container()->cacheWarmupService()->warmup(...$signatures);
}
/**
- * Warms up the injected cache implementation with the provided class names.
- *
- * By passing a class which contains recursive objects, every nested object
- * will be cached as well.
+ * Clears all persisted cache entries from the registered cache
+ * implementation.
*/
- public function warmup(string ...$signatures): void
+ public function clearCache(): void
{
- $this->container()->cacheWarmupService()->warmup(...$signatures);
+ if (! isset($this->settings->cache)) {
+ return;
+ }
+
+ $this->settings->cache->clear();
}
public function mapper(): TreeMapper
@@ -585,17 +633,6 @@ public function argumentsMapper(): ArgumentsMapper
return $this->container()->argumentsMapper();
}
- /**
- * @template T of Normalizer
- *
- * @param Format $format
- * @return T
- */
- public function normalizer(Format $format): Normalizer
- {
- return $this->container()->normalizer($format);
- }
-
public function __clone()
{
$this->settings = clone $this->settings;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/ArrayNormalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/ArrayNormalizer.php
index da9e3099aa5..30037bad842 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/ArrayNormalizer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/ArrayNormalizer.php
@@ -14,10 +14,16 @@
*/
final class ArrayNormalizer implements Normalizer
{
+ /**
+ * @internal
+ */
public function __construct(
private Transformer $transformer,
) {}
+ /**
+ * @pure
+ */
public function normalize(mixed $value): mixed
{
/** @var array|scalar|null */
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php
index 7263e9a8b3b..b70266ccd92 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/AsTransformer.php
@@ -10,7 +10,7 @@
* This attribute can be used to automatically register a transformer attribute.
*
* When there is no control over the transformer attribute class, the following
- * method can be used: @see \CuyZ\Valinor\MapperBuilder::registerTransformer
+ * method can be used: @see \CuyZ\Valinor\NormalizerBuilder::registerTransformer
*
* ```php
* namespace My\App;
@@ -36,7 +36,7 @@
* ) {}
* }
*
- * (new \CuyZ\Valinor\MapperBuilder())
+ * (new \CuyZ\Valinor\NormalizerBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::array())
* ->normalize(new \My\App\Event(
* eventName: 'Release of legendary album',
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Format.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Format.php
index 758b6c3c1f5..f32db364de1 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Format.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Format.php
@@ -18,7 +18,7 @@ final class Format
* ```php
* namespace My\App;
*
- * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+ * $normalizer = (new \CuyZ\Valinor\NormalizerBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::array());
*
* $userAsArray = $normalizer->normalize(
@@ -58,7 +58,7 @@ public static function array(): self
* ```php
* namespace My\App;
*
- * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+ * $normalizer = (new \CuyZ\Valinor\NormalizerBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::json());
*
* $userAsJson = $normalizer->normalize(
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php
index 21e88b6a4db..48d2eeda684 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/JsonNormalizer.php
@@ -63,6 +63,8 @@ final class JsonNormalizer implements Normalizer
* for PHPStan but Psalm does have some (not all) issues as well.
* - Using this annotation provokes *severe* performance issues when
* running PHPStan analysis, therefore it is preferable to avoid it.
+ *
+ * @internal
*/
public function __construct(
Transformer $transformer,
@@ -80,7 +82,7 @@ public function __construct(
* This can be achieved by passing these flags to this method:
*
* ```php
- * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+ * $normalizer = (new \CuyZ\Valinor\NormalizerBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
* ->withOptions(\JSON_PRESERVE_ZERO_FRACTION);
*
@@ -100,6 +102,9 @@ public function withOptions(int $options): self
return new self($this->transformer, $options);
}
+ /**
+ * @pure
+ */
public function normalize(mixed $value): string
{
$result = $this->transformer->transform($value);
@@ -136,7 +141,7 @@ public function normalize(mixed $value): string
*
* $file = fopen('path/to/some_file.json', 'w');
*
- * $normalizer = (new \CuyZ\Valinor\MapperBuilder())
+ * $normalizer = (new \CuyZ\Valinor\NormalizerBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
* ->streamTo($file);
*
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Normalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Normalizer.php
index cd56c8f2806..857b0edbdf9 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Normalizer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Normalizer.php
@@ -19,6 +19,7 @@ interface Normalizer
* a data format (JSON, CSV, XML, etc.). The normalizer will take care of
* recursively transforming the data into a format that can be serialized.
*
+ * @pure
* @return T
*/
public function normalize(mixed $value): mixed;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php
index 4770c98ca7f..a638a8cbdd3 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/StreamNormalizer.php
@@ -14,11 +14,17 @@
*/
final class StreamNormalizer implements Normalizer
{
+ /**
+ * @internal
+ */
public function __construct(
private Transformer $transformer,
private JsonFormatter $formatter,
) {}
+ /**
+ * @pure
+ */
public function normalize(mixed $value): mixed
{
$result = $this->transformer->transform($value);
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CacheTransformer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php
similarity index 85%
rename from wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CacheTransformer.php
rename to wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php
index cf18327a95e..93ae34ee240 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CacheTransformer.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php
@@ -5,6 +5,9 @@
namespace CuyZ\Valinor\Normalizer\Transformer;
use Closure;
+use CuyZ\Valinor\Cache\Cache;
+use CuyZ\Valinor\Cache\CacheEntry;
+use CuyZ\Valinor\Cache\TypeFilesWatcher;
use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Normalizer\Exception\TypeUnhandledByNormalizer;
@@ -28,7 +31,6 @@
use Generator;
use Iterator;
use IteratorAggregate;
-use Psr\SimpleCache\CacheInterface;
use UnitEnum;
use function array_is_list;
@@ -38,12 +40,13 @@
use function is_scalar;
/** @internal */
-final class CacheTransformer implements Transformer
+final class CompiledTransformer implements Transformer
{
public function __construct(
private TransformerDefinitionBuilder $definitionBuilder,
- /** @var CacheInterface, Transformer): Transformer> */
- private CacheInterface $cache,
+ private TypeFilesWatcher $typeFilesWatcher,
+ /** @var Cache */
+ private Cache $cache,
/** @var list */
private array $transformers,
) {}
@@ -54,23 +57,20 @@ public function transform(mixed $value): mixed
$key = "transformer-\0" . $type->toString();
- $entry = $this->cache->get($key);
-
- if ($entry) {
- $transformer = $entry($this->transformers, $this);
+ $transformer = $this->cache->get($key, $this->transformers, $this);
+ if ($transformer) {
return $transformer->transform($value);
}
- $transformer = new EvaluatedTransformer($this->compileFor($type));
+ $code = $this->compileFor($type);
+ $filesToWatch = $this->typeFilesWatcher->for($type);
- // @phpstan-ignore argument.type (this is a temporary workaround, while waiting for the cache API to be refined)
- $this->cache->set($key, $transformer);
+ $this->cache->set($key, new CacheEntry($code, $filesToWatch));
- $entry = $this->cache->get($key);
+ $transformer = $this->cache->get($key, $this->transformers, $this);
- // @phpstan-ignore callable.nonCallable (this is a temporary workaround, while waiting for the cache API to be refined)
- $transformer = $entry($this->transformers, $this);
+ assert($transformer instanceof Transformer);
return $transformer->transform($value);
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php
deleted file mode 100644
index 82858eac462..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Normalizer/Transformer/EvaluatedTransformer.php
+++ /dev/null
@@ -1,13 +0,0 @@
-settings = new Settings();
+ }
+
+ /**
+ * Inject a cache implementation that will be in charge of caching heavy
+ * data used by the normalizer. It is *strongly* recommended to use it when
+ * the application runs in a production environment.
+ *
+ * An implementation is provided out of the box, which writes cache entries
+ * in the file system.
+ *
+ * When the application runs in a development environment, the cache
+ * implementation should be decorated with `FileWatchingCache`. This service
+ * will watch the files of the application and invalidate cache entries when
+ * a PHP file is modified by a developer — preventing the library not
+ * behaving as expected when the signature of a property or a method changes.
+ *
+ * ```php
+ * $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir');
+ *
+ * if ($isApplicationInDevelopmentEnvironment) {
+ * $cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache);
+ * }
+ *
+ * (new \CuyZ\Valinor\NormalizerBuilder())
+ * ->withCache($cache)
+ * ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
+ * ->normalize($someData);
+ * ```
+ */
+ public function withCache(Cache $cache): self
+ {
+ $clone = clone $this;
+ $clone->settings->cache = $cache;
+
+ return $clone;
+ }
+
+ /**
+ * A transformer is responsible for transforming specific values during a
+ * normalization process.
+ *
+ * Transformers can be chained, the last registered one will take precedence
+ * over the previous ones.
+ *
+ * By specifying the type of its first parameter, the given callable will
+ * determine when the transformer is used. Advanced type annotations like
+ * `non-empty-string` can be used to target a more specific type.
+ *
+ * A second `callable` parameter may be declared, allowing to call the next
+ * transformer in the chain and get the modified value from it, before
+ * applying its own transformations.
+ *
+ * A priority can be given to a transformer, to make sure it is called
+ * before or after another one. The higher the priority, the sooner the
+ * transformer will be called. Default priority is 0.
+ *
+ * An attribute on a property or a class can act as a transformer if:
+ * 1. It defines a `normalize` or `normalizeKey` method.
+ * 2. It is registered using either the `registerTransformer()` method or
+ * the following attribute: @see \CuyZ\Valinor\Normalizer\AsTransformer
+ *
+ * Example:
+ *
+ * ```php
+ * (new \CuyZ\Valinor\NormalizerBuilder())
+ *
+ * // The type of the first parameter of the transformer will determine
+ * // when it will be used by the normalizer.
+ * ->registerTransformer(
+ * fn (string $value, callable $next) => strtoupper($next())
+ * )
+ *
+ * // Transformers can be chained, the last registered one will take
+ * // precedence over the previous ones, which can be called using the
+ * // `$next` parameter.
+ * ->registerTransformer(
+ * fn (string $value, callable $next) => $next() . '!'
+ * )
+ *
+ * // A priority can be given to a transformer, to make sure it is
+ * // called before or after another one.
+ * ->registerTransformer(
+ * fn (string $value, callable $next) => $next() . '?',
+ * priority: -100 // Negative priority: transformer is called early
+ * )
+ *
+ * // External transformer attributes must be registered before they are
+ * // used by the normalizer.
+ * ->registerTransformer(\Some\External\TransformerAttribute::class)
+ *
+ * ->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
+ * ->normalize('Hello world'); // HELLO WORLD?!
+ * ```
+ *
+ * The transformer *must* be pure, its output must be deterministic.
+ * @see https://en.wikipedia.org/wiki/Pure_function
+ *
+ * @param pure-callable|class-string $transformer
+ */
+ public function registerTransformer(callable|string $transformer, int $priority = 0): self
+ {
+ $clone = clone $this;
+
+ if (is_callable($transformer)) {
+ $clone->settings->normalizerTransformers[$priority][] = $transformer;
+ } else {
+ $clone->settings->normalizerTransformerAttributes[$transformer] = null;
+ }
+
+ return $clone;
+ }
+
+ /**
+ * Clears all persisted cache entries from the registered cache
+ * implementation.
+ */
+ public function clearCache(): void
+ {
+ if (! isset($this->settings->cache)) {
+ return;
+ }
+
+ $this->settings->cache->clear();
+ }
+
+ /**
+ * @template T of Normalizer
+ *
+ * @param Format $format
+ * @return T
+ */
+ public function normalizer(Format $format): Normalizer
+ {
+ return $this->container()->normalizer($format);
+ }
+
+ public function __clone()
+ {
+ $this->settings = clone $this->settings;
+ unset($this->container);
+ }
+
+ private function container(): Container
+ {
+ return ($this->container ??= new Container($this->settings));
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php
index 2cb8f190dcc..70ea53e825d 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Parser/Lexer/TokensExtractor.php
@@ -27,6 +27,7 @@ final class TokensExtractor
'Opening curly bracket' => '\{',
'Closing curly bracket' => '\}',
'Colon' => '\:',
+ 'Equal' => '=',
'Question mark' => '\?',
'Comma' => ',',
'Single quote' => "'",
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayKeyType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayKeyType.php
index cf1ccd4272d..db25e96d708 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayKeyType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayKeyType.php
@@ -176,7 +176,9 @@ public function cast(mixed $value): string|int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid array key.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid array key.')
+ ->withCode('invalid_array_key')
+ ->build();
}
public function nativeType(): Type
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayType.php
index 963600e348d..86b1a7c04dc 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ArrayType.php
@@ -62,6 +62,10 @@ public function accepts(mixed $value): bool
return false;
}
+ if ($this === self::native()) {
+ return true;
+ }
+
return Polyfill::array_all(
$value,
fn (mixed $item, mixed $key) => $this->keyType->accepts($key) && $this->subType->accepts($item),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/BooleanValueType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/BooleanValueType.php
index 7f75a53efef..3f3261081d1 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/BooleanValueType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/BooleanValueType.php
@@ -81,6 +81,7 @@ public function cast(mixed $value): bool
public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} does not match boolean value {expected_value}.')
+ ->withCode('invalid_boolean_value')
->withParameter('expected_value', $this->toString())
->build();
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ClassStringType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ClassStringType.php
index cf103d62e45..0fb8735d3b5 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ClassStringType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ClassStringType.php
@@ -147,11 +147,14 @@ public function errorMessage(): ErrorMessage
{
if ($this->subType) {
return MessageBuilder::newError('Value {source_value} is not a valid class string of `{expected_class_type}`.')
+ ->withCode('invalid_class_string')
->withParameter('expected_class_type', $this->subType->toString())
->build();
}
- return MessageBuilder::newError('Value {source_value} is not a valid class string.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid class string.')
+ ->withCode('invalid_class_string')
+ ->build();
}
public function subType(): ObjectType|UnionType|null
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/FloatValueType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/FloatValueType.php
index 9cad0eb62e4..2ed60e4d852 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/FloatValueType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/FloatValueType.php
@@ -59,6 +59,7 @@ public function cast(mixed $value): float
public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} does not match float value {expected_value}.')
+ ->withCode('invalid_float_value')
->withParameter('expected_value', (string)$this->value)
->build();
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerRangeType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerRangeType.php
index 0723c984cf2..5232102eaf6 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerRangeType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerRangeType.php
@@ -113,6 +113,7 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} is not a valid integer between {min} and {max}.')
+ ->withCode('invalid_integer_range')
->withParameter('min', (string)$this->min)
->withParameter('max', (string)$this->max)
->build();
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerValueType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerValueType.php
index b3fcb1730af..49bb3ba3030 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerValueType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IntegerValueType.php
@@ -62,6 +62,7 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} does not match integer value {expected_value}.')
+ ->withCode('invalid_integer_value')
->withParameter('expected_value', (string)$this->value)
->build();
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IterableType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IterableType.php
index 32977043fcc..30632b50fec 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IterableType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/IterableType.php
@@ -50,6 +50,10 @@ public function accepts(mixed $value): bool
return false;
}
+ if ($this === self::native()) {
+ return true;
+ }
+
foreach ($value as $key => $item) {
if (! $this->keyType->accepts($key)) {
return false;
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ListType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ListType.php
index c8b20f4f1db..8c893d36036 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ListType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ListType.php
@@ -51,6 +51,10 @@ public function accepts(mixed $value): bool
return false;
}
+ if ($this === self::native()) {
+ return true;
+ }
+
return Polyfill::array_all(
$value,
fn (mixed $item) => $this->subType->accepts($item),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeBooleanType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeBooleanType.php
index 580e4e7a0a1..ac5c7b698f8 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeBooleanType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeBooleanType.php
@@ -65,7 +65,9 @@ public function cast(mixed $value): bool
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid boolean.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid boolean.')
+ ->withCode('invalid_boolean')
+ ->build();
}
public function nativeType(): NativeBooleanType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeFloatType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeFloatType.php
index 1f706d749bf..35e8a126da8 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeFloatType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeFloatType.php
@@ -56,7 +56,9 @@ public function cast(mixed $value): float
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid float.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid float.')
+ ->withCode('invalid_float')
+ ->build();
}
public function nativeType(): NativeFloatType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeIntegerType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeIntegerType.php
index 7dac6aee95f..c5522d29e03 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeIntegerType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeIntegerType.php
@@ -63,7 +63,9 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid integer.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid integer.')
+ ->withCode('invalid_integer')
+ ->build();
}
public function nativeType(): NativeIntegerType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeStringType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeStringType.php
index f7ee0ba5095..f86240465c1 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeStringType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NativeStringType.php
@@ -59,7 +59,9 @@ public function cast(mixed $value): string
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid string.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid string.')
+ ->withCode('invalid_string')
+ ->build();
}
public function nativeType(): NativeStringType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NegativeIntegerType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NegativeIntegerType.php
index e92af3ac790..a88111d7cdf 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NegativeIntegerType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NegativeIntegerType.php
@@ -60,7 +60,9 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid negative integer.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid negative integer.')
+ ->withCode('invalid_negative_integer')
+ ->build();
}
public function nativeType(): NativeIntegerType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyArrayType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyArrayType.php
index d4606176799..1b66e4869e5 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyArrayType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyArrayType.php
@@ -58,6 +58,10 @@ public function accepts(mixed $value): bool
return false;
}
+ if ($this === self::native()) {
+ return true;
+ }
+
return Polyfill::array_all(
$value,
fn (mixed $item, mixed $key) => $this->keyType->accepts($key) && $this->subType->accepts($item),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyListType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyListType.php
index 7b7dee334b1..de40f8bd881 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyListType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyListType.php
@@ -57,6 +57,10 @@ public function accepts(mixed $value): bool
return false;
}
+ if ($this === self::native()) {
+ return true;
+ }
+
return Polyfill::array_all(
$value,
fn (mixed $item) => $this->subType->accepts($item),
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyStringType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyStringType.php
index c633c5820ee..4e08c546752 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyStringType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonEmptyStringType.php
@@ -59,7 +59,9 @@ public function cast(mixed $value): string
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid non-empty string.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid non-empty string.')
+ ->withCode('invalid_non_empty_string')
+ ->build();
}
public function nativeType(): NativeStringType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonNegativeIntegerType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonNegativeIntegerType.php
index 48f4f9e93e0..6a0a19b16ad 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonNegativeIntegerType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonNegativeIntegerType.php
@@ -62,7 +62,9 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid non-negative integer.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid non-negative integer.')
+ ->withCode('invalid_non_negative_integer')
+ ->build();
}
public function nativeType(): NativeIntegerType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonPositiveIntegerType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonPositiveIntegerType.php
index cec08d3849e..87af44fcdc0 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonPositiveIntegerType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NonPositiveIntegerType.php
@@ -55,7 +55,9 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid non-positive integer.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid non-positive integer.')
+ ->withCode('invalid_non_positive_integer')
+ ->build();
}
public function nativeType(): NativeIntegerType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NumericStringType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NumericStringType.php
index 50a1b33d1f8..3bdb4b01a2f 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NumericStringType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/NumericStringType.php
@@ -63,7 +63,9 @@ public function cast(mixed $value): string
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid numeric string.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid numeric string.')
+ ->withCode('invalid_numeric_string')
+ ->build();
}
public function nativeType(): NativeStringType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/PositiveIntegerType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/PositiveIntegerType.php
index 3276f1838a7..0c0fd8c5cb9 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/PositiveIntegerType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/PositiveIntegerType.php
@@ -66,7 +66,9 @@ public function cast(mixed $value): int
public function errorMessage(): ErrorMessage
{
- return MessageBuilder::newError('Value {source_value} is not a valid positive integer.')->build();
+ return MessageBuilder::newError('Value {source_value} is not a valid positive integer.')
+ ->withCode('invalid_positive_integer')
+ ->build();
}
public function nativeType(): NativeIntegerType
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ShapedArrayElement.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ShapedArrayElement.php
index fb76a8d7a31..c4de1545fb5 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ShapedArrayElement.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/ShapedArrayElement.php
@@ -4,6 +4,7 @@
namespace CuyZ\Valinor\Type\Types;
+use CuyZ\Valinor\Definition\Attributes;
use CuyZ\Valinor\Type\Type;
/** @internal */
@@ -12,7 +13,8 @@ final class ShapedArrayElement
public function __construct(
private StringValueType|IntegerValueType $key,
private Type $type,
- private bool $optional = false
+ private bool $optional = false,
+ private ?Attributes $attributes = null,
) {}
public function key(): StringValueType|IntegerValueType
@@ -30,6 +32,11 @@ public function isOptional(): bool
return $this->optional;
}
+ public function attributes(): Attributes
+ {
+ return $this->attributes ?? Attributes::empty();
+ }
+
public function toString(): string
{
return $this->isOptional()
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/StringValueType.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/StringValueType.php
index c1631b7d473..53bb7e0c598 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/StringValueType.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Type/Types/StringValueType.php
@@ -88,6 +88,7 @@ public function value(): string
public function errorMessage(): ErrorMessage
{
return MessageBuilder::newError('Value {source_value} does not match string value {expected_value}.')
+ ->withCode('invalid_string_value')
->withParameter('expected_value', ValueDumper::dump($this->value))
->build();
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Polyfill.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Polyfill.php
index c55d72848cf..f6c75cea172 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Polyfill.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Polyfill.php
@@ -22,4 +22,20 @@ public static function array_all(array $array, callable $callback): bool
return true;
}
+ /**
+ * PHP8.4 use native function `array_find` instead.
+ *
+ * @infection-ignore-all
+ * @param array $array
+ */
+ public static function array_find(array $array, callable $callback): mixed
+ {
+ foreach ($array as $key => $value) {
+ if ($callback($value, $key)) {
+ return $value;
+ }
+ }
+
+ return null;
+ }
}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Priority/HasPriority.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Priority/HasPriority.php
deleted file mode 100644
index aec61e673d0..00000000000
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/Priority/HasPriority.php
+++ /dev/null
@@ -1,19 +0,0 @@
-
- */
-final class PrioritizedList implements IteratorAggregate
-{
- /** @var array */
- private array $objects = [];
-
- /**
- * @param T ...$objects
- */
- public function __construct(object ...$objects)
- {
- foreach ($objects as $object) {
- $this->objects[$this->priority($object)][] = $object;
- }
-
- krsort($this->objects, SORT_NUMERIC);
- }
-
- /**
- * @return Traversable
- */
- public function getIterator(): Traversable
- {
- foreach ($this->objects as $priority => $objects) {
- foreach ($objects as $object) {
- yield $priority => $object;
- }
- }
- }
-
- private function priority(object $object): int
- {
- return $object instanceof HasPriority
- ? $object->priority()
- : 0;
- }
-}
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/String/StringFormatter.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/String/StringFormatter.php
index 7a70da5e077..d5249faa395 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/String/StringFormatter.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/String/StringFormatter.php
@@ -4,7 +4,6 @@
namespace CuyZ\Valinor\Utility\String;
-use CuyZ\Valinor\Mapper\Tree\Message\HasParameters;
use IntlException;
use MessageFormatter;
@@ -28,11 +27,6 @@ public static function format(string $locale, string $body, array $parameters =
: self::formatWithRegex($body, $parameters);
}
- public static function for(HasParameters $message): string
- {
- return self::formatWithRegex($message->body(), $message->parameters());
- }
-
/**
* @param array $parameters
*/
diff --git a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/TypeHelper.php b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/TypeHelper.php
index 84c8a3be83a..27065fc70e9 100644
--- a/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/TypeHelper.php
+++ b/wcfsetup/install/files/lib/system/api/cuyz/valinor/src/Utility/TypeHelper.php
@@ -7,11 +7,13 @@
use CuyZ\Valinor\Mapper\Object\Argument;
use CuyZ\Valinor\Mapper\Object\Arguments;
use CuyZ\Valinor\Type\BooleanType;
+use CuyZ\Valinor\Type\CompositeTraversableType;
use CuyZ\Valinor\Type\CompositeType;
use CuyZ\Valinor\Type\FixedType;
use CuyZ\Valinor\Type\FloatType;
use CuyZ\Valinor\Type\IntegerType;
use CuyZ\Valinor\Type\ObjectType;
+use CuyZ\Valinor\Type\ScalarType;
use CuyZ\Valinor\Type\StringType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\EnumType;
@@ -20,9 +22,22 @@
final class TypeHelper
{
/**
- * Sorting the scalar types by priority: int, float, string, bool.
+ * Sorting the types by priority: objects, arrays, scalars, everything else.
*/
public static function typePriority(Type $type): int
+ {
+ return match (true) {
+ $type instanceof ObjectType => 3,
+ $type instanceof CompositeTraversableType => 2,
+ $type instanceof ScalarType => 1,
+ default => 0,
+ };
+ }
+
+ /**
+ * Sorting the scalar types by priority: int, float, string, bool.
+ */
+ public static function scalarTypePriority(ScalarType $type): int
{
return match (true) {
$type instanceof IntegerType => 4,
diff --git a/wcfsetup/install/files/lib/system/valinor/formatter/PrependPath.class.php b/wcfsetup/install/files/lib/system/valinor/formatter/PrependPath.class.php
index 7f1ec7a4a9d..0ec3c60ca2c 100644
--- a/wcfsetup/install/files/lib/system/valinor/formatter/PrependPath.class.php
+++ b/wcfsetup/install/files/lib/system/valinor/formatter/PrependPath.class.php
@@ -20,6 +20,6 @@ final class PrependPath implements MessageFormatter
*/
public function format(NodeMessage $m): NodeMessage
{
- return $m->withBody("{$m->node()->path()}: {$m->body()}");
+ return $m->withBody("{$m->path()}: {$m->body()}");
}
}