Passed
Push — master ( 53f704...4e0528 )
by Magnar Ovedal
02:54
created

CharacterClass::getMessage()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 42
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 5
nop 1
dl 0
loc 42
ccs 25
cts 25
cp 1
crap 5
rs 9.1768
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\Rule;
6
7
use InvalidArgumentException;
8
use Stadly\PasswordPolice\Rule;
9
use Stadly\PasswordPolice\RuleException;
10
use Symfony\Component\Translation\Translator;
11
12
class CharacterClass implements Rule
13
{
14
    /**
15
     * @var string Characters matching the rule.
16
     */
17
    private $characters;
18
19
    /**
20
     * @var int Minimum number of characters matching the rule in password.
21
     */
22
    private $min;
23
24
    /**
25
     * @var int|null Maximum number of characters matching the rule in password.
26
     */
27
    private $max;
28
29 8
    public function __construct(string $characters, int $min, ?int $max = null)
30
    {
31 8
        if ($characters === '') {
32 1
            throw new InvalidArgumentException('At least one character must be specified.');
33
        }
34 7
        if ($min < 0) {
35 1
            throw new InvalidArgumentException('Min cannot be negative.');
36
        }
37 6
        if ($max !== null && $max < $min) {
38 1
            throw new InvalidArgumentException('Max cannot be smaller than min.');
39
        }
40 5
        if ($min === 0 && $max === null) {
41 1
            throw new InvalidArgumentException('Min cannot be zero when max is unconstrained.');
42
        }
43
44 4
        $this->characters = $characters;
45 4
        $this->min = $min;
46 4
        $this->max = $max;
47 4
    }
48
49 1
    public function getCharacters(): string
50
    {
51 1
        return $this->characters;
52
    }
53
54 1
    public function getMin(): int
55
    {
56 1
        return $this->min;
57
    }
58
59 1
    public function getMax(): ?int
60
    {
61 1
        return $this->max;
62
    }
63
64 4
    public function test(string $password): bool
65
    {
66 4
        if ($this->getCount($password) < $this->min) {
67 1
            return false;
68
        }
69
70 3
        if (null !== $this->max && $this->max < $this->getCount($password)) {
71 1
            return false;
72
        }
73
74 2
        return true;
75
    }
76
77
    /**
78
     * @throws RuleException If the rule cannot be enforced.
79
     */
80 2
    public function enforce(string $password, Translator $translator): void
81
    {
82 2
        if (!$this->test($password)) {
83 1
            throw new RuleException($this, $this->getMessage($translator));
84
        }
85 1
    }
86
87 5
    public function getMessage(Translator $translator): string
88
    {
89 5
        if ($this->getMax() === null) {
90 1
            return $translator->transChoice(
91
                'There must be at least one character matching %characters%.|'.
92 1
                'There must be at least %count% characters matching %characters%.',
93 1
                $this->getMin(),
94 1
                ['%characters%' => $this->getCharacters()]
95
            );
96
        }
97
98 4
        if ($this->getMax() === 0) {
99 1
            return $translator->trans(
100 1
                'There must be no characters matching %characters%.',
101 1
                ['%characters%' => $this->getCharacters()]
102
            );
103
        }
104
105 3
        if ($this->getMin() === 0) {
106 1
            return $translator->transChoice(
107
                'There must be at most one character matching %characters%.|'.
108 1
                'There must be at most %count% characters matching %characters%.',
109 1
                $this->getMax(),
110 1
                ['%characters%' => $this->getCharacters()]
111
            );
112
        }
113
114 2
        if ($this->getMin() === $this->getMax()) {
115 1
            return $translator->transChoice(
116
                'There must be exactly one character matching %characters%.|'.
117 1
                'There must be exactly %count% characters matching %characters%.',
118 1
                $this->getMin(),
119 1
                ['%characters%' => $this->getCharacters()]
120
            );
121
        }
122
123 1
        return $translator->trans(
124 1
            'There must be between %min% and %max% characters matching %characters%.',
125
            [
126 1
                '%min%' => $this->getMin(),
127 1
                '%max%' => $this->getMax(),
128 1
                '%characters%' => $this->getCharacters()
129
            ]
130
        );
131
    }
132
133 6
    private function getCount(string $password): int
134
    {
135 6
        $escapedCharacters = preg_quote($this->characters);
136 6
        $count = preg_match_all('{['.$escapedCharacters.']}u', $password);
137 6
        assert(false !== $count);
138
139 6
        return $count;
140
    }
141
}
142