Passed
Push — master ( ae0a03...686af4 )
by Magnar Ovedal
03:17
created

CharacterClass::getMin()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Password;
9
use Stadly\PasswordPolice\Policy;
10
11
class CharacterClass implements RuleInterface
12
{
13
    /**
14
     * @var string Characters matched by the rule.
15
     */
16
    protected $characters;
17
18
    /**
19
     * @var int Minimum number of characters matching the rule.
20
     */
21
    protected $min;
22
23
    /**
24
     * @var int|null Maximum number of characters matching the rule.
25
     */
26
    protected $max;
27
28
    /**
29
     * @param string $characters Characters matched by the rule.
30
     * @param int $min Minimum number of characters matching the rule.
31
     * @param int|null $max Maximum number of characters matching the rule.
32
     */
33 8
    public function __construct(string $characters, int $min = 1, ?int $max = null)
34
    {
35 8
        if ($characters === '') {
36 1
            throw new InvalidArgumentException('At least one character must be specified.');
37
        }
38 7
        if ($min < 0) {
39 1
            throw new InvalidArgumentException('Min cannot be negative.');
40
        }
41 6
        if ($max !== null && $max < $min) {
42 1
            throw new InvalidArgumentException('Max cannot be smaller than min.');
43
        }
44 5
        if ($min === 0 && $max === null) {
45 1
            throw new InvalidArgumentException('Min cannot be zero when max is unconstrained.');
46
        }
47
48 4
        $this->characters = $characters;
49 4
        $this->min = $min;
50 4
        $this->max = $max;
51 4
    }
52
53
    /**
54
     * @return string Characters matched by the rule.
55
     */
56 1
    public function getCharacters(): string
57
    {
58 1
        return $this->characters;
59
    }
60
61
    /**
62
     * @return int Minimum number of characters matching the rule.
63
     */
64 2
    public function getMin(): int
65
    {
66 2
        return $this->min;
67
    }
68
69
    /**
70
     * @return int|null Maximum number of characters matching the rule.
71
     */
72 2
    public function getMax(): ?int
73
    {
74 2
        return $this->max;
75
    }
76
77
    /**
78
     * Check whether a password is in compliance with the rule.
79
     *
80
     * @param Password|string $password Password to check.
81
     * @return bool Whether the password is in compliance with the rule.
82
     */
83 8
    public function test($password): bool
84
    {
85 8
        $count = $this->getNoncompliantCount((string)$password);
86
87 8
        return $count === null;
88
    }
89
90
    /**
91
     * Enforce that a password is in compliance with the rule.
92
     *
93
     * @param Password|string $password Password that must adhere to the rule.
94
     * @throws RuleException If the password does not adhrere to the rule.
95
     */
96 4
    public function enforce($password): void
97
    {
98 4
        $count = $this->getNoncompliantCount((string)$password);
99
100 4
        if ($count !== null) {
101 2
            throw new RuleException($this, $this->getMessage());
102
        }
103 2
    }
104
105
    /**
106
     * @param string $password Password to count characters in.
107
     * @return int Number of characters matching the rule if not in compliance with the rule.
108
     */
109 12
    private function getNoncompliantCount(string $password): ?int
110
    {
111 12
        $count = $this->getCount($password);
112
113 12
        if ($count < $this->min) {
114 4
            return $count;
115
        }
116
117 8
        if (null !== $this->max && $this->max < $count) {
118 2
            return $count;
119
        }
120
121 6
        return null;
122
    }
123
124
    /**
125
     * @param string $password Password to count characters in.
126
     * @return int Number of characters matching the rule.
127
     */
128 12
    private function getCount(string $password): int
129
    {
130 12
        $escapedCharacters = preg_quote($this->characters);
131 12
        $count = preg_match_all('{['.$escapedCharacters.']}u', $password);
132 12
        assert(false !== $count);
133
134 12
        return $count;
135
    }
136
137
    /**
138
     * {@inheritDoc}
139
     */
140 5
    public function getMessage(): string
141
    {
142 5
        $translator = Policy::getTranslator();
143
144 5
        if ($this->max === null) {
145 1
            return $translator->trans(
146
                'There must be at least one character matching %characters%.|'.
147 1
                'There must be at least %count% characters matching %characters%.',
148
                [
149 1
                    '%count%' => $this->min,
150 1
                    '%characters%' => $this->characters,
151
                ]
152
            );
153
        }
154
155 4
        if ($this->max === 0) {
156 1
            return $translator->trans(
157 1
                'There must be no characters matching %characters%.',
158 1
                ['%characters%' => $this->characters]
159
            );
160
        }
161
162 3
        if ($this->min === 0) {
163 1
            return $translator->trans(
164
                'There must be at most one character matching %characters%.|'.
165 1
                'There must be at most %count% characters matching %characters%.',
166
                [
167 1
                    '%count%' => $this->max,
168 1
                    '%characters%' => $this->characters,
169
                ]
170
            );
171
        }
172
173 2
        if ($this->min === $this->max) {
174 1
            return $translator->trans(
175
                'There must be exactly one character matching %characters%.|'.
176 1
                'There must be exactly %count% characters matching %characters%.',
177
                [
178 1
                    '%count%' => $this->min,
179 1
                    '%characters%' => $this->characters,
180
                ]
181
            );
182
        }
183
184 1
        return $translator->trans(
185 1
            'There must be between %min% and %max% characters matching %characters%.',
186
            [
187 1
                '%min%' => $this->min,
188 1
                '%max%' => $this->max,
189 1
                '%characters%' => $this->characters,
190
            ]
191
        );
192
    }
193
}
194