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()}"); } }