Passed
Push — master ( 5be2ac...dd6b03 )
by Magnar Ovedal
03:09
created

CharacterClass::getViolation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 3
nop 1
dl 0
loc 9
ccs 5
cts 5
cp 1
crap 3
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 StableSort\StableSort;
9
use Stadly\PasswordPolice\Constraint\Count;
10
use Stadly\PasswordPolice\Password;
11
use Stadly\PasswordPolice\Policy;
12
13
abstract class CharacterClass implements RuleInterface
14
{
15
    /**
16
     * @var string Characters matched by the rule.
17
     */
18
    protected $characters;
19
20
    /**
21
     * @var Count[] Rule constraints.
22
     */
23
    private $constraints;
24
25
    /**
26
     * @param string $characters Characters matched by the rule.
27
     * @param int $min Minimum number of characters matching the rule.
28
     * @param int|null $max Maximum number of characters matching the rule.
29
     * @param int $weight Constraint weight.
30
     */
31 16
    public function __construct(string $characters, int $min = 1, ?int $max = null, int $weight = 1)
32
    {
33 16
        if ($characters === '') {
34 2
            throw new InvalidArgumentException('At least one character must be specified.');
35
        }
36
37 14
        $this->characters = $characters;
38 14
        $this->addConstraint($min, $max, $weight);
39 10
    }
40
41
    /**
42
     * @param int $min Minimum number of characters matching the rule.
43
     * @param int|null $max Maximum number of characters matching the rule.
44
     * @param int $weight Constraint weight.
45
     * @return $this
46
     */
47 6
    public function addConstraint(int $min = 1, ?int $max = null, int $weight = 1): self
48
    {
49 6
        $this->constraints[] = new Count($min, $max, $weight);
50
51
        StableSort::usort($this->constraints, function (Count $a, Count $b): int {
52 6
            return $b->getWeight() <=> $a->getWeight();
53 6
        });
54
55 6
        return $this;
56
    }
57
58
    /**
59
     * @return string Characters matched by the rule.
60
     */
61 2
    public function getCharacters(): string
62
    {
63 2
        return $this->characters;
64
    }
65
66
    /**
67
     * Check whether a password is in compliance with the rule.
68
     *
69
     * @param Password|string $password Password to check.
70
     * @return bool Whether the password is in compliance with the rule.
71
     */
72 12
    public function test($password): bool
73
    {
74 12
        $count = $this->getCount((string)$password);
75 12
        $constraint = $this->getViolation($count);
76
77 12
        return $constraint === null;
78
    }
79
80
    /**
81
     * Enforce that a password is in compliance with the rule.
82
     *
83
     * @param Password|string $password Password that must adhere to the rule.
84
     * @throws RuleException If the password does not adhrere to the rule.
85
     */
86 16
    public function enforce($password): void
87
    {
88 16
        $count = $this->getCount((string)$password);
89 16
        $constraint = $this->getViolation($count);
90
91 16
        if ($constraint !== null) {
92 13
            throw new RuleException($this, $this->getMessage($constraint, $count));
93
        }
94 3
    }
95
96
    /**
97
     * @param int $count Number of characters matching the rule.
98
     * @return Count|null Constraint violated by the count.
99
     */
100 28
    private function getViolation(int $count): ?Count
101
    {
102 28
        foreach ($this->constraints as $constraint) {
103 28
            if (!$constraint->test($count)) {
104 28
                return $constraint;
105
            }
106
        }
107
108 9
        return null;
109
    }
110
111
    /**
112
     * @param string $password Password to count characters in.
113
     * @return int Number of characters matching the rule.
114
     */
115 28
    private function getCount(string $password): int
116
    {
117 28
        $escapedCharacters = preg_quote($this->characters);
118 28
        $count = preg_match_all('{['.$escapedCharacters.']}u', $password);
119 28
        assert(false !== $count);
120
121 28
        return $count;
122
    }
123
124
    /**
125
     * @param Count $constraint Constraint that is violated.
126
     * @param int $count Count that violates the constraint.
127
     * @return string Message explaining the violation.
128
     */
129
    abstract protected function getMessage(Count $constraint, int $count): string;
130
}
131