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:
1 | <?php namespace Sample; |
||
33 | class Application |
||
34 | { |
||
35 | /** |
||
36 | * @var bool |
||
37 | */ |
||
38 | private $isOutputToConsole; |
||
39 | |||
40 | /** |
||
41 | * @param bool $isOutputToConsole |
||
42 | */ |
||
43 | public function __construct(bool $isOutputToConsole = true) |
||
47 | |||
48 | /** |
||
49 | * @return void |
||
50 | */ |
||
51 | public function run(): void |
||
57 | |||
58 | /** |
||
59 | * Shows single value validation with built-in rules. |
||
60 | */ |
||
61 | private function showSingleValueValidation(): void |
||
62 | { |
||
63 | $this->console('Basic usage sample.' . PHP_EOL); |
||
64 | $this->console('===================' . PHP_EOL); |
||
65 | |||
66 | // Let's build a rule that validates an input to be either `null` or a string from 5 to 10 characters. |
||
67 | $validator = v::validator( |
||
68 | r::nullable(r::isString(r::stringLengthBetween(5, 10))) |
||
69 | ); |
||
70 | |||
71 | // let's try validation with valid input |
||
72 | $input = null; |
||
73 | View Code Duplication | if ($validator->validate($input) === true) { |
|
|
|||
74 | $this->console("Validation OK for `null`." . PHP_EOL); |
||
75 | } else { |
||
76 | assert(false, 'We should not be here.'); |
||
77 | } |
||
78 | // another one |
||
79 | $input = 'Hello'; |
||
80 | View Code Duplication | if ($validator->validate($input) === true) { |
|
81 | $this->console("Validation OK for `$input`." . PHP_EOL); |
||
82 | } else { |
||
83 | assert(false, 'We should not be here.'); |
||
84 | } |
||
85 | // this one should not pass the validation |
||
86 | $input = 'This string is too long.'; |
||
87 | if ($validator->validate($input) === false) { |
||
88 | $this->console("Input `$input` has not passed validation." . PHP_EOL); |
||
89 | $this->printErrors($validator->getErrors()); |
||
90 | } else { |
||
91 | assert(false, 'We should not be here.'); |
||
92 | } |
||
93 | |||
94 | // next example demonstrates |
||
95 | // - parsing strings as dates |
||
96 | // - validation for dates |
||
97 | // - data capture so you don't need to parse the input second time after validation |
||
98 | $fromDate = new DateTime('2001-02-03'); |
||
99 | $toDate = new DateTime('2001-04-05'); |
||
100 | $validator = v::validator( |
||
101 | r::isString(r::stringToDateTime(DATE_ATOM, r::between($fromDate, $toDate))) |
||
102 | ->setName('my_date')->enableCapture() |
||
103 | ); |
||
104 | $input = '2001-03-04T05:06:07+08:00'; |
||
105 | if ($validator->validate($input) === true) { |
||
106 | $this->console("Validation OK for `$input`." . PHP_EOL); |
||
107 | $myDate = $validator->getCaptures()->get()['my_date']; |
||
108 | // note that captured date is already DateTime |
||
109 | assert($myDate instanceof DateTimeInterface); |
||
110 | } else { |
||
111 | assert(false, 'We should not be here.'); |
||
112 | } |
||
113 | |||
114 | $this->console(PHP_EOL . PHP_EOL . PHP_EOL); |
||
115 | |||
116 | // The output would be |
||
117 | // ------------------------------------------------------------------------------------------------------- |
||
118 | // Basic usage sample. |
||
119 | // =================== |
||
120 | // Validation OK for `null`. |
||
121 | // Validation OK for `Hello`. |
||
122 | // Input `This string is too long to pass validation.` has not passed validation. |
||
123 | // Validation failed for `This string is too long.` with: The value should be between 5 and 10 characters. |
||
124 | // Validation OK for `2001-03-04T05:06:07+08:00`. |
||
125 | // ------------------------------------------------------------------------------------------------------- |
||
126 | } |
||
127 | |||
128 | /** |
||
129 | * Shows validation for array values with custom rules. |
||
130 | */ |
||
131 | private function showArrayValuesValidation(): void |
||
132 | { |
||
133 | $this->console('Advanced usage sample.' . PHP_EOL); |
||
134 | $this->console('===================' . PHP_EOL); |
||
135 | |||
136 | // Validation rules for input are |
||
137 | // - `email` must be a string and a valid email value (as FILTER_VALIDATE_EMAIL describes) |
||
138 | // - `first_name` required in input, must be a string with length from 1 to 255 characters |
||
139 | // - `last_name` could be either `null` or if given it must be a string with length from 1 to 255 characters |
||
140 | // - `payment_plan` must be a valid index for data in database (we will emulate request to database) |
||
141 | $validator = vv::validator([ |
||
142 | 'email' => r::isEmail(), |
||
143 | 'first_name' => r::isRequiredString(255), |
||
144 | 'last_name' => r::isNullOrNonEmptyString(255), |
||
145 | 'payment_plan' => r::isExistingPaymentPlan(), |
||
146 | ]); |
||
147 | |||
148 | // Check with invalid data |
||
149 | $invalidInput = [ |
||
150 | 'email' => 'john.dow', |
||
151 | //'first_name' => 'John', |
||
152 | 'last_name' => '', |
||
153 | 'payment_plan' => '123', |
||
154 | ]; |
||
155 | $this->console('Invalid data (errors)' . PHP_EOL); |
||
156 | $validator->validate($invalidInput); |
||
157 | $this->printErrors($validator->getErrors()); |
||
158 | $this->console('Invalid data (captures)' . PHP_EOL); |
||
159 | $this->printCaptures($validator->getCaptures()); |
||
160 | |||
161 | // Check with valid data |
||
162 | $validInput = [ |
||
163 | 'email' => '[email protected]', |
||
164 | 'first_name' => 'John', |
||
165 | 'last_name' => null, |
||
166 | 'payment_plan' => '2', |
||
167 | ]; |
||
168 | $this->console(PHP_EOL . 'Valid data (errors)' . PHP_EOL); |
||
169 | $validator->validate($validInput); |
||
170 | $this->printErrors($validator->getErrors()); |
||
171 | $this->console('Valid data (captures)' . PHP_EOL); |
||
172 | $this->printCaptures($validator->getCaptures()); |
||
173 | |||
174 | // The output would be |
||
175 | // ------------------------------------------------------------------------------------------------------- |
||
176 | // Advanced usage sample. |
||
177 | // =================== |
||
178 | // Invalid data (errors) |
||
179 | // Param `email` failed for `john.dow` with: The value should be a valid email address. |
||
180 | // Param `last_name` failed for `` with: The value should be between 1 and 255 characters. |
||
181 | // Param `payment_plan` failed for `123` with: The value should be a valid payment plan. |
||
182 | // Param `first_name` failed for `` with: The value is required. |
||
183 | // Invalid data (captures) |
||
184 | // No captures |
||
185 | // |
||
186 | // Valid data (errors) |
||
187 | // No errors |
||
188 | // Valid data (captures) |
||
189 | // `email` = `[email protected]` (string) |
||
190 | // `first_name` = `John` (string) |
||
191 | // `last_name` = `` (NULL) |
||
192 | // `payment_plan` = `2` (integer) |
||
193 | // ------------------------------------------------------------------------------------------------------- |
||
194 | } |
||
195 | |||
196 | /** |
||
197 | * @param ErrorAggregatorInterface $errors |
||
198 | * |
||
199 | * @return void |
||
200 | */ |
||
201 | private function printErrors(ErrorAggregatorInterface $errors): void |
||
214 | |||
215 | /** |
||
216 | * @param ErrorInterface $error |
||
217 | * |
||
218 | * @return void |
||
219 | */ |
||
220 | private function printError(ErrorInterface $error): void |
||
231 | |||
232 | /** |
||
233 | * @param CaptureAggregatorInterface $captures |
||
234 | * |
||
235 | * @return void |
||
236 | */ |
||
237 | private function printCaptures(CaptureAggregatorInterface $captures): void |
||
251 | |||
252 | /** |
||
253 | * @param string $string |
||
254 | */ |
||
255 | private function console(string $string): void |
||
261 | } |
||
262 |
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.