Releases: CuyZ/Valinor
1.6.0
Notable changes
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']);
Features
- Add support for PHP 8.3 (5c44f8)
- Allow any string or integer in array key (12af3e)
- Support microseconds in the Atom / RFC 3339 / ISO 8601 format (c25721)
Bug Fixes
- Correctly handle type inferring for method coming from interface (2657f8)
- Detect missing closing bracket after comma in shaped array type (2aa4b6)
- Handle class name collision while parsing types inside a class (044072)
- Handle invalid Intl formats with
intl.use_exceptions=1
(29da9a) - Improve cache warmup by creating required directories (a3341a)
- Load attributes lazily during runtime and cache access (3e7c63)
- Properly handle class/enum name in shaped array key (1964d4)
Other
1.5.0
Features
- Introduce method to get date formats supported during mapping (873961)
Bug Fixes
- Allow filesystem cache to be cleared when directory does not exist (782408)
- Allow negative timestamp to be mapped to a datetime (d358e8)
- Allow overriding of supported datetime formats (1c70c2)
- Correctly handle message formatting for long truncated UTF8 strings (0a8f37)
- Make serialization of attributes possible (e8ca2f)
- Remove exception inheritance from
UnresolvableType
(eaa128) - Remove previous exception from
UnresolvableType
(5c89c6)
Other
1.4.0
Notable changes
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()`
}
Features
- Introduce
InvalidSource
thrown when using invalid JSON/YAML (0739d1)
Bug Fixes
- Allow integer values in float types (c6df24)
- Make
array-key
type matchmixed
(ccebf7) - Prevent infinite loop when class has parent class with same name (83eb05)
Other
- Add previous exception in various custom exceptions (b9e381)
1.3.1
Bug Fixes
- Check if temporary cache file exists before deletion (3177bf)
- Display useful error message for invalid constructor return type (dc7f5c)
- Keep input path when error occurs in single node (d70257)
- Properly handle class static constructor for other class (d34974)
- Properly handle union of
null
and objects (8f03a7)
Other
- Update dependencies (f7e7f2)
1.3.0
Notable changes
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.
Features
- Handle custom enum constructors registration (217e12)
Other
- Handle enum type as class type (5a3caf)
0.17.1
1.2.0
Notable changes
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 72cba32
Features
- Allow usage of array input for single node during mapping (686186)
Bug Fixes
- Do not re-validate single node with existing error (daaaac)
Other
1.1.0
Notable changes
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');
Features
Bug Fixes
- Handle
object
return type in PHPStan extension (201728) - Import plugin class file in PHPStan configuration (58d540)
- Keep nested errors when superfluous keys are detected (813b3b)
Other
1.0.0
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! 🤗
Notable changes
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…',
]);
Upgrading from 0.x to 1.0
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 support removal
Doctrine annotations cannot be used anymore, PHP attributes must be used.
`BackwardCompatibilityDateTimeConstructor` class removal
You must use the method available in the mapper builder, see dealing with dates chapter.
Mapper builder `flexible` method removal
The flexible has been splitted in three disctint modes, see type strictness & flexibility chapter.
Mapper builder `withCacheDir` method removal
You must now register a cache instance directly, see performance & caching chapter.
`StaticMethodConstructor` class removal
You must now register the constructors using the mapper builder, see custom object constructors chapter.
Mapper builder `bind` method removal
You must now register the constructors using the mapper builder, see custom object constructors chapter.
`ThrowableMessage` class removal
You must now use the MessageBuilder
class, see error handling chapter.
`MessagesFlattener` class removal
You must now use the Messages
class, see error handling chapter.
`TranslatableMessage` class removal
You must now use the HasParameters
class, see custom exception chapter.
Message methods removal
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}
.
`PlaceHolderMessageFormatter` class removal
Other features are available to format message, see error messages customization chapter.
`Identifier` attribute removal
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.
⚠ BREAKING CHANGES
- Disallow array when mapping to object with one argument (72cba3)
- Mark tree mapper and arguments mapper as
@pure
(0d9855) - Remove deprecated backward compatibility datetime constructor (a65e8d)
- Remove deprecated class
ThrowableMessage
(d36ca9) - Remove deprecated class to flatten messages (f9ed93)
- Remove deprecated interface
TranslatableMessage
(ceb197) - Remove deprecated message methods (e6557d)
- Remove deprecated method constructor attribute (d76467)
- Remove deprecated method to enable flexible mode (a2bef3)
- Remove deprecated method to set cache directory (b0d6d2)
- Remove deprecated method used to bind a callback (b79ed8)
- Remove deprecated placeholder message formatter (c2723d)
- Remove Doctrine annotations support (66c182)
- Remove identifier attribute (8a7486)
- Remove PHP 7.4 support (5f5a50)
- Remove support for
strict-array
type (22c3b4)
Features
- Add constructor for
DateTimeZone
with error support ([a0a4d6](a0a4d63...
0.17.0
Notable changes
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
andobject
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 ] );
Full list of changes
Features
- Add support for
strict-array
type (d456eb) - Introduce new callback message formatter (93f898)
- Introduce new helper class to list messages (513827)
- Split mapper flexible mode in three distinct modes (549e5f)
Bug Fixes
- Allow missing and null value for array node in flexible mode (034f1c)
- Allow missing value for shaped array nullable node in flexible mode (08fb0e)
- Handle scalar value casting in union types only in flexible mode (752ad9)