PHP library that helps to map any input into a strongly-typed value object structure.

Overview

Valinor • PHP object mapper with strong type support

Total Downloads Latest Stable Version PHP Version Require

Mutation testing badge

Valinor is a PHP library that helps to map any input into a strongly-typed value object structure.

The conversion can handle native PHP types as well as other well-known advanced type annotations like array shapes, generics and more.

Why?

There are many benefits of using value objects instead of plain arrays and scalar values in a modern codebase, among which:

  1. Data and behaviour encapsulation — locks an object's behaviour inside its class, preventing it from being scattered across the codebase.
  2. Data validation — guarantees the valid state of an object.
  3. Immutability — ensures the state of an object cannot be changed during runtime.

When mapping any source to an object structure, this library will ensure that all input values are properly converted to match the types of the nodes — class properties or method parameters. Any value that cannot be converted to the correct type will trigger an error and prevent the mapping from completing.

These checks guarantee that if the mapping succeeds, the object structure is perfectly valid, hence there is no need for further validation nor type conversion: the objects are ready to be used.

Static analysis

A strongly-typed codebase allows the usage of static analysis tools like PHPStan and Psalm that can identify issues in a codebase without running it.

Moreover, static analysis can help during a refactoring of a codebase with tools like an IDE or Rector.

Usage

Installation

composer require cuyz/valinor

Example

An application must handle the data coming from an external API; the response has a JSON format and describes a thread and its answers. The validity of this input is unsure, besides manipulating a raw JSON string is laborious and inefficient.

{
    "id": 1337,
    "content": "Do you like potatoes?",
    "date": "1957-07-23 13:37:42",
    "answers": [
        {
            "user": "Ella F.",
            "message": "I like potatoes",
            "date": "1957-07-31 15:28:12"
        },
        {
            "user": "Louis A.",
            "message": "And I like tomatoes",
            "date": "1957-08-13 09:05:24"
        }
    ]
}

The application must be certain that it can handle this data correctly; wrapping the input in a value object will help.


A schema representing the needed structure must be provided, using classes.

final class Thread
{
    public function __construct(
        public readonly int $id,
        public readonly string $content,
        public readonly DateTimeInterface $date,
        /** @var Answer[] */
        public readonly array $answers, 
    ) {}
}

final class Answer
{
    public function __construct(
        public readonly string $user,
        public readonly string $message,
        public readonly DateTimeInterface $date,
    ) {}
}

Then a mapper is used to hydrate a source into these objects.

public function getThread(int $id): Thread
{
    $rawJson = $this->client->request("https://example.com/thread/$id");

    try {   
       return (new \CuyZ\Valinor\MapperBuilder())
           ->mapper()
           ->map(
               Thread::class,
               new \CuyZ\Valinor\Mapper\Source\JsonSource($rawJson)
           );
   } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
       $this->logger->error(
           'Invalid JSON returned by API', 
           $error->describe() // This gives more information about what was wrong
       );

       throw $error;
   }
}

Validation

The source given to a mapper can never be trusted, this is actually the very goal of this library: transforming an unstructured input to a well-defined object structure. If the mapper cannot guess how to cast a certain value, it means that it is not able to guarantee the validity of the desired object thus it will fail.

Any issue encountered during the mapping will add an error to an upstream exception of type \CuyZ\Valinor\Mapper\MappingError. It is therefore always recommended wrapping the mapping function call with a try/catch statement and handle the error properly.

More specific validation should be done in the constructor of the value object, by throwing an exception if something is wrong with the given data. A good practice would be to use lightweight validation tools like Webmozart Assert.

final class SomeClass
{
    public function __construct(private string $value)
    {
        Assert::startsWith($value, 'foo_');
    }
}

try {
   (new \CuyZ\Valinor\MapperBuilder())
       ->mapper()
       ->map(
           SomeClass::class,
           ['value' => 'bar_baz']
       );
} catch (\CuyZ\Valinor\Mapper\MappingError $error) {
    // Contains an error similar to:
    // > Expected a value to start with "foo_". Got: "bar_baz"
    var_dump($error->describe());
}

Source

Any source can be given to the mapper, but some helpers can be used for more convenience:

function map($source) {
    return (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(SomeClass::class, $source);
}

map(new \CuyZ\Valinor\Mapper\Source\JsonSource($jsonString));

map(new \CuyZ\Valinor\Mapper\Source\YamlSource($yamlString));

// File containing valid Json or Yaml content and with valid extension
map(new \CuyZ\Valinor\Mapper\Source\FileSource(
    new SplFileObject('path/to/my/file.json')
));

Construction strategy

During the mapping, instances of the objects are created and hydrated with the correct values. construction strategies will determine what values are needed and how an object is built.

An object can provide either…

  • …a constructor that will be called with proper parameters.
  • …a list of properties that will be filled with proper values — even if they are private.

Handled types

To prevent conflicts or duplication of the type annotations, this library tries to handle most of the type annotations that are accepted by PHPStan and Psalm.

Scalar

final class SomeClass
{
    public function __construct(
        private bool $boolean,

        private float $float,

        private int $integer,

        /** @var positive-int */
        private int $positiveInteger,

        /** @var negative-int */
        private int $negativeInteger,

        private string $string,
        
        /** @var non-empty-string */
        private string $nonEmptyString,

        /** @var class-string */
        private string $classString,

        /** @var class-string<SomeInterface> */
        private string $classStringOfAnInterface,
    ) {}
}

Object

final class SomeClass
{
    public function __construct(
        private SomeClass $class,

        private DateTimeInterface $interface,

        /** @var SomeInterface&AnotherInterface */
        private object $intersection,

        /** @var SomeCollection<SomeClass> */
        private SomeCollection $classWithGeneric,
    ) {}
}

/**
 * @template T of object 
 */
final class SomeCollection
{
    public function __construct(
        /** @var array<T> */
        private array $objects,
    ) {}
}

Array & lists

final class SomeClass
{
    public function __construct(
        /** @var string[] */
        private array $simpleArray,

        /** @var array<string> */
        private array $arrayOfStrings,

        /** @var array<string, SomeClass> */
        private array $arrayOfClassWithStringKeys,

        /** @var array<int, SomeClass> */
        private array $arrayOfClassWithIntegerKeys,

        /** @var non-empty-array<string> */
        private array $nonEmptyArrayOfStrings,

        /** @var non-empty-array<string, SomeClass> */
        private array $nonEmptyArrayWithStringKeys,
        
        /** @var list<string> */
        private array $listOfStrings,
        
        /** @var non-empty-list<string> */
        private array $nonEmptyListOfStrings,

        /** @var array{foo: string, bar: int} */
        private array $shapedArray,

        /** @var array{foo: string, bar?: int} */
        private array $shapedArrayWithOptionalElement,

        /** @var array{string, bar: int} */
        private array $shapedArrayWithUndefinedKey,
    ) {}
}

Union

final class SomeClass
{
    public function __construct(
        private int|string $simpleUnion,
        
        /** @var class-string<SomeInterface>|class-string<AnotherInterface> */
        private string $unionOfClassString,
        
        /** @var array<SomeInterface|AnotherInterface> */
        private array $unionInsideArray,
    ) {}
}
Comments
  • Expand API of `CuyZ\Valinor\Mapper\Tree\Message\Message` to allow for i18n and API customization?

    Expand API of `CuyZ\Valinor\Mapper\Tree\Message\Message` to allow for i18n and API customization?

    Currently, Message is only an instance of Stringable: https://github.com/CuyZ/Valinor/blob/396f64a5246ccfe3f6f6d3211bac7f542a9c7fc6/src/Mapper/Tree/Message/Message.php#L9-L11

    When using this library, you can expect the most common usage to be (pseudo-code):

    try {
        parse_validate_and_map($request);
    } catch (MappingError $failed) {
        response_code(422);
        mapping_error_to_failure_response($failed);
    }
    

    In order to effectively write mapping_error_to_failure_response(), especially considering concerns like translation, i18n, and general re-shaping of the response, it would be helpful to have:

    • the internal message (like now), valid for loggers and such
    • a declared code (currently, Exception#getCode() is used, but unsure if it is stable long-term?)
    • the original value (which failed mapping - at the level we're at) - this is useful for interpolating in error messages
    • the wished type (to which we failed to cast/map towards) - this is useful for interpolating in error messages
    • the logical path of the value, not the string as key in MappingError, useful for processing/filtering errors later on

    In practice, the use-case is mostly around i18n of error messages, plus providing a bit more information on each Message.

    A good example of such serialization is https://datatracker.ietf.org/doc/html/rfc7807

    opened by Ocramius 16
  • Consider using built-in types from `phpstan/phpstan` or `vimeo/psalm` directly?

    Consider using built-in types from `phpstan/phpstan` or `vimeo/psalm` directly?

    The spec about how to declare annotated types changes constantly: this library seems to infer types with its own mechanisms, which is possibly why issues like #1 and #5 arise.

    In order to reduce such issues, it may be a good idea to directly build on top of phpstan/phpstan or vimeo/psalm for their internal type definitions (value objects, practically):

    • https://github.com/phpstan/phpstan-src/tree/447cd102d946a2d9883ac82793195275f54296a9/src/Type
    • https://github.com/vimeo/psalm/tree/f1d47cc662500589e200a978e3af85488a5236cf/src/Psalm/Type

    By leveraging these, and having a "default" case that throws ("I don't know this type yet"), it would potentially be clear what is and isn't supported yet, at the cost of introducing a runtime dependency.

    opened by Ocramius 13
  • How to apply custom transformations?

    How to apply custom transformations?

    Context: as of today, I map my API REST request bodies from json to DTO by using Symfony's Forms. While it works fairly well, Symfony's Form are meant to handle forms. I would like to avoid their inherited complexity and limitations since are not the best fit for my use case, so I'm looking for an alternative and I want to try out Valinor.

    Problem: I have cases where in the request I receive things like "productId" and I want my DTO to contain an object of class Product that should be retrieved by doing a query by id using the ORM. Is there an hook/way that I can build upon to fulfil this use case? I was looking for some sort of way to define the transformation/mapping for those custom cases, but I didn't find anything about it in the documentation. Similarly I have cases where I receive monetary amounts as a string like "12.03" but in my DTO I want a Money object constructed with something like new Money((float) $value), so I would need some sort of custom "type"/"mapping" for this as well.

    What are Valinor recommendations in those cases?

    opened by danydev 12
  • How can I add the path to a Message

    How can I add the path to a Message

    I'd like to add the subject of the issue so that catching the MappingError I can compose an API response similar to

    {
        "issues": [
            {
                "subject": "my_subject",
                "message": "Want is wrong with the subject"
            }
        ]
    }
    
    

    I'm throwing a custom Exception like this one in the mapping signature Object constructor

    use CuyZ\Valinor\Mapper\Tree\Message\Message;
    
    use function implode;
    use function sprintf;
    
    class InvalidEnumValue extends \DomainException implements Message
    {
        /**
         * @param list<Enum> $enumValues
         */
        public static function forEnum(array $enumValues, string $value): self
        {
            return new self(sprintf('Value \'%s\' is not valid, valid values are %s', $value, '\''
                . implode('\', \'', $enumValues) . '\''));
        }
    }
    

    Do you have any suggestion?

    By the way the reason why I need to throw a custom exception here is because the mapper isn't able to map a string to an \MyCLabs\Enum\Enum. The issue seems to be the current Psalm annotations inside \MyCLabs\Enum\Enum. Are Psalm annotations used in some way to map the object?

    opened by DavideBicego 11
  • Named constructor that received input as array

    Named constructor that received input as array

    Hi,

    I've an object which deserialization can only be done in a constructor that receive all sub elements as an array.

    Something like

    {
      "foo": {
         "test": true,
         "other": "yes", 
         "something_else": false
       }
    }
    

    That is handled by :

    class Foo {
      public static function fromArray(array $data) {
        $result = new self();
        $result->somethingSuperStrange = $data['test'] ?? $data['something_else']
        return $result;
      }
    }
    

    Don't mind the something super strange as this is not my real use case, I really need to handle some weird data.

    Can something like this be done with valinor ?

    opened by magnetik 11
  • Better type inference, improved test coverage throughout locked dependencies, automated dependency upgrades

    Better type inference, improved test coverage throughout locked dependencies, automated dependency upgrades

    This patch is mostly raised by the upgrade of 0.3.0 to 0.4.0 breaking BC (expected, but preventable).

    In practice, Valinor is probably going to be used because of TreeMapper#map(class-string<T>, mixed): T in the grand majority of cases.

    To cover this scenario, and prevent regressions, I introduced:

    • testing against locked dependencies, so that we can pinpoint tiny upgrades that break cuyz/valinor as they come up
    • automated dependency widening (I would prefer bumping, but that's really your decision)
    • vimeo/psalm type inference testing
    • better types around TreeMapper#map(), so that it works without coupling cuyz/valinor to a specific vimeo/psalm version (the type declarations for the majority scenario are in the code, without any need for plugins)
    opened by Ocramius 11
  • feat: allow to declare development environment

    feat: allow to declare development environment

    Currently, the compilation of a class definition will detect file modification in order to re-run the compilation, see:

    https://github.com/CuyZ/Valinor/blob/dd4624c5e0376cf1c590117dcad10c659a614701/src/Definition/Repository/Cache/Compiler/ClassDefinitionCompiler.php#L67-L82

    While this is often needed during a user's development phase (because the value-objects used for the mapping are always changing), this is useless in a production environment, where the PHP files should not change (or during a deployment phase, but the caches should then be cleared).

    The issue is that filemtime function is quite heavy and resource-hungry, where this library tries to be as optimized as possible.

    Proposal

    The MapperBuilder should give access to a new method that would allow to "activate" development mode — this setting would then be used by ClassDefinitionCompiler to handle, or not, the file modification feature.

    enhancement 
    opened by romm 11
  • Support class constants properties

    Support class constants properties

    As defined at phpstan here: https://phpstan.org/writing-php-code/phpdoc-types#literals-and-constants

    class DtoValues {
        public const FOO = 1;
        public const FOO_BAR = 2;
        public const BAR = 'FooBar!';
    }
    
    class FooBar
    {
        /**
         * @var DtoValues::FOO*
         */
        public $id;
    
        /**
         * @var DtoValues::BAR|null
         */
        public $title;
    }
    

    Actual use-case

    Optional properties in payload.

    class DtoValues {
        public const None = 'SomeNoneValueString';
    }
    
    class WithOptionalDto
    {
        /**
         * @var DtoValues::None|null|int
         */
        public $priority = DtoValues::None;
    }
    
    // Payload does not need to contain `priority`, it has a default value (None).
    $object = buildDto(WithOptionalDto::class, $source);
    
    if ($object->priority !== DtoValues::None) {
        // Payload actually contains some value, lets use it.
    }
    

    Today, the error message is:

    CuyZ\Valinor\Definition\Exception\InvalidPropertyDefaultValue: Default value of property WithOptionalDto::$priority is not accepted by DtoValues image (Screenshot taken in ReflectionPropertyDefinitionBuilder)

    Notice it simply ignores the rest of the types, and is not even an UnionType as expected.

    In PHP 8.1 is this a lot easier because we could use a global const which value is an object (const None = new None) and use None as a valid type. Though we are still on 7.4 and must rely on some weird strings - if we decide that this is the best approuch for optional properties.

    opened by eigan 10
  • Inconsistent results with nullable property

    Inconsistent results with nullable property

    When the source is empty, is the mapper not able to set null to nullable property.

    Reproduction

    use CuyZ\Valinor\Mapper\MappingError;
    
    require "vendor/autoload.php";
    
    final class Foo
    {
        public string|null $note;
    }
    
    final class Bar
    {
        public string|null $note;
    
        public string $title;
    }
    
    try {
        $foo = (new \CuyZ\Valinor\MapperBuilder())
            ->mapper()
            ->map(Foo::class, []);
    } catch (MappingError $e) {
        // Foo: Cannot cast value of type `array` to `string`.
        // Caused by ClassNodeBuilder::transformSource()
        // $source is [] and since the property $note does not exist in $source
        // will it return $source = [$name => $source];
    
        foreach($e->node()->children() as $child) {
            foreach($child->messages() as $message) {
                echo "Foo: " . $message . "\n";
            }
        }
    }
    
    // No issues:
    $bar = (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(Bar::class, ['title' => 'Foo']);
    
    $bar->note === null; // true
    

    Expected behavior

    Foo::$note should be assigned null as value to be consistent with example Bar, but see my other issue here: #113.

    opened by eigan 10
  • Feature: Signature cache warmup via `MapperBuilder#warmup`

    Feature: Signature cache warmup via `MapperBuilder#warmup`

    As requested in #115, it would be awesome to have the ability to warmup known signatures (e.g. during build time).

    One suggestion was to provide a dedicated method for this in the MapperBuilder (which is also used to specify the cache directory).

    So this Pull-Request does provide that MapperBuilder#warmup method and consumes at least 1 but also multiple signature(s) which will be parsed and pre-cached to the underlying filesystem cache.

    opened by boesing 9
  • fix: Attributes now combine correctly with foreign attributes

    fix: Attributes now combine correctly with foreign attributes

    Without this change I needed to define the Valinor attributes first, otherwise an undefined index error occurred (was using Doctrine annotations in our project).

    opened by fred-jan 9
  • Enum not allowed as registered constructor return type

    Enum not allowed as registered constructor return type

    Valinor refuses registered constructors whose return type is an enum.

    The following mapper fails with the error Invalid return type Foo for constructor Foo::from(), it must be a valid class name when Foo is an enum:

    $value = (new MapperBuilder())
        ->registerConstructor(Foo::from(...))
        ->mapper()
        ->map(C::class, $source);
    

    Full reproducer: https://gist.github.com/arnaud-lb/748fa5f38437b2a0c32850adc88158e3

    Backtrace:

    PHP Fatal error:  Uncaught CuyZ\Valinor\Mapper\Object\Exception\InvalidConstructorReturnType: Invalid return type `Foo` for constructor `Foo::from()`, it must be a valid class name. in /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php:97
    Stack trace:
    #0 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php(65): CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory->constructorMatches()
    #1 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php(40): CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory->builders()
    #2 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/DateTimeZoneObjectBuilderFactory.php(36): CuyZ\Valinor\Mapper\Object\Factory\ConstructorObjectBuilderFactory->for()
    #3 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/DateTimeObjectBuilderFactory.php(34): CuyZ\Valinor\Mapper\Object\Factory\DateTimeZoneObjectBuilderFactory->for()
    #4 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/CollisionObjectBuilderFactory.php(24): CuyZ\Valinor\Mapper\Object\Factory\DateTimeObjectBuilderFactory->for()
    #5 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/StrictTypesObjectBuilderFactory.php(21): CuyZ\Valinor\Mapper\Object\Factory\CollisionObjectBuilderFactory->for()
    #6 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/CacheObjectBuilderFactory.php(31): CuyZ\Valinor\Mapper\Object\Factory\StrictTypesObjectBuilderFactory->for()
    #7 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ProxyClassNodeBuilder.php(38): CuyZ\Valinor\Mapper\Object\Factory\CacheObjectBuilderFactory->for()
    #8 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterNodeBuilder.php(25): CuyZ\Valinor\Mapper\Tree\Builder\ProxyClassNodeBuilder->build()
    #9 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/UnionNodeBuilder.php(39): CuyZ\Valinor\Mapper\Tree\Builder\CasterNodeBuilder->build()
    #10 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/InterfaceNodeBuilder.php(52): CuyZ\Valinor\Mapper\Tree\Builder\UnionNodeBuilder->build()
    #11 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/CasterProxyNodeBuilder.php(26): CuyZ\Valinor\Mapper\Tree\Builder\InterfaceNodeBuilder->build()
    #12 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/IterableNodeBuilder.php(28): CuyZ\Valinor\Mapper\Tree\Builder\CasterProxyNodeBuilder->build()
    #13 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/StrictNodeBuilder.php(37): CuyZ\Valinor\Mapper\Tree\Builder\IterableNodeBuilder->build()
    #14 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/ErrorCatcherNodeBuilder.php(33): CuyZ\Valinor\Mapper\Tree\Builder\StrictNodeBuilder->build()
    #15 /tmp/bug/vendor/cuyz/valinor/src/Mapper/Tree/Builder/RootNodeBuilder.php(18): CuyZ\Valinor\Mapper\Tree\Builder\ErrorCatcherNodeBuilder->build()
    #16 /tmp/bug/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(45): CuyZ\Valinor\Mapper\Tree\Builder\RootNodeBuilder->build()
    #17 /tmp/bug/vendor/cuyz/valinor/src/Mapper/TypeTreeMapper.php(26): CuyZ\Valinor\Mapper\TypeTreeMapper->node()
    #18 /tmp/bug/bug.php(31): CuyZ\Valinor\Mapper\TypeTreeMapper->map()
    #19 {main}
      thrown in /tmp/bug/vendor/cuyz/valinor/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php on line 97
    

    $returnType is a NativeEnumType in https://github.com/CuyZ/Valinor/blob/1577233ac9dce444af61bb482f5a744e38eaf739/src/Mapper/Object/Factory/ConstructorObjectBuilderFactory.php#L96-L97

    opened by arnaud-lb 0
  • Nullable union of classes error

    Nullable union of classes error

    class Foo
    {
        public function __construct(
            public string $one,
            public string $two,
        ) {
        }
    }
    
    class Bar
    {
        public function __construct(
            public string $three,
            public string $four,
        ) {
        }
    }
    
    class Wtf
    {
        public function __construct(
            public string $six,
            public Foo|Bar|null $five = null,
        ) {
        }
    }
    
    (new MapperBuilder())
        ->mapper()
        ->map(Wtf::class, Source::array([
            'five' => [
                'one' => 'aaa',
                'two' => 'bbb',
            ],
            'six' => 'ccc',
        ]));
    

    Throws Could not map type 'Wtf'. An error occurred at path five: Invalid value array{one: 'aaa', two: 'bbb'}. If I make the property not nullable, it works fine.

    opened by lookyman 1
  • chore(deps): bump symfony/console from 5.4.15 to 5.4.17

    chore(deps): bump symfony/console from 5.4.15 to 5.4.17

    Bumps symfony/console from 5.4.15 to 5.4.17.

    Release notes

    Sourced from symfony/console's releases.

    v5.4.17

    Changelog (https://github.com/symfony/console/compare/v5.4.16...v5.4.17)

    • bug #48784 Correctly overwrite progressbars with different line count per step (ncharalampidis)

    v5.4.16

    Changelog (https://github.com/symfony/console/compare/v5.4.15...v5.4.16)

    • bug #48179 Support completion for bash functions (Chi-teck)
    • bug #48217 Improve error message when shell is not detected in completion command (GromNaN)
    • bug #48210  Fix signal handlers called after event listeners and skip exit (GromNaN)
    • bug #47998 Fix console ProgressBar::override() after manual ProgressBar::cleanup() (maxbeckers)
    • bug #48085 Tell about messenger:consume invalid limit options (MatTheCat)
    Commits
    • 58422fd [Console] Correctly overwrite progressbars with different line count per step
    • 9bd719e [Console] Fix a test when pcntl is not available (following #48329)
    • 8e9b9c8 skip tests if the signal to be sent is not available
    • 3042c61 skip a test if the signal to be sent is not available
    • f2dd071 Support completion for bash functions
    • dcda527 Improve message when shell is not detected
    • 3836ffa bug #48210 [Console]  Fix signal handlers called after event listeners and sk...
    • 0c6f9ac Merge branch '4.4' into 5.4
    • 01c90db Fix signal handlers called after event listeners and skip exit
    • 005ed05 bug #47998 [Console] Fix console ProgressBar::override() after manual `Prog...
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies php 
    opened by dependabot[bot] 0
  • chore(deps): bump actions/cache from 3.0.11 to 3.2.2

    chore(deps): bump actions/cache from 3.0.11 to 3.2.2

    Bumps actions/cache from 3.0.11 to 3.2.2.

    Release notes

    Sourced from actions/cache's releases.

    v3.2.2

    What's Changed

    New Contributors

    Full Changelog: https://github.com/actions/cache/compare/v3.2.1...v3.2.2

    v3.2.1

    What's Changed

    Full Changelog: https://github.com/actions/cache/compare/v3.2.0...v3.2.1

    v3.2.0

    What's Changed

    New Contributors

    ... (truncated)

    Changelog

    Sourced from actions/cache's changelog.

    3.0.11

    • Update toolkit version to 3.0.5 to include @actions/core@^1.10.0
    • Update @actions/cache to use updated saveState and setOutput functions from @actions/core@^1.10.0

    3.1.0-beta.1

    • Update @actions/cache on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. (issue)

    3.1.0-beta.2

    • Added support for fallback to gzip to restore old caches on windows.

    3.1.0-beta.3

    • Bug fixes for bsdtar fallback if gnutar not available and gzip fallback if cache saved using old cache action on windows.

    3.2.0-beta.1

    • Added two new actions - restore and save for granular control on cache.

    3.2.0

    • Released the two new actions - restore and save for granular control on cache

    3.2.1

    • Update @actions/cache on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. (issue)
    • Added support for fallback to gzip to restore old caches on windows.
    • Added logs for cache version in case of a cache miss.

    3.2.2

    • Reverted the changes made in 3.2.1 to use gnu tar and zstd by default on windows.
    Commits
    • 4723a57 Revert compression changes related to windows but keep version logging (#1049)
    • d1507cc Merge pull request #1042 from me-and/correct-readme-re-windows
    • 3337563 Merge branch 'main' into correct-readme-re-windows
    • 60c7666 save/README.md: Fix typo in example (#1040)
    • b053f2b Fix formatting error in restore/README.md (#1044)
    • 501277c README.md: remove outdated Windows cache tip link
    • c1a5de8 Upgrade codeql to v2 (#1023)
    • 9b0be58 Release compression related changes for windows (#1039)
    • c17f4bf GA for granular cache (#1035)
    • ac25611 docs: fix an invalid link in workarounds.md (#929)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    dependencies github_actions 
    opened by dependabot[bot] 0
  • Union literal keys are not allowed for some reason

    Union literal keys are not allowed for some reason

    <?php
    
    use CuyZ\Valinor\MapperBuilder;
    
    require 'vendor/autoload.php';
    
    (new MapperBuilder)->mapper()->map('array<"a"|"b", true>', ['a' => true]);
    
    

    throws "Could not parse the type array<"a"|"b", true> that should be mapped: Invalid key type "a"|"b" for array<"a"|"b", true>. It must be one of array-key, int or string."

    opened by danog 0
  • Must registered constructors be pure?

    Must registered constructors be pure?

    MapperBuilder::registerConstructor has the annotation @psalm-param pure-callable|class-string ...$constructors, which seems a bit odd to me. Is there a reason constructors must be pure? Most libraries don't mark their __construct functions as pure so it's often difficult to abide by that requirement, and when using class-string it's still going to be calling impure __construct functions without complaint anyway.

    opened by AndrolGenhald 0
Releases(1.1.0)
  • 1.1.0(Dec 20, 2022)

    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

    • Add support for class inferring (5a90ad)
    • Handle class generic types inheritance (6506b7)

    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

    • Adapt code with PHP 8.0 syntax (3fac3e)
    • Add isAbstract flag in class definition (ad0c06)
    • Add isFinal flag in class definition (25da31)
    • Enhance TreeMapper::map() return type signature (dc32d3)
    • Improve return type signature for TreeMapper (c8f362)
    • Prevent multiple cache round-trip (13b620)
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Nov 28, 2022)

    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)
    • Introduce mapper to map arguments of a callable (9c7e88)

    Bug Fixes

    • Allow mapping null to single node nullable type (0a98ec)
    • Handle single argument mapper properly (d7bf6a)
    • Handle tree mapper call without argument in PHPStan extension (3f3a01)
    • Handle tree mapper call without argument in Psalm plugin (b425af)

    Other

    • Activate value altering feature only when callbacks are registered (0f33a5)
    • Bump psr/simple-cache supported version (e4059a)
    • Remove @ from comments for future PHP versions changes (68774c)
    • Update dependencies (4afcda)
    Source code(tar.gz)
    Source code(zip)
  • 0.17.0(Nov 8, 2022)

    Notable changes

    The main feature introduced in this release is the split of the flexible mode in three distinct modes:

    1. 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' => []]
      
    2. 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
              ]
          );
      
    3. 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
              ]
          );
      

    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)

    Other

    • Do not use uniqid() (b81847)
    • Transform missing source value to null in flexible mode (92a41a)
    Source code(tar.gz)
    Source code(zip)
  • 0.16.0(Oct 19, 2022)

  • 0.15.0(Oct 6, 2022)

    Notable changes

    Two similar features are introduced in this release: constants and enums wildcard notations. This is mainly useful when several cases of an enum or class constants share a common prefix.

    Example for class constants:

    final class SomeClassWithConstants
    {
        public const FOO = 1337;
    
        public const BAR = 'bar';
    
        public const BAZ = 'baz';
    }
    
    $mapper = (new MapperBuilder())->mapper();
    
    $mapper->map('SomeClassWithConstants::BA*', 1337); // error
    $mapper->map('SomeClassWithConstants::BA*', 'bar'); // ok
    $mapper->map('SomeClassWithConstants::BA*', 'baz'); // ok
    

    Example for enum:

    enum SomeEnum: string
    {
        case FOO = 'foo';
        case BAR = 'bar';
        case BAZ = 'baz';
    }
    
    $mapper = (new MapperBuilder())->mapper();
    
    $mapper->map('SomeEnum::BA*', 'foo'); // error
    $mapper->map('SomeEnum::BA*', 'bar'); // ok
    $mapper->map('SomeEnum::BA*', 'baz'); // ok
    

    Full list of changes

    Features

    • Add support for class constant type (1244c2)
    • Add support for wildcard in enumeration type (69ebd1)
    • Introduce utility class to build messages (cb8792)

    Bug Fixes

    • Add return types for cache implementations (0e8f12)
    • Correctly handle type inferring during mapping (37f96f)
    • Fetch correct node value for children (3ee526)
    • Improve scalar values casting (212b77)
    • Properly handle static anonymous functions (c009ab)

    Other

    • Import namespace token parser inside library (0b8ca9)
    • Remove unused code (b2889a, de8aa9)
    • Save type token symbols during lexing (ad0f8f)
    Source code(tar.gz)
    Source code(zip)
  • 0.14.0(Sep 1, 2022)

    Notable changes

    Until this release, the behaviour of the date objects creation was very opinionated: a huge list of date formats were tested out, and if one was working it was used to create the date.

    This approach resulted in two problems. First, it led to (minor) performance issues, because a lot of date formats were potentially tested for nothing. More importantly, it was not possible to define which format(s) were to be allowed (and in result deny other formats).

    A new method can now be used in the MapperBuilder:

    (new \CuyZ\Valinor\MapperBuilder())
        // Both `Cookie` and `ATOM` formats will be accepted
        ->supportDateFormats(DATE_COOKIE, DATE_ATOM)
        ->mapper()
        ->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
    

    Please note that the old behaviour has been removed. From now on, only valid timestamp or ATOM-formatted value will be accepted by default.

    If needed and to help with the migration, the following deprecated constructor can be registered to reactivate the previous behaviour:

    (new \CuyZ\Valinor\MapperBuilder())
        ->registerConstructor(
            new \CuyZ\Valinor\Mapper\Object\BackwardCompatibilityDateTimeConstructor()
        )
        ->mapper()
        ->map(DateTimeInterface::class, 'Monday, 08-Nov-1971 13:37:42 UTC');
    

    Full list of changes

    ⚠ BREAKING CHANGES

    • Introduce constructor for custom date formats (f232cc)

    Features

    • Handle abstract constructor registration (c37ac1)
    • Introduce attribute DynamicConstructor (e437d9)
    • Introduce helper method to describe supported date formats (11a7ea)

    Bug Fixes

    • Allow trailing comma in shaped array (bf445b)
    • Correctly fetch file system cache entries (48208c)
    • Detect invalid constructor handle type (b3cb59)
    • Handle classes in a case-sensitive way in type parser (254074)
    • Handle concurrent cache file creation (fd39ae)
    • Handle inherited private constructor in class definition (73b622)
    • Handle invalid nodes recursively (a401c2)
    • Prevent illegal characters in PSR-16 cache keys (3c4d29)
    • Properly handle callable objects of the same class (ae7ddc)

    Other

    • Add singleton usage of ClassStringType (4bc50e)
    • Change ObjectBuilderFactory::for return signature (57849c)
    • Extract native constructor object builder (2b46a6)
    • Fetch attributes for function definition (ec494c)
    • Refactor arguments instantiation (6414e9)
    Source code(tar.gz)
    Source code(zip)
  • 0.13.0(Jul 31, 2022)

    Notable changes

    Reworking of messages body and parameters features

    The \CuyZ\Valinor\Mapper\Tree\Message\Message interface is no longer a Stringable, however it defines a new method body that must return the body of the message, which can contain placeholders that will be replaced by parameters.

    These parameters can now be defined by implementing the interface \CuyZ\Valinor\Mapper\Tree\Message\HasParameters.

    This leads to the deprecation of the no longer needed interface \CuyZ\Valinor\Mapper\Tree\Message\TranslatableMessage which had a confusing name.

    final class SomeException extends DomainException implements ErrorMessage, HasParameters, HasCode
    {
        private string $someParameter;
    
        public function __construct(string $someParameter)
        {
            parent::__construct();
    
            $this->someParameter = $someParameter;
        }
    
        public function body() : string
        {
            return 'Some message / {some_parameter} / {source_value}';
        }
    
        public function parameters(): array
        {
            return [
                'some_parameter' => $this->someParameter,
            ];
        }
    
        public function code() : string
        {
            // A unique code that can help to identify the error
            return 'some_unique_code';
        }
    }
    

    Handle numeric-string type

    Kudos to @sergkash7 for this new feature!

    The new numeric-string type can be used in docblocks.

    It will accept any string value that is also numeric.

    (new MapperBuilder())->mapper()->map('numeric-string', '42'); // ✅
    (new MapperBuilder())->mapper()->map('numeric-string', 'foo'); // ❌
    

    Better mapping error message

    Kudos to @Slamdunk for this!

    The message of the exception will now contain more information, especially the total number of errors and the source that was given to the mapper. This change aims to have a better understanding of what is wrong when debugging.

    Before:

    Could not map type `array{foo: string, bar: int}` with the given source.

    After:

    Could not map type `array{foo: string, bar: int}`. An error occurred at path bar: Value 'some other string' does not match type `int`.

    Full list of changes

    ⚠ BREAKING CHANGES

    • Rework messages body and parameters features (ad1207)

    Features

    • Allow to declare parameter for message (f61eb5)
    • Display more information in mapping error message (9c1e7c)
    • Handle numeric string type (96a493)
    • Make MessagesFlattener countable (2c1c7c)

    Bug Fixes

    • Handle native attribute on promoted parameter (897ca9)

    Other

    • Add fixed value for root node path (0b37b4)
    • Remove types stringable behavior (b47a1b)
    Source code(tar.gz)
    Source code(zip)
  • 0.12.0(Jul 10, 2022)

    Notable changes

    SECURITY — Userland exception filtering

    See advisory GHSA-5pgm-3j3g-2rc7 for more information.

    Userland exception thrown in a constructor will not be automatically caught by the mapper anymore. This prevents messages with sensible information from reaching the final user — for instance an SQL exception showing a part of a query.

    To allow exceptions to be considered as safe, the new method MapperBuilder::filterExceptions() must be used, with caution.

    final 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\ThrowableMessage::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->node()->messages()[0];
    }
    

    Tree node API rework

    The class \CuyZ\Valinor\Mapper\Tree\Node has been refactored to remove access to unwanted methods that were not supposed to be part of the public API. Below are a list of all changes:

    • New methods $node->sourceFilled() and $node->sourceValue() allow accessing the source value.

    • The method $node->value() has been renamed to $node->mappedValue() and will throw an exception if the node is not valid.

    • The method $node->type() now returns a string.

    • The methods $message->name(), $message->path(), $message->type() and $message->value() have been deprecated in favor of the new method $message->node().

    • The message parameter {original_value} has been deprecated in favor of {source_value}.

    Access removal of several parts of the library public API

    The access to class/function definition, types and exceptions did not add value to the actual goal of the library. Keeping these features under the public API flag causes more maintenance burden whereas revoking their access allows more flexibility with the overall development of the library.

    Full list of changes

    ⚠ BREAKING CHANGES

    • Filter userland exceptions to hide potential sensible data (6ce1a4)
    • Refactor tree node API (d3b1dc)
    • Remove API access from several parts of library (316d91)
    • Remove node visitor feature (63c87a)

    Bug Fixes

    • Handle inferring methods with same names properly (dc45dd)
    • Process invalid type default value as unresolvable type (7c9ac1)
    • Properly display unresolvable type (3020db)

    Other

    • Ignore .idea folder (84ead0)
    Source code(tar.gz)
    Source code(zip)
  • 0.11.0(Jun 23, 2022)

    Notable changes

    Strict mode

    The mapper is now more type-sensitive and will fail in the following situations:

    • When a value does not match exactly the awaited scalar type, for instance a string "42" given to a node that awaits an integer.

    • When unnecessary array keys are present, for instance mapping an array ['foo' => …, 'bar' => …, 'baz' => …] to an object that needs only foo and bar.

    • When permissive types like mixed or object are encountered.

    These limitations can be bypassed by enabling the flexible mode:

    (new \CuyZ\Valinor\MapperBuilder())
        ->flexible()
        ->mapper();
        ->map('array{foo: int, bar: bool}', [
            'foo' => '42', // Will be cast from `string` to `int`
            'bar' => 'true', // Will be cast from `string` to `bool`
            'baz' => '…', // Will be ignored
        ]);
    

    When using this library for a provider application — for instance an API endpoint that can be called with a JSON payload — it is recommended to use the strict mode. This ensures that the consumers of the API provide the exact awaited data structure, and prevents unknown values to be passed.

    When using this library as a consumer of an external source, it can make sense to enable the flexible mode. This allows for instance to convert string numeric values to integers or to ignore data that is present in the source but not needed in the application.

    Interface inferring

    It is now mandatory to list all possible class-types that can be inferred by the mapper. This change is a step towards the library being able to deliver powerful new features such as compiling a mapper for better performance.

    The existing calls to MapperBuilder::infer that could return several class-names must now add a signature to the callback. The callbacks that require no parameter and always return the same class-name can remain unchanged.

    For instance:

    $builder = (new \CuyZ\Valinor\MapperBuilder())
        // Can remain unchanged
        ->infer(SomeInterface::class, fn () => SomeImplementation::class);
    
    $builder = (new \CuyZ\Valinor\MapperBuilder())
        ->infer(
            SomeInterface::class,
            fn (string $type) => match($type) {
                'first' => ImplementationA::class,
                'second' => ImplementationB::class,
                default => throw new DomainException("Unhandled `$type`.")
            }
        )
        // …should be modified with:
        ->infer(
            SomeInterface::class,
            /** @return class-string<ImplementationA|ImplementationB> */
            fn (string $type) => match($type) {
                'first' => ImplementationA::class,
                'second' => ImplementationB::class,
                default => throw new DomainException("Unhandled `$type`.")
            }
        );
    

    Object constructors collision

    All these changes led to a new check that runs on all registered object constructors. If a collision is found between several constructors that have the same signature (the same parameter names), an exception will be thrown.

    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 […]
    

    Full list of changes

    ⚠ BREAKING CHANGES

    • Handle exhaustive list of interface inferring (1b0ff3)
    • Make mapper more strict and allow flexible mode (90dc58)

    Features

    • Improve cache warmup (44c5f1)
    Source code(tar.gz)
    Source code(zip)
  • 0.10.0(Jun 10, 2022)

  • 0.9.0(May 23, 2022)

    Notable changes

    Cache injection and warmup

    The cache feature has been revisited, to give more control to the user on how and when to use it.

    The method MapperBuilder::withCacheDir() has been deprecated in favor of a new method MapperBuilder::withCache() which accepts any PSR-16 compliant implementation.

    Warning

    These changes lead up to the default cache not being automatically registered anymore. If you still want to enable the cache (which you should), you will have to explicitly inject it (see below).

    A default implementation is provided out of the box, which saves cache entries into the file system.

    When the application runs in a development environment, the cache implementation should be decorated with FileWatchingCache, which will watch the files of the application and invalidate cache entries when a PHP file is modified by a developer — preventing the library not behaving as expected when the signature of a property or a method changes.

    The cache can be warmed up, for instance in a pipeline during the build and deployment of the application — kudos to @boesing for the feature!

    Note The cache has to be registered first, otherwise the warmup will end up being useless.

    $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-directory');
    
    if ($isApplicationInDevelopmentEnvironment) {
        $cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache);
    }
    
    $mapperBuilder = (new \CuyZ\Valinor\MapperBuilder())->withCache($cache);
    
    // During the build:
    $mapperBuilder->warmup(SomeClass::class, SomeOtherClass::class);
    
    // In the application:
    $mapperBuilder->mapper()->map(SomeClass::class, [/* … */]);
    

    Message formatting & translation

    Major changes have been made to the messages being returned in case of a mapping error: the actual texts are now more accurate and show better information.

    Warning

    The method NodeMessage::format() has been removed, message formatters should be used instead. If needed, the old behaviour can be retrieved with the formatter PlaceHolderMessageFormatter, although it is strongly advised to use the new placeholders feature (see below).

    The signature of the method MessageFormatter::format() has changed as well.

    It is now also easier to format the messages, for instance when they need to be translated. Placeholders can now be used in a message body, and will be replaced with useful information.

    | Placeholder | Description | |----------------------|:-----------------------------------------------------| | {message_code} | the code of the message | | {node_name} | name of the node to which the message is bound | | {node_path} | path of the node to which the message is bound | | {node_type} | type of the node to which the message is bound | | {original_value} | the source value that was given to the node | | {original_message} | the original message before being customized |

    try {
        (new \CuyZ\Valinor\MapperBuilder())
            ->mapper()
            ->map(SomeClass::class, [/* … */]);
    } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
        $node = $error->node();
        $messages = new \CuyZ\Valinor\Mapper\Tree\Message\MessagesFlattener($node);
    
        foreach ($messages as $message) {
            if ($message->code() === 'some_code') {
                $message = $message->withBody('new message / {original_message}');
            }
    
            echo $message;
        }
    }
    

    The messages are formatted using the ICU library, enabling the placeholders to use advanced syntax to perform proper translations, for instance currency support.

    try {
        (new \CuyZ\Valinor\MapperBuilder())->mapper()->map('int<0, 100>', 1337);
    } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
        $message = $error->node()->messages()[0];
    
        if (is_numeric($message->value())) {
            $message = $message->withBody(
                'Invalid amount {original_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');
    }
    

    See ICU documentation for more information on available syntax.

    Warning If the intl extension is not installed, a shim will be available to replace the placeholders, but it won't handle advanced syntax as described above.

    The formatter TranslationMessageFormatter can be used to translate the content of messages.

    The library provides a list of all messages that can be returned; this list can be filled or modified with custom translations.

    \CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter::default()
        // Create/override a single entry…
        ->withTranslation('fr', 'some custom message', 'un message personnalisé')
        // …or several entries.
        ->withTranslations([
            'some custom message' => [
                'en' => 'Some custom message',
                'fr' => 'Un message personnalisé',
                'es' => 'Un mensaje personalizado',
            ], 
            'some other message' => [
                // …
            ], 
        ])
        ->format($message);
    

    It is possible to join several formatters into one formatter by using the AggregateMessageFormatter. This instance can then easily be injected in a service that will handle messages.

    The formatters will be called in the same order they are given to the aggregate.

    (new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\AggregateMessageFormatter(
        new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\LocaleMessageFormatter('fr'),
        new \CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter([
            // …
        ],
        \CuyZ\Valinor\Mapper\Tree\Message\Formatter\TranslationMessageFormatter::default(),
    ))->format($message)
    

    Full list of changes

    ⚠ BREAKING CHANGES

    • Improve message customization with formatters (60a665)
    • Revoke ObjectBuilder API access (11e126)

    Features

    • Allow injecting a cache implementation that is used by the mapper (69ad3f)
    • Extract file watching feature in own cache implementation (2d70ef)
    • Improve mapping error messages (05cf4a)
    • Introduce method to warm the cache up (ccf09f)

    Bug Fixes

    • Make interface type match undefined object type (105eef)

    Other

    • Change InvalidParameterIndex exception inheritance type (b75adb)
    • Introduce layer for object builder arguments (48f936)
    Source code(tar.gz)
    Source code(zip)
  • 0.8.0(May 9, 2022)

    Notable changes

    Float values handling

    Allows the usage of float values, as follows:

    class Foo
    {
        /** @var 404.42|1337.42 */
        public readonly float $value;
    }
    

    Literal boolean true / false values handling

    Thanks @danog for this feature!

    Allows the usage of boolean values, as follows:

    class Foo
    {
        /** @var int|false */
        public readonly int|bool $value;
    }
    

    Class string of union of object handling

    Allows to declare several class names in a class-string:

    class Foo
    {
        /** @var class-string<SomeClass|SomeOtherClass> */
        public readonly string $className;
    }
    

    Allow psalm and phpstan prefix in docblocks

    Thanks @boesing for this feature!

    The following annotations are now properly handled: @psalm-param, @phpstan-param, @psalm-return and @phpstan-return.

    If one of those is found along with a basic @param or @return annotation, it will take precedence over the basic value.

    Full list of changes

    Features

    • Allow psalm and phpstan prefix in docblocks (64e0a2)
    • Handle class string of union of object (b7923b)
    • Handle filename in function definition (0b042b)
    • Handle float value type (790df8)
    • Handle literal boolean true / false types (afcedf)
    • Introduce composite types (892f38)

    Bug Fixes

    • Call value altering function only if value is accepted (2f08e1)
    • Handle function definition cache invalidation when file is modified (511a0d)

    Other

    • Add configuration for Composer allowed plugins (2f310c)
    • Add Psalm configuration file to .gitattributes (979272)
    • Bump dev-dependencies (844384)
    • Declare code type in docblocks (03c84a)
    • Ignore Polyfill coverage (c08fe5)
    • Remove symfony/polyfill-php80 dependency (368737)
    Source code(tar.gz)
    Source code(zip)
  • 0.7.0(Mar 24, 2022)

    Notable changes

    Warning This release introduces a major breaking change that must be considered before updating

    Constructor registration

    The automatic named constructor discovery has been disabled. It is now mandatory to explicitly register custom constructors that can be used by the mapper.

    This decision was made because of a security issue reported by @Ocramius and described in advisory GHSA-xhr8-mpwq-2rr2.

    As a result, existing code must list all named constructors that were previously automatically used by the mapper, and registerer them using the method MapperBuilder::registerConstructor().

    The method MapperBuilder::bind() has been deprecated in favor of the method above that should be used instead.

    final class SomeClass
    {
        public static function namedConstructor(string $foo): self
        {
            // …
        }
    }
    
    (new \CuyZ\Valinor\MapperBuilder())
        ->registerConstructor(
            SomeClass::namedConstructor(...),
            // …or for PHP < 8.1:
            [SomeClass::class, 'namedConstructor'],
        )
        ->mapper()
        ->map(SomeClass::class, [
            // …
        ]);
    

    See documentation for more information.


    Source builder

    The Source class is a new entry point for sources that are not plain array or iterable. It allows accessing other features like camel-case keys or custom paths mapping in a convenient way.

    It should be used as follows:

    $source = \CuyZ\Valinor\Mapper\Source\Source::json($jsonString)
        ->camelCaseKeys()
        ->map([
            'towns' => 'cities',
            'towns.*.label' => 'name',
        ]);
    
    $result = (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(SomeClass::class, $source);
    

    See documentation for more details about its usage.

    Full list of changes

    ⚠ BREAKING CHANGES

    • Change Attributes::ofType return type to array (1a599b)
    • Introduce method to register constructors used during mapping (ecafba)

    Features

    • Introduce a path-mapping source modifier (b7a7d2)
    • Introduce a source builder (ad5103)

    Bug Fixes

    • Handle numeric key with camel case source key modifier (b8a18f)
    • Handle parameter default object value compilation (fdef93)
    • Handle variadic arguments in callable constructors (b646cc)
    • Properly handle alias types for function reflection (e5b515)

    Other

    • Add Striker HTML report when running infection (79c7a4)
    • Handle class name in function definition (e2451d)
    • Introduce functions container to wrap definition handling (fd1117)
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Feb 24, 2022)

    ⚠ BREAKING CHANGES

    • Improve interface inferring API (1eb6e6)
    • Improve object binding API (6d4270)

    Features

    • Handle variadic parameters in constructors (b6b329)
    • Improve value altering API (422e6a)
    • Introduce a camel case source key modifier (d94652)
    • Introduce function definition repository (b49ebf)
    • Introduce method to get parameter by index (380961)

    Bug Fixes

    • Change license in composer.json (6fdd62)
    • Ensure native mixed types remain valid (18ccbe)
    • Remove string keys when unpacking variadic parameter values (cbf4e1)
    • Transform exception thrown during object binding into a message (359e32)
    • Write temporary cache file inside cache subdirectory (1b80a1)

    Other

    • Check value acceptance in separate node builder (30d447)
    • Narrow union types during node build (06e9de)
    Source code(tar.gz)
    Source code(zip)
  • 0.5.0(Jan 26, 2022)

    Features

    • Introduce automatic named constructor resolution (718d3c)
    • Set up dependabot for automated weekly dependency upgrades (23b611)
    • Simplify type signature of TreeMapper#map() (e28003)

    Bug Fixes

    • Correct regex that detects @internal or @api annotations (39f0b7)
    • Improve type definitions to allow Psalm automatic inferring (f9b04c)
    • Return indexed list of attributes when filtering on type (66aa4d)
    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Jan 7, 2022)

    Notable changes

    Allow mapping to any type

    Previously, the method TreeMapper::map would allow mapping only to an object. It is now possible to map to any type handled by the library.

    It is for instance possible to map to an array of objects:

    $objects = (new MapperBuilder())->mapper()->map(
        'array<' . SomeClass::class . '>',
        [/* … */]
    );
    

    For simple use-cases, an array shape can be used:

    $array = (new MapperBuilder())->mapper()->map(
        'array{foo: string, bar: int}',
        [/* … */]
    );
    
    echo $array['foo'];
    echo $array['bar'] * 2;
    

    This new feature changes the possible behaviour of the mapper, meaning static analysis tools need help to understand the types correctly. An extension for PHPStan and a plugin for Psalm are now provided and can be included in a project to automatically increase the type coverage.


    Better handling of messages

    When working with messages, it can sometimes be useful to customize the content of a message — for instance to translate it.

    The helper class \CuyZ\Valinor\Mapper\Tree\Message\Formatter\MessageMapFormatter can be used to provide a list of new formats. It can be instantiated with an array where each key represents either:

    • The code of the message to be replaced
    • The content of the message to be replaced
    • The class name of the message to be replaced

    If none of those is found, the content of the message will stay unchanged unless a default one is given to the class.

    If one of these keys is found, the array entry will be used to replace the content of the message. This entry can be either a plain text or a callable that takes the message as a parameter and returns a string; it is for instance advised to use a callable in cases where a translation service is used — to avoid useless greedy operations.

    In any case, the content can contain placeholders that will automatically be replaced by, in order:

    1. The original code of the message
    2. The original content of the message
    3. A string representation of the node type
    4. The name of the node
    5. The path of the node
    try {
        (new \CuyZ\Valinor\MapperBuilder())
            ->mapper()
            ->map(SomeClass::class, [/* … */]);
    } catch (\CuyZ\Valinor\Mapper\MappingError $error) {
        $node = $error->node();
        $messages = new \CuyZ\Valinor\Mapper\Tree\Message\MessagesFlattener($node);
    
        $formatter = (new MessageMapFormatter([
            // Will match if the given message has this exact code
            'some_code' => 'new content / previous code was: %1$s',
        
            // Will match if the given message has this exact content
            'Some message content' => 'new content / previous message: %2$s',
        
            // Will match if the given message is an instance of `SomeError`
            SomeError::class => '
                - Original code of the message: %1$s
                - Original content of the message: %2$s
                - Node type: %3$s
                - Node name: %4$s
                - Node path: %5$s
            ',
        
            // A callback can be used to get access to the message instance
            OtherError::class => function (NodeMessage $message): string {
                if ((string)$message->type() === 'string|int') {
                    // …
                }
        
                return 'Some message content';
            },
        
            // For greedy operation, it is advised to use a lazy-callback
            'bar' => fn () => $this->translator->translate('foo.bar'),
        ]))
            ->defaultsTo('some default message')
            // …or…
            ->defaultsTo(fn () => $this->translator->translate('default_message'));
    
        foreach ($messages as $message) {
            echo $formatter->format($message);    
        }
    }
    

    Automatic union of objects inferring during mapping

    When the mapper needs to map a source to a union of objects, it will try to guess which object it will map to, based on the needed arguments of the objects, and the values contained in the source.

    final class UnionOfObjects
    {
        public readonly SomeFooObject|SomeBarObject $object;
    }
    
    final class SomeFooObject
    {
        public readonly string $foo;
    }
    
    final class SomeBarObject
    {
        public readonly string $bar;
    }
    
    // Will map to an instance of `SomeFooObject`
    (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(UnionOfObjects::class, ['foo' => 'foo']);
    
    // Will map to an instance of `SomeBarObject`
    (new \CuyZ\Valinor\MapperBuilder())
        ->mapper()
        ->map(UnionOfObjects::class, ['bar' => 'bar']);
    

    Full list of changes

    ⚠ BREAKING CHANGES

    • Add access to root node when error occurs during mapping (54f608)
    • Allow mapping to any type (b2e810)
    • Allow object builder to yield arguments without source (8a7414)
    • Wrap node messages in proper class (a805ba)

    Features

    • Introduce automatic union of objects inferring during mapping (79d7c2)
    • Introduce helper class MessageMapFormatter (ddf69e)
    • Introduce helper class MessagesFlattener (a97b40)
    • Introduce helper NodeTraverser for recursive operations on nodes (cc1bc6)

    Bug Fixes

    • Handle nested attributes compilation (d2795b)
    • Treat forbidden mixed type as invalid type (36bd36)
    • Treat union type resolving error as message (e834cd)
    • Use locked package versions for quality assurance workflow (626f13)

    Other

    • Ignore changelog configuration file in git export (85a6a4)
    • Raise PHPStan version (0144bf)
    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Dec 18, 2021)

  • 0.2.0(Dec 7, 2021)

    Features

    • Handle integer range type (9f99a2)
    • Handle local type aliasing in class definition (56142d)
    • Handle type alias import in class definition (fa3ce5)

    Bug Fixes

    • Do not accept shaped array with excessive key(s) (5a578e)
    • Handle integer value match properly (9ee2cc)

    Other

    • Delete commented code (4f5612)
    • Move exceptions to more specific folder (185edf)
    • Rename GenericAssignerLexer to TypeAliasLexer (680941)
    • Use marcocesarato/php-conventional-changelog for changelog (178aa9)
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Dec 1, 2021)

    ⚠ BREAKING CHANGES

    • Change license from GPL 3 to MIT (a77b28)

    Features

    • Handle multiline type declaration (d99c59)

    Bug Fixes

    • Filter type symbols with strict string comparison (6cdea3)
    • Handle correctly iterable source during mapping (dd4624)
    • Handle shaped array integer key (5561d0)
    • Resolve single/double quotes when parsing doc-block type (1c628b)

    Other

    • Change PHPStan stub file extension (8fc6af)
    • Delete unwanted code (e3e169)
    • Syntax highlight stub files (#9) (9ea95f)
    • Use composer runtime API (1f754a)
    Source code(tar.gz)
    Source code(zip)
  • 0.1.0(Nov 28, 2021)

Owner
Team CuyZ
Team CuyZ
Allows generate class files parse from json and map json to php object, including multi-level and complex objects;

nixihz/php-object Allows generate class files parse from json and map json to php object, including multi-level and complex objects; Installation You

zhixin 2 Sep 9, 2022
An article about alternative solution for convert object into a JSON Object for your api.

Do we really need a serializer for our JSON API? The last years I did build a lot of JSON APIs but personally was never happy about the magic of using

Alexander Schranz 1 Feb 1, 2022
Yet another Value Object Library (YAVOL)

Yet Another DDD Library Value object This library is a foundation in order to implement the Value Object pattern. It helps you to introduce some DDD s

YADDDL 3 Nov 17, 2022
The VarExporter component allows exporting any serializable PHP data structure to plain PHP code.

The VarExporter component allows exporting any serializable PHP data structure to plain PHP code. While doing so, it preserves all the semantics associated with the serialization mechanism of PHP (__wakeup, __sleep, Serializable).

Symfony 1.8k Jan 1, 2023
Immutable value object for IPv4 and IPv6 addresses, including helper methods and Doctrine support.

IP is an immutable value object for (both version 4 and 6) IP addresses. Several helper methods are provided for ranges, broadcast and network address

Darsyn 224 Dec 28, 2022
Decimal handling as value object instead of plain strings.

Decimal Object Decimal value object for PHP. Background When working with monetary values, normal data types like int or float are not suitable for ex

Spryker 16 Oct 24, 2022
Provides JSON pointer as a value object

json-pointer Provides JSON pointer as a value object. Installation Run composer require ergebnis/json-pointer Usage ReferenceToken You can create a Re

null 3 Dec 15, 2022
Zilliqa PHP is a typed PHP-7.1+ interface to Zilliqa JSON-RPC API.

Zilliqa PHP is a typed PHP-7.1+ interface to Zilliqa JSON-RPC API. Check out the latest API documentation. Add library in a composer.json file.

Patrick Falize 6 Oct 7, 2021
Enable method chaining or fluent expressions for any value and method.

PHP Pipe Operator A (hopefully) temporary solution to implement the pipe operator in PHP. Table of contents Requirements How to install How to use The

Sebastiaan Luca 268 Dec 26, 2022
Parse DSN strings into value objects to make them easier to use, pass around and manipulate

DSN parser Parse DSN strings into value objects to make them easier to use, pass around and manipulate. Install Via Composer composer require nyholm/d

Tobias Nyholm 77 Dec 13, 2022
Your alter ego object. Takes the best of object and array worlds.

Supporting Opensource formapro\values is an MIT-licensed open source project with its ongoing development made possible entirely by the support of com

FormaPro 31 Jun 25, 2021
A PHP 7 value objects helper library.

valueobjects Requirements Requires PHP >= 7.1 Installation Through Composer, obviously: composer require funeralzone/valueobjects Extensions This lib

Funeral Zone 56 Dec 16, 2022
Pug Renderer - a (heavily based on the PhpRenderer) renderer for rendering Pug view scripts into a PSR-7 Response object

Pug Renderer This is a (heavily based on the PhpRenderer) renderer for rendering Pug view scripts into a PSR-7 Response object. It works well with Sli

Marcello Duarte 6 Oct 27, 2020
Maps Railroad Online Savegames to a SVG map

RailroadOnlineMapper @ian76g#6577 Environments DEV - https://zwackelmann.online/ QA - https://ohnezahn.online/ PROD - https://minizwerg.online/ Gettin

Sebastian Köhler 5 Jan 3, 2023
A plugin to test Server->getPlayerExact() vs Map performance

PeformanceTests A plugin to test Server->getPlayerExact() vs Map<username, Player> performance Timings with Server->getPlayerExact() https://timings.p

KingOfTurkey38 1 Feb 25, 2022
Google map with autocomplete

Nova Google Map With Autocomplete Field Package This field allows you to work with Google Places API to autocomplete and Google Map on user input and

TrinityRank DevTeam 2 Jul 29, 2022
Generate static map url for maptiler.com

PHP static maps url generator The package is used for static maps (images) URL generate. For now, it works only with maptiler.com. I welcome your comm

Andrey Surzhikov 2 Sep 8, 2022
PHP functions that help you validate structure of complex nested PHP arrays.

PHP functions that help you validate structure of complex nested PHP arrays.

cd rubin 7 May 22, 2022
YL MVC Structure (PHP MVC) is a pattern made in PHP used to implement user interfaces, data, and controlling logic.

YL MVC Structure (PHP MVC) is a pattern made in PHP used to implement user interfaces, data, and controlling logic. It is built based on the combination of ideas from the Yii framework and Laravel framework (yl).

Tan Nguyen 3 Jan 3, 2023