Library / SDK
CuyZ/Valinor avatar
CuyZ/Valinor

CuyZ/Valinor for Typed PHP Input Mapping

Dependency free PHP library that helps to map any input into a strongly-typed structure.

1,534 stars95 forksPHPMIT

At a glance

What is it?
CuyZ/Valinor is a dependency-free PHP library for mapping JSON, arrays, and other raw input into strongly typed objects with validation. It suits applications that want constructor-based integrity, precise mapping errors, and support for PHPStan or Psalm types.
Who is it for?
CuyZ/Valinor fits PHP applications that need raw input converted into validated objects, especially when constructors, readonly properties, PHPStan, or Psalm annotations define the domain model. It is less suitable for projects that need an undocumented runtime schema language or a separately managed validation service.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 32 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why CuyZ/Valinor Exists for PHP Domain Models

CuyZ/Valinor addresses a recurring PHP boundary problem: raw data enters an application as JSON, a plain array, or another untyped input, but application code expects objects with known properties and valid values. The README describes the library as a dependency-free PHP library that maps input into a strongly typed structure. Its intended users are PHP developers who want object state established during construction rather than filled through scattered assignments.

The library is especially relevant when domain classes already describe their accepted values through constructor parameters, PHPDoc types, or related annotations. In the documented Country and City example, a Country contains a list of City objects, each city has a non-empty name, and each city contains a DateTimeZone. The mapper therefore has a concrete model to work with. It is not presented as a general-purpose application framework or a replacement for a database layer.

The central design goal is integrity during construction. According to the README, Valinor constructs and validates raw inputs so the resulting objects can be used without requiring developers to worry about their integrity throughout the application lifecycle. That is a narrower claim than guaranteeing every application invariant. The library can validate what the supplied type model describes, but the repository documentation does not claim that it can infer undocumented business rules.

Mapping JSON Through MapperBuilder and Typed Constructors

The documented data flow begins with a mapper created from MapperBuilder. The application supplies a class name and a source containing the raw input. Valinor then constructs the target object, resolves its constructor parameters, and reports invalid input through MappingError. The README gives the following shape for JSON input, which is wrapped with Source::json($json).

The example uses both native PHP types and PHPDoc-style advanced types. Country::$name is a string annotated as non-empty-string, while Country::$cities is an array annotated as list<City>. City::$timeZone is a DateTimeZone instance. This shows that mapping is not limited to converting scalar JSON properties. It can build nested objects and validate structural information such as list shape and non-empty strings.

The source object is part of the mapper API rather than a separate parsing convention shown in the repository. The README does not document how CSV input is passed to the mapper, even though the library description also mentions normalization to JSON or CSV. That distinction matters: the repository confirms JSON and plain arrays as raw input examples, while the complete normalization API is left to the online documentation.

If mapping succeeds, the example reads $country->name and $country->cities[0]->name directly. If it fails, the application catches MappingError and can handle the precise, human-readable errors described by the project. The documentation does not show a custom error renderer or a retry flow, so those behaviors should be checked in the full documentation before designing an error interface.

Composer Installation and First Country Mapping

The README gives Composer as the installation route. After installation, the first practical use is to define typed value objects, wrap the input in a source, and call the mapper. The command below installs the package through Composer.

bash
composer require cuyz/valinor

The next example is the core mapping operation from the README. It expects a JSON string containing France, Paris, and Lyon, then returns a Country object or throws MappingError.

php
$country = (new \CuyZ\Valinor\MapperBuilder())
    ->mapper()
    ->map(Country::class, \CuyZ\Valinor\Mapper\Source\Source::json($json));

The Country class supplies the structure that the mapper expects. Its constructor annotations are significant because they tell Valinor what the nested cities list contains and what constraints apply to names.

php
final class Country
{
    public function __construct(
        /** @var non-empty-string */
        public readonly string $name,
        /** @var list<City> */
        public readonly array $cities,
    ) {}
}

A useful first check is to change one input value to an invalid type or an empty name and confirm that the application reaches the MappingError branch. The repository does not provide an exact sample error string, so the expected message should come from the library documentation for the chosen version rather than being inferred.

Advanced Types and the Limits of Documented Coverage

Valinor's value is not limited to mapping a flat JSON object into scalar properties. The README states that the mapper supports native PHP types and advanced types supported by PHPStan and Psalm, including shaped arrays, generics, integer ranges, and more. The Country and City example uses a non-empty-string annotation and a list<City>, so the project's basic documentation already demonstrates type information beyond the PHP type declaration alone.

This approach gives PHP developers a familiar place to describe the boundary between external data and domain objects. It also avoids requiring a second schema definition in the examples shown here. However, the repository does not claim that annotations from PHPStan or Psalm are all interchangeable in every context. Their exact behavior should be verified against the online documentation and the installed version.

The same narrowness is visible in the constructor example. Valinor can construct the objects represented by the supplied type information, but a class-level business rule such as requiring a country to contain a minimum number of cities would still need a mechanism that the README does not describe. The library's validation strength is therefore tied to the type annotations and constructor model that the application provides.

The normalization feature is broader than the basic mapping example. The README says Valinor can transform input into formats such as JSON or CSV while preserving the original structure. Since the repository excerpt does not show the configuration or method signatures for that mechanism, it is not possible to give a verified command or code example for it.

Manual Hydration as the Alternative Approach

The practical alternative is manual hydration: read keys from an array or decoded JSON document, instantiate each domain class, and validate values separately. This approach differs from Valinor in where the construction logic and validation rules live. Manual code gives the application direct control over every branch and transformation, but it also requires the developer to keep that logic synchronized with every class and nested object.

Valinor instead uses the target class and its type information as the construction specification. In the README example, mapping a Country produces nested City objects, and annotations such as list<City> and non-empty-string participate in the result. The application then receives typed properties instead of writing its own recursive assembly code.

Neither approach is automatically correct for every project. Manual hydration may be preferable when the input format is highly irregular, mapping must preserve special legacy transformations, or the project has existing validation infrastructure. The repository does not document interoperability with Symfony serializers, Laravel validation, or another mapper, so this review does not claim that Valinor replaces any of those systems.

The main adoption question is therefore not whether typed objects are useful. PHP applications already need them in many cases. The question is whether the team wants constructor-based mapping to become the boundary for its input data, and whether its type annotations and runtime requirements match the project.

Maintenance, License, and Upgrade Considerations

The repository is not archived, and its last push was on 2026-09-21. The release history includes 2.6.0 on 2026-08-11, 2.5.1 on 2026-07-28, and 2.5.0 on 2026-06-28. Those dates indicate that the project has recent release activity, but they do not establish compatibility for every Valinor upgrade.

The package is licensed under MIT. That is the license identifier stated in the repository information and README context, but license interpretation for a particular distribution should be handled according to the project's LICENSE.md and the user's own compliance requirements. The README does not describe a separate commercial support plan or a long-term support policy.

The dependency-free claim reduces the need to account for a network of runtime dependencies, although the application still needs a compatible PHP runtime and Composer-managed package version. The repository includes composer.json and composer.lock, plus PHPStan, PHPUnit, Rector, and PHPBench configuration files, which shows a development and quality-tooling setup. The excerpt does not specify the minimum PHP version or the cost of breaking changes between releases. Check composer.json and the changelog before pinning a version.

Upgrade work is likely to involve reviewing type annotations, constructor changes, and mapping exceptions in application code. That is a project-specific concern rather than a generic maintenance claim: Valinor sits directly at the point where external data becomes application objects.

Editorial conclusion

CuyZ/Valinor fits PHP applications that need raw input converted into validated objects, especially when constructors, readonly properties, PHPStan, or Psalm annotations define the domain model. It is less suitable for projects that need an undocumented runtime schema language or a separately managed validation service. Before adoption, verify the exact PHP version requirement and the support for the project-specific types you need.

Frequently asked questions

How do I install CuyZ/Valinor?

The README gives the installation command `composer require cuyz/valinor`. It then directs users to the online documentation for more information.

How does CuyZ/Valinor map JSON into PHP objects?

Create a mapper with `MapperBuilder`, wrap the JSON with `Source::json($json)`, and pass the target class to `map()`. If the input does not satisfy the target type, the example catches `MappingError`.

What types can CuyZ/Valinor validate?

The README says Valinor handles native PHP types and advanced types supported by PHPStan and Psalm, including shaped arrays, generics, and integer ranges. Its example also uses `non-empty-string` and `list<City>` annotations.

Official sources

  1. CuyZ/Valinor on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cuyz-valinor.svg)](https://hysenlabs.com/projects/cuyz-valinor)

Was this useful?