1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the FOSRestBundle package. |
5
|
|
|
* |
6
|
|
|
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/> |
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 FOS\RestBundle\DependencyInjection; |
13
|
|
|
|
14
|
|
|
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition; |
15
|
|
|
use Symfony\Component\Config\Definition\Builder\TreeBuilder; |
16
|
|
|
use Symfony\Component\Config\Definition\ConfigurationInterface; |
17
|
|
|
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; |
18
|
|
|
use Symfony\Component\HttpFoundation\Response; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* This class contains the configuration information for the bundle. |
22
|
|
|
* |
23
|
|
|
* This information is solely responsible for how the different configuration |
24
|
|
|
* sections are normalized, and merged. |
25
|
|
|
* |
26
|
|
|
* @author Lukas Kahwe Smith <[email protected]> |
27
|
|
|
* |
28
|
|
|
* @internal |
29
|
|
|
*/ |
30
|
|
|
final class Configuration implements ConfigurationInterface |
31
|
|
|
{ |
32
|
|
|
/** |
33
|
|
|
* Default debug mode value. |
34
|
|
|
* |
35
|
|
|
* @var bool |
36
|
|
|
*/ |
37
|
|
|
private $debug; |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @param bool $debug |
41
|
|
|
*/ |
42
|
65 |
|
public function __construct($debug) |
43
|
|
|
{ |
44
|
65 |
|
$this->debug = (bool) $debug; |
45
|
65 |
|
} |
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* Generates the configuration tree. |
49
|
|
|
* |
50
|
|
|
* @return TreeBuilder |
51
|
|
|
*/ |
52
|
64 |
|
public function getConfigTreeBuilder() |
53
|
|
|
{ |
54
|
64 |
|
$treeBuilder = new TreeBuilder(); |
55
|
64 |
|
$rootNode = $treeBuilder->root('fos_rest', 'array'); |
56
|
|
|
|
57
|
|
|
$rootNode |
58
|
64 |
|
->children() |
59
|
64 |
|
->scalarNode('disable_csrf_role')->defaultNull()->end() |
60
|
64 |
|
->arrayNode('access_denied_listener') |
61
|
64 |
|
->canBeEnabled() |
62
|
64 |
|
->beforeNormalization() |
63
|
|
View Code Duplication |
->ifArray()->then(function ($v) { if (!empty($v) && empty($v['formats'])) { |
|
|
|
|
64
|
|
|
unset($v['enabled']); |
65
|
|
|
$v = ['enabled' => true, 'formats' => $v]; |
66
|
|
|
} |
67
|
|
|
|
68
|
64 |
|
return $v; }) |
69
|
64 |
|
->end() |
70
|
64 |
|
->fixXmlConfig('format', 'formats') |
71
|
64 |
|
->children() |
72
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
73
|
64 |
|
->arrayNode('formats') |
74
|
64 |
|
->useAttributeAsKey('name') |
75
|
64 |
|
->prototype('boolean')->end() |
76
|
64 |
|
->end() |
77
|
64 |
|
->end() |
78
|
64 |
|
->end() |
79
|
64 |
|
->scalarNode('unauthorized_challenge')->defaultNull()->end() |
80
|
64 |
|
->arrayNode('param_fetcher_listener') |
81
|
64 |
|
->beforeNormalization() |
82
|
64 |
|
->ifString() |
83
|
|
View Code Duplication |
->then(function ($v) { return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v]; }) |
|
|
|
|
84
|
64 |
|
->end() |
85
|
64 |
|
->canBeEnabled() |
86
|
64 |
|
->children() |
87
|
64 |
|
->booleanNode('enabled')->defaultFalse()->end() |
88
|
64 |
|
->booleanNode('force')->defaultFalse()->end() |
89
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
90
|
64 |
|
->end() |
91
|
64 |
|
->end() |
92
|
64 |
|
->scalarNode('cache_dir')->cannotBeEmpty()->defaultValue('%kernel.cache_dir%/fos_rest')->end() |
93
|
64 |
|
->arrayNode('allowed_methods_listener') |
94
|
64 |
|
->canBeEnabled() |
95
|
64 |
|
->children() |
96
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
97
|
64 |
|
->end() |
98
|
64 |
|
->end() |
99
|
64 |
|
->arrayNode('routing_loader') |
100
|
64 |
|
->addDefaultsIfNotSet() |
101
|
64 |
|
->children() |
102
|
64 |
|
->scalarNode('default_format')->defaultNull()->end() |
103
|
64 |
|
->scalarNode('include_format')->defaultTrue()->end() |
104
|
64 |
|
->end() |
105
|
64 |
|
->end() |
106
|
64 |
|
->arrayNode('body_converter') |
107
|
64 |
|
->addDefaultsIfNotSet() |
108
|
64 |
|
->children() |
109
|
64 |
|
->scalarNode('enabled')->defaultFalse()->end() |
110
|
64 |
|
->scalarNode('validate')->defaultFalse()->end() |
111
|
64 |
|
->scalarNode('validation_errors_argument')->defaultValue('validationErrors')->end() |
112
|
64 |
|
->end() |
113
|
64 |
|
->end() |
114
|
64 |
|
->arrayNode('service') |
115
|
64 |
|
->addDefaultsIfNotSet() |
116
|
64 |
|
->children() |
117
|
64 |
|
->scalarNode('router')->defaultValue('router')->end() |
118
|
64 |
|
->scalarNode('templating')->defaultValue('templating')->end() |
119
|
64 |
|
->scalarNode('serializer')->defaultNull()->end() |
120
|
64 |
|
->scalarNode('view_handler')->defaultValue('fos_rest.view_handler.default')->end() |
121
|
64 |
|
->scalarNode('inflector')->defaultValue('fos_rest.inflector.doctrine')->end() |
122
|
64 |
|
->scalarNode('validator')->defaultValue('validator')->end() |
123
|
64 |
|
->end() |
124
|
64 |
|
->end() |
125
|
64 |
|
->arrayNode('serializer') |
126
|
64 |
|
->addDefaultsIfNotSet() |
127
|
64 |
|
->children() |
128
|
64 |
|
->scalarNode('version')->defaultNull()->end() |
129
|
64 |
|
->arrayNode('groups') |
130
|
64 |
|
->prototype('scalar')->end() |
131
|
64 |
|
->end() |
132
|
64 |
|
->booleanNode('serialize_null')->defaultFalse()->end() |
133
|
64 |
|
->end() |
134
|
64 |
|
->end() |
135
|
64 |
|
->arrayNode('zone') |
136
|
64 |
|
->cannotBeOverwritten() |
137
|
64 |
|
->prototype('array') |
138
|
64 |
|
->fixXmlConfig('ip') |
139
|
64 |
|
->children() |
140
|
64 |
|
->scalarNode('path') |
141
|
64 |
|
->defaultNull() |
142
|
64 |
|
->info('use the urldecoded format') |
143
|
64 |
|
->example('^/path to resource/') |
144
|
64 |
|
->end() |
145
|
64 |
|
->scalarNode('host')->defaultNull()->end() |
146
|
64 |
|
->arrayNode('methods') |
147
|
|
|
->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() |
148
|
64 |
|
->prototype('scalar')->end() |
149
|
64 |
|
->end() |
150
|
64 |
|
->arrayNode('ips') |
151
|
|
|
->beforeNormalization()->ifString()->then(function ($v) { return array($v); })->end() |
152
|
64 |
|
->prototype('scalar')->end() |
153
|
64 |
|
->end() |
154
|
64 |
|
->end() |
155
|
64 |
|
->end() |
156
|
64 |
|
->end() |
157
|
64 |
|
->end(); |
158
|
|
|
|
159
|
64 |
|
$this->addViewSection($rootNode); |
160
|
64 |
|
$this->addExceptionSection($rootNode); |
161
|
64 |
|
$this->addBodyListenerSection($rootNode); |
162
|
64 |
|
$this->addFormatListenerSection($rootNode); |
163
|
64 |
|
$this->addVersioningSection($rootNode); |
164
|
|
|
|
165
|
64 |
|
return $treeBuilder; |
166
|
|
|
} |
167
|
|
|
|
168
|
64 |
|
private function addViewSection(ArrayNodeDefinition $rootNode) |
169
|
|
|
{ |
170
|
|
|
$rootNode |
171
|
64 |
|
->children() |
172
|
64 |
|
->arrayNode('view') |
173
|
64 |
|
->fixXmlConfig('format', 'formats') |
174
|
64 |
|
->fixXmlConfig('mime_type', 'mime_types') |
175
|
64 |
|
->fixXmlConfig('templating_format', 'templating_formats') |
176
|
64 |
|
->fixXmlConfig('force_redirect', 'force_redirects') |
177
|
64 |
|
->addDefaultsIfNotSet() |
178
|
64 |
|
->children() |
179
|
64 |
|
->scalarNode('default_engine')->defaultValue('twig')->end() |
180
|
64 |
|
->arrayNode('force_redirects') |
181
|
64 |
|
->useAttributeAsKey('name') |
182
|
64 |
|
->defaultValue(['html' => true]) |
183
|
64 |
|
->prototype('boolean')->end() |
184
|
64 |
|
->end() |
185
|
64 |
|
->arrayNode('mime_types') |
186
|
64 |
|
->canBeEnabled() |
187
|
64 |
|
->beforeNormalization() |
188
|
|
View Code Duplication |
->ifArray()->then(function ($v) { |
|
|
|
|
189
|
1 |
|
if (!empty($v) && empty($v['formats'])) { |
190
|
1 |
|
unset($v['enabled']); |
191
|
1 |
|
$v = ['enabled' => true, 'formats' => $v]; |
192
|
1 |
|
} |
193
|
|
|
|
194
|
1 |
|
return $v; |
195
|
64 |
|
}) |
196
|
64 |
|
->end() |
197
|
64 |
|
->fixXmlConfig('format', 'formats') |
198
|
64 |
|
->children() |
199
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
200
|
64 |
|
->arrayNode('formats') |
201
|
64 |
|
->useAttributeAsKey('name') |
202
|
64 |
|
->prototype('variable')->end() |
203
|
64 |
|
->end() |
204
|
64 |
|
->end() |
205
|
64 |
|
->end() |
206
|
64 |
|
->arrayNode('formats') |
207
|
64 |
|
->useAttributeAsKey('name') |
208
|
64 |
|
->defaultValue(['json' => true, 'xml' => true]) |
209
|
64 |
|
->prototype('boolean')->end() |
210
|
64 |
|
->end() |
211
|
64 |
|
->arrayNode('templating_formats') |
212
|
64 |
|
->useAttributeAsKey('name') |
213
|
64 |
|
->defaultValue(['html' => true]) |
214
|
64 |
|
->prototype('boolean')->end() |
215
|
64 |
|
->end() |
216
|
64 |
|
->arrayNode('view_response_listener') |
217
|
64 |
|
->beforeNormalization() |
218
|
64 |
|
->ifString() |
219
|
|
View Code Duplication |
->then(function ($v) { return ['enabled' => in_array($v, ['force', 'true']), 'force' => 'force' === $v]; }) |
|
|
|
|
220
|
64 |
|
->end() |
221
|
64 |
|
->canBeEnabled() |
222
|
64 |
|
->children() |
223
|
64 |
|
->booleanNode('enabled')->defaultFalse()->end() |
224
|
64 |
|
->booleanNode('force')->defaultFalse()->end() |
225
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
226
|
64 |
|
->end() |
227
|
64 |
|
->end() |
228
|
64 |
|
->scalarNode('failed_validation')->defaultValue(Response::HTTP_BAD_REQUEST)->end() |
229
|
64 |
|
->scalarNode('empty_content')->defaultValue(Response::HTTP_NO_CONTENT)->end() |
230
|
64 |
|
->booleanNode('serialize_null')->defaultFalse()->end() |
231
|
64 |
|
->arrayNode('jsonp_handler') |
232
|
64 |
|
->canBeUnset() |
233
|
64 |
|
->children() |
234
|
64 |
|
->scalarNode('callback_param')->defaultValue('callback')->end() |
235
|
64 |
|
->scalarNode('mime_type')->defaultValue('application/javascript+jsonp')->end() |
236
|
64 |
|
->end() |
237
|
64 |
|
->end() |
238
|
64 |
|
->end() |
239
|
64 |
|
->end() |
240
|
64 |
|
->end(); |
241
|
64 |
|
} |
242
|
|
|
|
243
|
64 |
|
private function addBodyListenerSection(ArrayNodeDefinition $rootNode) |
244
|
|
|
{ |
245
|
|
|
$rootNode |
246
|
64 |
|
->children() |
247
|
64 |
|
->arrayNode('body_listener') |
248
|
64 |
|
->fixXmlConfig('decoder', 'decoders') |
249
|
64 |
|
->addDefaultsIfNotSet() |
250
|
64 |
|
->canBeUnset() |
251
|
64 |
|
->canBeDisabled() |
252
|
64 |
|
->children() |
253
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
254
|
64 |
|
->scalarNode('default_format')->defaultNull()->end() |
255
|
64 |
|
->booleanNode('throw_exception_on_unsupported_content_type') |
256
|
64 |
|
->defaultFalse() |
257
|
64 |
|
->end() |
258
|
64 |
|
->arrayNode('decoders') |
259
|
64 |
|
->useAttributeAsKey('name') |
260
|
64 |
|
->defaultValue(['json' => 'fos_rest.decoder.json', 'xml' => 'fos_rest.decoder.xml']) |
261
|
64 |
|
->prototype('scalar')->end() |
262
|
64 |
|
->end() |
263
|
64 |
|
->arrayNode('array_normalizer') |
264
|
64 |
|
->addDefaultsIfNotSet() |
265
|
64 |
|
->beforeNormalization() |
266
|
|
|
->ifString()->then(function ($v) { return ['service' => $v]; }) |
267
|
64 |
|
->end() |
268
|
64 |
|
->children() |
269
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
270
|
64 |
|
->booleanNode('forms')->defaultFalse()->end() |
271
|
64 |
|
->end() |
272
|
64 |
|
->end() |
273
|
64 |
|
->end() |
274
|
64 |
|
->end() |
275
|
64 |
|
->end(); |
276
|
64 |
|
} |
277
|
|
|
|
278
|
64 |
|
private function addFormatListenerSection(ArrayNodeDefinition $rootNode) |
279
|
|
|
{ |
280
|
|
|
$rootNode |
281
|
64 |
|
->children() |
282
|
64 |
|
->arrayNode('format_listener') |
283
|
64 |
|
->fixXmlConfig('rule', 'rules') |
284
|
64 |
|
->addDefaultsIfNotSet() |
285
|
64 |
|
->canBeUnset() |
286
|
64 |
|
->beforeNormalization() |
287
|
|
|
->ifTrue(function ($v) { |
288
|
|
|
// check if we got an assoc array in rules |
289
|
8 |
|
return isset($v['rules']) |
290
|
8 |
|
&& is_array($v['rules']) |
291
|
8 |
|
&& array_keys($v['rules']) !== range(0, count($v['rules']) - 1); |
292
|
64 |
|
}) |
293
|
|
|
->then(function ($v) { |
294
|
1 |
|
$v['rules'] = [$v['rules']]; |
295
|
|
|
|
296
|
1 |
|
return $v; |
297
|
64 |
|
}) |
298
|
64 |
|
->end() |
299
|
64 |
|
->canBeEnabled() |
300
|
64 |
|
->children() |
301
|
64 |
|
->scalarNode('service')->defaultNull()->end() |
302
|
64 |
|
->arrayNode('rules') |
303
|
64 |
|
->cannotBeOverwritten() |
304
|
64 |
|
->prototype('array') |
305
|
64 |
|
->fixXmlConfig('priority', 'priorities') |
306
|
64 |
|
->fixXmlConfig('attribute', 'attributes') |
307
|
64 |
|
->children() |
308
|
64 |
|
->scalarNode('path')->defaultNull()->info('URL path info')->end() |
309
|
64 |
|
->scalarNode('host')->defaultNull()->info('URL host name')->end() |
310
|
64 |
|
->variableNode('methods')->defaultNull()->info('Method for URL')->end() |
311
|
64 |
|
->arrayNode('attributes') |
312
|
64 |
|
->useAttributeAsKey('name') |
313
|
64 |
|
->prototype('variable')->end() |
314
|
64 |
|
->end() |
315
|
64 |
|
->booleanNode('stop')->defaultFalse()->end() |
316
|
64 |
|
->booleanNode('prefer_extension')->defaultTrue()->end() |
317
|
64 |
|
->scalarNode('fallback_format')->defaultValue('html')->end() |
318
|
64 |
|
->arrayNode('priorities') |
319
|
|
|
->beforeNormalization()->ifString()->then(function ($v) { return preg_split('/\s*,\s*/', $v); })->end() |
320
|
64 |
|
->prototype('scalar')->end() |
321
|
64 |
|
->end() |
322
|
64 |
|
->end() |
323
|
64 |
|
->end() |
324
|
64 |
|
->end() |
325
|
64 |
|
->end() |
326
|
64 |
|
->end() |
327
|
64 |
|
->end(); |
328
|
64 |
|
} |
329
|
|
|
|
330
|
64 |
|
private function addVersioningSection(ArrayNodeDefinition $rootNode) |
331
|
|
|
{ |
332
|
|
|
$rootNode |
333
|
64 |
|
->children() |
334
|
64 |
|
->arrayNode('versioning') |
335
|
64 |
|
->canBeEnabled() |
336
|
64 |
|
->children() |
337
|
64 |
|
->scalarNode('default_version')->defaultNull()->end() |
338
|
64 |
|
->arrayNode('resolvers') |
339
|
64 |
|
->addDefaultsIfNotSet() |
340
|
64 |
|
->children() |
341
|
64 |
|
->arrayNode('query') |
342
|
64 |
|
->canBeDisabled() |
343
|
64 |
|
->children() |
344
|
64 |
|
->scalarNode('parameter_name')->defaultValue('version')->end() |
345
|
64 |
|
->end() |
346
|
64 |
|
->end() |
347
|
64 |
|
->arrayNode('custom_header') |
348
|
64 |
|
->canBeDisabled() |
349
|
64 |
|
->children() |
350
|
64 |
|
->scalarNode('header_name')->defaultValue('X-Accept-Version')->end() |
351
|
64 |
|
->end() |
352
|
64 |
|
->end() |
353
|
64 |
|
->arrayNode('media_type') |
354
|
64 |
|
->canBeDisabled() |
355
|
64 |
|
->children() |
356
|
64 |
|
->scalarNode('regex')->defaultValue('/(v|version)=(?P<version>[0-9\.]+)/')->end() |
357
|
64 |
|
->end() |
358
|
64 |
|
->end() |
359
|
64 |
|
->end() |
360
|
64 |
|
->end() |
361
|
64 |
|
->arrayNode('guessing_order') |
362
|
64 |
|
->defaultValue(['query', 'custom_header', 'media_type']) |
363
|
64 |
|
->validate() |
364
|
|
|
->ifTrue(function ($v) { |
365
|
1 |
|
foreach ($v as $resolver) { |
366
|
1 |
|
if (!in_array($resolver, ['query', 'custom_header', 'media_type'])) { |
367
|
|
|
return true; |
368
|
|
|
} |
369
|
1 |
|
} |
370
|
64 |
|
}) |
371
|
64 |
|
->thenInvalid('Versioning guessing order can only contain "query", "custom_header", "media_type".') |
372
|
64 |
|
->end() |
373
|
64 |
|
->prototype('scalar')->end() |
374
|
64 |
|
->end() |
375
|
64 |
|
->end() |
376
|
64 |
|
->end() |
377
|
64 |
|
->end(); |
378
|
64 |
|
} |
379
|
|
|
|
380
|
64 |
|
private function addExceptionSection(ArrayNodeDefinition $rootNode) |
381
|
|
|
{ |
382
|
|
|
$rootNode |
383
|
64 |
|
->children() |
384
|
64 |
|
->arrayNode('exception') |
385
|
64 |
|
->fixXmlConfig('code', 'codes') |
386
|
64 |
|
->fixXmlConfig('message', 'messages') |
387
|
64 |
|
->addDefaultsIfNotSet() |
388
|
64 |
|
->canBeEnabled() |
389
|
64 |
|
->children() |
390
|
64 |
|
->scalarNode('exception_controller')->defaultNull()->end() |
391
|
64 |
|
->arrayNode('codes') |
392
|
64 |
|
->useAttributeAsKey('name') |
393
|
64 |
|
->beforeNormalization() |
394
|
64 |
|
->ifArray() |
395
|
|
|
->then(function (array $items) { |
396
|
13 |
|
foreach ($items as &$item) { |
397
|
13 |
|
if (is_int($item)) { |
398
|
3 |
|
continue; |
399
|
|
|
} |
400
|
|
|
|
401
|
10 |
|
if (!defined('Symfony\Component\HttpFoundation\Response::'.$item)) { |
402
|
9 |
|
throw new InvalidConfigurationException( |
403
|
|
|
'Invalid HTTP code in fos_rest.exception.codes, see Symfony\Component\HttpFoundation\Response for all valid codes.' |
404
|
9 |
|
); |
405
|
|
|
} |
406
|
|
|
|
407
|
1 |
|
$item = constant('Symfony\Component\HttpFoundation\Response::'.$item); |
408
|
4 |
|
} |
409
|
|
|
|
410
|
4 |
|
return $items; |
411
|
64 |
|
}) |
412
|
64 |
|
->end() |
413
|
64 |
|
->prototype('integer')->end() |
414
|
64 |
|
->validate() |
415
|
64 |
|
->ifArray() |
416
|
|
|
->then(function (array $items) { |
417
|
4 |
|
foreach ($items as $class => $code) { |
418
|
4 |
|
$this->testExceptionExists($class); |
419
|
3 |
|
} |
420
|
|
|
|
421
|
3 |
|
return $items; |
422
|
64 |
|
}) |
423
|
64 |
|
->end() |
424
|
64 |
|
->end() |
425
|
64 |
|
->arrayNode('messages') |
426
|
64 |
|
->useAttributeAsKey('name') |
427
|
64 |
|
->prototype('boolean')->end() |
428
|
64 |
|
->validate() |
429
|
64 |
|
->ifArray() |
430
|
64 |
|
->then(function (array $items) { |
431
|
5 |
|
foreach ($items as $class => $nomatter) { |
432
|
5 |
|
$this->testExceptionExists($class); |
433
|
4 |
|
} |
434
|
|
|
|
435
|
4 |
|
return $items; |
436
|
64 |
|
}) |
437
|
64 |
|
->end() |
438
|
64 |
|
->end() |
439
|
64 |
|
->booleanNode('debug') |
440
|
64 |
|
->defaultValue($this->debug) |
441
|
64 |
|
->end() |
442
|
64 |
|
->end() |
443
|
64 |
|
->end() |
444
|
64 |
|
->end(); |
445
|
64 |
|
} |
446
|
|
|
|
447
|
|
|
/** |
448
|
|
|
* Checks if an exception is loadable. |
449
|
|
|
* |
450
|
|
|
* @param string $exception Class to test |
451
|
|
|
* |
452
|
|
|
* @throws InvalidConfigurationException if the class was not found |
453
|
|
|
*/ |
454
|
9 |
|
private function testExceptionExists($exception) |
455
|
|
|
{ |
456
|
9 |
|
if (!is_subclass_of($exception, \Exception::class) && !is_a($exception, \Exception::class, true)) { |
|
|
|
|
457
|
2 |
|
throw new InvalidConfigurationException("FOSRestBundle exception mapper: Could not load class '$exception' or the class does not extend from '\\Exception'. Most probably this is a configuration problem."); |
458
|
|
|
} |
459
|
7 |
|
} |
460
|
|
|
} |
461
|
|
|
|
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.