1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the puli/discovery package. |
5
|
|
|
* |
6
|
|
|
* (c) Bernhard Schussek <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Puli\Discovery\Test; |
13
|
|
|
|
14
|
|
|
use Puli\Discovery\Api\Binding\Binding; |
15
|
|
|
use Puli\Discovery\Api\Binding\Initializer\BindingInitializer; |
16
|
|
|
use Puli\Discovery\Api\Discovery; |
17
|
|
|
use Puli\Discovery\Api\EditableDiscovery; |
18
|
|
|
use Puli\Discovery\Api\Type\BindingParameter; |
19
|
|
|
use Puli\Discovery\Api\Type\BindingType; |
20
|
|
|
use Puli\Discovery\Test\Fixtures\Bar; |
21
|
|
|
use Puli\Discovery\Test\Fixtures\Foo; |
22
|
|
|
use Puli\Discovery\Test\Fixtures\StringBinding; |
23
|
|
|
use stdClass; |
24
|
|
|
use Webmozart\Expression\Expr; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @since 1.0 |
28
|
|
|
* |
29
|
|
|
* @author Bernhard Schussek <[email protected]> |
30
|
|
|
*/ |
31
|
|
|
abstract class AbstractEditableDiscoveryTest extends AbstractDiscoveryTest |
32
|
|
|
{ |
33
|
|
|
/** |
34
|
|
|
* Creates a discovery that can be written in the test. |
35
|
|
|
* |
36
|
|
|
* @param BindingInitializer[] $initializers |
37
|
|
|
* |
38
|
|
|
* @return EditableDiscovery |
39
|
|
|
*/ |
40
|
|
|
abstract protected function createDiscovery(array $initializers = array()); |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* Creates a discovery that can be read in the test. |
44
|
|
|
* |
45
|
|
|
* This method is needed to test whether the discovery actually synchronized |
46
|
|
|
* all in-memory changes to the backing data store: |
47
|
|
|
* |
48
|
|
|
* * If the method returns the passed $discovery, the in-memory data |
49
|
|
|
* structures are tested. |
50
|
|
|
* * If the method returns a new discovery with the same backing data store, |
51
|
|
|
* that data store is tested. |
52
|
|
|
* |
53
|
|
|
* @param EditableDiscovery $discovery |
54
|
|
|
* @param BindingInitializer[] $initializers |
55
|
|
|
* |
56
|
|
|
* @return EditableDiscovery |
57
|
|
|
*/ |
58
|
|
|
abstract protected function loadDiscoveryFromStorage(EditableDiscovery $discovery, array $initializers = array()); |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* @param BindingType[] $types |
62
|
|
|
* @param Binding[] $bindings |
63
|
|
|
* @param BindingInitializer[] $initializers |
64
|
|
|
* |
65
|
|
|
* @return Discovery |
66
|
|
|
*/ |
67
|
100 |
|
protected function createLoadedDiscovery(array $types = array(), array $bindings = array(), array $initializers = array()) |
68
|
|
|
{ |
69
|
100 |
|
$discovery = $this->createDiscovery($initializers); |
70
|
|
|
|
71
|
100 |
|
foreach ($types as $type) { |
72
|
50 |
|
$discovery->addBindingType($type); |
73
|
|
|
} |
74
|
|
|
|
75
|
100 |
|
foreach ($bindings as $binding) { |
76
|
30 |
|
$discovery->addBinding($binding); |
77
|
|
|
} |
78
|
|
|
|
79
|
100 |
|
return $this->loadDiscoveryFromStorage($discovery); |
80
|
|
|
} |
81
|
|
|
|
82
|
5 |
View Code Duplication |
public function testAddBinding() |
|
|
|
|
83
|
|
|
{ |
84
|
5 |
|
$binding = new StringBinding('string', Foo::clazz); |
85
|
|
|
|
86
|
5 |
|
$discovery = $this->createDiscovery(); |
87
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
88
|
5 |
|
$discovery->addBinding($binding); |
89
|
|
|
|
90
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
91
|
|
|
|
92
|
5 |
|
$this->assertCount(1, $discovery->findBindings(Foo::clazz)); |
93
|
5 |
|
$this->assertCount(1, $discovery->getBindings()); |
94
|
5 |
|
} |
95
|
|
|
|
96
|
|
|
/** |
97
|
|
|
* @expectedException \Puli\Discovery\Api\Type\NoSuchTypeException |
98
|
|
|
* @expectedExceptionMessage Foo |
99
|
|
|
*/ |
100
|
5 |
|
public function testAddBindingFailsIfTypeNotFound() |
101
|
|
|
{ |
102
|
5 |
|
$discovery = $this->createDiscovery(); |
103
|
5 |
|
$discovery->addBinding(new StringBinding('string', Foo::clazz)); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* @expectedException \Puli\Discovery\Api\Type\BindingNotAcceptedException |
108
|
|
|
* @expectedExceptionMessage Foo |
109
|
|
|
*/ |
110
|
5 |
|
public function testAddBindingFailsIfTypeDoesNotAcceptBinding() |
111
|
|
|
{ |
112
|
5 |
|
$discovery = $this->createDiscovery(); |
113
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::CLASS_BINDING)); |
114
|
5 |
|
$discovery->addBinding(new StringBinding('string', Foo::clazz)); |
115
|
|
|
} |
116
|
|
|
|
117
|
5 |
View Code Duplication |
public function testAddBindingAcceptsDuplicates() |
|
|
|
|
118
|
|
|
{ |
119
|
|
|
// The idea behind accepting duplicates is that depending on the use |
120
|
|
|
// case, multiple modules may define the same binding but the order |
121
|
|
|
// in which the modules is loaded is important. Hence if we load |
122
|
|
|
// [m1, m2, m3] and m1 and m3 contain the same binding, we cannot simply |
123
|
|
|
// discard one of the bindings, since the order might be important for |
124
|
|
|
// the end user. |
125
|
|
|
|
126
|
5 |
|
$binding = new StringBinding('string', Foo::clazz); |
127
|
|
|
|
128
|
5 |
|
$discovery = $this->createDiscovery(); |
129
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
130
|
5 |
|
$discovery->addBinding($binding); |
131
|
5 |
|
$discovery->addBinding($binding); |
132
|
|
|
|
133
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
134
|
|
|
|
135
|
5 |
|
$this->assertCount(2, $discovery->findBindings(Foo::clazz)); |
136
|
5 |
|
$this->assertCount(2, $discovery->getBindings()); |
137
|
5 |
|
} |
138
|
|
|
|
139
|
5 |
|
public function testRemoveBindings() |
140
|
|
|
{ |
141
|
5 |
|
$binding1 = new StringBinding('string1', Foo::clazz); |
142
|
5 |
|
$binding2 = new StringBinding('string2', Foo::clazz); |
143
|
|
|
|
144
|
5 |
|
$discovery = $this->createDiscovery(); |
145
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
146
|
5 |
|
$discovery->addBinding($binding1); |
147
|
5 |
|
$discovery->addBinding($binding2); |
148
|
5 |
|
$discovery->removeBindings(); |
149
|
|
|
|
150
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
151
|
|
|
|
152
|
5 |
|
$this->assertCount(0, $discovery->findBindings(Foo::clazz)); |
153
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
154
|
5 |
|
} |
155
|
|
|
|
156
|
5 |
View Code Duplication |
public function testRemoveBindingsDoesNothingIfNoneFound() |
|
|
|
|
157
|
|
|
{ |
158
|
5 |
|
$discovery = $this->createDiscovery(); |
159
|
5 |
|
$discovery->removeBindings(); |
160
|
|
|
|
161
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
162
|
|
|
|
163
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
164
|
5 |
|
} |
165
|
|
|
|
166
|
5 |
|
public function testRemoveBindingsWithType() |
167
|
|
|
{ |
168
|
5 |
|
$binding1 = new StringBinding('string1', Foo::clazz); |
169
|
5 |
|
$binding2 = new StringBinding('string2', Foo::clazz); |
170
|
5 |
|
$binding3 = new StringBinding('string3', Bar::clazz); |
171
|
|
|
|
172
|
5 |
|
$discovery = $this->createDiscovery(); |
173
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
174
|
5 |
|
$discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); |
175
|
5 |
|
$discovery->addBinding($binding1); |
176
|
5 |
|
$discovery->addBinding($binding2); |
177
|
5 |
|
$discovery->addBinding($binding3); |
178
|
5 |
|
$discovery->removeBindings(Foo::clazz); |
179
|
|
|
|
180
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
181
|
|
|
|
182
|
5 |
|
$this->assertEquals(array(), $discovery->findBindings(Foo::clazz)); |
183
|
5 |
|
$this->assertEquals(array($binding3), $discovery->findBindings(Bar::clazz)); |
184
|
5 |
|
$this->assertEquals(array($binding3), $discovery->getBindings()); |
185
|
5 |
|
} |
186
|
|
|
|
187
|
5 |
View Code Duplication |
public function testRemoveBindingsWithTypeDoesNothingIfNoneFound() |
|
|
|
|
188
|
|
|
{ |
189
|
5 |
|
$discovery = $this->createDiscovery(); |
190
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
191
|
5 |
|
$discovery->removeBindings(Foo::clazz); |
192
|
|
|
|
193
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
194
|
|
|
|
195
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
196
|
5 |
|
} |
197
|
|
|
|
198
|
5 |
View Code Duplication |
public function testRemoveBindingsWithTypeDoesNothingIfTypeNotFound() |
|
|
|
|
199
|
|
|
{ |
200
|
5 |
|
$discovery = $this->createDiscovery(); |
201
|
5 |
|
$discovery->removeBindings(Foo::clazz); |
202
|
|
|
|
203
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
204
|
|
|
|
205
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
206
|
5 |
|
} |
207
|
|
|
|
208
|
|
|
/** |
209
|
|
|
* @expectedException \InvalidArgumentException |
210
|
|
|
* @expectedExceptionMessage stdClass |
211
|
|
|
*/ |
212
|
5 |
|
public function testRemoveBindingsWithTypeFailsIfInvalidType() |
213
|
|
|
{ |
214
|
5 |
|
$discovery = $this->createDiscovery(); |
215
|
5 |
|
$discovery->removeBindings(new stdClass()); |
|
|
|
|
216
|
|
|
} |
217
|
|
|
|
218
|
5 |
View Code Duplication |
public function testRemoveBindingsWithExpression() |
|
|
|
|
219
|
|
|
{ |
220
|
5 |
|
$binding1 = new StringBinding('string1', Foo::clazz, array('param1' => 'foo', 'param2' => 'bar')); |
221
|
5 |
|
$binding2 = new StringBinding('string2', Foo::clazz, array('param1' => 'foo')); |
222
|
5 |
|
$binding3 = new StringBinding('string3', Foo::clazz, array('param1' => 'bar')); |
223
|
|
|
|
224
|
5 |
|
$discovery = $this->createDiscovery(); |
225
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING, array( |
226
|
5 |
|
new BindingParameter('param1'), |
227
|
5 |
|
new BindingParameter('param2'), |
228
|
|
|
))); |
229
|
5 |
|
$discovery->addBinding($binding1); |
230
|
5 |
|
$discovery->addBinding($binding2); |
231
|
5 |
|
$discovery->addBinding($binding3); |
232
|
5 |
|
$discovery->removeBindings(null, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); |
233
|
|
|
|
234
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
235
|
|
|
|
236
|
5 |
|
$this->assertEquals(array($binding3), $discovery->findBindings(Foo::clazz)); |
237
|
5 |
|
$this->assertEquals(array($binding3), $discovery->getBindings()); |
238
|
5 |
|
} |
239
|
|
|
|
240
|
5 |
View Code Duplication |
public function testRemoveBindingsWithTypeAndExpression() |
|
|
|
|
241
|
|
|
{ |
242
|
5 |
|
$binding1 = new StringBinding('string1', Foo::clazz, array('param1' => 'foo', 'param2' => 'bar')); |
243
|
5 |
|
$binding2 = new StringBinding('string2', Foo::clazz, array('param1' => 'foo')); |
244
|
5 |
|
$binding3 = new StringBinding('string3', Foo::clazz, array('param1' => 'bar')); |
245
|
|
|
|
246
|
5 |
|
$discovery = $this->createDiscovery(); |
247
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING, array( |
248
|
5 |
|
new BindingParameter('param1'), |
249
|
5 |
|
new BindingParameter('param2'), |
250
|
|
|
))); |
251
|
5 |
|
$discovery->addBinding($binding1); |
252
|
5 |
|
$discovery->addBinding($binding2); |
253
|
5 |
|
$discovery->addBinding($binding3); |
254
|
5 |
|
$discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); |
255
|
|
|
|
256
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
257
|
|
|
|
258
|
5 |
|
$this->assertEquals(array($binding3), $discovery->findBindings(Foo::clazz)); |
259
|
5 |
|
$this->assertEquals(array($binding3), $discovery->getBindings()); |
260
|
5 |
|
} |
261
|
|
|
|
262
|
5 |
|
public function testRemoveBindingsWithTypeAndParametersDoesNothingIfNoneFound() |
263
|
|
|
{ |
264
|
5 |
|
$discovery = $this->createDiscovery(); |
265
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
266
|
5 |
|
$discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); |
267
|
|
|
|
268
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
269
|
|
|
|
270
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
271
|
5 |
|
} |
272
|
|
|
|
273
|
5 |
|
public function testRemoveBindingsWithTypeAndParametersDoesNothingIfTypeNotFound() |
274
|
|
|
{ |
275
|
5 |
|
$discovery = $this->createDiscovery(); |
276
|
5 |
|
$discovery->removeBindings(Foo::clazz, Expr::method('getParameterValue', 'param1', Expr::same('foo'))); |
277
|
|
|
|
278
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
279
|
|
|
|
280
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
281
|
5 |
|
} |
282
|
|
|
|
283
|
5 |
View Code Duplication |
public function testAddBindingType() |
|
|
|
|
284
|
|
|
{ |
285
|
5 |
|
$type = new BindingType(Foo::clazz, self::STRING_BINDING); |
286
|
|
|
|
287
|
5 |
|
$discovery = $this->createDiscovery(); |
288
|
5 |
|
$discovery->addBindingType($type); |
289
|
|
|
|
290
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
291
|
|
|
|
292
|
5 |
|
$this->assertEquals($type, $discovery->getBindingType(Foo::clazz)); |
293
|
5 |
|
} |
294
|
|
|
|
295
|
5 |
|
public function testAddBindingTypeAfterReadingStorage() |
296
|
|
|
{ |
297
|
5 |
|
$type1 = new BindingType(Foo::clazz, self::STRING_BINDING); |
298
|
5 |
|
$type2 = new BindingType(Bar::clazz, self::STRING_BINDING); |
299
|
|
|
|
300
|
5 |
|
$discovery = $this->createDiscovery(); |
301
|
5 |
|
$discovery->addBindingType($type1); |
302
|
|
|
|
303
|
|
|
// Make sure that the previous call to addBindingType() stored all |
304
|
|
|
// necessary information in order to add further types (e.g. nextId) |
305
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
306
|
5 |
|
$discovery->addBindingType($type2); |
307
|
|
|
|
308
|
5 |
|
$this->assertEquals($type1, $discovery->getBindingType(Foo::clazz)); |
309
|
5 |
|
$this->assertEquals($type2, $discovery->getBindingType(Bar::clazz)); |
310
|
5 |
|
} |
311
|
|
|
|
312
|
|
|
/** |
313
|
|
|
* @expectedException \Puli\Discovery\Api\Type\DuplicateTypeException |
314
|
|
|
* @expectedExceptionMessage Foo |
315
|
|
|
*/ |
316
|
5 |
|
public function testAddBindingTypeFailsIfAlreadyDefined() |
317
|
|
|
{ |
318
|
5 |
|
$discovery = $this->createDiscovery(); |
319
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
320
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
321
|
|
|
} |
322
|
|
|
|
323
|
5 |
View Code Duplication |
public function testRemoveBindingType() |
|
|
|
|
324
|
|
|
{ |
325
|
5 |
|
$discovery = $this->createDiscovery(); |
326
|
5 |
|
$discovery->addBindingType($type1 = new BindingType(Foo::clazz, self::STRING_BINDING)); |
327
|
5 |
|
$discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); |
328
|
5 |
|
$discovery->removeBindingType(Bar::clazz); |
329
|
|
|
|
330
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
331
|
|
|
|
332
|
5 |
|
$this->assertEquals(array($type1), $discovery->getBindingTypes()); |
333
|
5 |
|
$this->assertTrue($discovery->hasBindingType(Foo::clazz)); |
334
|
5 |
|
$this->assertFalse($discovery->hasBindingType(Bar::clazz)); |
335
|
5 |
|
} |
336
|
|
|
|
337
|
5 |
|
public function testRemoveBindingTypeIgnoresUnknownTypes() |
338
|
|
|
{ |
339
|
5 |
|
$discovery = $this->createDiscovery(); |
340
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
341
|
5 |
|
$discovery->removeBindingType(Bar::clazz); |
342
|
|
|
|
343
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
344
|
|
|
|
345
|
5 |
|
$this->assertTrue($discovery->hasBindingType(Foo::clazz)); |
346
|
5 |
|
$this->assertFalse($discovery->hasBindingType(Bar::clazz)); |
347
|
5 |
|
} |
348
|
|
|
|
349
|
|
|
/** |
350
|
|
|
* @expectedException \InvalidArgumentException |
351
|
|
|
* @expectedExceptionMessage stdClass |
352
|
|
|
*/ |
353
|
5 |
|
public function testRemoveBindingTypeFailsIfInvalidType() |
354
|
|
|
{ |
355
|
5 |
|
$discovery = $this->createDiscovery(); |
356
|
5 |
|
$discovery->removeBindingType(new stdClass()); |
|
|
|
|
357
|
|
|
} |
358
|
|
|
|
359
|
5 |
|
public function testRemoveBindingTypeRemovesCorrespondingBindings() |
360
|
|
|
{ |
361
|
5 |
|
$discovery = $this->createDiscovery(); |
362
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
363
|
5 |
|
$discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); |
364
|
5 |
|
$discovery->addBinding($binding1 = new StringBinding('string1', Foo::clazz)); |
365
|
5 |
|
$discovery->addBinding($binding2 = new StringBinding('string2', Foo::clazz)); |
366
|
5 |
|
$discovery->addBinding($binding3 = new StringBinding('string3', Bar::clazz)); |
367
|
|
|
|
368
|
5 |
|
$discovery->removeBindingType(Foo::clazz); |
369
|
|
|
|
370
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
371
|
|
|
|
372
|
5 |
|
$this->assertEquals(array($binding3), $discovery->getBindings()); |
373
|
5 |
|
} |
374
|
|
|
|
375
|
5 |
View Code Duplication |
public function testRemoveBindingTypes() |
|
|
|
|
376
|
|
|
{ |
377
|
5 |
|
$discovery = $this->createDiscovery(); |
378
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
379
|
5 |
|
$discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); |
380
|
5 |
|
$discovery->removeBindingTypes(); |
381
|
|
|
|
382
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
383
|
|
|
|
384
|
5 |
|
$this->assertEquals(array(), $discovery->getBindingTypes()); |
385
|
5 |
|
$this->assertFalse($discovery->hasBindingType(Foo::clazz)); |
386
|
5 |
|
$this->assertFalse($discovery->hasBindingType(Bar::clazz)); |
387
|
5 |
|
} |
388
|
|
|
|
389
|
5 |
|
public function testRemoveBindingTypesRemovesBindings() |
390
|
|
|
{ |
391
|
5 |
|
$discovery = $this->createDiscovery(); |
392
|
5 |
|
$discovery->addBindingType(new BindingType(Foo::clazz, self::STRING_BINDING)); |
393
|
5 |
|
$discovery->addBindingType(new BindingType(Bar::clazz, self::STRING_BINDING)); |
394
|
5 |
|
$discovery->addBinding($binding1 = new StringBinding('string1', Foo::clazz)); |
395
|
5 |
|
$discovery->addBinding($binding2 = new StringBinding('string2', Bar::clazz)); |
396
|
5 |
|
$discovery->removeBindingTypes(); |
397
|
|
|
|
398
|
5 |
|
$discovery = $this->loadDiscoveryFromStorage($discovery); |
399
|
|
|
|
400
|
5 |
|
$this->assertCount(0, $discovery->getBindings()); |
401
|
5 |
|
} |
402
|
|
|
} |
403
|
|
|
|
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.