JFIF x x C C " } !1AQa "q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w !1AQ aq"2B #3Rbr{
File "MapperBuilder.php"
Full Path: /home/u735268861/domains/palsarh.in/public_html/vendor/cuyz/valinor/src/MapperBuilder.php
File size: 20.61 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
namespace CuyZ\Valinor;
use CuyZ\Valinor\Cache\Cache;
use CuyZ\Valinor\Library\Container;
use CuyZ\Valinor\Library\Settings;
use CuyZ\Valinor\Mapper\ArgumentsMapper;
use CuyZ\Valinor\Mapper\Tree\Message\ErrorMessage;
use CuyZ\Valinor\Mapper\TreeMapper;
use Throwable;
use function array_unique;
use function array_values;
use function is_callable;
/** @api */
final class MapperBuilder
{
private Settings $settings;
private Container $container;
public function __construct()
{
$this->settings = new Settings();
}
/**
* Allows the mapper to infer an implementation for a given interface.
*
* The callback can take any arguments, that will automatically be mapped
* using the given source. These arguments can then be used to decide which
* implementation should be used.
*
* The callback *must* be pure, its output must be deterministic.
* @see https://en.wikipedia.org/wiki/Pure_function
*
* Example:
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->infer(UuidInterface::class, fn () => MyUuid::class)
* ->infer(SomeInterface::class, fn (string $type) => match($type) {
* 'foo' => Foo::class,
* 'bar' => Bar::class,
* default => throw new DomainException("Unhandled type `$type`.")
* })
* ->mapper()
* ->map(SomeInterface::class, [
* 'type' => 'foo',
* 'uuid' => 'a6868d61-acba-406d-bcff-30ecd8c0ceb6',
* ]);
* ```
*
* @pure
* @param interface-string|class-string $name
* @param pure-callable $callback
*/
public function infer(string $name, callable $callback): self
{
$clone = clone $this;
$clone->settings->inferredMapping[$name] = $callback;
return $clone;
}
/**
* Registers a constructor that can be used by the mapper to create an
* instance of an object.
*
* Note that depending on your needs, a more straightforward way to register
* a constructor is to use the following attribute on a static method:
* @see \CuyZ\Valinor\Mapper\Object\Constructor
*
* A constructor is a callable that can be either:
*
* 1. A named constructor, also known as a static factory method
* 2. The method of a service — for instance a repository
* 3. A "callable object" — a class that declares an `__invoke` method
* 4. Any other callable — including anonymous functions
*
* In any case, the return type of the callable will be resolved by the
* mapper to know when to use it. Any argument can be provided and will
* automatically be mapped using the given source. These arguments can then
* be used to instantiate the object in the desired way.
*
* Registering any constructor will disable the native constructor — the
* `__construct` method — of the targeted class. If for some reason it still
* needs to be handled as well, the name of the class must be given to this
* method.
*
* ```php
* final class SomeClass
* {
* private string $foo;
*
* private int $bar;
*
* private array $otherClasses = [];
*
* public function __construct(string $foo)
* {
* $this->foo = $foo;
* }
*
* public static function namedConstructor(string $foo, int $bar): self
* {
* $instance = new self($foo);
* $instance->bar = $bar;
*
* return $instance;
* }
*
* public function addOtherClass(OtherClass $otherClass): void
* {
* $this->otherClasses[] = $otherClass;
* }
* }
*
* final class SomeRepository
* {
* public function findById(int $id): SomeClass
* {
* // …
* }
* }
*
* final class SomeCallableObject
* {
* public function __invoke(string $foo, int $bar, int $baz): SomeClass
* {
* // …
* }
* }
*
* (new \CuyZ\Valinor\MapperBuilder())
* ->registerConstructor(
* // Named constructor
* SomeClass::namedConstructor(...),
* // …or for PHP < 8.1:
* [SomeClass::class, 'namedConstructor'],
*
* // Method of an object
* (new SomeRepository())->findById(...),
* // …or for PHP < 8.1:
* [new SomeRepository(), 'findById'],
*
* // Callable object
* new SomeCallableObject(),
*
* // Anonymous function
* function(string $string, OtherClass $otherClass): SomeClass {
* $someClass = new SomeClass($string);
* $someClass->addOtherClass($otherClass);
*
* return $someClass;
* },
*
* // Also allow the native constructor — the `__construct` method
* SomeClass::class,
* )
* ->mapper()
* ->map(SomeClass::class, [
* // …
* ]);
* ```
*
* Enum constructors can be registered the same way:
*
* * ```php
* enum SomeEnum: string
* {
* case CASE_A = 'FOO_VALUE_1';
* case CASE_B = 'FOO_VALUE_2';
* case CASE_C = 'BAR_VALUE_1';
* case CASE_D = 'BAR_VALUE_2';
*
* /**
* * \@param 'FOO'|'BAR' $type
* * \@param int<1, 2> $number
* * /
* public static function fromMatrix(string $type, int $number): self
* {
* return self::from("{$type}_VALUE_{$number}");
* }
* }
*
* (new \CuyZ\Valinor\MapperBuilder())
* ->registerConstructor(
* // Allow the native constructor to be used
* SomeEnum::class,
*
* // Register a named constructor
* SomeEnum::fromMatrix(...)
* )
* ->mapper()
* ->map(SomeEnum::class, [
* 'type' => 'FOO',
* 'number' => 'BAR',
* ]);
* ```
*
* The constructor *must* be pure, its output must be deterministic.
* @see https://en.wikipedia.org/wiki/Pure_function
*
* @pure
* @param pure-callable|class-string ...$constructors
*/
public function registerConstructor(callable|string ...$constructors): self
{
$clone = clone $this;
foreach ($constructors as $constructor) {
if (is_callable($constructor)) {
$clone->settings->customConstructors[] = $constructor;
} else {
$clone->settings->nativeConstructors[$constructor] = null;
}
}
return $clone;
}
/**
* Describes which date formats will be supported during mapping.
*
* By default, the dates will accept any valid timestamp or RFC 3339-formatted
* value.
*
* ```php
* (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');
* ```
*
* @pure
* @param non-empty-string $format
* @param non-empty-string ...$formats
*/
public function supportDateFormats(string $format, string ...$formats): self
{
$clone = clone $this;
$clone->settings->supportedDateFormats = array_values(array_unique([$format, ...$formats]));
return $clone;
}
/**
* Returns the date formats supported during mapping.
*
* By default, any valid timestamp or RFC 3339-formatted value are accepted.
* Custom formats can be set using method `supportDateFormats()`.
*
* @pure
* @return non-empty-array<non-empty-string>
*/
public function supportedDateFormats(): array
{
return $this->settings->supportedDateFormats;
}
/**
* Inject a cache implementation that will be in charge of caching heavy
* data used by the mapper. It is *strongly* recommended to use it when the
* application runs in a production environment.
*
* An implementation is provided out of the box, which writes cache entries
* in the file system.
*
* When the application runs in a development environment, the cache
* implementation should be decorated with `FileWatchingCache`. This service
* 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.
*
* ```php
* $cache = new \CuyZ\Valinor\Cache\FileSystemCache('path/to/cache-dir');
*
* if ($isApplicationInDevelopmentEnvironment) {
* $cache = new \CuyZ\Valinor\Cache\FileWatchingCache($cache);
* }
*
* (new \CuyZ\Valinor\MapperBuilder())
* ->withCache($cache)
* ->mapper()
* ->map(SomeClass::class, [
* // …
* ]);
* ```
*
* @pure
*/
public function withCache(Cache $cache): self
{
$clone = clone $this;
$clone->settings->cache = $cache;
return $clone;
}
/**
* With this setting enabled, scalar types will accept castable values:
*
* - Integer types will accept any valid numeric value, for instance the
* string value "42".
*
* - Float types will accept any valid numeric value, for instance the
* string value "1337.42".
*
* - String types will accept any integer, float or object implementing the
* `Stringable` interface.
*
* - Boolean types will accept any truthy or falsy value:
* - "true" (string), "1" (string) and 1 (int) will be cast to `true`
* - "false" (string), "0" (string) and 0 (int) will be cast to `false`
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->allowScalarValueCasting()
* ->mapper()
* ->map('array{id: string, price: float, active: bool}', [
* 'id' => 549465210, // Will be cast to string
* 'price' => '42.39', // Will be cast to float
* 'active' => 1, // Will be cast to bool
* ]);
* ```
*
* @pure
*/
public function allowScalarValueCasting(): self
{
$clone = clone $this;
$clone->settings->allowScalarValueCasting = true;
return $clone;
}
/**
* By default, list types will only accept sequential keys starting from 0.
*
* This setting allows the mapper to convert associative arrays to a list
* with sequential keys.
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->allowNonSequentialList()
* ->mapper()
* ->map('list<int>', [
* 'foo' => 42,
* 'bar' => 1337,
* ]);
*
* // => [0 => 42, 1 => 1337]
* ```
*
* @pure
*/
public function allowNonSequentialList(): self
{
$clone = clone $this;
$clone->settings->allowNonSequentialList = true;
return $clone;
}
/**
* Allows the mapper to accept undefined values (missing from the input), by
* converting them to `null` (if the current type is nullable) or an empty
* array (if the current type is an object or an iterable).
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->allowUndefinedValues()
* ->mapper()
* ->map('array{name: string, age: int|null}', [
* 'name' => 'John Doe',
* // 'age' is not defined
* ]);
*
* // => ['name' => 'John Doe', 'age' => null]
* ```
*
* @pure
*/
public function allowUndefinedValues(): self
{
$clone = clone $this;
$clone->settings->allowUndefinedValues = true;
return $clone;
}
/**
* By default, an error is raised when a source array contains keys that
* do not match a class property/parameter or a shaped array element.
*
* This setting allows the mapper to ignore these superfluous keys.
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->allowSuperfluousKeys()
* ->mapper()
* ->map('array{name: string, age: int}', [
* 'name' => 'John Doe',
* 'age' => 42,
* 'city' => 'Paris', // Will be ignored
* ]);
* ```
*
* @pure
*/
public function allowSuperfluousKeys(): self
{
$clone = clone $this;
$clone->settings->allowSuperfluousKeys = true;
return $clone;
}
/**
* Allows permissive types `mixed` and `object` to be used during mapping.
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->allowPermissiveTypes()
* ->mapper()
* ->map('array{name: string, data: mixed}', [
* 'name' => 'some_product',
* 'data' => 42, // Could be any value
* ]);
* ```
*
* @pure
*/
public function allowPermissiveTypes(): self
{
$clone = clone $this;
$clone->settings->allowPermissiveTypes = true;
return $clone;
}
/**
* A mapper converter allows users to hook into the mapping process and
* apply custom logic to the input, by defining a callable signature that
* properly describes when it should be called:
*
* - A first argument with a type matching the expected input being mapped
* - A return type representing the targeted mapped type
*
* These two types are enough for the library to know when to call the
* converter and can contain advanced type annotations for more specific
* use cases.
*
* Below is a basic example of a converter that converts string inputs to
* uppercase:
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
* ->registerConverter(fn (string $value): string => strtoupper($value))
* ->mapper()
* ->map('string', 'hello world'); // 'HELLO WORLD'
* ```
*
* Converters can be chained, allowing multiple transformations to be
* applied to a value. A second `callable` parameter can be declared,
* allowing the current converter to call the next one in the chain.
*
* A priority can be given to a converter to control the order in which
* converters are applied. The higher the priority, the earlier the
* converter will be executed. The default priority is 0.
*
* An attribute on a property or a class can act as a converter if:
* 1. It defines a `map` method.
* 2. It is registered using either the `registerConverter()` method or
* the following attribute: @see \CuyZ\Valinor\Mapper\AsConverter
*
* ```php
* (new \CuyZ\Valinor\MapperBuilder())
*
* // The type of the first parameter of the converter will determine
* // when it will be used by the mapper.
* ->registerConverter(
* fn (string $value, callable $next): string => $next(strtoupper($value))
* )
*
* // Converters can be chained, the last registered one will take
* // precedence over the previous ones, which can be called using the
* // `$next` parameter.
* ->registerConverter(
* fn (string $value, callable $next): string => $next($value . '!')
* )
*
* // A priority can be given to a converter, to make sure it is called
* // before or after another one.
* ->registerConverter(
* fn (string $value, callable $next): string => $next($value . '?'),
* priority: -100 // Negative priority: converter is called early
* )
*
* // External converter attributes must be registered before they are
* // used by the mapper.
* ->registerConverter(\Some\External\ConverterAttribute::class)
*
* ->mapper()
* ->map('string', 'hello world'); // 'HELLO WORLD!?'
* ```
*
* It is also possible to register attributes that share a common interface
* by giving the interface name to the registration method.
*
* ```php
* namespace My\App;
*
* interface MyAttributeInterface {}
*
* #[\Attribute]
* final class SomeAttribute implements \My\App\MyAttributeInterface {}
*
* #[\Attribute]
* final class SomeOtherAttribute implements \My\App\MyAttributeInterface {}
*
* (new \CuyZ\Valinor\MapperBuilder())
* // Registers both `SomeAttribute` and `SomeOtherAttribute` attributes
* ->registerConverter(\My\App\MyAttributeInterface::class)
* ->mapper()
* ->map(…);
* ```
*
* The converter *must* be pure, its output must be deterministic.
* @see https://en.wikipedia.org/wiki/Pure_function
*
* @pure
* @param pure-callable|class-string $converter
*/
public function registerConverter(callable|string $converter, int $priority = 0): self
{
$clone = clone $this;
if (is_callable($converter)) {
$clone->settings->mapperConverters[$priority][] = $converter;
} else {
$clone->settings->mapperConverterAttributes[$converter] = null;
}
return $clone;
}
/**
* Filters which userland exceptions are allowed during the mapping.
*
* It is advised to use this feature with caution: userland exceptions may
* contain sensible information — for instance an SQL exception showing a
* part of a query should never be allowed. Therefore, only an exhaustive
* list of carefully chosen exceptions should be filtered.
*
* ```php
* final class SomeClass
* {
* public function __construct(string $value)
* {
* \Webmozart\Assert\Assert::startsWith($value, 'foo_');
* }
* }
*
* (new \CuyZ\Valinor\MapperBuilder())
* ->filterExceptions(function (Throwable $exception) {
* if ($exception instanceof \Webmozart\Assert\InvalidArgumentException) {
* return \CuyZ\Valinor\Mapper\Tree\Message\MessageBuilder::from($exception);
* }
*
* // If the exception should not be caught by this library, it must
* // be thrown again.
* throw $exception;
* })
* ->mapper()
* ->map(SomeClass::class, [
* // …
* ]);
* ```
*
* @pure
* @param callable(Throwable): ErrorMessage $filter
*/
public function filterExceptions(callable $filter): self
{
$clone = clone $this;
$clone->settings->exceptionFilter = $filter;
return $clone;
}
/**
* Warms up the injected cache implementation with the provided type
* signatures. This will improve the performance when the first call to the
* mapper is done for each of these types.
*
* ```php
* $mapperBuilder = (new \CuyZ\Valinor\MapperBuilder())
* ->withCache(new \CuyZ\Valinor\Cache\FileSystemCache('path/to/dir'));
*
* // During the build:
* $mapperBuilder->warmupCacheFor(
* // This will also recursively warm up the cache for the types of
* // the class properties.
* SomeClass::class,
*
* // Any valid type signature can be used.
* 'non-empty-list<string, SomeClass>',
* 'array{name: string, age: int}',
* );
*
* // In the application:
* $mapperBuilder->mapper()->map(SomeClass::class, […]);
* ```
*/
public function warmupCacheFor(string ...$signatures): void
{
if (! isset($this->settings->cache)) {
return;
}
$this->container()->cacheWarmupService()->warmup(...$signatures);
}
/**
* Clears all persisted cache entries from the registered cache
* implementation.
*/
public function clearCache(): void
{
if (! isset($this->settings->cache)) {
return;
}
$this->settings->cache->clear();
}
/** @pure */
public function mapper(): TreeMapper
{
return $this->container()->treeMapper();
}
/** @pure */
public function argumentsMapper(): ArgumentsMapper
{
return $this->container()->argumentsMapper();
}
public function __clone()
{
$this->settings = clone $this->settings;
unset($this->container);
}
private function container(): Container
{
return ($this->container ??= new Container($this->settings));
}
}