Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
93.94% covered (success)
93.94%
62 / 66
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SnowflakeIdentifier
93.94% covered (success)
93.94%
62 / 66
60.00% covered (warning)
60.00%
3 / 5
19.08
0.00% covered (danger)
0.00%
0 / 1
 getSeparator
n/a
0 / 0
n/a
0 / 0
0
 toNative
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
4
 __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
 fromNative
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
6.01
 getRegularExpression
91.43% covered (success)
91.43%
32 / 35
0.00% covered (danger)
0.00%
0 / 1
7.03
1<?php
2namespace Apie\Core\ValueObjects;
3
4use Apie\Core\Exceptions\InvalidTypeException;
5use Apie\Core\RegexUtils;
6use Apie\Core\Utils\ConverterUtils;
7use Apie\Core\ValueObjects\Exceptions\InvalidStringForValueObjectException;
8use Apie\Core\ValueObjects\Interfaces\HasRegexValueObjectInterface;
9use Apie\Core\ValueObjects\Interfaces\ValueObjectInterface;
10use Apie\RegexTools\CompiledRegularExpression;
11use ReflectionClass;
12use ReflectionNamedType;
13
14abstract class SnowflakeIdentifier implements ValueObjectInterface, HasRegexValueObjectInterface
15{
16    private string $calculated;
17
18    abstract protected static function getSeparator(): string;
19
20    final public function toNative(): string
21    {
22        if (!isset($this->calculated)) {
23            $refl = new ReflectionClass($this);
24            $separator = static::getSeparator();
25            $result = [];
26            foreach ($refl->getConstructor()->getParameters() as $parameter) {
27                $propertyName = $parameter->getName();
28                $propertyValue = $refl->getProperty($propertyName)->getValue($this);
29                $stringPropertyValue = Utils::toString($propertyValue);
30                if (strpos($stringPropertyValue, $separator) !== false) {
31                    throw new InvalidStringForValueObjectException($stringPropertyValue, $propertyValue);
32                }
33                $result[] = $stringPropertyValue;
34            }
35
36            $this->calculated = implode($separator, $result);
37        }
38        return $this->calculated;
39    }
40
41    final public function __toString(): string
42    {
43        return $this->toNative();
44    }
45
46    final public function jsonSerialize(): string
47    {
48        return $this->toNative();
49    }
50
51    public static function fromNative(mixed $input): self
52    {
53        $input = Utils::toString($input);
54        $refl = new ReflectionClass(static::class);
55        $parameters = $refl->getConstructor()->getParameters();
56        $separator = static::getSeparator();
57        $split = explode($separator, $input, count($parameters));
58        if (count($split) !== count($parameters)) {
59            throw new InvalidStringForValueObjectException($input, new ReflectionClass(static::class));
60        }
61        $constructorArguments = [];
62        foreach ($parameters as $key => $parameter) {
63            $parameterType = $parameter->getType();
64            if (!($parameterType instanceof ReflectionNamedType)) {
65                throw new InvalidTypeException($parameterType, 'ReflectionNamedType');
66            }
67            if ($parameterType->allowsNull() && $split[$key] === '') {
68                $constructorArguments[] = null;
69            } else {
70                $constructorArguments[] = Utils::toTypehint($parameterType, $split[$key]);
71            }
72        }
73        return $refl->newInstanceArgs($constructorArguments);
74    }
75
76    final public static function getRegularExpression(): string
77    {
78        $refl = new ReflectionClass(static::class);
79        $parameters = $refl->getConstructor()->getParameters();
80        $separator = preg_quote(static::getSeparator());
81
82        $expressions = [];
83        foreach ($parameters as $parameter) {
84            $parameterType = $parameter->getType();
85            if (!($parameterType instanceof ReflectionNamedType)) {
86                throw new InvalidTypeException($parameterType, 'ReflectionNamedType');
87            }
88            $regex = '[^' . $separator . ']+';
89            $class = ConverterUtils::toReflectionClass($parameterType);
90            if (in_array(HasRegexValueObjectInterface::class, $class?->getInterfaceNames() ?? [])) {
91                $foundRegex = '(' . RegexUtils::removeDelimiters($class->getMethod('getRegularExpression')->invoke(null)) . ')';
92                if (strpos($foundRegex, '?=') === false) {
93                    $regex = $foundRegex;
94                }
95            } else {
96                switch ($parameterType->getName()) {
97                    case 'int':
98                        $regex = '-?(0|[1-9]\d*)';
99                        break;
100                    case 'float':
101                        $regex = '-?(0|[1-9]\d*)(\.\d+)?';
102                        break;
103                }
104            }
105            $expressions[] = $regex;
106            $expressions[] = $separator;
107        }
108        array_pop($expressions);
109
110        $expressions = array_map(
111            function (string $expression) {
112                return CompiledRegularExpression::createFromRegexWithoutDelimiters($expression)
113                            ->removeStartAndEndMarkers();
114            },
115            $expressions
116        );
117        array_unshift($expressions, CompiledRegularExpression::createFromRegexWithoutDelimiters('^'));
118        array_push($expressions, CompiledRegularExpression::createFromRegexWithoutDelimiters('$'));
119
120        $tmp = CompiledRegularExpression::createFromRegexWithoutDelimiters('');
121
122        return $tmp->merge(...$expressions)->__toString();
123    }
124}