### Install Mezzio Integration Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/app-and-framework-integration.md Use Composer to install the official Mezzio integration package for CuyZ/Valinor. ```bash composer require mezzio/mezzio-valinor ``` -------------------------------- ### Install Symfony Bundle Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/app-and-framework-integration.md Use Composer to install the official Symfony bundle for CuyZ/Valinor integration. ```bash composer require cuyz/valinor-bundle ``` -------------------------------- ### PHP: Unsealed Shaped Array Syntax Examples Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.12.0.md Demonstrates the new unsealed shaped array syntax in Valinor for mapping arrays with additional values and keys. Shows valid and invalid examples for different type configurations. ```php $mapper = (new \CuyZ\Valinor\MapperBuilder())->mapper(); // Default syntax can be used like this: $mapper->map( 'array{foo: string, ...array}', [ 'foo' => 'foo', 'bar' => 'bar', // ✅ valid additional value ] ); $mapper->map( 'array{foo: string, ...array}', [ 'foo' => 'foo', 'bar' => 1337, // ❌ invalid value 1337 ] ); // Key type can be added as well: $mapper->map( 'array{foo: string, ...array}', [ 'foo' => 'foo', 42 => 'bar', // ✅ valid additional key ] ); $mapper->map( 'array{foo: string, ...array}', [ 'foo' => 'foo', 'bar' => 'bar' // ❌ invalid key ] ); // Advanced types can be used: $mapper->map( "array{ 'en_US': non-empty-string, ...array }", [ 'en_US' => 'Hello', 'fr_FR' => 'Salut', // ✅ valid additional value ] ); $mapper->map( "array{ 'en_US': non-empty-string, ...array }", [ 'en_US' => 'Hello', 'fr_FR' => '', // ❌ invalid value ] ); ``` ```php (new \CuyZ\Valinor\MapperBuilder()) ->allowPermissiveTypes() ->mapper() ->map( 'array{foo: string, ...}', ['foo' => 'foo', 'bar' => 'bar', 42 => 1337] ); ``` -------------------------------- ### Install Valinor using Composer Source: https://github.com/cuyz/valinor/blob/master/README.md Use Composer to add Valinor to your project dependencies. ```bash composer require cuyz/valinor ``` -------------------------------- ### Implement a MapperBuilderConfigurator Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/use-mapper-configurators.md Implement the MapperBuilderConfigurator interface to define reusable mapping logic. This example registers a constructor for CustomerId and allows superfluous keys. ```php namespace My\App; use CuyZ\Valinor\MapperBuilder; use CuyZ\Valinor\Mapper\Configurator\MapperBuilderConfigurator; final class ApplicationMappingConfigurator implements MapperBuilderConfigurator { public function configureMapperBuilder(MapperBuilder $builder): MapperBuilder { return $builder ->allowSuperfluousKeys() ->registerConstructor( \My\App\CustomerId::fromString(...), ); } } ``` -------------------------------- ### Object Constructor Parameter Type Inferring Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.13.0.md Illustrates improved object constructor parameter type inferring in Valinor. This example shows how the mapper can now correctly map different types (int and string) to constructors of the same object based on parameter types and names. ```php final readonly class Money { private function __construct( public int $value, ) {} #[ CuyZ Valinor Mapper Object Constructor] public static function fromInt(int $value): self { return new self($value); } #[ CuyZ Valinor Mapper Object Constructor] public static function fromString(string $value): self { if (! preg_match('/^ €$/', $value)) { throw new InvalidArgumentException ('Invalid money format'); } return new self((int)rtrim($value, '€')); } } $mapper = (new CuyZ Valinor MapperBuilder()) ->mapper(); $mapper->map(Money::class, 42); // ✅ $mapper->map(Money::class, '42€'); // ✅ ``` -------------------------------- ### Apply a MapperBuilderConfigurator Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/use-mapper-configurators.md Apply a custom MapperBuilderConfigurator to a MapperBuilder instance using the configureWith method. This example maps user data, allowing superfluous keys and registering a CustomerId constructor. ```php $result = (new \CuyZ\Valinor\MapperBuilder()) ->configureWith(new \My\App\ApplicationMappingConfigurator()) ->mapper() ->map(\My\App\User::class, [ 'id' => '604e4b36-5b76-4b1a-9e6c-02d5acb53a4d', 'name' => 'John Doe', 'extraField' => 'ignored because superfluous keys are allowed', ]); ``` -------------------------------- ### Registering and Using a String Length Converter in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.2.1.md Demonstrates registering a custom converter that returns null for strings shorter than 5 characters. This example highlights a bug fix where the converter should not be called if the target type does not match the converter's return type (e.g., mapping to 'string' when the converter returns '?string'). ```php (new \CuyZ\Valinor\MapperBuilder()) ->registerConverter( // If the string length is lower than 5, we return `null` fn (string $val): ?string => strlen($val) < 5 ? null : $val ) ->mapper() ->map('string', 'foo'); ``` -------------------------------- ### Define and Use AsTransformer Attribute for Normalization in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.11.0.md Demonstrates how to use the `AsTransformer` attribute to register a custom transformer for the Valinor normalizer. This example shows formatting a DateTime object into a string. ```php namespace My\App; #[\]CuyZ\Valinor\Normalizer\AsTransformer] #[\]Attribute(\Attribute::TARGET_PROPERTY)] final class DateTimeFormat { public function __construct(private string $format) {} public function normalize(\DateTimeInterface $date): string { return $date->format($this->format); } } final readonly class Event { public function __construct( public string $eventName, #[\]My\App\DateTimeFormat('Y/m/d')] public \DateTimeInterface $date, ) {} } (new \CuyZ\Valinor\MapperBuilder()) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize(new \My\App\Event( eventName: 'Release of legendary album', date: new \DateTimeImmutable('1971-11-08'), )); // [ // 'eventName' => 'Release of legendary album', // 'date' => '1971/11/08', // ] ``` -------------------------------- ### Improve Mapping Error Messages in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.2.0.md Demonstrates improved error messages for type mapping in Valinor. This example shows how to map a User class and the resulting error message when an invalid value is provided. ```php final class User { public function __construct( public string $name, public int $age, ) {} } (new MapperBuilder()) ->mapper() ->map(User::class, 'invalid value'); // Could not map type `User`. An error occurred at path *root*: Value // 'invalid value' does not match `array{name: string, age: int}`. ``` -------------------------------- ### Register a Basic Callable Converter Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/convert-input.md Use `registerConverter` to add a callable that transforms input types during mapping. This example converts strings to uppercase. ```php (new CuyZValinorMapperBuilder()) ->registerConverter( fn (string $value): string => strtoupper($value) ) ->mapper() ->map('string', 'hello world'); // 'HELLO WORLD' ``` -------------------------------- ### Map JSON to Typed PHP Objects Source: https://github.com/cuyz/valinor/blob/master/README.md This example demonstrates mapping a JSON string to a PHP object structure using Valinor's MapperBuilder. Ensure the JSON structure matches the defined classes and their types. The mapping process can throw a MappingError if validation fails. ```php final class Country { public function __construct( /** @var non-empty-string */ public readonly string $name, /** @var list */ public readonly array $cities, ) {} } final class City { public function __construct( /** @var non-empty-string */ public readonly string $name, public readonly DateTimeZone $timeZone, ) {} } $json = <<mapper() ->map(Country::class, CuyZ Valinor Mapper Source Source::json($json)); echo $country->name; // France echo $country->cities[0]->name; // Paris } catch ( CuyZ Valinor Mapper MappingError $error) { // Handle the error… } ``` -------------------------------- ### Using NormalizerBuilder Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/upgrading.md The new entry point for configuring and instantiating normalizers. ```php $normalizer = (new \CuyZ\Valinor\NormalizerBuilder()) ->registerTransformer( fn (\DateTimeInterface $date) => $date->format('Y/m/d') ) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize($someData); ``` -------------------------------- ### Define a NormalizerBuilderConfigurator Source: https://github.com/cuyz/valinor/blob/master/docs/pages/serialization/use-normalizer-configurators.md Implement NormalizerBuilderConfigurator to define reusable normalization logic. This example registers transformers for DateTimeInterface and a custom Money object. ```php namespace My\App; use CuyZ\Valinor\NormalizerBuilder; use CuyZ\Valinor\Normalizer\Configurator\NormalizerBuilderConfigurator; final class ApiResponseConfigurator implements NormalizerBuilderConfigurator { public function configureNormalizerBuilder(NormalizerBuilder $builder): NormalizerBuilder { return $builder ->registerTransformer( fn (\DateTimeInterface $date) => $date->format('Y-m-d') ) ->registerTransformer( fn (\My\App\Money $money) => [ 'amount' => $money->amount, 'currency' => $money->currency->value, ] ); } } ``` -------------------------------- ### Create Benchmark Baseline Source: https://github.com/cuyz/valinor/blob/master/docs/pages/internals/benchmarks.md Establish a baseline for performance comparison by running benchmarks and storing the results. This serves as a reference point for future performance checks. ```sh composer run-script benchmark-baseline ``` -------------------------------- ### Define Prefixed Key Attribute Transformer Source: https://github.com/cuyz/valinor/blob/master/docs/pages/serialization/extending-normalizer.md Implement `normalizeKey` in an attribute to transform property keys during normalization. This example prefixes keys with 'address_'. ```php namespace My\App; #[\CuyZ\Valinor\Normalizer\AsTransformer] #[\Attribute(\Attribute::TARGET_PROPERTY)] final class PrefixedWith { public function __construct(private string $prefix) {} public function normalizeKey(string $value): string { return $this->prefix . $value; } } final readonly class Address { public function __construct( #[\My\App\PrefixedWith('address_')] public string $road, #[\My\App\PrefixedWith('address_')] public string $zipCode, #[\My\App\PrefixedWith('address_')] public string $city, ) {} } (new \CuyZ\Valinor\NormalizerBuilder()) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize( new \My\App\Address( road: '221B Baker Street', zipCode: 'NW1 6XE', city: 'London', ) ); // [ // 'address_road' => '221B Baker Street', // 'address_zipCode' => 'NW1 6XE', // 'address_city' => 'London', // ] ``` -------------------------------- ### Initialize and Use File System Cache Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/performance-and-caching.md Instantiate a file system cache and optionally decorate it with FileWatchingCache for development environments. This cache can be shared between mapper and normalizer builders. ```php $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-directory'); if ($isApplicationInDevelopmentEnvironment) { $cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache); } (new \CuyZ\Valinor\MapperBuilder()) ->withCache($cache) ->mapper() ->map(SomeClass::class, [/* … */]); (new \CuyZ\Valinor\NormalizerBuilder()) ->withCache($cache) ->normalizer(\CuyZ\Valinor\Normalizer\Format::json()) ->normalize($someData); ``` -------------------------------- ### Run All Benchmarks Source: https://github.com/cuyz/valinor/blob/master/docs/pages/internals/benchmarks.md Execute all defined benchmarks for the project. This command is used to gather performance data. ```sh composer run-script benchmark ``` -------------------------------- ### Initialize Translation Message Formatter Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-0.9.0.md Get the default TranslationMessageFormatter instance. This formatter can be used to translate message content and manage custom translations. ```php \CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter::default() // Create/override a single entry… ``` -------------------------------- ### Catching and Handling Mapping Errors Source: https://github.com/cuyz/valinor/blob/master/docs/pages/usage/validation-and-error-handling.md Wrap mapping calls in a try-catch block to handle `MappingError`. This example shows how to retrieve and format all detected error messages. ```php try { (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, [/* … */ ]); } catch (\CuyZ\Valinor\Mapper\MappingError $error) { // 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( new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter([ // … ]), (new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter()) ->withTranslations([ // … ]) ); foreach ($messages as $message) { echo $message; } } ``` -------------------------------- ### Map to Array Shape in PHP Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-0.4.0.md Illustrates mapping a source to a simple array with defined string and integer keys using Valinor. This is useful for straightforward array structures. ```php $array = (new MapperBuilder())->mapper()->map( 'array{foo: string, bar: int}', [/* … */] ); echo $array['foo']; echo $array['bar'] * 2; ``` -------------------------------- ### Create Custom Data Source Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/import-formatted-source.md Implement the `IteratorAggregate` interface to create custom data sources. These can be combined with Valinor's builder for flexible data handling. ```php final readonly class AcmeSource implements IteratorAggregate { private iterable $source; public function __construct(iterable $source) { $this->source = $this->doSomething($source); } private function doSomething(iterable $source): iterable { // Do something with $source return $source; } public function getIterator() { yield from $this->source; } } $source = \CuyZ\Valinor\Mapper\Source\Source::iterable( new AcmeSource([ 'valueA' => 'foo', 'valueB' => 'bar', ]) )->camelCaseKeys(); (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, $source); ``` -------------------------------- ### Map All Query Parameters to a Single Object Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.4.0.md Use `asRoot: true` with `#[FromQuery]` to map all query parameters to a single object. This is useful for handling complex or numerous parameters efficiently. Ensure the target object's constructor or properties match the expected query parameters. ```php use CuyZ\Valinor\Mapper\Http\FromQuery; use CuyZ\Valinor\Mapper\Http\FromRoute; final readonly class ArticleFilters { public function __construct( /** @var non-empty-string */ public string $status, /** @var positive-int */ public int $page = 1, /** @var int<10, 100> */ public int $limit = 10, ) {} } final class ListArticles { /** * GET /api/authors/{authorId}/articles?status=X&page=X&limit=X */ public function __invoke( #[FromRoute] string $authorId, #[FromQuery(asRoot: true)] ArticleFilters $filters, ): ResponseInterface { … } } ``` -------------------------------- ### Allow Non-Sequential List Casting in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.17.0.md Enable this to allow the mapper to convert associative arrays into lists with sequential keys, bypassing the default requirement for sequential keys starting from 0. ```php (new \CuyZ\Valinor\MapperBuilder()) ->allowNonSequentialList() ->mapper() ->map('list', [ 'foo' => 42, 'bar' => 1337, ]); // => [0 => 42, 1 => 1337] ``` -------------------------------- ### PHP: Registering Interface Constructor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.12.0.md Shows how to register a constructor for an interface using `MapperBuilder::registerConstructor()` to enable mapping to interface types. Requires using a static method with a return type hint. ```php (new \CuyZ\Valinor\MapperBuilder()) ->registerConstructor( // The static method below has return type `UuidInterface`; // therefore, the mapper will build an instance of `Uuid` when // it needs to instantiate an implementation of `UuidInterface`. Ramsey\Uuid\Uuid::fromString(...) ) ->mapper() ->map( Ramsey\Uuid\UuidInterface::class, '663bafbf-c3b5-4336-b27f-1796be8554e0' ); ``` -------------------------------- ### Filter Third-Party Exceptions Source: https://github.com/cuyz/valinor/blob/master/docs/pages/usage/validation-and-error-handling.md Use `filterExceptions` to catch specific exceptions from libraries like Webmozart Assert. If an exception is not meant to be caught, re-throw it. This example demonstrates mapping a value that fails an assertion. ```php final readonly class SomeClass { public function __construct(private string $value) { \Webmozart\Assert\Assert::startsWith($value, 'foo_'); } } try { (new \CuyZ\Valinor\MapperBuilder()) ->filterExceptions(function (Throwable $exception) { if ($exception instanceof \Webmozart\Assert\InvalidArgumentException) { return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception); } // If the exception should not be caught by this library, it // must be thrown again. throw $exception; }) ->mapper() ->map(SomeClass::class, 'bar_baz'); } catch (\CuyZ\Valinor\Mapper\MappingError $exception) { // Should print something similar to: // > Expected a value to start with "foo_". Got: "bar_baz" echo $exception->messages()->toArray()[0]; } ``` -------------------------------- ### Using key-of type support Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.5.0.md Demonstrates how to use the key-of type annotation with enums, shaped arrays, standard arrays, and class constants. ```php enum SomeBackedEnum: string { case FOO = 'foo'; case BAR = 'bar'; } final readonly class SomeClassWithConstants { public const SOME_ARRAY = ['foo' => 1, 'bar' => 2]; } final readonly class SomeClass { public function __construct( // Accepts 'FOO' or 'BAR' (the case names of the enum) /** @var key-of */ public string $enumKey, // Accepts 'foo' or 'bar' (the keys of the shaped array) /** @var key-of */ public string $shapedArrayKey, // Accepts the key type of the array (string here) /** @var key-of> */ public string $arrayKey, // Accepts 'foo' or 'bar' (the keys of the class constant array) /** @var key-of */ public string $constantArrayKey, ) {} } ``` -------------------------------- ### Format Dates with Global Transformer in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.8.0.md Register a global transformer to format specific data types, such as dates, during normalization. This example uses a closure to format DateTimeInterface objects into 'Y/m/d' strings. ```php (new \CuyZ\Valinor\MapperBuilder()) ->registerTransformer( fn (\DateTimeInterface $date) => $date->format('Y/m/d') ) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize( new \My\App\Event( eventName: 'Release of legendary album', date: new \DateTimeImmutable('1971-11-08'), ) ); ``` -------------------------------- ### Map to Array of Objects in PHP Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-0.4.0.md Demonstrates mapping a source to an array of specific class instances using Valinor's MapperBuilder. Ensure the target type is correctly specified as 'array<' . SomeClass::class . '>'. ```php $objects = (new MapperBuilder())->mapper()->map( 'array<' . SomeClass::class . '>', [/* … */] ); ``` -------------------------------- ### Map to 'numeric-string' Type Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-0.13.0.md Use the 'numeric-string' type in docblocks to accept string values that are also numeric. This example shows a successful mapping of a numeric string and a failed mapping of a non-numeric string. ```php (new MapperBuilder())->mapper()->map('numeric-string', '42'); // ✅ (new MapperBuilder())->mapper()->map('numeric-string', 'foo'); // ❌ ``` -------------------------------- ### Instantiate and use NormalizerBuilder Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.0.0.md The NormalizerBuilder is the main entry point for configuring normalizers. It allows registering transformers and specifying normalization formats. ```php $normalizer = (new \CuyZ\Valinor\NormalizerBuilder()) ->registerTransformer( fn (\DateTimeInterface $date) => $date->format('Y/m/d') ) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize($someData); ``` -------------------------------- ### Define an Attribute Converter for Property Mapping Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/convert-input.md Create an attribute class with an `AsConverter` attribute and a `map` method to apply custom logic to specific properties during mapping. This example converts specific strings to booleans. ```php namespace MyApp; #[CuyZValinorMapperAsConverter] #[Attribute(Attribute::TARGET_PROPERTY)] final class CastToBool { /** * @param callable(mixed): bool $next */ public function map(string $value, callable $next): bool { $value = match ($value) { 'yes', 'on' => true, 'no', 'off' => false, default => $value, }; return $next($value); } } final readonly class User { public string $name; #[MyAppCastToBool] public bool $isActive; } $user = (new CuyZValinorMapperBuilder()) ->mapper() ->map(User::class, [ 'name' => 'John Doe', 'isActive' => 'yes', ]); $user->name === 'John Doe'; $user->isActive === true; ``` -------------------------------- ### Format Dates with Attribute Transformer in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.8.0.md Define a custom attribute to control data transformation during normalization. This example uses a DateTimeFormat attribute to format DateTimeInterface objects, providing more granular control over the transformation process. ```php namespace My\App; #[\Attribute(\Attribute::TARGET_PROPERTY)] final class DateTimeFormat { public function __construct(private string $format) {} public function normalize(\DateTimeInterface $date): string { return $date->format($this->format); } } final readonly class Event { public function __construct( public string $eventName, #[ My\App\DateTimeFormat('Y/m/d') ] public \DateTimeInterface $date, ) {} } (new \CuyZ\Valinor\MapperBuilder()) ->registerTransformer(\My\App\DateTimeFormat::class) ->normalizer(\CuyZ\Valinor\Normalizer\Format::array()) ->normalize( new \My\App\Event( eventName: 'Release of legendary album', date: new \DateTimeImmutable('1971-11-08'), ) ); ``` -------------------------------- ### Warm up Cache for Classes Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/performance-and-caching.md Pre-populate the cache with necessary class information during build or deployment. Ensure the cache is registered before attempting to warm it up. The warmed cache can then be used by the application. ```php $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir'); $mapperBuilder = (new \CuyZ\Valinor\MapperBuilder())->withCache($cache); // During the build: $mapperBuilder->warmupCacheFor(SomeClass::class, SomeOtherClass::class); // In the application: $mapperBuilder->mapper()->map(SomeClass::class, [/* … */]); ``` -------------------------------- ### Format Error Message with ICU Number and Currency Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/customize-error-messages.md Customize an error message to format numeric values using ICU's number and currency formatting. This example demonstrates how to display source values as currency in different locales. ```php try { (new CuyZ \Valinor\MapperBuilder())->mapper()->map('int<0, 100>', 1337); } catch ( CuyZ \Valinor\Mapper\MappingError $error) { $message = $error->messages()->toArray()[0]; if (is_numeric($message->sourceValue())) { $message = $message->withBody( 'Invalid amount {source_value, number, currency}' ); } // Invalid amount: $1,337.00 echo $message->withLocale('en_US'); // Invalid amount: £1,337.00 echo $message->withLocale('en_GB'); // Invalid amount: 1 337,00 € echo $message->withLocale('fr_FR'); } ``` -------------------------------- ### Combine Key Conversion and Restriction Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.4.0.md Demonstrates combining key restriction with key conversion. The restriction configurator must be applied before the conversion configurator to validate original input keys. ```php use CuyZ\Valinor\\\MapperBuilder; use CuyZ\Valinor\\\Mapper\\Configurator\\ConvertKeysToCamelCase; use CuyZ\Valinor\\\Mapper\\Configurator ``` ```text ``` -------------------------------- ### Mapping with Flattened Input for Single Value Objects Source: https://github.com/cuyz/valinor/blob/master/docs/pages/usage/object-construction.md Demonstrates how to map an object with a single-value property using a flattened input structure. This approach simplifies the input by directly providing the value instead of nesting it under a key that matches the property name. ```php final readonly class Identifier { public string $value; } final readonly class SomeClass { public Identifier $identifier; public string $description; } $mapper = (new CuyZ ``` ```php $mapper->map(SomeClass::class, [ // 👍 The input has been flattened and is easier to read 'identifier' => 'some-identifier', 'description' => 'Lorem ipsum…', ]); ``` -------------------------------- ### Use Attribute Converter on Function Parameters Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-2.1.0.md Attribute converters can be applied to function parameters when mapping arguments, allowing for type conversion directly within function calls. This example demonstrates mapping a string 'yes' to a boolean `true` for the `$isActive` parameter. ```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; ``` -------------------------------- ### Compare Benchmarks Against Baseline Source: https://github.com/cuyz/valinor/blob/master/docs/pages/internals/benchmarks.md Compare current benchmark results against a previously established baseline. This command will fail if a performance regression is detected (e.g., average execution time increases by more than 10%). ```sh composer run-script benchmark-compare ``` -------------------------------- ### Register Constructors with Signature Collision in Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-0.11.0.md Demonstrates registering multiple static constructors for a class that have the same parameter signature. This will result in an exception being thrown due to the detected collision. ```php final class SomeClass { public static function constructorA(string $foo, string $bar): self { // … } public static function constructorB(string $foo, string $bar): self { // … } } (new \CuyZ\Valinor\MapperBuilder()) ->registerConstructor( SomeClass::constructorA(...), SomeClass::constructorB(...), ) ->mapper(); ->map(SomeClass::class, [ 'foo' => 'foo', 'bar' => 'bar', ]); // Exception: A collision was detected […] ``` -------------------------------- ### Create a Domain Constructors Configurator Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/use-mapper-configurators.md Define a configurator that registers constructors for domain-specific value objects like CustomerId and Email. This promotes cleaner data handling. ```php namespace My\App; use CuyZ\Valinor\MapperBuilder; use CuyZ\Valinor\Mapper\Configurator\MapperBuilderConfigurator; final class DomainConstructorsConfigurator implements MapperBuilderConfigurator { public function configureMapperBuilder(MapperBuilder $builder): MapperBuilder { return $builder ->registerConstructor( \My\App\CustomerId::fromString(...), \My\App\Email::fromString(...), ); } } ``` -------------------------------- ### Using Constructor Attribute for Object Creation in PHP Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.10.0.md Demonstrates how to use the \#["CuyZ\Valinor\Mapper\Object\Constructor"] attribute to define custom constructors for classes. This attribute can be applied to public static methods that return an instance of the class. When using this attribute, the native constructor is disabled and must be explicitly re-registered if needed. This example shows creating an Email object using a custom factory method `createFrom`. ```php final readonly class Email { // When another constructor is registered for the class, the native // constructor is disabled. To enable it again, it is mandatory to // explicitly register it again. #["CuyZ\Valinor\Mapper\Object\Constructor"] public function __construct(public string $value) {} #["CuyZ\Valinor\Mapper\Object\Constructor"] public static function createFrom( string $userName, string $domainName ): self { return new self($userName . '@' . $domainName); } } (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(Email::class, [ 'userName' => 'john.doe', 'domainName' => 'example.com', ]); // john.doe@example.com ``` -------------------------------- ### Import Data from JSON, YAML, or File Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/import-formatted-source.md Use the `Source` helper to import data from JSON strings, YAML strings, or files. The data is converted to a plain PHP structure before mapping. ```php $source = \CuyZ\Valinor\Mapper\Source\Source::json($jsonString); // or… $source = \CuyZ\Valinor\Mapper\Source\Source::yaml($yamlString); // or… // File containing valid Json or Yaml content and with valid extension $source = \CuyZ\Valinor\Mapper\Source\Source::file( new SplFileObject('path/to/my/file.json') ); (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(SomeClass::class, $source); ``` -------------------------------- ### Create a Flexible Mapping Configurator Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/use-mapper-configurators.md Define a configurator that allows scalar value casting and superfluous keys. This is useful for flexible data mapping scenarios. ```php namespace My\App; use CuyZ\Valinor\MapperBuilder; use CuyZ\Valinor\Mapper\Configurator\MapperBuilderConfigurator; final class FlexibleMappingConfigurator implements MapperBuilderConfigurator { public function configureMapperBuilder(MapperBuilder $builder): MapperBuilder { return $builder ->allowScalarValueCasting() ->allowSuperfluousKeys(); } } ``` -------------------------------- ### Infer Class Mapping for Abstract Classes Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.1.0.md Shows how to infer the concrete class for an abstract type using the `infer` method. This is useful when Valinor needs to know which specific child class to instantiate. ```php abstract class SomeAbstractClass { public string $foo; public string $bar; } final class SomeChildClass extends SomeAbstractClass { public string $baz; } $result = (new CuyZ\Valinor\MapperBuilder()) ->infer( SomeAbstractClass::class, fn () => SomeChildClass::class ) ->mapper() ->map(SomeAbstractClass::class, [ 'foo' => 'foo', 'bar' => 'bar', 'baz' => 'baz', ]); assert($result instanceof SomeChildClass); assert($result->foo === 'foo'); assert($result->bar === 'bar'); assert($result->baz === 'baz'); ``` -------------------------------- ### Chain Multiple Key Converters Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/convert-input.md Register multiple key converters to create a pipeline. Each converter processes the output of the previous one, allowing for sequential key transformations. ```php (new CuyZ Valinor\MapperBuilder()) ->registerKeyConverter(static function (string $key): string { // Strip the "billing_" prefix if (str_starts_with($key, 'billing_')) { return substr($key, 8); } return $key; }) ->registerKeyConverter( // Replace hyphens with underscores static fn (string $key): string => str_replace('-', '_', $key), ) ->mapper() ->map('array{zip_code: string, country_name: string}', [ 'billing_zip-code' => '62701', 'billing_country-name' => 'United Kingdom', ]); ``` -------------------------------- ### Pretty JSON Output with Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.14.0.md Use `JSON_PRETTY_PRINT` with the JSON normalizer to format output with whitespaces and line breaks. Ensure the `JSON_PRETTY_PRINT` option is passed to `withOptions`. ```php $input = [ 'value' => 'foo', 'list' => [ 'foo', 42, ['sub'] ], 'associative' => [ 'value' => 'foo', 'sub' => [ 'string' => 'foo', 'integer' => 42, ], ], ]; (new \Cuy\Z\\Valinor\\MapperBuilder()) ->normalizer(\Cuy\Z\\Valinor\\Normalizer\\Format::json()) ->withOptions(\J\SON_PRETTY_PRINT) ->normalize($input); ``` -------------------------------- ### Map query parameters to a class using asRoot Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/map-http-request.md Use the asRoot attribute on a parameter to map all query values into a dedicated data object. ```php use CuyZ\Valinor\Mapper\Http\FromQuery; use CuyZ\Valinor\Mapper\Http\FromRoute; final readonly class ArticleFilters { public function __construct( /** @var non-empty-string */ public string $status, /** @var positive-int */ public int $page = 1, /** @var int<10, 100> */ public int $limit = 10, ) {} } final class ListArticles { /** * GET /api/authors/{authorId}/articles?status=X&&page=X&limit=X */ public function __invoke( #[FromRoute] string $authorId, #[FromQuery(asRoot: true)] ArticleFilters $filters, ): ResponseInterface { … } ``` -------------------------------- ### Register and Configure Cache Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/app-and-framework-integration.md Provide a cache implementation to the mapper builder for performance. Use FileWatchingCache in development to automatically clear the cache when files change. ```php $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-directory'); // If the application can detect when it is in development environment, it is // advised to wrap the cache with a `FileWatchingCache` instance, to avoid // having to manually clear the cache when a file changes during development. if ($isApplicationInDevelopmentEnvironment) { $cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache); } $mapperBuilder = $mapperBuilder->withCache($cache); $normalizerBuilder = $normalizerBuilder->withCache($cache); ``` -------------------------------- ### Applying JSON Encoding Flags Source: https://github.com/cuyz/valinor/blob/master/docs/pages/serialization/normalizing-json.md Configure JSON output behavior by passing standard PHP JSON constants to the withOptions method. ```php namespace My\App; $normalizer = (new \CuyZ\Valinor\NormalizerBuilder()) ->normalizer(\CuyZ\Valinor\Normalizer\Format::json()) ->withOptions(\JSON_PRESERVE_ZERO_FRACTION); $lowerManhattanAsJson = $normalizer->normalize( new \My\App\Coordinates( longitude: 40.7128, latitude: -74.0000 ) ); // `$lowerManhattanAsJson` is a valid JSON string representing the data: // {"longitude":40.7128,"latitude":-74.0000} ``` -------------------------------- ### Map HTTP request parameters using attributes Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/map-http-request.md Use #[FromRoute] and #[FromQuery] attributes to explicitly define the source of controller arguments. ```php use CuyZ\Valinor\Mapper\Http\FromQuery; use CuyZ\Valinor\Mapper\Http\FromRoute; use CuyZ\Valinor\Mapper\Http\HttpRequest; use CuyZ\Valinor\MapperBuilder; final class ListArticles { /** * GET /api/authors/{authorId}/articles?status=X&page=X&limit=X * * @param non-empty-string $status * @param positive-int $page * @param int<10, 100> $limit */ public function __invoke( // Comes from the route #[FromRoute] string $authorId, // All come from query parameters #[FromQuery] string $status, #[FromQuery] int $page = 1, #[FromQuery] int $limit = 10, ): ResponseInterface { … } } // GET /api/authors/42/articles?status=published&page=2 $request = new HttpRequest( routeParameters: ['authorId' => 42], queryParameters: [ 'status' => 'published', 'page' => 2, ], ); $controller = new ListArticles(); $arguments = (new MapperBuilder()) ->argumentsMapper() ->mapArguments($controller, $request); $response = $controller(...$arguments); ``` -------------------------------- ### Registering custom date formats Source: https://github.com/cuyz/valinor/blob/master/docs/pages/how-to/deal-with-dates.md Use `supportDateFormats` to allow Valinor to parse dates in specified formats. This method accepts a variable number of format constants. ```php (new \CuyZ\Valinor\MapperBuilder()) ->supportDateFormats(DATE_COOKIE, DATE_ATOM) ->mapper() ->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC'); ``` -------------------------------- ### Configure PHPStan for Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/static-analysis.md Include the Valinor PHPStan configuration file in your phpstan.neon to enable static analysis extensions. ```yaml includes: - vendor/cuyz/valinor/qa/PHPStan/valinor-phpstan-configuration.php ``` -------------------------------- ### PHP: JSON Normalizer with Custom Options Source: https://github.com/cuyz/valinor/blob/master/docs/pages/project/changelog/version-1.12.0.md Demonstrates how to configure the JSON normalizer with custom formatting options using `JsonNormalizer::withOptions()`. The `JSON_THROW_ON_ERROR` flag is always enforced. ```php namespace My\App; $normalizer = (new \CuyZ\Valinor\MapperBuilder()) ->normalizer(\CuyZ\Valinor\Normalizer\Format::json()) ->withOptions(\JSON_PRESERVE_ZERO_FRACTION); $lowerManhattanAsJson = $normalizer->normalize( new \My\App\Coordinates( longitude: 40.7128, latitude: -74.0000 ) ); // `$lowerManhattanAsJson` is a valid JSON string representing the data: // {"longitude":40.7128,"latitude":-74.0000} ``` -------------------------------- ### Map to a shaped array with PHPStan Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/static-analysis.md Illustrates mapping to a shaped array with specific keys and types. Static analysis checks for correct key usage and type compatibility. ```php $array = (new \CuyZ\Valinor\MapperBuilder()) ->mapper() ->map( 'array{foo: string, bar: int}', [/* … */] ); // ✅ echo $array['foo']; // ❌ Expected `string` but got `int` echo strtolower($array['bar']); // ❌ Cannot perform operation between `string` and `int` echo $array['foo'] * $array['bar']; // ❌ Offset `fiz` does not exist on array echo $array['fiz']; ``` -------------------------------- ### Configure Psalm for Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/other/static-analysis.md Register the Valinor Psalm plugin in your composer.json and psalm.xml to enable static analysis. ```json "autoload-dev": { "files": [ "vendor/cuyz/valinor/qa/Psalm/ValinorPsalmPlugin.php" ] } ``` ```xml ``` -------------------------------- ### Data Mapping with Valinor Source: https://github.com/cuyz/valinor/blob/master/docs/pages/index.md Shows how to use Valinor's MapperBuilder to map raw data into a typed object, significantly reducing boilerplate code compared to manual validation. This method centralizes error handling within a try-catch block for MappingError. ```php $data = $client->request('GET', 'https://example.com/person/42')->toArray(); try { $person = (new CuyZ\Valinor\MapperBuilder()) ->mapper() ->map(Person::class, $data); } catch ( CuyZ \Valinor\Mapper\MappingError $error) { // Detailed error handling } ```