Created
June 2, 2024 16:49
-
-
Save the-toster/dd715988d6d5cb9eec3d76b7e7c0145a to your computer and use it in GitHub Desktop.
Первая версия (на 0.3), проходила тесты, но не псалм
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
namespace App\Infrastructure\Hdrtr; | |
use Typhoon\Reflection\TyphoonReflector; | |
use Typhoon\Type\Argument; | |
use Typhoon\Type\AtClass; | |
use Typhoon\Type\AtFunction; | |
use Typhoon\Type\AtMethod; | |
use Typhoon\Type\Type; | |
use Typhoon\Type\TypeVisitor; | |
use Typhoon\Type\types; | |
use Typhoon\Type\Variance; | |
/** | |
* | |
* тут я не понял, можно ли как-то сделать чтоб HydrateTo сеттился | |
* в зависимости от того с каким $self вызвали | |
* | |
* @template HydrateTo | |
* @implements TypeVisitor<HydrateTo|Error> | |
*/ | |
final class HydrateVisitor implements TypeVisitor | |
{ | |
public function __construct( | |
private mixed $data, | |
private array $path, | |
) { | |
} | |
private function unexpectedType(Type $targetType): Error | |
{ | |
return Error::unexpectedType($this->data, $targetType, $this->path); | |
} | |
private function unexpectedValue(Type $targetType): Error | |
{ | |
return Error::unexpectedValue($targetType, $this->data, $this->path); | |
} | |
private function unsupportedType(Type $targetType): Error | |
{ | |
return Error::unsupportedType($targetType, $this->path); | |
} | |
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, string $class, string $name, array $arguments): mixed | |
{ | |
return types::object($name, $arguments)->accept($this); | |
} | |
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, string $name): 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): 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 intRange(Type $self, ?int $min, ?int $max): mixed | |
{ | |
if (!is_int($this->data)) { | |
return $this->unexpectedType($self); | |
} | |
if (is_int($min) && $this->data > $min | |
|| is_int($max) && $this->data > $max | |
) { | |
$this->unexpectedValue($self); | |
} | |
return $this->data; | |
} | |
public function iterable(Type $self, Type $key, Type $value): mixed | |
{ | |
return $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 literalValue(Type $self, float|bool|int|string $value): mixed | |
{ | |
if (!is_scalar($this->data)) { | |
return $this->unexpectedType($self); | |
} | |
if ($this->data !== $value) { | |
return $this->unexpectedValue($self); | |
} | |
return $this->data; | |
} | |
public function mixed(Type $self): mixed | |
{ | |
return $this->data; | |
} | |
public function namedObject(Type $self, string $class, array $arguments): mixed | |
{ | |
$reflection = TyphoonReflector::build()->reflectClass($class); | |
$result = $reflection->newInstanceWithoutConstructor(); | |
$constructor = $reflection->getConstructor(); | |
foreach ($reflection->getProperties() as $property) { | |
if ($property->isStatic()) { | |
continue; | |
} | |
$offset = $property->getName(); | |
$hasOffset = isset($this->data[$offset]); | |
$hasDefaultValue = $property->hasDefaultValue(); | |
if (!$hasOffset && !$hasDefaultValue) { | |
return $this->unexpectedValue($self); | |
} | |
if ($hasOffset) { | |
$value = $property->getTyphoonType()->accept($this->next($offset)); | |
} else { | |
if (!$property->isPromoted()) { | |
$value = $property->getDefaultValue(); | |
} else { | |
$value = $constructor | |
->getParameter($offset) | |
->getDefaultValue() | |
; | |
} | |
} | |
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); | |
} | |
return $r; | |
} | |
public function null(Type $self): mixed | |
{ | |
if (!is_null($this->data)) { | |
return $this->unexpectedValue($self); | |
} | |
return $this->data; | |
} | |
public function numericString(Type $self): mixed | |
{ | |
if (!is_string($this->data)) { | |
return $this->unexpectedType($self); | |
} | |
if (!is_numeric($this->data)) { | |
return $this->unexpectedValue($self); | |
} | |
return $this->data; | |
} | |
public function object(Type $self): mixed | |
{ | |
return $this->namedObject($self, \stdClass::class, []); | |
} | |
public function objectShape(Type $self, array $properties): mixed | |
{ | |
return $this->unsupportedType($self); | |
} | |
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 string(Type $self): mixed | |
{ | |
if (!is_string($this->data)) { | |
return $this->unsupportedType($self); | |
} | |
return $this->data; | |
} | |
public function template(Type $self, string $name, AtClass|AtFunction|AtMethod $declaredAt, array $arguments): 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); | |
} | |
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 $this->unsupportedType($self); | |
} | |
public function value(Type $self, Type $type): mixed | |
{ | |
return $this->unsupportedType($self); | |
} | |
public function varianceAware(Type $self, Type $type, Variance $variance): mixed | |
{ | |
return $type->accept($this); | |
} | |
public function void(Type $self): mixed | |
{ | |
$this->unsupportedType($self); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment