Skip to content

Commit

Permalink
feat: add generic type to normalizer and use external formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
romm committed Dec 22, 2023
1 parent 3f90c98 commit abba5eb
Show file tree
Hide file tree
Showing 17 changed files with 275 additions and 33 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ providing precise and human-readable error messages.
The mapper can handle native PHP types as well as other advanced types supported
by [PHPStan] and [Psalm] like shaped arrays, generics, integer ranges and more.

The library also provides a normalization mechanism that can help transform any
input into a data format (JSON, CSV, …), while preserving the original
structure.

## Installation

```bash
Expand Down
20 changes: 16 additions & 4 deletions src/Library/Container.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@
use CuyZ\Valinor\Mapper\TreeMapper;
use CuyZ\Valinor\Mapper\TypeArgumentsMapper;
use CuyZ\Valinor\Mapper\TypeTreeMapper;
use CuyZ\Valinor\Normalizer\FormatNormalizer;
use CuyZ\Valinor\Normalizer\Formatter\Formatter;
use CuyZ\Valinor\Normalizer\Formatter\FormatterFactory;
use CuyZ\Valinor\Normalizer\Normalizer;
use CuyZ\Valinor\Normalizer\RecursiveNormalizer;
use CuyZ\Valinor\Normalizer\Transformer\KeyTransformersHandler;
Expand Down Expand Up @@ -181,7 +184,7 @@ public function __construct(Settings $settings)
return new CacheObjectBuilderFactory($factory, $cache);
},

Normalizer::class => fn () => new RecursiveNormalizer(
RecursiveNormalizer::class => fn () => new RecursiveNormalizer(
$this->get(ClassDefinitionRepository::class),
new ValueTransformersHandler(
$this->get(FunctionDefinitionRepository::class),
Expand All @@ -198,7 +201,7 @@ public function __construct(Settings $settings)
$this->get(TypeParserFactory::class),
$this->get(AttributesRepository::class),
),
$this->get(CacheInterface::class)
$this->get(CacheInterface::class),
),

FunctionDefinitionRepository::class => fn () => new CacheFunctionDefinitionRepository(
Expand Down Expand Up @@ -245,9 +248,18 @@ public function argumentsMapper(): ArgumentsMapper
return $this->get(ArgumentsMapper::class);
}

public function normalizer(): Normalizer
/**
* @template T
*
* @param FormatterFactory<Formatter<T>> $formatterFactory
* @return Normalizer<T>
*/
public function normalizer(FormatterFactory $formatterFactory): Normalizer
{
return $this->get(Normalizer::class);
return new FormatNormalizer(
$formatterFactory,
$this->get(RecursiveNormalizer::class),
);
}

public function cacheWarmupService(): RecursiveCacheWarmupService
Expand Down
16 changes: 13 additions & 3 deletions src/MapperBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
use CuyZ\Valinor\Mapper\ArgumentsMapper;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Mapper\TreeMapper;
use CuyZ\Valinor\Normalizer\Format;
use CuyZ\Valinor\Normalizer\Formatter\Formatter;
use CuyZ\Valinor\Normalizer\Formatter\FormatterFactory;
use CuyZ\Valinor\Normalizer\Normalizer;
use Psr\SimpleCache\CacheInterface;
use Throwable;
Expand Down Expand Up @@ -550,9 +551,18 @@ public function argumentsMapper(): ArgumentsMapper
return $this->container()->argumentsMapper();
}

public function normalizer(Format $format): Normalizer
/**
* Returns a normalizer instance. To see list of available formats, check
* out: @see \CuyZ\Valinor\Normalizer\Format
*
* @template T
*
* @param FormatterFactory<Formatter<T>> $formatterFactory
* @return Normalizer<T>
*/
public function normalizer(FormatterFactory $formatterFactory): Normalizer
{
return $this->container()->normalizer();
return $this->container()->normalizer($formatterFactory);
}

public function __clone()
Expand Down
40 changes: 38 additions & 2 deletions src/Normalizer/Format.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,47 @@

namespace CuyZ\Valinor\Normalizer;

use CuyZ\Valinor\Normalizer\Formatter\ArrayFormatterFactory;

/** @api */
final class Format
{
public static function array(): self
/**
* Allows a normalizer to format an input to a PHP array, containing only
* scalar values.
*
* ```php
* namespace My\App;
*
* $normalizer = (new \CuyZ\Valinor\MapperBuilder())
* ->normalizer(\CuyZ\Valinor\Normalizer\Format::array());
*
* $userAsArray = $normalizer->normalize(
* new \My\App\User(
* name: 'John Doe',
* age: 42,
* country: new Country(
* name: 'France',
* countryCode: 'FR',
* ),
* )
* );
*
* // `$userAsArray` is now an array and can be manipulated much more easily, for
* // instance to be serialized to the wanted data format.
* //
* // [
* // 'name' => 'John Doe',
* // 'age' => 42,
* // 'country' => [
* // 'name' => 'France',
* // 'countryCode' => 'FR',
* // ],
* // ];
* ```
*/
public static function array(): ArrayFormatterFactory
{
return new self();
return new ArrayFormatterFactory();
}
}
30 changes: 30 additions & 0 deletions src/Normalizer/FormatNormalizer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer;

use CuyZ\Valinor\Normalizer\Formatter\Formatter;
use CuyZ\Valinor\Normalizer\Formatter\FormatterFactory;

/**
* @internal
*
* @template T
* @implements Normalizer<T>
*/
final class FormatNormalizer implements Normalizer
{
/**
* @param FormatterFactory<Formatter<T>> $formatterFactory
*/
public function __construct(
private FormatterFactory $formatterFactory,
private RecursiveNormalizer $recursiveNormalizer,
) {}

public function normalize(mixed $value): mixed
{
return $this->recursiveNormalizer->normalize($value, $this->formatterFactory->new());
}
}
34 changes: 34 additions & 0 deletions src/Normalizer/Formatter/ArrayFormatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer\Formatter;

use function is_array;
use function is_iterable;
use function iterator_to_array;

/**
* @internal
*
* @implements Formatter<array<mixed>|scalar|null>
*/
final class ArrayFormatter implements Formatter
{
/** @var iterable<mixed>|scalar|null */
private mixed $value;

public function push(mixed $value): void
{
$this->value = $value;
}

public function value(): mixed
{
if (is_iterable($this->value) && ! is_array($this->value)) {
$this->value = iterator_to_array($this->value);
}

return $this->value;
}
}
18 changes: 18 additions & 0 deletions src/Normalizer/Formatter/ArrayFormatterFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer\Formatter;

/**
* @internal
*
* @implements FormatterFactory<ArrayFormatter>
*/
final class ArrayFormatterFactory implements FormatterFactory
{
public function new(): ArrayFormatter
{
return new ArrayFormatter();
}
}
23 changes: 23 additions & 0 deletions src/Normalizer/Formatter/Formatter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer\Formatter;

/**
* @internal
*
* @template-covariant T
*/
interface Formatter
{
/**
* @param iterable<mixed>|scalar|null $value
*/
public function push(mixed $value): void;

/**
* @return T
*/
public function value(): mixed;
}
18 changes: 18 additions & 0 deletions src/Normalizer/Formatter/FormatterFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace CuyZ\Valinor\Normalizer\Formatter;

/**
* @internal
*
* @template-covariant T of Formatter
*/
interface FormatterFactory
{
/**
* @return T
*/
public function new(): Formatter;
}
15 changes: 13 additions & 2 deletions src/Normalizer/Normalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@

namespace CuyZ\Valinor\Normalizer;

/** @api */
/**
* @api
*
* @template-covariant T
*/
interface Normalizer
{
/**
* @todo doc
* A normalizer is a service that transforms a given input into scalar and
* array values, while preserving the original structure.
*
* This feature can be used to share information with other systems that use
* a data format (JSON, CSV, XML, etc.). The normalizer will take care of
* recursively transforming the data into a format that can be serialized.
*
* @return T
*/
public function normalize(mixed $value): mixed;
}
Loading

0 comments on commit abba5eb

Please sign in to comment.