Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
72.55% covered (warning)
72.55%
37 / 51
75.00% covered (warning)
75.00%
6 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
IsStringValueObject
72.55% covered (warning)
72.55%
37 / 51
75.00% covered (warning)
75.00%
6 / 8
32.01
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 fromNative
66.67% covered (warning)
66.67%
16 / 24
0.00% covered (danger)
0.00%
0 / 1
15.48
 toValidConstructorArgument
68.42% covered (warning)
68.42%
13 / 19
0.00% covered (danger)
0.00%
0 / 1
5.79
 toNative
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 validate
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 convert
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Apie\Core\ValueObjects;
3
4use Apie\Core\Exceptions\InvalidTypeException;
5use Apie\Core\TypeUtils;
6use Apie\Core\Utils\ConverterUtils;
7use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
8use Apie\TypeConverter\ReflectionTypeFactory;
9use DateTime;
10use DateTimeInterface;
11use ReflectionClass;
12use ReflectionType;
13use Stringable;
14use UnitEnum;
15
16trait IsStringValueObject
17{
18    private string $internal;
19    private static bool $hasStringConstructor;
20    private static ReflectionType $constructorType;
21
22    public function __construct(string|int|float|bool|Stringable $input)
23    {
24        $input = $this->convert((string) $input);
25        static::validate($input);
26        $this->internal = $input;
27    }
28
29    public static function fromNative(mixed $input): self
30    {
31        if (gettype($input) == 'boolean') {
32            $input = $input ? 'true' : 'false';
33        }
34        if ($input instanceof ValueObjectInterface) {
35            $input = $input->toNative();
36        }
37        if ($input instanceof DateTimeInterface) {
38            $input = $input->format(DateTime::ATOM);
39        }
40        if ($input instanceof UnitEnum) {
41            $input = $input->value;
42        }
43        if (is_array($input)) {
44            throw new InvalidTypeException($input, (new ReflectionClass(self::class))->getShortName());
45        }
46        if (is_object($input) && !$input instanceof Stringable) {
47            throw new InvalidTypeException(
48                $input,
49                (new ReflectionClass(self::class))->getShortName()
50            );
51        }
52        $class = new ReflectionClass(static::class);
53        if (!$class->isInstantiable()) {
54            self::validate((string) $input);
55            return new self((string) $input);
56        }
57        if (self::toValidConstructorArgument($input)) {
58            // @phpstan-ignore new.static
59            return new static($input);
60        }
61        $instance = (new ReflectionClass(static::class))->newInstanceWithoutConstructor();
62        $instance->internal = (string) $input;
63
64        return $instance;
65    }
66
67    private static function toValidConstructorArgument(mixed& $input): bool
68    {
69        if (!isset(self::$hasStringConstructor)) {
70            $refl = (new ReflectionClass(static::class));
71            $constructor = $refl->getConstructor();
72            $arguments = $constructor?->getParameters() ?? [];
73            self::$hasStringConstructor = count($arguments) === 1;
74        }
75        if (!self::$hasStringConstructor) {
76            return false;
77        };
78        if (!isset(self::$constructorType)) {
79            $refl = (new ReflectionClass(static::class));
80            $constructor = $refl->getConstructor();
81            $arguments = $constructor?->getParameters() ?? [];
82            self::$constructorType = reset($arguments)->getType() ?? ReflectionTypeFactory::createReflectionType('mixed');
83        }
84        if (TypeUtils::matchesType(self::$constructorType, $input)) {
85            return true;
86        }
87        $input = ConverterUtils::dynamicCast(
88            $input,
89            self::$constructorType
90        );
91        return true;
92    }
93
94    public function toNative(): string
95    {
96        return $this->internal;
97    }
98
99    public function __toString(): string
100    {
101        return $this->toNative();
102    }
103
104    public function jsonSerialize(): string
105    {
106        return $this->toNative();
107    }
108    
109    public static function validate(string $input): void
110    {
111    }
112
113    protected function convert(string $input): string
114    {
115        return $input;
116    }
117}