Passed
Pull Request — master (#20)
by Mathias
02:04
created

HashidsParamConverter::decodeArgumentsController()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 9
nc 5
nop 2
dl 0
loc 17
rs 9.6111
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
        $decodedSuccessfuly = $this->decodeHashidOnRoute($request);
30
31
        if ($decodedSuccessfuly === false) {
32
            $this->decodeArgumentsController($request, $configuration);
33
        }
34
35
        return $this->continueWithNextParamConverters();
36
    }
37
38
    public function supports(ParamConverter $configuration): bool
39
    {
40
        return true;
41
    }
42
43
    private function decodeHashidOnRoute(Request $request): bool
44
    {
45
        foreach (['hashid', 'id'] as $item) {
46
            $hashids = $this->hashids->decode($request->attributes->get($item));
47
            if ($this->hasHashidDecoded($hashids)) {
48
                $hashid = current($hashids);
49
50
                $request->attributes->remove($item);
51
                $request->attributes->set('id', $hashid);
52
53
                return true;
54
            }
55
        }
56
57
        return false;
58
    }
59
60
    private function decodeArgumentsController(Request $request, ParamConverter $configuration): void
61
    {
62
        $name = $configuration->getName();
63
        $hash = $this->getHash($request, $name);
64
65
        if ($this->isSkippable($hash)) {
66
            return;
67
        }
68
69
        $hashids = $this->hashids->decode($hash);
70
71
        if ($this->hasHashidDecoded($hashids)) {
72
            $request->attributes->set($name, current($hashids));
73
        }
74
75
        if (!$this->autoConvert && !$this->hasHashidDecoded($hashids)) {
76
            throw new \LogicException(sprintf('Unable to decode parameter "%s".', $name));
77
        }
78
    }
79
80
    private function getHash(Request $request, ?string $name)
81
    {
82
        $attributes = array_map(function ($name) {
83
            return str_replace('_hash_', '', $name);
84
        }, $request->attributes->keys());
85
86
        $hash = null;
87
88
        if (in_array($name, $attributes, true)) {
89
            $hash = $request->attributes->get('_hash_' . $name);
90
        }
91
92
        if (!isset($hash) && $this->autoConvert) {
93
            $hash = (string) $request->attributes->get($name);
0 ignored issues
show
Bug introduced by
It seems like $name can also be of type null; however, parameter $key of Symfony\Component\HttpFo...ion\ParameterBag::get() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
            $hash = (string) $request->attributes->get(/** @scrutinizer ignore-type */ $name);
Loading history...
94
        }
95
96
        return $hash;
97
    }
98
99
    private function isSkippable($hash): bool
100
    {
101
        return empty($hash) || !$this->allCharsAreInAlphabet($hash);
102
    }
103
104
    private function allCharsAreInAlphabet($hash): bool
105
    {
106
        return (bool) preg_match(sprintf('{^[%s]+$}', $this->alphabet), $hash);
107
    }
108
109
    private function hasHashidDecoded($hashids): bool
110
    {
111
        return $hashids && is_iterable($hashids) && is_int(reset($hashids));
112
    }
113
114
    private function continueWithNextParamConverters(): bool
115
    {
116
        return !$this->passthrough;
117
    }
118
}
119