Passed
Pull Request — master (#30)
by Mathias
12:53
created

HashidsValueResolver::getHash()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 24
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 8
eloc 13
c 1
b 0
f 0
nc 7
nop 3
dl 0
loc 24
rs 8.4444
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Roukmoute\HashidsBundle\ValueResolver;
6
7
use Hashids\HashidsInterface;
8
use Roukmoute\HashidsBundle\Attribute\Hashid;
9
use Symfony\Component\HttpFoundation\Request;
10
use Symfony\Component\HttpKernel\Controller\ValueResolverInterface;
11
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
12
13
class HashidsValueResolver implements ValueResolverInterface
14
{
15
    public function __construct(
16
        private readonly HashidsInterface $hashids,
17
        private readonly bool $passthrough,
18
        private readonly bool $autoConvert,
19
        private readonly string $alphabet,
20
    ) {
21
    }
22
23
    /**
24
     * @return iterable<int>
25
     */
26
    public function resolve(Request $request, ArgumentMetadata $argument): iterable
27
    {
28
        $name = $argument->getName();
29
        $hasHashidAttribute = $this->hasHashidAttribute($argument);
30
        [$hash, $isExplicit] = $this->getHash($request, $name, $hasHashidAttribute);
31
32
        if ($this->isSkippable($hash)) {
33
            return [];
34
        }
35
36
        $hashids = $this->hashids->decode($hash);
37
38
        if ($this->hasHashidDecoded($hashids)) {
39
            /** @var int $decodedValue */
40
            $decodedValue = reset($hashids);
41
42
            if ($this->passthrough) {
43
                $request->attributes->set($name, $decodedValue);
44
45
                return [];
46
            }
47
48
            return [$decodedValue];
49
        }
50
51
        if ($isExplicit) {
52
            throw new \LogicException(sprintf('Unable to decode parameter "%s".', $name));
53
        }
54
55
        return [];
56
    }
57
58
    private function hasHashidAttribute(ArgumentMetadata $argument): bool
59
    {
60
        return $argument->getAttributes(Hashid::class, ArgumentMetadata::IS_INSTANCEOF) !== [];
61
    }
62
63
    /**
64
     * @return array{0: string, 1: bool}
65
     */
66
    private function getHash(Request $request, string $name, bool $hasHashidAttribute): array
67
    {
68
        if (empty($name)) {
69
            return ['', false];
70
        }
71
72
        $hash = $request->attributes->get('_hash_' . $name);
73
        if (isset($hash) && is_string($hash)) {
74
            return [$hash, true];
75
        }
76
77
        if ($this->autoConvert || $hasHashidAttribute) {
78
            $hash = $request->attributes->get($name);
79
            if (is_string($hash)) {
80
                return [$hash, $hasHashidAttribute];
81
            }
82
        }
83
84
        $hash = $this->getHashFromAliases($request);
85
        if ($hash !== '') {
86
            return [$hash, true];
87
        }
88
89
        return ['', false];
90
    }
91
92
    private function getHashFromAliases(Request $request): string
93
    {
94
        $hash = '';
95
96
        if (!$request->attributes->has('hashids_prevent_alias')) {
97
            foreach (['hashid', 'id'] as $alias) {
98
                if ($request->attributes->has($alias)) {
99
                    $aliasAttribute = $request->attributes->get($alias);
100
                    if (!is_string($aliasAttribute)) {
101
                        continue;
102
                    }
103
                    $hash = $aliasAttribute;
104
                    $request->attributes->set('hashids_prevent_alias', true);
105
                    break;
106
                }
107
            }
108
        }
109
110
        return $hash;
111
    }
112
113
    private function isSkippable(string $hash): bool
114
    {
115
        return empty($hash) || !$this->allCharsAreInAlphabet($hash);
116
    }
117
118
    private function allCharsAreInAlphabet(string $hash): bool
119
    {
120
        return (bool) preg_match(sprintf('{^[%s]+$}', preg_quote($this->alphabet, '{')), $hash);
121
    }
122
123
    /**
124
     * @param array<int, ?int> $hashids
125
     */
126
    private function hasHashidDecoded(array $hashids): bool
127
    {
128
        return is_int(reset($hashids));
129
    }
130
}
131