Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.00% covered (success)
95.00%
76 / 80
60.00% covered (warning)
60.00%
3 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
SnowflakeIdentifier
95.00% covered (success)
95.00%
76 / 80
60.00% covered (warning)
60.00%
3 / 5
24
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%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 __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
95.45% covered (success)
95.45%
21 / 22
0.00% covered (danger)
0.00%
0 / 1
8
 getRegularExpression
92.50% covered (success)
92.50%
37 / 40
0.00% covered (danger)
0.00%
0 / 1
9.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            $prefix = '';
24            if (is_callable([static::class, 'getPrefix'])) {
25                $prefix = static::getPrefix() . '-';
26            }
27            $refl = new ReflectionClass($this);
28            $separator = static::getSeparator();
29            $result = [];
30            foreach ($refl->getConstructor()->getParameters() as $parameter) {
31                $propertyName = $parameter->getName();
32                $propertyValue = $refl->getProperty($propertyName)->getValue($this);
33                $stringPropertyValue = Utils::toString($propertyValue);
34                if (strpos($stringPropertyValue, $separator) !== false) {
35                    throw new InvalidStringForValueObjectException($stringPropertyValue, $propertyValue);
36                }
37                $result[] = $stringPropertyValue;
38            }
39
40            $this->calculated = $prefix . implode($separator, $result);
41        }
42        return $this->calculated;
43    }
44
45    final public function __toString(): string
46    {
47        return $this->toNative();
48    }
49
50    final public function jsonSerialize(): string
51    {
52        return $this->toNative();
53    }
54
55    public static function fromNative(mixed $input): self
56    {
57        $input = Utils::toString($input);
58        $prefix = '';
59        if (is_callable([static::class, 'getPrefix'])) {
60            $prefix = static::getPrefix() . '-';
61        }
62        if (strpos($input, $prefix) === 0) {
63            $input = substr($input, strlen($prefix));
64        } else {
65            throw new InvalidStringForValueObjectException($input, new ReflectionClass(static::class));
66        }
67        $refl = new ReflectionClass(static::class);
68        $parameters = $refl->getConstructor()->getParameters();
69        $separator = static::getSeparator();
70        $split = explode($separator, $input, count($parameters));
71        if (count($split) !== count($parameters)) {
72            throw new InvalidStringForValueObjectException($input, new ReflectionClass(static::class));
73        }
74        $constructorArguments = [];
75        foreach ($parameters as $key => $parameter) {
76            $parameterType = $parameter->getType();
77            if (!($parameterType instanceof ReflectionNamedType)) {
78                throw new InvalidTypeException($parameterType, 'ReflectionNamedType');
79            }
80            if ($parameterType->allowsNull() && $split[$key] === '') {
81                $constructorArguments[] = null;
82            } else {
83                $constructorArguments[] = Utils::toTypehint($parameterType, $split[$key]);
84            }
85        }
86        return $refl->newInstanceArgs($constructorArguments);
87    }
88
89    final public static function getRegularExpression(): string
90    {
91        $refl = new ReflectionClass(static::class);
92        $parameters = $refl->getConstructor()->getParameters();
93        $separator = preg_quote(static::getSeparator());
94
95        $expressions = [];
96        $prefix = '';
97        if (is_callable([static::class, 'getPrefix'])) {
98            $prefix = static::getPrefix() . '-';
99        }
100        if ($prefix !== '') {
101            $expressions[] = preg_quote($prefix, static::getSeparator());
102        }
103        foreach ($parameters as $parameter) {
104            $parameterType = $parameter->getType();
105            if (!($parameterType instanceof ReflectionNamedType)) {
106                throw new InvalidTypeException($parameterType, 'ReflectionNamedType');
107            }
108            $regex = '[^' . $separator . ']+';
109            $class = ConverterUtils::toReflectionClass($parameterType);
110            if (in_array(HasRegexValueObjectInterface::class, $class?->getInterfaceNames() ?? [])) {
111                $foundRegex = '(' . RegexUtils::removeDelimiters($class->getMethod('getRegularExpression')->invoke(null)) . ')';
112                if (strpos($foundRegex, '?=') === false) {
113                    $regex = $foundRegex;
114                }
115            } else {
116                switch ($parameterType->getName()) {
117                    case 'int':
118                        $regex = '-?(0|[1-9]\d*)';
119                        break;
120                    case 'float':
121                        $regex = '-?(0|[1-9]\d*)(\.\d+)?';
122                        break;
123                }
124            }
125            $expressions[] = $regex;
126            $expressions[] = $separator;
127        }
128        array_pop($expressions);
129
130        $expressions = array_map(
131            function (string $expression) {
132                return CompiledRegularExpression::createFromRegexWithoutDelimiters($expression)
133                            ->removeStartAndEndMarkers();
134            },
135            $expressions
136        );
137        array_unshift($expressions, CompiledRegularExpression::createFromRegexWithoutDelimiters('^'));
138        array_push($expressions, CompiledRegularExpression::createFromRegexWithoutDelimiters('$'));
139
140        $tmp = CompiledRegularExpression::createFromRegexWithoutDelimiters('');
141
142        return $tmp->merge(...$expressions)->__toString();
143    }
144}