Passed
Pull Request — master (#20)
by Mathias
01:46
created

HashidsParamConverter::getHash()   B

Complexity

Conditions 7
Paths 13

Size

Total Lines 26
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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