Completed
Push — master ( 1d4d60...6612df )
by Magnar Ovedal
09:45 queued 07:05
created

SubstringGenerator   A

Complexity

Total Complexity 16

Size/Duplication

Total Lines 90
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 32
dl 0
loc 90
ccs 34
cts 34
cp 1
rs 10
c 0
b 0
f 0
wmc 16

4 Methods

Rating   Name   Duplication   Size   Complexity  
A formatWordsUnique() 0 20 6
A formatWords() 0 8 4
A applyCurrent() 0 6 2
A __construct() 0 12 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Stadly\PasswordPolice\WordFormatter;
6
7
use InvalidArgumentException;
8
use Traversable;
9
10
final class SubstringGenerator extends ChainableFormatter
11
{
12
    /**
13
     * @var int Minimum substring length.
14
     */
15
    private $minLength;
16
17
    /**
18
     * @var int|null Maximum substring length.
19
     */
20
    private $maxLength;
21
22
    /**
23
     * @var bool Whether the result should only contain unique words.
24
     */
25
    private $filterUnique;
26
27
    /**
28
     * @param int $minLength Ignore substrings shorter thant this.
29
     * @param int|null $maxLength Ignore substrings longer than this.
30
     * @param bool $filterUnique Whether the result should only contain unique words.
31
     */
32 6
    public function __construct(int $minLength = 3, ?int $maxLength = 25, bool $filterUnique = true)
33
    {
34 6
        if ($minLength < 1) {
35 2
            throw new InvalidArgumentException('Min length must be positive.');
36
        }
37 4
        if ($maxLength !== null && $maxLength < $minLength) {
38 1
            throw new InvalidArgumentException('Max length cannot be smaller than min length.');
39
        }
40
41 3
        $this->minLength = $minLength;
42 3
        $this->maxLength = $maxLength;
43 3
        $this->filterUnique = $filterUnique;
44 3
    }
45
46
    /**
47
     * @param iterable<string> $words Words to format.
48
     * @return Traversable<string> All substrings of the words.
49
     */
50 3
    protected function applyCurrent(iterable $words): Traversable
51
    {
52 3
        if ($this->filterUnique) {
53 2
            yield from $this->formatWordsUnique($words);
54
        } else {
55 1
            yield from $this->formatWords($words);
56
        }
57 3
    }
58
59
    /**
60
     * @param iterable<string> $words Words to format.
61
     * @return Traversable<string> All substrings of the words without duplicates.
62
     */
63 2
    private function formatWordsUnique(iterable $words): Traversable
64
    {
65 2
        $unique = [];
66 2
        foreach ($words as $word) {
67 2
            for ($start = 0; $start < mb_strlen($word); ++$start) {
68 2
                $substring = mb_substr($word, $start, $this->maxLength);
69
70 2
                if (isset($unique[$substring])) {
71 1
                    break;
72
                }
73
74 2
                for ($length = mb_strlen($substring); $this->minLength <= $length; --$length) {
75 2
                    $substring = mb_substr($substring, 0, $length);
76
77 2
                    if (isset($unique[$substring])) {
78 1
                        break;
79
                    }
80
81 2
                    $unique[$substring] = true;
82 2
                    yield $substring;
83
                }
84
            }
85
        }
86 2
    }
87
88
    /**
89
     * @param iterable<string> $words Words to format.
90
     * @return Traversable<string> All substrings of the words. May contain duplicates.
91
     */
92 1
    private function formatWords(iterable $words): Traversable
93
    {
94 1
        foreach ($words as $word) {
95 1
            for ($start = 0; $start < mb_strlen($word); ++$start) {
96 1
                $substring = mb_substr($word, $start, $this->maxLength);
97
98 1
                for ($length = mb_strlen($substring); $this->minLength <= $length; --$length) {
99 1
                    yield mb_substr($substring, 0, $length);
100
                }
101
            }
102
        }
103 1
    }
104
}
105