PHP library that helps to map any input into a strongly-typed value object structure.
MIT License
Bot releases are visible (Hide)
Microseconds support for timestamp format
Prior to this patch, this would require a custom constructor in the form of:
static fn(float | int $timestamp): DateTimeImmutable => new
DateTimeImmutable(sprintf("@%d", $timestamp)),
This bypasses the datetime format support of Valinor entirely. This is required because the library does not support floats as valid DateTimeInterface
input values.
This commit adds support for floats and registers timestamp.microseconds
(U.u
) as a valid default format.
Support for value-of<BackedEnum>
type
This type can be used as follows:
enum Suit: string
{
case Hearts = 'H';
case Diamonds = 'D';
case Clubs = 'C';
case Spades = 'S';
}
$suit = (new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map('value-of<Suit>', 'D');
// $suit === 'D'
Object constructors parameters types inferring improvements
The collision system that checks object constructors parameters types is now way more clever, as it no longer checks for parameters' names only. Types are now also checked, and only true collision will be detected, for instance when two constructors share a parameter with the same name and type.
Note that when two parameters share the same name, the following type priority operates:
With this change, the code below is now valid:
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('/^\d+€$/', $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€'); // ✅
value-of<BackedEnum>
type (b1017c)Published by romm 7 months ago
Introduce unsealed shaped array syntax
This syntax enables an extension of the shaped array type by allowing additional values that must respect a certain type.
$mapper = (new \CuyZ\Valinor\MapperBuilder())->mapper();
// Default syntax can be used like this:
$mapper->map(
'array{foo: string, ...array<string>}',
[
'foo' => 'foo',
'bar' => 'bar', // ✅ valid additional value
]
);
$mapper->map(
'array{foo: string, ...array<string>}',
[
'foo' => 'foo',
'bar' => 1337, // ❌ invalid value 1337
]
);
// Key type can be added as well:
$mapper->map(
'array{foo: string, ...array<int, string>}',
[
'foo' => 'foo',
42 => 'bar', // ✅ valid additional key
]
);
$mapper->map(
'array{foo: string, ...array<int, string>}',
[
'foo' => 'foo',
'bar' => 'bar' // ❌ invalid key
]
);
// Advanced types can be used:
$mapper->map(
"array{
'en_US': non-empty-string,
...array<non-empty-string, non-empty-string>
}",
[
'en_US' => 'Hello',
'fr_FR' => 'Salut', // ✅ valid additional value
]
);
$mapper->map(
"array{
'en_US': non-empty-string,
...array<non-empty-string, non-empty-string>
}",
[
'en_US' => 'Hello',
'fr_FR' => '', // ❌ invalid value
]
);
// If the permissive type is enabled, the following will work:
(new \CuyZ\Valinor\MapperBuilder())
->allowPermissiveTypes()
->mapper()
->map(
'array{foo: string, ...}',
['foo' => 'foo', 'bar' => 'bar', 42 => 1337]
); // ✅
Interface constructor registration
By default, the mapper cannot instantiate an interface, as it does not know which implementation to use. To do so, the MapperBuilder::infer()
method can be used, but it is cumbersome in most cases.
It is now also possible to register a constructor for an interface, in the same way as for a class.
Because the mapper cannot automatically guess which implementation can be used for an interface, it is not possible to use the Constructor
attribute, the MapperBuilder::registerConstructor()
method must be used instead.
In the example below, the mapper is taught how to instantiate an implementation of UuidInterface
from package ramsey/uuid
:
(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'
);
JSON normalizer formatting options — contributed by @boesing
By default, the JSON normalizer will only use JSON_THROW_ON_ERROR
to encode non-boolean scalar values. There might be use-cases where projects will need flags like JSON_JSON_PRESERVE_ZERO_FRACTION
.
This can be achieved by passing these flags to the new JsonNormalizer::withOptions()
method:
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}
The method accepts an int-mask of the following JSON_*
constant representations:
JSON_HEX_QUOT
JSON_HEX_TAG
JSON_HEX_AMP
JSON_HEX_APOS
JSON_INVALID_UTF8_IGNORE
JSON_INVALID_UTF8_SUBSTITUTE
JSON_NUMERIC_CHECK
JSON_PRESERVE_ZERO_FRACTION
JSON_UNESCAPED_LINE_TERMINATORS
JSON_UNESCAPED_SLASHES
JSON_UNESCAPED_UNICODE
JSON_THROW_ON_ERROR
is always enforced and thus is not accepted.
See official doc for more information:
https://www.php.net/manual/en/json.constants.php
array-key
type (5020d6)Published by romm 7 months ago
Improvement of union types narrowing
The algorithm used by the mapper to narrow a union type has been greatly improved, and should cover more edge-cases that would previously prevent the mapper from performing well.
If an interface, a class or a shaped array is matched by the input, it will take precedence over arrays or scalars.
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(
signature: 'array<int>|' . Color::class,
source: [
'red' => 255,
'green' => 128,
'blue' => 64,
],
); // Returns an instance of `Color`
When superfluous keys are allowed, if the input matches several interfaces, classes or shaped array, the one with the most children node will be prioritized, as it is considered the most specific type:
(new \CuyZ\Valinor\MapperBuilder())
->allowSuperfluousKeys()
->mapper()
->map(
// Even if the first shaped array matches the input, the second one is
// used because it's more specific.
signature: 'array{foo: int}|array{foo: int, bar: int}',
source: [
'foo' => 42,
'bar' => 1337,
],
);
If the input matches several types within the union, a collision will occur and cause the mapper to fail:
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(
// Even if the first shaped array matches the input, the second one is
// used because it's more specific.
signature: 'array{red: int, green: int, blue: int}|' . Color::class,
source: [
'red' => 255,
'green' => 128,
'blue' => 64,
],
);
// ⚠️ Invalid value array{red: 255, green: 128, blue: 64}, it matches at
// least two types from union.
Introducing AsTransformer
attribute
After the introduction of the Constructor
attribute used for the mapper, the new AsTransformer
attribute is now available for the normalizer to ease the registration of a transformer.
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',
// ]
ArrayObject
normalization (4f555d)Published by romm 7 months ago
Dropping support for PHP 8.0
PHP 8.0 security support has ended on the 26th of November 2023. Therefore, we are dropping support for PHP 8.0 in this version.
If any security issue was to be found, we might consider backporting the fix to the 1.9.x version if people need it, but we strongly recommend upgrading your application to a supported PHP version.
Introducing Constructor
attribute
A long awaited feature has landed in the library!
The Constructor
attribute can be assigned to any method inside an object, to automatically mark the method as a constructor for the class. This is a more convenient way of registering constructors than using the MapperBuilder::registerConstructor
method, although it does not replace it.
The method targeted by a Constructor
attribute must be public, static and return an instance of the class it is part of.
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',
]); // [email protected]
Constructor
attribute (d86295)Published by romm 9 months ago
JSON normalizer
The normalizer is able to normalize a data structure to JSON without using the native json_encode()
function.
Using the normalizer instead of the native json_encode()
function offers some benefits:
Basic usage:
namespace My\App;
$normalizer = (new \CuyZ\Valinor\MapperBuilder())
->normalizer(\CuyZ\Valinor\Normalizer\Format::json());
$userAsJson = $normalizer->normalize(
new \My\App\User(
name: 'John Doe',
age: 42,
country: new \My\App\Country(
name: 'France',
code: 'FR',
),
)
);
// `$userAsJson` is a valid JSON string representing the data:
// {"name":"John Doe","age":42,"country":{"name":"France","code":"FR"}}
By default, the JSON normalizer will return a JSON string representing the data it was given. Instead of getting a string, it is possible to stream the JSON data to a PHP resource:
$file = fopen('path/to/some_file.json', 'w');
$normalizer = (new \CuyZ\Valinor\MapperBuilder())
->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
->streamTo($file);
$normalizer->normalize(/* … */);
// The file now contains the JSON data
Another benefit of streaming the data to a PHP resource is that it may be more memory-efficient when using generators — for instance when querying a database:
// In this example, we assume that the result of the query below is a
// generator, every entry will be yielded one by one, instead of
// everything being loaded in memory at once.
$users = $database->execute('SELECT * FROM users');
$file = fopen('path/to/some_file.json', 'w');
$normalizer = (new \CuyZ\Valinor\MapperBuilder())
->normalizer(\CuyZ\Valinor\Normalizer\Format::json())
->streamTo($file);
// Even if there are thousands of users, memory usage will be kept low
// when writing JSON into the file.
$normalizer->normalize($users);
DateTimeZone
(acf097)Published by romm 10 months ago
Normalizer service (serialization)
This new service can be instantiated with the MapperBuilder
. It allows transformation of a given input into scalar and array values, while preserving the original structure.
This feature can be used to share information with other systems that use a data format (JSON, CSV, XML, etc.). The normalizer will take care of recursively transforming the data into a format that can be serialized.
Below is a basic example, showing the transformation of objects into an array of scalar values.
namespace My\App;
$normalizer = (new \CuyZ\Valinor\MapperBuilder())
->normalizer(\CuyZ\Valinor\Normalizer\Format::array());
$userAsArray = $normalizer->normalize(
new \My\App\User(
name: 'John Doe',
age: 42,
country: new \My\App\Country(
name: 'France',
countryCode: 'FR',
),
)
);
// `$userAsArray` is now an array and can be manipulated much more
// easily, for instance to be serialized to the wanted data format.
//
// [
// 'name' => 'John Doe',
// 'age' => 42,
// 'country' => [
// 'name' => 'France',
// 'countryCode' => 'FR',
// ],
// ];
A normalizer can be extended by using so-called transformers, which can be either an attribute or any callable object.
In the example below, a global transformer is used to format any date found by the normalizer.
(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'),
)
);
// [
// 'eventName' => 'Release of legendary album',
// 'date' => '1971/11/08',
// ]
This date transformer could have been an attribute for a more granular control, as shown below.
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'),
)
);
// [
// 'eventName' => 'Release of legendary album',
// 'date' => '1971/11/08',
// ]
More features are available, details about it can be found in the documentation.
Published by romm 12 months ago
Non-positive integer
Non-positive integer can be used as below. It will accept any value equal to or lower than zero.
final class SomeClass
{
/** @var non-positive-int */
public int $nonPositiveInteger;
}
Non-negative integer
Non-negative integer can be used as below. It will accept any value equal to or greater than zero.
final class SomeClass
{
/** @var non-negative-int */
public int $nonNegativeInteger;
}
@psalm-pure
annotation to pure methods (004eb1)Published by romm about 1 year ago
Symfony Bundle
A bundle is now available for Symfony applications, it will ease the integration and usage of the Valinor library in the framework. The documentation can be found in the CuyZ/Valinor-Bundle repository.
Note that the documentation has been updated to add information about the bundle as well as tips on how to integrate the library in other frameworks.
PHP 8.3 support
Thanks to @TimWolla, the library now supports PHP 8.3, which entered its beta phase. Do not hesitate to test the library with this new version, and report any encountered issue on the repository.
Better type parsing
The first layer of the type parser has been completely rewritten. The previous one would use regex to split a raw type in tokens, but that led to limitations — mostly concerning quoted strings — that are now fixed.
Although this change should not impact the end user, it is a major change in the library, and it is possible that some edge cases were not covered by tests. If that happens, please report any encountered issue on the repository.
Example of previous limitations, now solved:
// Union of strings containing space chars
(new MapperBuilder())
->mapper()
->map(
"'foo bar'|'baz fiz'",
'baz fiz'
);
// Shaped array with special chars in the key
(new MapperBuilder())
->mapper()
->map(
"array{'some & key': string}",
['some & key' => 'value']
);
More advanced array-key handling
It is now possible to use any string or integer as an array key. The following types are now accepted and will work properly with the mapper:
$mapper->map("array<'foo'|'bar', string>", ['foo' => 'foo']);
$mapper->map('array<42|1337, string>', [42 => 'foo']);
$mapper->map('array<positive-int, string>', [42 => 'foo']);
$mapper->map('array<negative-int, string>', [-42 => 'foo']);
$mapper->map('array<int<-42, 1337>, string>', [42 => 'foo']);
$mapper->map('array<non-empty-string, string>', ['foo' => 'foo']);
$mapper->map('array<class-string, string>', ['SomeClass' => 'foo']);
intl.use_exceptions=1
(29da9a)Published by romm about 1 year ago
UnresolvableType
(eaa128)UnresolvableType
(5c89c6)Published by romm over 1 year ago
Exception thrown when source is invalid
JSON or YAML given to a source may be invalid, in which case an exception can
now be caught and manipulated.
try {
$source = \CuyZ\Valinor\Mapper\Source\Source::json('invalid JSON');
} catch (\CuyZ\Valinor\Mapper\Source\Exception\InvalidSource $error) {
// Let the application handle the exception in the desired way.
// It is possible to get the original source with `$error->source()`
}
InvalidSource
thrown when using invalid JSON/YAML (0739d1)array-key
type match mixed
(ccebf7)Published by romm over 1 year ago
null
and objects (8f03a7)Published by romm over 1 year ago
Handle custom enum constructors registration
It is now possible to register custom constructors for enum, the same way it could be done for classes.
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
// Allow the native constructor to be used
SomeEnum::class,
// Register a named constructor
SomeEnum::fromMatrix(...)
)
->mapper()
->map(SomeEnum::class, [
'type' => 'FOO',
'number' => 2,
]);
enum SomeEnum: string
{
case CASE_A = 'FOO_VALUE_1';
case CASE_B = 'FOO_VALUE_2';
case CASE_C = 'BAR_VALUE_1';
case CASE_D = 'BAR_VALUE_2';
/**
* @param 'FOO'|'BAR' $type
* @param int<1, 2> $number
* /
public static function fromMatrix(string $type, int $number): self
{
return self::from("{$type}_VALUE_{$number}");
}
}
An enum constructor can be for a specific pattern:
enum SomeEnum
{
case FOO;
case BAR;
case BAZ;
}
(new \CuyZ\Valinor\MapperBuilder())
->registerConstructor(
/**
* This constructor will be called only when pattern
* `SomeEnum::BA*` is requested during mapping.
*
* @return SomeEnum::BA*
*/
fn (string $value): SomeEnum => /* Some custom domain logic */
)
->mapper()
->map(SomeEnum::class . '::BA*', 'some custom value');
Note that this commit required heavy refactoring work, leading to a regression for union types containing enums and other types. As these cases are considered marginal, this change is considered non-breaking.
Published by romm almost 2 years ago
Handle single property/constructor argument with array input
It is now possible, again, to use an array for a single node (single class property or single constructor argument), if this array has one value with a key matching the argument/property name.
This is a revert of a change that was introduced in a previous commit: see hash 72cba320f582c7cda63865880a1cbf7ea292d2b1
Published by romm almost 2 years ago
Handle class generic types inheritance
It is now possible to use the @extends
tag (already handled by PHPStan and Psalm) to declare the type of a parent class generic. This logic is recursively applied to all parents.
/**
* @template FirstTemplate
* @template SecondTemplate
*/
abstract class FirstClassWithGenerics
{
/** @var FirstTemplate */
public $valueA;
/** @var SecondTemplate */
public $valueB;
}
/**
* @template FirstTemplate
* @extends FirstClassWithGenerics<FirstTemplate, int>
*/
abstract class SecondClassWithGenerics extends FirstClassWithGenerics
{
/** @var FirstTemplate */
public $valueC;
}
/**
* @extends SecondClassWithGenerics<string>
*/
final class ChildClass extends SecondClassWithGenerics
{
}
$object = (new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(ChildClass::class, [
'valueA' => 'foo',
'valueB' => 1337,
'valueC' => 'bar',
]);
echo $object->valueA; // 'foo'
echo $object->valueB; // 1337
echo $object->valueC; // 'bar'
Added support for class inferring
It is now possible to infer abstract or parent classes the same way it can be done for interfaces.
Example with an abstract class:
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');
object
return type in PHPStan extension (201728)Published by romm almost 2 years ago
First stable version! 🥳 🎉
This release marks the end of the initial development phase. The library has been live for exactly one year at this date and is stable enough to start following the semantic versioning — it means that any backward incompatible change (aka breaking change) will lead to a bump of the major version.
This is the biggest milestone achieved by this project (yet™); I want to thank everyone who has been involved to make it possible, especially the contributors who submitted high-quality pull requests to improve the library.
There is also one person that I want to thank even more: my best friend Nathan, who has always been so supportive with my side-projects. Thanks, bro! 🙌
The last year marked a bigger investment of my time in OSS contributions; I've proven to myself that I am able to follow a stable way of managing my engagement to this community, and this is why I enabled sponsorship on my profile to allow people to ❤️ sponsor my work on GitHub — if you use this library in your applications, please consider offering me a 🍺 from time to time! 🤗
End of PHP 7.4 support
PHP 7.4 security support has ended on the 28th of November 2022; the minimum version supported by this library is now PHP 8.0.
New mapper to map arguments of a callable
This new mapper can be used to ensure a source has the right shape before calling a function/method.
The mapper builder can be configured the same way it would be with a tree mapper, for instance to customize the type strictness.
$someFunction = function(string $foo, int $bar): string {
return "$foo / $bar";
};
try {
$arguments = (new \CuyZ\Valinor\MapperBuilder())
->argumentsMapper()
->mapArguments($someFunction, [
'foo' => 'some value',
'bar' => 42,
]);
// some value / 42
echo $someFunction(...$arguments);
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
// Do something…
}
Support for TimeZone
objects
Native TimeZone
objects construction is now supported with a proper error handling.
try {
(new \CuyZ\Valinor\MapperBuilder())
->mapper()
->map(DateTimeZone::class, 'Jupiter/Europa');
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];
// Value 'Jupiter/Europa' is not a valid timezone.
echo $error->toString();
}
Mapping object with one property
When a class needs only one value, the source given to the mapper must match the type of the single property/parameter.
This change aims to bring consistency on how the mapper behaves when mapping an object that needs one argument. Before this change, the source could either match the needed type, or be an array with a single entry and a key named after the argument.
See example below:
final class Identifier
{
public readonly string $value;
}
final class SomeClass
{
public readonly Identifier $identifier;
public readonly string $description;
}
(new \CuyZ\Valinor\MapperBuilder())->mapper()->map(SomeClass::class, [
'identifier' => ['value' => 'some-identifier'], // ❌
'description' => 'Lorem ipsum…',
]);
(new \CuyZ\Valinor\MapperBuilder())->mapper()->map(SomeClass::class, [
'identifier' => 'some-identifier', // ✅
'description' => 'Lorem ipsum…',
]);
As this is a major release, all deprecated features have been removed, leading to an important number of breaking changes.
You can click on the entries below to get advice on available replacements.
Doctrine annotations cannot be used anymore, PHP attributes must be used.
You must use the method available in the mapper builder, see dealing with dates chapter.
The flexible has been splitted in three disctint modes, see type strictness & flexibility chapter.
You must now register a cache instance directly, see performance & caching chapter.
You must now register the constructors using the mapper builder, see custom object constructors chapter.
You must now register the constructors using the mapper builder, see custom object constructors chapter.
You must now use the MessageBuilder
class, see error handling chapter.
You must now use the Messages
class, see error handling chapter.
You must now use the HasParameters
class, see custom exception chapter.
The following methods have been removed:
\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::name()
\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::path()
\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::type()
\CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::value()
\CuyZ\Valinor\Mapper\Tree\Node::value()
It is still possible to get the wanted values using the method \CuyZ\Valinor\Mapper\Tree\Message\NodeMessage::node()
.
The placeholder {original_value}
has also been removed, the same value can be fetched with {source_value}
.
Other features are available to format message, see error messages customization chapter.
This feature has been part of the library since its first public release, but it was never documented because it did not fit one of the library's main philosophy which is to be almost entirely decoupled from an application's domain layer.
The feature is entirely removed and not planned to be replaced by an alternative, unless the community really feels like there is a need for something alike.
@pure
(0d9855)ThrowableMessage
(d36ca9)TranslatableMessage
(ceb197)strict-array
type (22c3b4)DateTimeZone
with error support (a0a4d6)null
to single node nullable type (0a98ec)Published by romm almost 2 years ago
The main feature introduced in this release is the split of the flexible mode in three distinct modes:
The flexible casting
Changes the behaviours explained below:
$flexibleMapper = (new \CuyZ\Valinor\MapperBuilder())
->enableFlexibleCasting()
->mapper();
// ---
// Scalar types will accept non-strict values; for instance an
// integer type will accept any valid numeric value like the
// *string* "42".
$flexibleMapper->map('int', '42');
// => 42
// ---
// List type will accept non-incremental keys.
$flexibleMapper->map('list<int>', ['foo' => 42, 'bar' => 1337]);
// => [0 => 42, 1 => 1338]
// ---
// If a value is missing in a source for a node that accepts `null`,
// the node will be filled with `null`.
$flexibleMapper->map(
'array{foo: string, bar: null|string}',
['foo' => 'foo'] // `bar` is missing
);
// => ['foo' => 'foo', 'bar' => null]
// ---
// Array and list types will convert `null` or missing values to an
// empty array.
$flexibleMapper->map(
'array{foo: string, bar: array<string>}',
['foo' => 'foo'] // `bar` is missing
);
// => ['foo' => 'foo', 'bar' => []]
The superfluous keys
Superfluous keys in source arrays will be allowed, preventing errors when a value is not bound to any object property/parameter or shaped array element.
(new \CuyZ\Valinor\MapperBuilder())
->allowSuperfluousKeys()
->mapper()
->map(
'array{foo: string, bar: int}',
[
'foo' => 'foo',
'bar' => 42,
'baz' => 1337.404, // `baz` will be ignored
]
);
The permissive types
Allows permissive types mixed
and object
to be used during mapping.
(new \CuyZ\Valinor\MapperBuilder())
->allowPermissiveTypes()
->mapper()
->map(
'array{foo: string, bar: mixed}',
[
'foo' => 'foo',
'bar' => 42, // Could be any value
]
);
strict-array
type (d456eb)