github CuyZ/Valinor 2.5.0

17 hours ago

Notable changes

This release brings a set of new features to the library:

  • Normalizer configurators support
  • Shaped list type support
  • key-of type support
  • and more!

Enjoy! 🎉


Normalizer configurators support

A set of configurators is now available for the normalizer, mirroring the mapper configurators introduced in the previous release. Each one can be used either globally through the configureWith() method or locally as an attribute targeting a specific class or property.

Keys case normalization

Four configurators normalize the keys of a normalized object to a given case. This is useful to expose data following a naming convention that differs from the one used in the PHP codebase.

Configurator Example
new NormalizeKeysToCamelCase() first_namefirstName
new NormalizeKeysToPascalCase() first_nameFirstName
new NormalizeKeysToSnakeCase() firstNamefirst_name
new NormalizeKeysToKebabCase() firstNamefirst-name

Used globally, the keys of every normalized object are converted:

use CuyZ\Valinor\Normalizer\Configurator\NormalizeKeysToSnakeCase;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\NormalizerBuilder;

$userAsArray = (new NormalizerBuilder())
    ->configureWith(new NormalizeKeysToSnakeCase())
    ->normalizer(Format::array())
    ->normalize($user);

// ['first_name' => 'John']

Used as an attribute, only the keys of the targeted class are converted:

use CuyZ\Valinor\Normalizer\Configurator\NormalizeKeysToSnakeCase;

#[NormalizeKeysToSnakeCase]
final readonly class User
{
    public function __construct(
        public string $firstName,
    ) {}
}

// ['first_name' => 'John']

Date and time normalization

The NormalizeDateTimeFormat configurator normalizes any DateTimeInterface instance to a string using the given format.

Used globally, every date and time encountered during normalization is formatted:

use CuyZ\Valinor\Normalizer\Configurator\NormalizeDateTimeFormat;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\NormalizerBuilder;

$userAsArray = (new NormalizerBuilder())
    ->configureWith(new NormalizeDateTimeFormat(DATE_ATOM))
    ->normalizer(Format::array())
    ->normalize($user);

// [
//     'name' => 'Jane Doe',
//     'createdAt' => '2000-01-01T00:00:00+00:00',
// ]

Used as an attribute, only the targeted property is formatted:

use CuyZ\Valinor\Normalizer\Configurator\NormalizeDateTimeFormat;

final readonly class User
{
    public function __construct(
        public string $name,

        #[NormalizeDateTimeFormat(DATE_ATOM)]
        public DateTimeInterface $createdAt,
    ) {}
}

Shaped list type support

The shaped list type list{…} is now supported. It works like a shaped array but enforces sequential integer keys starting at 0, making it the right type to describe a tuple-like list of values.

final readonly class SomeClass
{
    public function __construct(
        /** @var list{string, int, float} */
        public array $shapedList,

        /** @var list{0: string, 1: int} */
        public array $shapedListWithExplicitKeys,

        /** @var list{0: string, 1?: int} */
        public array $shapedListWithOptionalElement,

        /** @var list{string, int, ...} */
        public array $unsealedShapedList,

        /** @var list{string, int, ...list<float>} */
        public array $unsealedShapedListWithExplicitType,

        /** @var list{string, ...<float>} */
        public array $unsealedShapedListWithShorthandType,
    ) {}
}

key-of type support

The key-of<T> type is now supported. It extracts the key types from enums, arrays, lists, and shaped arrays, including array constants. It is compatible with the same syntax as accepted by PHPStan and Psalm.

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<SomeBackedEnum> */
        public string $enumKey,

        // Accepts 'foo' or 'bar' (the keys of the shaped array)
        /** @var key-of<array{foo: string, bar: int}> */
        public string $shapedArrayKey,

        // Accepts the key type of the array (string here)
        /** @var key-of<array<string, int>> */
        public string $arrayKey,

        // Accepts 'foo' or 'bar' (the keys of the class constant array)
        /** @var key-of<SomeClassWithConstants::SOME_ARRAY> */
        public string $constantArrayKey,
    ) {}
}

Features

  • Add normalizer configurator ConvertDateTime (bf688b)
  • Add normalizer configurator NormalizeKeysToCamelCase (b0d38f)
  • Add normalizer configurator NormalizeKeysToKebabCase (9826ca)
  • Add normalizer configurator NormalizeKeysToPascalCase (53bff2)
  • Add normalizer configurator NormalizeKeysToSnakeCase (c831e0)
  • Add support for key-of type mapping (ff16b2)
  • Add support for covariant templates (c31f24)
  • Add support for shaped list type (eeeb5c)
  • Support local alias types referencing other local aliases (757256)
  • Support null values for class constants (5cd356)
  • Support parenthesized union types (21a04b)

Bug Fixes

  • Prevent memory leak with functions' reflection (6d36a0)
  • Rank union candidates by matching arguments (126cf7)

Internal

  • Add security vulnerability reporting guidelines (b80e2a)
  • Memoize parent class definitions (17d8cf)
  • Move int to float casting outside Shell (f84e78)

Other

  • Rename ConvertDateTime configurator to NormalizeDateTimeFormat (b7683a)
  • Rename ConvertKeysTo*Case configurators to MapKeysTo*Case (e38e06)

Don't miss a new Valinor release

NewReleases is sending notifications on new releases.