Completed
Push — development ( 1fb984...7ea9e3 )
by Nils
08:29
created

RequirementPasswordGenerator   B

Complexity

Total Complexity 40

Size/Duplication

Total Lines 244
Duplicated Lines 24.59 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 60
loc 244
rs 8.2608
c 0
b 0
f 0
wmc 40
lcom 1
cbo 1

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A generatePassword() 0 16 4
A getMinimumCount() 0 4 2
A getMaximumCount() 0 4 2
B setMinimumCount() 22 22 5
B setMaximumCount() 22 22 5
D validLimits() 0 46 10
A validOption() 0 4 1
A generatePasswords() 16 16 4
B validatePassword() 0 18 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like RequirementPasswordGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RequirementPasswordGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PasswordGenerator\Generator;
4
5
use PasswordGenerator\Exception\ImpossibleMinMaxLimitsException;
6
use PasswordGenerator\Exception\InvalidOptionException;
7
8
/**
9
 * Class RequirementPasswordGenerator
10
 *
11
 * Works just like ComputerPasswordGenerator with the addition of minimum and maximum counts.
12
 *
13
 * @package Hackzilla\PasswordGenerator\Generator
14
 */
15
class RequirementPasswordGenerator extends ComputerPasswordGenerator
16
{
17
    private $minimumCounts = array();
18
    private $maximumCounts = array();
19
    private $validOptions = array();
20
    private $dirtyCheck = true;
21
22
    /**
23
     */
24
    public function __construct()
25
    {
26
        parent::__construct();
27
28
        $this->validOptions = array(
29
            self::OPTION_UPPER_CASE,
30
            self::OPTION_LOWER_CASE,
31
            self::OPTION_NUMBERS,
32
            self::OPTION_SYMBOLS,
33
        );
34
    }
35
36
    /**
37
     * Generate one password based on options.
38
     *
39
     * @return string password
40
     * @throws ImpossibleMinMaxLimitsException
41
     * @throws \Hackzilla\PasswordGenerator\Exception\CharactersNotFoundException
42
     */
43
    public function generatePassword()
44
    {
45
        if ($this->dirtyCheck) {
46
            if (!$this->validLimits()) {
47
                throw new ImpossibleMinMaxLimitsException();
48
            }
49
50
            $this->dirtyCheck = false;
51
        }
52
53
        do {
54
            $password = parent::generatePassword();
55
        } while (!$this->validatePassword($password));
56
57
        return $password;
58
    }
59
60
    /**
61
     * Password minimum count for option.
62
     *
63
     * @param string $option Use class constants
64
     *
65
     * @return int|null
66
     */
67
    public function getMinimumCount($option)
68
    {
69
        return isset($this->minimumCounts[$option]) ? $this->minimumCounts[$option] : null;
70
    }
71
72
    /**
73
     * Password maximum count for option.
74
     *
75
     * @param string $option Use class constants
76
     *
77
     * @return int|null
78
     */
79
    public function getMaximumCount($option)
80
    {
81
        return isset($this->maximumCounts[$option]) ? $this->maximumCounts[$option] : null;
82
    }
83
84
    /**
85
     * Set minimum count of option for desired password(s).
86
     *
87
     * @param string   $option Use class constants
88
     * @param int|null $characterCount
89
     *
90
     * @return $this
91
     *
92
     * @throws InvalidOptionException
93
     */
94 View Code Duplication
    public function setMinimumCount($option, $characterCount)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
95
    {
96
        $this->dirtyCheck = true;
97
98
        if (!$this->validOption($option)) {
99
            throw new InvalidOptionException('Invalid Option');
100
        }
101
102
        if (is_null($characterCount)) {
103
            unset($this->minimumCounts[$option]);
104
105
            return $this;
106
        }
107
108
        if (!is_int($characterCount) || $characterCount < 0) {
109
            throw new \InvalidArgumentException('Expected non-negative integer');
110
        }
111
112
        $this->minimumCounts[$option] = $characterCount;
113
114
        return $this;
115
    }
116
117
    /**
118
     * Set maximum count of option for desired password(s).
119
     *
120
     * @param string   $option Use class constants
121
     * @param int|null $characterCount
122
     *
123
     * @return $this
124
     *
125
     * @throws InvalidOptionException
126
     */
127 View Code Duplication
    public function setMaximumCount($option, $characterCount)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
128
    {
129
        $this->dirtyCheck = true;
130
131
        if (!$this->validOption($option)) {
132
            throw new InvalidOptionException('Invalid Option');
133
        }
134
135
        if (is_null($characterCount)) {
136
            unset($this->maximumCounts[$option]);
137
138
            return $this;
139
        }
140
141
        if (!is_int($characterCount) || $characterCount < 0) {
142
            throw new \InvalidArgumentException('Expected non-negative integer');
143
        }
144
145
        $this->maximumCounts[$option] = $characterCount;
146
147
        return $this;
148
    }
149
150
    public function validLimits()
151
    {
152
        $elements = 0;
153
154
        if ($this->getOptionValue(self::OPTION_UPPER_CASE)) {
155
            $elements++;
156
        }
157
158
        if ($this->getOptionValue(self::OPTION_LOWER_CASE)) {
159
            $elements++;
160
        }
161
162
        if ($this->getOptionValue(self::OPTION_NUMBERS)) {
163
            $elements++;
164
        }
165
166
        if ($this->getOptionValue(self::OPTION_SYMBOLS)) {
167
            $elements++;
168
        }
169
170
        // check if there is wiggle room in minimums
171
        $total = 0;
172
173
        foreach ($this->minimumCounts as $minOption => $minCount) {
174
            $total += $minCount;
175
        }
176
177
        if ($total > $this->getLength()) {
178
            return false;
179
        }
180
181
        // check if there is wiggle room in maximums
182
        if ($elements <= count($this->maximumCounts)) {
183
            $total = 0;
184
185
            foreach ($this->maximumCounts as $maxOption => $maxCount) {
186
                $total += $maxCount;
187
            }
188
189
            if ($total < $this->getLength()) {
190
                return false;
191
            }
192
        }
193
194
        return true;
195
    }
196
197
    /**
198
     * @param string $option
199
     *
200
     * @return bool
201
     */
202
    public function validOption($option)
203
    {
204
        return in_array($option, $this->validOptions, true);
205
    }
206
207
    /**
208
     * Generate $count number of passwords.
209
     *
210
     * @param int $count Number of passwords to return
211
     *
212
     * @return array
213
     *
214
     * @throws \InvalidArgumentException
215
     */
216 View Code Duplication
    public function generatePasswords($count = 1)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
    {
218
        if (!is_int($count)) {
219
            throw new \InvalidArgumentException('Expected integer');
220
        } elseif ($count < 0) {
221
            throw new \InvalidArgumentException('Expected positive integer');
222
        }
223
224
        $passwords = array();
225
226
        for ($i = 0; $i < $count; $i++) {
227
            $passwords[] = $this->generatePassword();
228
        }
229
230
        return $passwords;
231
    }
232
233
    /**
234
     * Check password is valid when comparing to minimum and maximum counts of options.
235
     *
236
     * @param string $password
237
     *
238
     * @return bool
239
     */
240
    public function validatePassword($password)
241
    {
242
        foreach ($this->validOptions as $option) {
243
            $minCount = $this->getMinimumCount($option);
244
            $maxCount = $this->getMaximumCount($option);
245
            $count = strlen(preg_replace('|[^'.preg_quote($this->getParameter($option)).']|', '', $password));
246
247
            if (!is_null($minCount) && $count < $minCount) {
248
                return false;
249
            }
250
251
            if (!is_null($maxCount) && $count > $maxCount) {
252
                return false;
253
            }
254
        }
255
256
        return true;
257
    }
258
}
259