JFIF x x C C " } !1AQa "q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w !1AQ aq"2B #3Rbr{
File "CompiledTransformer.php"
Full Path: /home/palsarh/web/palsarh.in/public_html/vendor/cuyz/valinor/src/Normalizer/Transformer/CompiledTransformer.php
File size: 5.46 KB
MIME-type: text/x-php
Charset: utf-8
<?php
declare(strict_types=1);
namespace CuyZ\Valinor\Normalizer\Transformer;
use Closure;
use CuyZ\Valinor\Cache\Cache;
use CuyZ\Valinor\Cache\CacheEntry;
use CuyZ\Valinor\Cache\TypeFilesWatcher;
use CuyZ\Valinor\Compiler\Compiler;
use CuyZ\Valinor\Compiler\Node;
use CuyZ\Valinor\Normalizer\Exception\TypeUnhandledByNormalizer;
use CuyZ\Valinor\Normalizer\Transformer\Compiler\TransformerDefinitionBuilder;
use CuyZ\Valinor\Normalizer\Transformer\Compiler\TransformerRootNode;
use CuyZ\Valinor\Type\CompositeTraversableType;
use CuyZ\Valinor\Type\Type;
use CuyZ\Valinor\Type\Types\ArrayKeyType;
use CuyZ\Valinor\Type\Types\ArrayType;
use CuyZ\Valinor\Type\Types\EnumType;
use CuyZ\Valinor\Type\Types\Factory\ValueTypeFactory;
use CuyZ\Valinor\Type\Types\IterableType;
use CuyZ\Valinor\Type\Types\NativeBooleanType;
use CuyZ\Valinor\Type\Types\NativeClassType;
use CuyZ\Valinor\Type\Types\NativeFloatType;
use CuyZ\Valinor\Type\Types\NativeIntegerType;
use CuyZ\Valinor\Type\Types\NativeStringType;
use CuyZ\Valinor\Type\Types\NonEmptyArrayType;
use CuyZ\Valinor\Type\Types\NonEmptyListType;
use CuyZ\Valinor\Type\Types\NullType;
use Generator;
use Iterator;
use IteratorAggregate;
use UnitEnum;
use function array_is_list;
use function is_array;
use function is_iterable;
use function is_object;
use function is_scalar;
/** @internal */
final class CompiledTransformer implements Transformer
{
public function __construct(
private TransformerDefinitionBuilder $definitionBuilder,
private TypeFilesWatcher $typeFilesWatcher,
/** @var Cache<Transformer> */
private Cache $cache,
/** @var list<callable> */
private array $transformers,
) {}
public function transform(mixed $value): mixed
{
$type = $this->inferType($value, isSure: true);
$key = "transformer-\0" . $type->toString();
$transformer = $this->cache->get($key, $this->transformers, $this);
if ($transformer) {
return $transformer->transform($value);
}
$code = $this->compileFor($type);
$filesToWatch = $this->typeFilesWatcher->for($type);
$this->cache->set($key, new CacheEntry($code, $filesToWatch));
$transformer = $this->cache->get($key, $this->transformers, $this);
assert($transformer instanceof Transformer);
return $transformer->transform($value);
}
private function compileFor(Type $type): string
{
$rootNode = new TransformerRootNode($this->definitionBuilder, $type);
$node = Node::shortClosure($rootNode)
->witParameters(
Node::parameterDeclaration('transformers', 'array'),
Node::parameterDeclaration('delegate', Transformer::class),
);
return (new Compiler())->compile($node)->code();
}
private function inferType(mixed $value, bool $isSure = false): Type
{
return match (true) {
$value instanceof UnitEnum => EnumType::native($value::class),
is_object($value) && ! $value instanceof Closure && ! $value instanceof Generator => $this->inferObjectType($value),
is_iterable($value) => $this->inferIterableType($value),
is_scalar($value) && $isSure => ValueTypeFactory::from($value),
is_string($value) => NativeStringType::get(),
is_int($value) => NativeIntegerType::get(),
is_float($value) => NativeFloatType::get(),
is_bool($value) => NativeBooleanType::get(),
is_null($value) => NullType::get(),
default => throw new TypeUnhandledByNormalizer($value),
};
}
private function inferObjectType(object $value): NativeClassType
{
if (is_iterable($value)) {
$iterableType = $this->inferIterableType($value);
return new NativeClassType($value::class, [$iterableType->subType()]);
}
return new NativeClassType($value::class);
}
/**
* This method contains a strongly opinionated rule: when normalizing an
* iterable, we assume that the iterable has a high probability of
* containing only one type of value, each iteration matching the type of
* the first value.
*
* This is a trade-off between performance and accuracy: the first value
* will always be normalized, so we can safely add its type to the compiling
* process. For other values, if their types match the first one, that is a
* big performance win; if they don't, the transformer will do its best to
* find an optimized way of dealing with it or, by default, fall back to
* the delegate transformer.
*
* @param iterable<mixed> $value
*/
private function inferIterableType(iterable $value): CompositeTraversableType
{
if (is_array($value)) {
if ($value === []) {
return ArrayType::native();
}
$firstValueType = $this->inferType(reset($value));
if (array_is_list($value)) {
return new NonEmptyListType($firstValueType);
}
return new NonEmptyArrayType(ArrayKeyType::default(), $firstValueType);
}
if ($value instanceof IteratorAggregate) {
$value = $value->getIterator();
}
if ($value instanceof Iterator && $value->valid()) {
$firstValueType = $this->inferType($value->current());
return new IterableType(ArrayKeyType::default(), $firstValueType);
}
return IterableType::native();
}
}