Skip to content

Instantly share code, notes, and snippets.

@the-toster
Created June 2, 2024 19:20
Show Gist options
  • Save the-toster/5eecf6bc9302e6f6ce7e63a160076925 to your computer and use it in GitHub Desktop.
Save the-toster/5eecf6bc9302e6f6ce7e63a160076925 to your computer and use it in GitHub Desktop.
Набросок гидратора на 0.4 версии
<?php
declare(strict_types=1);
namespace App\Infrastructure\Hdrtr;
use Typhoon\DeclarationId\AliasId;
use Typhoon\DeclarationId\ClassId;
use Typhoon\DeclarationId\ConstantId;
use Typhoon\DeclarationId\NamedClassId;
use Typhoon\DeclarationId\TemplateId;
use Typhoon\Reflection\TyphoonReflector;
use Typhoon\Type\Argument;
use Typhoon\Type\Type;
use Typhoon\Type\TypeVisitor;
use Typhoon\Type\types;
use Typhoon\Type\Variance;
/**
* @template HydrateTo
* @implements TypeVisitor<HydrateTo|Error>
*/
final readonly class HydrateVisitor implements TypeVisitor
{
public function __construct(
private mixed $data,
private array $path,
) {
}
private function unexpectedType(Type $targetType): Error
{
return Error::unexpectedType($this->path, $targetType, $this->data);
}
private function unexpectedValue(Type $targetType, mixed $expected): Error
{
return Error::unexpectedValue($this->path, $targetType, $this->data, $expected);
}
private function missedOffset(Type $targetType, string $offset): Error
{
return Error::missedOffset($this->path, $targetType, $offset);
}
private function unsupportedType(Type $targetType): Error
{
return Error::unsupportedType($this->path, $targetType);
}
private function next(int|string $offset): self
{
return new self(
$this->data[$offset]
?? throw new \LogicException('invalid offset'),
[...$this->path, $offset]
);
}
public function alias(Type $self, AliasId $alias, array $arguments): mixed
{
$this->unsupportedType($self);
}
public function array(Type $self, Type $key, Type $value, array $elements): mixed
{
if (!is_array($this->data)) {
return $this->unexpectedType($self);
}
$result = [];
foreach ($this->data as $k => $v) {
$resultKey = $key->accept(new self($k, [...$this->path, '#key']));
if ($resultKey instanceof Error) {
return $resultKey;
}
$resultValue = $value->accept($this->next($k));
if ($resultValue instanceof Error) {
return $resultValue;
}
$result[$resultKey] = $resultValue;
}
return $result;
}
public function bool(Type $self): mixed
{
if (!is_bool($this->data)) {
return $this->unexpectedType($self);
}
return $this->data;
}
public function callable(Type $self, array $parameters, Type $return): mixed
{
return $this->unsupportedType($self);
}
public function classConstant(Type $self, Type $class, string $name): mixed
{
return $this->unsupportedType($self);
}
public function classString(Type $self, Type $class): mixed
{
return $this->string($self);
}
public function closure(Type $self, array $parameters, Type $return): mixed
{
return $this->unsupportedType($self);
}
public function conditional(Type $self, Argument|Type $subject, Type $if, Type $then, Type $else): mixed
{
return $this->unsupportedType($self);
}
public function constant(Type $self, ConstantId $constant): mixed
{
return $this->unsupportedType($self);
}
public function float(Type $self): mixed
{
if (
!is_float($this->data)
&& !is_int($this->data)
) {
$this->unexpectedType($self);
}
return (float)$this->data;
}
public function int(Type $self, ?int $min, ?int $max): mixed
{
if (!is_int($this->data)) {
$this->unexpectedType($self);
}
return $this->data;
}
public function intersection(Type $self, array $types): mixed
{
$this->unsupportedType($self);
}
public function intMask(Type $self, Type $type): mixed
{
$this->unsupportedType($self);
}
public function iterable(Type $self, Type $key, Type $value): mixed
{
$this->unsupportedType($self);
}
public function key(Type $self, Type $type): mixed
{
return $this->unsupportedType($self);
}
public function list(Type $self, Type $value, array $elements): mixed
{
return $this->array($self, types::int, $value, $elements);
}
public function literal(Type $self, Type $type): mixed
{
return $this->unsupportedType($self);
}
public function true(Type $self): mixed
{
$r = $this->bool($self);
if ($r instanceof Error) {
return $r;
}
if ($r !== true) {
return $this->unexpectedValue($self, true);
}
return $r;
}
public function false(Type $self): mixed
{
$r = $this->bool($self);
if ($r instanceof Error) {
return $r;
}
if ($r !== false) {
return $this->unexpectedValue($self, false);
}
return $r;
}
public function floatValue(Type $self, float $value): mixed
{
$r = $this->float($self);
if ($r instanceof Error) {
return $r;
}
if ($r !== $value) {
return $this->unexpectedValue($self, $value);
}
return $r;
}
public function stringValue(Type $self, string $value): mixed
{
$r = $this->string($self);
if ($r instanceof Error) {
return $r;
}
if ($r !== $value) {
return $this->unexpectedValue($self, $value);
}
return $r;
}
public function mixed(Type $self): mixed
{
return $this->data;
}
public function namedObject(Type $self, ClassId $class, array $arguments): mixed
{
$reflection = TyphoonReflector::build()->reflectClass($class->name);
$result = $reflection->newInstanceWithoutConstructor();
$constructorParameters = ($reflection->methods['__construct'] ?? null)
?->parameters ?? [];
foreach ($reflection->properties as $property) {
if ($property->isStatic()) {
continue;
}
$offset = $property->getName();
$hasOffset = isset($this->data[$offset]);
$hasDefaultValue = $property->hasDefaultValue();
if (!$hasOffset && !$hasDefaultValue) {
return $this->missedOffset($property->getTyphoonType(), $offset);
}
if ($hasOffset) {
$value = $property->getTyphoonType()->accept($this->next($offset));
} else {
if (!$property->isPromoted()) {
$value = $property->defaultValue();
} else {
$value = $constructorParameters[$offset] ?? throw new \LogicException('it should be here');
}
}
if ($value instanceof Error) {
return $value;
}
$property->setValue($result, $value);
}
return $result;
}
public function never(Type $self): mixed
{
return $this->unsupportedType($self);
}
public function nonEmpty(Type $self, Type $type): mixed
{
$r = $type->accept($this);
if ($r instanceof Error) {
return $r;
}
if (empty($r)) {
return $this->unexpectedValue($self, 'non-empty');
}
return $r;
}
public function null(Type $self): mixed
{
if (!is_null($this->data)) {
return $this->unexpectedValue($self, 'null');
}
return $this->data;
}
public function numericString(Type $self): mixed
{
$r = $this->string($self);
if ($r instanceof Error) {
return $r;
}
if (!is_numeric($r)) {
return $this->unexpectedValue($self, 'numeric');
}
return $r;
}
public function object(Type $self, array $properties): mixed
{
$r = new \stdClass();
foreach ($properties as $propertyName => $property) {
$hasOffset = isset($this->data[$propertyName]);
if (!$property->optional && !$hasOffset) {
return $this->missedOffset($property->type, $propertyName);
}
$value = $property->type->accept($this->next($propertyName));
if ($value instanceof Error) {
return $value;
}
$r->{$propertyName} = $value;
}
return $r;
}
public function offset(Type $self, Type $type, Type $offset): mixed
{
return $this->unsupportedType($self);
}
public function resource(Type $self): mixed
{
return $this->unsupportedType($self);
}
public function self(Type $self, ?ClassId $resolvedClass, array $arguments): mixed
{
return $this->unsupportedType($self);
}
public function parent(Type $self, ?NamedClassId $resolvedClass, array $arguments): mixed
{
return $this->unsupportedType($self);
}
public function static(Type $self, ?ClassId $resolvedClass, array $arguments): mixed
{
return $this->unsupportedType($self);
}
public function string(Type $self): mixed
{
if (!is_string($this->data)) {
return $this->unsupportedType($self);
}
return $this->data;
}
public function template(Type $self, TemplateId $template): mixed
{
return $this->unsupportedType($self);
}
public function truthyString(Type $self): mixed
{
$r = $this->string($self);
if ($r instanceof Error) {
return $r;
}
if ((bool)$r !== true) {
return $this->unexpectedValue($self, 'truthyString');
}
return $r;
}
public function union(Type $self, array $types): mixed
{
foreach ($types as $type) {
$r = $type->accept($this);
if (!($r instanceof Error)) {
return $r;
}
}
return $r;
}
public function varianceAware(Type $self, Type $type, Variance $variance): mixed
{
return $this->unsupportedType($self);
}
public function void(Type $self): mixed
{
return $this->unsupportedType($self);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment