Skip to content

Releases: CuyZ/Valinor

1.8.0

26 Dec 14:54
Compare
Choose a tag to compare

Notable changes

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.

Features

  • Introduce normalizer service (1c9368)

Bug Fixes

  • Allow leading zeros in numeric string in flexible mode (f000c1)
  • Allow mapping union of scalars and classes (4f4af0)
  • Properly handle single-namespaced classes (a53ef9)
  • Properly parse class name in same single-namespace (a462fe)

1.7.0

23 Oct 11:05
Compare
Choose a tag to compare

Notable changes

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;
}

Features

  • Handle non-negative integer type (f444ea)
  • Handle non-positive integer type (53e404)

Bug Fixes

  • Add missing @psalm-pure annotation to pure methods (004eb1)
  • Handle comments in classes when parsing types imports (3b663a)

Other

  • Add comment for future PHP version change (461898)
  • Fix some typos (5cf8ae)
  • Make NativeBooleanType a BooleanType (d57ffa)

1.6.1

11 Oct 08:41
Compare
Choose a tag to compare

Bug Fixes

  • Correctly handle multiline type alias in classes (c23102)
  • Handle integer key in path mapping modifier (9419f6)
  • Handle variadic parameters declared in docblock (f4884c)

1.6.0

25 Aug 10:27
Compare
Choose a tag to compare

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

  • Improve attributes arguments compilation (c4acb1)
  • Replace regex-based type parser with character-based one (ae8303)
  • Simplify symbol parsing algorithm (f260cf)
  • Update Rector dependency (669ff9)

1.5.0

07 Aug 18:30
Compare
Choose a tag to compare

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

  • Avoid using unserialize when caching NULL default values (5e9b4c)
  • Catch json_encode exception to help identifying parsing errors (861c3b)
  • Update dependencies (c31e5c, 5fa107)

1.4.0

17 Apr 11:26
Compare
Choose a tag to compare

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 match mixed (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

13 Feb 05:56
Compare
Choose a tag to compare

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

08 Feb 09:40
Compare
Choose a tag to compare

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

18 Jan 09:52
Compare
Choose a tag to compare

Bug Fixes

  • Use PHP 8.0 Polyfill where needed (d90a95)

1.2.0

09 Jan 12:55
Compare
Choose a tag to compare

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

  • Remove unneeded internal check (86cca5)
  • Remove unneeded internal checks and exceptions (157723)