HashidsParamConverter::allCharsAreInAlphabet()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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