Skip to content

Instantly share code, notes, and snippets.

@the-toster
Created June 2, 2024 16:49
Show Gist options
  • Save the-toster/dd715988d6d5cb9eec3d76b7e7c0145a to your computer and use it in GitHub Desktop.
Save the-toster/dd715988d6d5cb9eec3d76b7e7c0145a to your computer and use it in GitHub Desktop.
Первая версия (на 0.3), проходила тесты, но не псалм
<?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