Passed
Push — master ( 49a7e1...6c8d7b )
by Magnar Ovedal
02:50
created

SubstringGenerator::applyCurrent()   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 1
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\Formatter;
6
7
use InvalidArgumentException;
8
use Stadly\PasswordPolice\CharTree;
9
use Stadly\PasswordPolice\Formatter;
10
11
final class SubstringGenerator implements Formatter
12
{
13
    use Chaining;
14
15
    /**
16
     * @var int Minimum substring length.
17
     */
18
    private $minLength;
19
20
    /**
21
     * @var int|null Maximum substring length.
22
     */
23
    private $maxLength;
24
25
    /**
26
     * @var CharTree[] Memoization for already filtered character trees.
27
     */
28
    private $substringMemoization = [];
29
30
    /**
31
     * @var CharTree[] Memoization for already filtered character trees.
32
     */
33
    private $startsWithMemoization = [];
34
35
    /**
36
     * @param int $minLength Ignore substrings shorter than this.
37
     * @param int|null $maxLength Ignore substrings longer than this.
38
     */
39 6
    public function __construct(int $minLength = 3, ?int $maxLength = 25)
40
    {
41 6
        if ($minLength < 0) {
42 1
            throw new InvalidArgumentException('Min length must be non-negative.');
43
        }
44 5
        if ($maxLength !== null && $maxLength < $minLength) {
45 1
            throw new InvalidArgumentException('Max length cannot be smaller than min length.');
46
        }
47
48 4
        $this->minLength = $minLength;
49 4
        $this->maxLength = $maxLength;
50 4
    }
51
52
    /**
53
     * @param CharTree $charTree Character tree to format.
54
     * @return CharTree Substrings of the character tree.
55
     */
56 4
    protected function applyCurrent(CharTree $charTree): CharTree
57
    {
58 4
        return $this->applyInternal($charTree, $this->minLength, $this->maxLength);
59
    }
60
61
    /**
62
     * @param CharTree $charTree Character tree to format.
63
     * @param int $minLength Minimum substring length.
64
     * @param int|null $maxLength Maximum substring length.
65
     * @return CharTree Substrings of the character tree.
66
     */
67 4
    private function applyInternal(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
68
    {
69
        // When PHP 7.1 is no longer supported, change to using spl_object_id.
70 4
        $hash = spl_object_hash($charTree).';'.$minLength.';'.$maxLength;
71
72 4
        if (!isset($this->substringMemoization[$hash])) {
73 4
            $this->substringMemoization[$hash] = $this->generate($charTree, $minLength, $maxLength);
74
        }
75
76 4
        return $this->substringMemoization[$hash];
77
    }
78
79
    /**
80
     * @param CharTree $charTree Character tree to format.
81
     * @param int $minLength Minimum substring length.
82
     * @param int|null $maxLength Maximum substring length.
83
     * @return CharTree Substrings of the character tree. Memoization is not used.
84
     */
85 4
    private function generate(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
86
    {
87 4
        $charTrees = [];
88
89 4
        $containsCharTree = $this->generateContains($charTree, $minLength, $maxLength);
90 4
        if ($containsCharTree->getRoot() !== null) {
91 4
            $charTrees[] = $containsCharTree;
92
        }
93
94 4
        $startsWithCharTree = $this->applyInternalStartsWith($charTree, $minLength, $maxLength);
95 4
        if ($startsWithCharTree->getRoot() !== null) {
96 4
            $charTrees[] = $startsWithCharTree;
97
        }
98
99 4
        return CharTree::fromArray($charTrees);
100
    }
101
102
    /**
103
     * @param CharTree $charTree Character tree to format.
104
     * @param int $minLength Minimum substring length.
105
     * @param int|null $maxLength Maximum substring length.
106
     * @return CharTree Substrings of the character tree not starting with root.
107
     */
108 4
    private function generateContains(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
109
    {
110 4
        $root = $charTree->getRoot();
111
112 4
        if ($root === null) {
113 1
            return $charTree;
114
        }
115
116 4
        $branches = $charTree->getBranches();
117 4
        $substringBranches = [];
118 4
        if (0 < $maxLength || $maxLength === null) {
119 4
            foreach ($branches as $branch) {
120 4
                $substringBranch = $this->applyInternal($branch, $minLength, $maxLength);
121 4
                if ($substringBranch->getRoot() !== null) {
122 4
                    $substringBranches[] = $substringBranch;
123
                }
124
            }
125
        }
126
127 4
        if ($substringBranches !== [] || $minLength === 0) {
128 4
            if ($substringBranches !== [] && $minLength === 0) {
129 1
                $substringBranches[] = CharTree::fromNothing();
130
            }
131 4
            return CharTree::fromString('', $substringBranches);
132
        }
133
134 3
        return CharTree::fromNothing();
135
    }
136
137
    /**
138
     * @param CharTree $charTree Character tree to format.
139
     * @param int $minLength Minimum substring length.
140
     * @param int|null $maxLength Maximum substring length.
141
     * @return CharTree Substrings of the character tree starting with root.
142
     */
143 4
    private function applyInternalStartsWith(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
144
    {
145
        // When PHP 7.1 is no longer supported, change to using spl_object_id.
146 4
        $hash = spl_object_hash($charTree).';'.$minLength.';'.$maxLength;
147
148 4
        if (!isset($this->startsWithMemoization[$hash])) {
149 4
            $this->startsWithMemoization[$hash] = $this->generateStartsWith($charTree, $minLength, $maxLength);
150
        }
151
152 4
        return $this->startsWithMemoization[$hash];
153
    }
154
155
    /**
156
     * @param CharTree $charTree Character tree to format.
157
     * @param int $minLength Minimum substring length.
158
     * @param int|null $maxLength Maximum substring length.
159
     * @return CharTree Substrings of the character tree starting with root. Memoization is not used.
160
     */
161 4
    private function generateStartsWith(CharTree $charTree, int $minLength, ?int $maxLength): CharTree
162
    {
163 4
        $root = $charTree->getRoot();
164
165 4
        if ($root === null) {
166 1
            return $charTree;
167
        }
168
169 4
        $rootLength = mb_strlen($root);
170 4
        if ($rootLength <= $maxLength || $maxLength === null) {
171 4
            $branches = $charTree->getBranches();
172 4
            $branchMinLength = $minLength <= $rootLength ? 0 : $minLength-$rootLength;
173 4
            $branchMaxLength = $maxLength === null ? null : $maxLength-$rootLength;
174
175 4
            $substringBranches = [];
176 4
            foreach ($branches as $branch) {
177 4
                $substringBranch = $this->applyInternalStartsWith($branch, $branchMinLength, $branchMaxLength);
178 4
                if ($substringBranch->getRoot() !== null) {
179 4
                    $substringBranches[] = $substringBranch;
180
                }
181
            }
182
183 4
            if ($substringBranches !== [] || $minLength <= $rootLength) {
184 4
                if ($substringBranches !== [] && $minLength <= $rootLength) {
185 3
                    $substringBranches[] = CharTree::fromNothing();
186
                }
187 4
                return CharTree::fromString($root, $substringBranches);
188
            }
189
        }
190
191 3
        return CharTree::fromNothing();
192
    }
193
}
194