Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
71.76% covered (warning)
71.76%
61 / 85
71.43% covered (warning)
71.43%
10 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
AbstractTranslation
71.76% covered (warning)
71.76%
61 / 85
71.43% covered (warning)
71.43%
10 / 14
27.13
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 getPlaceholders
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getRegularExpression
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 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
 fromNative
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
1
 createRandom
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 withoutBoundedContextId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 withBoundedContextId
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 withoutResourceIdentifier
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 withResourceIdentifier
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getSpecifity
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSimplifications
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
4
 getFallbackText
n/a
0 / 0
n/a
0 / 0
0
1<?php
2namespace Apie\Core\Translator\ValueObjects;
3
4use Apie\Core\ApieLib;
5use Apie\Core\Attributes\Description;
6use Apie\Core\Attributes\ExampleValue;
7use Apie\Core\Attributes\FakeMethod;
8use Apie\Core\BoundedContext\BoundedContextId;
9use Apie\Core\Identifiers\SnakeCaseSlug;
10use Apie\Core\Lists\ItemHashmap;
11use Apie\Core\RegexUtils;
12use Apie\Core\Translator\Lists\TranslationStringSet;
13use Apie\Core\ValueObjects\Exceptions\InvalidStringForValueObjectException;
14use Apie\Core\ValueObjects\Interfaces\HasRegexValueObjectInterface;
15use Apie\Core\ValueObjects\Utils;
16use Faker\Generator as FakerGenerator;
17use ReflectionClass;
18use RegRev\RegRev;
19
20#[Description('A translation string for an Apie translation following a rigid, predictable structure.')]
21#[ExampleValue('apie.bounded.example.resource.user.example.test.singular.authenticated')]
22#[FakeMethod('createRandom')]
23abstract class AbstractTranslation implements HasRegexValueObjectInterface
24{
25    protected const PREFIX_REGEX = 'apie\.(bounded\.([a-z][a-z0-9]*)\.)?(resource\.([a-z0-9]+(_[a-z0-9]+)*)\.)?';
26    /**
27     * Override this to define the translation namespace.
28     * Must not include prefix or suffix separators.
29     */
30    protected const MIDDLE_REGEX = '[^.]+(\.[^.]+)*';
31    protected const SUFFIX_REGEX = '(\.(singular|plural))?(\.(authenticated|unauthenticated))?';
32
33    final protected function __construct(
34        protected TranslationStringPrefix $prefix,
35        protected string $middleSection,
36        protected TranslationStringSuffix $suffix,
37        protected ItemHashmap $placeholderValue
38    ) {
39        $regex = RegexUtils::fromPlaceholderToRegularExpression(static::MIDDLE_REGEX, includeAsCaptureGroup: false);
40        if (!preg_match($regex, $middleSection)) {
41            throw new InvalidStringForValueObjectException(
42                $prefix . $middleSection . $suffix,
43                new ReflectionClass(static::class)
44            );
45        }
46    }
47
48    public function getPlaceholders(): ItemHashmap
49    {
50        return $this->placeholderValue;
51    }
52
53    public static function getRegularExpression(): string
54    {
55        $regex = RegexUtils::fromPlaceholderToRegularExpression(static::MIDDLE_REGEX, false, false);
56        // the str_replace is a temporary fix because RegRev seems to read [^.] incorrectly.
57        return str_replace(
58            '[^.]+',
59            '[a-zA-Z0-9]+',
60            '/^' . self::PREFIX_REGEX . $regex . self::SUFFIX_REGEX . '$/'
61        );
62    }
63
64    final public function toNative(): string
65    {
66        return $this->prefix . $this->middleSection . $this->suffix;
67    }
68
69    final public function __toString(): string
70    {
71        return $this->toNative();
72    }
73
74    final public function jsonSerialize(): string
75    {
76        return $this->toNative();
77    }
78
79    final public static function fromNative(mixed $input): static
80    {
81        $input = Utils::toString($input);
82        $prefix = TranslationStringPrefix::createFromTranslation($input);
83        $inputSection = substr($input, strlen($prefix->toNative()));
84        $suffix = TranslationStringSuffix::createFromTranslation($inputSection);
85    
86        $middleSection = substr($inputSection, 0, strlen($inputSection) - strlen($suffix->toNative()));
87
88        $regex = RegexUtils::fromPlaceholderToRegularExpression(static::MIDDLE_REGEX);
89        preg_match($regex, $middleSection, $placeholders);
90        // @phpstan-ignore-next-line nullCoalesce.variable
91        $placeholders = array_filter($placeholders ?? [], fn ($k) => !is_numeric($k), ARRAY_FILTER_USE_KEY);
92        return new static(
93            $prefix,
94            $middleSection,
95            $suffix,
96            (new ItemHashmap($placeholders))
97        );
98    }
99
100    public static function createRandom(FakerGenerator $factory): static
101    {
102        if (static::class === AbstractTranslation::class) {
103            $aliases = explode('|', ApieLib::getAlias(AbstractTranslation::class));
104            $alias = $factory->randomElement($aliases);
105
106            return $alias::createRandom($factory);
107        }
108
109        $regularExpressionWithDelimiter = static::getRegularExpression();
110        $regex = RegexUtils::removeDelimiters($regularExpressionWithDelimiter);
111        return static::fromNative(RegRev::generate($regex));
112    }
113
114    public function withoutBoundedContextId(): static
115    {
116        return new static(
117            $this->prefix->withoutBoundedContextId(),
118            $this->middleSection,
119            $this->suffix,
120            $this->placeholderValue
121        );
122    }
123
124    public function withBoundedContextId(BoundedContextId $boundedContextId): static
125    {
126        return new static(
127            $this->prefix->withBoundedContextId($boundedContextId),
128            $this->middleSection,
129            $this->suffix,
130            $this->placeholderValue
131        );
132    }
133
134    public function withoutResourceIdentifier(): static
135    {
136        return new static(
137            $this->prefix->withoutResourceIdentifier(),
138            $this->middleSection,
139            $this->suffix,
140            $this->placeholderValue
141        );
142    }
143
144    public function withResourceIdentifier(SnakeCaseSlug $resourceIdentifier): static
145    {
146        return new static(
147            $this->prefix->withResourceIdentifier($resourceIdentifier),
148            $this->middleSection,
149            $this->suffix,
150            $this->placeholderValue
151        );
152    }
153
154    final public function getSpecifity(): int
155    {
156        return $this->prefix->getSpecifity() + $this->suffix->getSpecifity();
157    }
158
159    final public function getSimplifications(): TranslationStringSet
160    {
161        $list = [];
162        foreach ($this->prefix->getSimplifications() as $prefixSimplification) {
163            $list[] = new static(
164                $prefixSimplification,
165                $this->middleSection,
166                $this->suffix,
167                $this->placeholderValue
168            );
169            foreach ($this->suffix->getSimplifications() as $suffixSimplification) {
170                $list[] = new static(
171                    $prefixSimplification,
172                    $this->middleSection,
173                    $suffixSimplification,
174                    $this->placeholderValue
175                );
176            }
177        }
178        foreach ($this->suffix->getSimplifications() as $suffixSimplification) {
179            $list[] = new static(
180                $this->prefix,
181                $this->middleSection,
182                $suffixSimplification,
183                $this->placeholderValue
184            );
185        }
186        return new TranslationStringSet($list);
187    }
188
189    abstract public function getFallbackText(): string;
190}