Completed
Push — 1.0 ( 32d139...1bbf5e )
by Kamil
79:12 queued 52:28
created

ProductSpec   B

Complexity

Total Complexity 42

Size/Duplication

Total Lines 425
Duplicated Lines 0 %

Coupling/Cohesion

Components 0
Dependencies 5

Importance

Changes 0
Metric Value
wmc 42
lcom 0
cbo 5
dl 0
loc 425
rs 8.295
c 0
b 0
f 0

42 Methods

Rating   Name   Duplication   Size   Complexity  
A let() 0 5 1
A it_implements_product_interface() 0 4 1
A it_implements_toggleable_interface() 0 4 1
A it_has_no_id_by_default() 0 4 1
A it_has_no_name_by_default() 0 4 1
A its_name_is_mutable() 0 5 1
A it_has_no_slug_by_default() 0 4 1
A its_slug_is_mutable() 0 5 1
A it_has_no_description_by_default() 0 4 1
A its_description_is_mutable() 0 5 1
A it_initializes_attribute_collection_by_default() 0 4 1
A it_adds_attribute() 0 7 1
A it_removes_attribute() 0 12 1
A it_refuses_to_add_non_product_attribute() 0 5 1
A it_refuses_to_remove_non_product_attribute() 0 4 1
B it_returns_attributes_by_a_locale_without_a_base_locale() 0 28 1
B it_returns_attributes_by_a_locale_with_a_base_locale() 0 36 1
A it_returns_attributes_by_a_fallback_locale_when_there_is_no_value_for_a_given_locale() 0 20 1
B it_returns_attributes_by_a_fallback_locale_when_there_is_an_empty_value_for_a_given_locale() 0 28 1
A it_returns_attributes_by_a_base_locale_when_there_is_no_value_for_a_given_locale_or_a_fallback_locale() 0 19 1
B it_returns_attributes_by_a_base_locale_when_there_is_an_empty_value_for_a_given_locale_or_a_fallback_locale() 0 35 1
A it_has_no_variants_by_default() 0 4 1
A its_says_it_has_variants_only_if_multiple_variants_are_defined() 0 11 1
A it_initializes_variants_collection_by_default() 0 4 1
A it_does_not_include_unavailable_variants_in_available_variants() 0 6 1
A it_returns_available_variants() 0 10 1
A it_initializes_options_collection_by_default() 0 4 1
A it_has_no_options_by_default() 0 4 1
A its_says_it_has_options_only_if_any_option_defined() 0 5 1
A it_adds_option_properly() 0 5 1
A it_removes_option_properly() 0 8 1
A it_initializes_creation_date_by_default() 0 4 1
A its_creation_date_is_mutable() 0 5 1
A it_has_no_last_update_date_by_default() 0 4 1
A its_last_update_date_is_mutable() 0 5 1
A it_is_enabled_by_default() 0 4 1
A it_is_toggleable() 0 8 1
A it_adds_association() 0 7 1
A it_allows_to_remove_association() 0 10 1
A it_is_simple_if_it_has_one_variant_and_no_options() 0 8 1
A it_is_configurable_if_it_has_at_least_two_variants() 0 12 1
A it_is_configurable_if_it_has_one_variant_and_at_least_one_option() 0 11 1

How to fix   Complexity   

Complex Class

Complex classes like ProductSpec 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 ProductSpec, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of the Sylius package.
5
 *
6
 * (c) Paweł Jędrzejewski
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
declare(strict_types=1);
13
14
namespace spec\Sylius\Component\Product\Model;
15
16
use Doctrine\Common\Collections\ArrayCollection;
17
use Doctrine\Common\Collections\Collection;
18
use PhpSpec\ObjectBehavior;
19
use Sylius\Component\Attribute\Model\AttributeValueInterface;
20
use Sylius\Component\Product\Model\ProductAssociationInterface;
21
use Sylius\Component\Product\Model\ProductAttributeInterface;
22
use Sylius\Component\Product\Model\ProductAttributeValueInterface;
23
use Sylius\Component\Product\Model\ProductInterface;
24
use Sylius\Component\Product\Model\ProductOptionInterface;
25
use Sylius\Component\Product\Model\ProductVariantInterface;
26
use Sylius\Component\Resource\Model\ToggleableInterface;
27
28
/**
29
 * @author Paweł Jędrzejewski <[email protected]>
30
 * @author Gonzalo Vilaseca <[email protected]>
31
 */
32
final class ProductSpec extends ObjectBehavior
33
{
34
    function let()
35
    {
36
        $this->setCurrentLocale('en_US');
37
        $this->setFallbackLocale('en_US');
38
    }
39
40
    function it_implements_product_interface(): void
41
    {
42
        $this->shouldImplement(ProductInterface::class);
43
    }
44
45
    function it_implements_toggleable_interface(): void
46
    {
47
        $this->shouldImplement(ToggleableInterface::class);
48
    }
49
50
    function it_has_no_id_by_default(): void
51
    {
52
        $this->getId()->shouldReturn(null);
53
    }
54
55
    function it_has_no_name_by_default(): void
56
    {
57
        $this->getName()->shouldReturn(null);
58
    }
59
60
    function its_name_is_mutable(): void
61
    {
62
        $this->setName('Super product');
63
        $this->getName()->shouldReturn('Super product');
64
    }
65
66
    function it_has_no_slug_by_default(): void
67
    {
68
        $this->getSlug()->shouldReturn(null);
69
    }
70
71
    function its_slug_is_mutable(): void
72
    {
73
        $this->setSlug('super-product');
74
        $this->getSlug()->shouldReturn('super-product');
75
    }
76
77
    function it_has_no_description_by_default(): void
78
    {
79
        $this->getDescription()->shouldReturn(null);
80
    }
81
82
    function its_description_is_mutable(): void
83
    {
84
        $this->setDescription('This product is super cool because...');
85
        $this->getDescription()->shouldReturn('This product is super cool because...');
86
    }
87
88
    function it_initializes_attribute_collection_by_default(): void
89
    {
90
        $this->getAttributes()->shouldHaveType(Collection::class);
91
    }
92
93
    function it_adds_attribute(ProductAttributeValueInterface $attribute): void
94
    {
95
        $attribute->setProduct($this)->shouldBeCalled();
96
97
        $this->addAttribute($attribute);
98
        $this->hasAttribute($attribute)->shouldReturn(true);
99
    }
100
101
    function it_removes_attribute(ProductAttributeValueInterface $attribute): void
102
    {
103
        $attribute->setProduct($this)->shouldBeCalled();
104
105
        $this->addAttribute($attribute);
106
        $this->hasAttribute($attribute)->shouldReturn(true);
107
108
        $attribute->setProduct(null)->shouldBeCalled();
109
110
        $this->removeAttribute($attribute);
111
        $this->hasAttribute($attribute)->shouldReturn(false);
112
    }
113
114
    function it_refuses_to_add_non_product_attribute(AttributeValueInterface $attribute): void
115
    {
116
        $this->shouldThrow('\InvalidArgumentException')->duringAddAttribute($attribute);
117
        $this->hasAttribute($attribute)->shouldReturn(false);
118
    }
119
120
    function it_refuses_to_remove_non_product_attribute(AttributeValueInterface $attribute): void
121
    {
122
        $this->shouldThrow('\InvalidArgumentException')->duringRemoveAttribute($attribute);
123
    }
124
125
    function it_returns_attributes_by_a_locale_without_a_base_locale(
126
        ProductAttributeInterface $attribute,
127
        ProductAttributeValueInterface $attributeValueEN,
128
        ProductAttributeValueInterface $attributeValuePL
129
    ): void
130
    {
131
        $attribute->getCode()->willReturn('colour');
132
133
        $attributeValueEN->setProduct($this)->shouldBeCalled();
134
        $attributeValueEN->getLocaleCode()->willReturn('en_US');
135
        $attributeValueEN->getAttribute()->willReturn($attribute);
136
        $attributeValueEN->getCode()->willReturn('colour');
137
        $attributeValueEN->getValue()->willReturn('Blue');
138
139
        $attributeValuePL->setProduct($this)->shouldBeCalled();
140
        $attributeValuePL->getLocaleCode()->willReturn('pl_PL');
141
        $attributeValuePL->getAttribute()->willReturn($attribute);
142
        $attributeValuePL->getCode()->willReturn('colour');
143
        $attributeValuePL->getValue()->willReturn('Niebieski');
144
145
        $this->addAttribute($attributeValueEN);
146
        $this->addAttribute($attributeValuePL);
147
148
        $this
149
            ->getAttributesByLocale('pl_PL', 'en_US')
150
            ->shouldIterateAs([$attributeValuePL->getWrappedObject()])
151
        ;
152
    }
153
154
    function it_returns_attributes_by_a_locale_with_a_base_locale(
155
        ProductAttributeInterface $attribute,
156
        ProductAttributeValueInterface $attributeValueEN,
157
        ProductAttributeValueInterface $attributeValuePL,
158
        ProductAttributeValueInterface $attributeValueFR
159
    ): void
160
    {
161
        $attribute->getCode()->willReturn('colour');
162
163
        $attributeValueEN->setProduct($this)->shouldBeCalled();
164
        $attributeValueEN->getLocaleCode()->willReturn('en_US');
165
        $attributeValueEN->getAttribute()->willReturn($attribute);
166
        $attributeValueEN->getCode()->willReturn('colour');
167
        $attributeValueEN->getValue()->willReturn('Blue');
168
169
        $attributeValuePL->setProduct($this)->shouldBeCalled();
170
        $attributeValuePL->getLocaleCode()->willReturn('pl_PL');
171
        $attributeValuePL->getAttribute()->willReturn($attribute);
172
        $attributeValuePL->getCode()->willReturn('colour');
173
        $attributeValuePL->getValue()->willReturn('Niebieski');
174
175
        $attributeValueFR->setProduct($this)->shouldBeCalled();
176
        $attributeValueFR->getLocaleCode()->willReturn('fr_FR');
177
        $attributeValueFR->getAttribute()->willReturn($attribute);
178
        $attributeValueFR->getCode()->willReturn('colour');
179
        $attributeValueFR->getValue()->willReturn('Bleu');
180
181
        $this->addAttribute($attributeValueEN);
182
        $this->addAttribute($attributeValuePL);
183
        $this->addAttribute($attributeValueFR);
184
185
        $this
186
            ->getAttributesByLocale('pl_PL', 'en_US', 'fr_FR')
187
            ->shouldIterateAs([$attributeValuePL->getWrappedObject()])
188
        ;
189
    }
190
191
    function it_returns_attributes_by_a_fallback_locale_when_there_is_no_value_for_a_given_locale(
192
        ProductAttributeInterface $attribute,
193
        ProductAttributeValueInterface $attributeValueEN
194
    ): void
195
    {
196
        $attribute->getCode()->willReturn('colour');
197
198
        $attributeValueEN->setProduct($this)->shouldBeCalled();
199
        $attributeValueEN->getLocaleCode()->willReturn('en_US');
200
        $attributeValueEN->getAttribute()->willReturn($attribute);
201
        $attributeValueEN->getCode()->willReturn('colour');
202
        $attributeValueEN->getValue()->willReturn('Blue');
203
204
        $this->addAttribute($attributeValueEN);
205
206
        $this
207
            ->getAttributesByLocale('pl_PL', 'en_US')
208
            ->shouldIterateAs([$attributeValueEN->getWrappedObject()])
209
        ;
210
    }
211
212
    function it_returns_attributes_by_a_fallback_locale_when_there_is_an_empty_value_for_a_given_locale(
213
        ProductAttributeInterface $attribute,
214
        ProductAttributeValueInterface $attributeValueEN,
215
        ProductAttributeValueInterface $attributeValuePL
216
    ): void
217
    {
218
        $attribute->getCode()->willReturn('colour');
219
220
        $attributeValueEN->setProduct($this)->shouldBeCalled();
221
        $attributeValueEN->getLocaleCode()->willReturn('en_US');
222
        $attributeValueEN->getAttribute()->willReturn($attribute);
223
        $attributeValueEN->getCode()->willReturn('colour');
224
        $attributeValueEN->getValue()->willReturn('Blue');
225
226
        $attributeValuePL->setProduct($this)->shouldBeCalled();
227
        $attributeValuePL->getLocaleCode()->willReturn('pl_PL');
228
        $attributeValuePL->getAttribute()->willReturn($attribute);
229
        $attributeValuePL->getCode()->willReturn('colour');
230
        $attributeValuePL->getValue()->willReturn('');
231
232
        $this->addAttribute($attributeValueEN);
233
        $this->addAttribute($attributeValuePL);
234
235
        $this
236
            ->getAttributesByLocale('pl_PL', 'en_US')
237
            ->shouldIterateAs([$attributeValueEN->getWrappedObject()])
238
        ;
239
    }
240
241
    function it_returns_attributes_by_a_base_locale_when_there_is_no_value_for_a_given_locale_or_a_fallback_locale(
242
        ProductAttributeInterface $attribute,
243
        ProductAttributeValueInterface $attributeValueFR
244
    ): void {
245
        $attribute->getCode()->willReturn('colour');
246
247
        $attributeValueFR->setProduct($this)->shouldBeCalled();
248
        $attributeValueFR->getLocaleCode()->willReturn('fr_FR');
249
        $attributeValueFR->getAttribute()->willReturn($attribute);
250
        $attributeValueFR->getCode()->willReturn('colour');
251
        $attributeValueFR->getValue()->willReturn('Bleu');
252
253
        $this->addAttribute($attributeValueFR);
254
255
        $this
256
            ->getAttributesByLocale('pl_PL', 'en_US', 'fr_FR')
257
            ->shouldIterateAs([$attributeValueFR->getWrappedObject()])
258
        ;
259
    }
260
261
    function it_returns_attributes_by_a_base_locale_when_there_is_an_empty_value_for_a_given_locale_or_a_fallback_locale(
262
        ProductAttributeInterface $attribute,
263
        ProductAttributeValueInterface $attributeValueEN,
264
        ProductAttributeValueInterface $attributeValuePL,
265
        ProductAttributeValueInterface $attributeValueFR
266
    ): void {
267
        $attribute->getCode()->willReturn('colour');
268
269
        $attributeValueEN->setProduct($this)->shouldBeCalled();
270
        $attributeValueEN->getLocaleCode()->willReturn('en_US');
271
        $attributeValueEN->getAttribute()->willReturn($attribute);
272
        $attributeValueEN->getCode()->willReturn('colour');
273
        $attributeValueEN->getValue()->willReturn('');
274
275
        $attributeValuePL->setProduct($this)->shouldBeCalled();
276
        $attributeValuePL->getLocaleCode()->willReturn('pl_PL');
277
        $attributeValuePL->getAttribute()->willReturn($attribute);
278
        $attributeValuePL->getCode()->willReturn('colour');
279
        $attributeValuePL->getValue()->willReturn(null);
280
281
        $attributeValueFR->setProduct($this)->shouldBeCalled();
282
        $attributeValueFR->getLocaleCode()->willReturn('fr_FR');
283
        $attributeValueFR->getAttribute()->willReturn($attribute);
284
        $attributeValueFR->getCode()->willReturn('colour');
285
        $attributeValueFR->getValue()->willReturn('Bleu');
286
287
        $this->addAttribute($attributeValueEN);
288
        $this->addAttribute($attributeValuePL);
289
        $this->addAttribute($attributeValueFR);
290
291
        $this
292
            ->getAttributesByLocale('pl_PL', 'en_US', 'fr_FR')
293
            ->shouldIterateAs([$attributeValueFR->getWrappedObject()])
294
        ;
295
    }
296
297
    function it_has_no_variants_by_default(): void
298
    {
299
        $this->hasVariants()->shouldReturn(false);
300
    }
301
302
    function its_says_it_has_variants_only_if_multiple_variants_are_defined(
303
        ProductVariantInterface $firstVariant,
304
        ProductVariantInterface $secondVariant
305
    ): void {
306
        $firstVariant->setProduct($this)->shouldBeCalled();
307
        $secondVariant->setProduct($this)->shouldBeCalled();
308
309
        $this->addVariant($firstVariant);
310
        $this->addVariant($secondVariant);
311
        $this->hasVariants()->shouldReturn(true);
312
    }
313
314
    function it_initializes_variants_collection_by_default(): void
315
    {
316
        $this->getVariants()->shouldHaveType(Collection::class);
317
    }
318
319
    function it_does_not_include_unavailable_variants_in_available_variants(ProductVariantInterface $variant): void
320
    {
321
        $variant->setProduct($this)->shouldBeCalled();
322
323
        $this->addVariant($variant);
324
    }
325
326
    function it_returns_available_variants(
327
        ProductVariantInterface $unavailableVariant,
328
        ProductVariantInterface $variant
329
    ): void {
330
        $unavailableVariant->setProduct($this)->shouldBeCalled();
331
        $variant->setProduct($this)->shouldBeCalled();
332
333
        $this->addVariant($unavailableVariant);
334
        $this->addVariant($variant);
335
    }
336
337
    function it_initializes_options_collection_by_default(): void
338
    {
339
        $this->getOptions()->shouldHaveType(Collection::class);
340
    }
341
342
    function it_has_no_options_by_default(): void
343
    {
344
        $this->hasOptions()->shouldReturn(false);
345
    }
346
347
    function its_says_it_has_options_only_if_any_option_defined(ProductOptionInterface $option): void
348
    {
349
        $this->addOption($option);
350
        $this->hasOptions()->shouldReturn(true);
351
    }
352
353
    function it_adds_option_properly(ProductOptionInterface $option): void
354
    {
355
        $this->addOption($option);
356
        $this->hasOption($option)->shouldReturn(true);
357
    }
358
359
    function it_removes_option_properly(ProductOptionInterface $option): void
360
    {
361
        $this->addOption($option);
362
        $this->hasOption($option)->shouldReturn(true);
363
364
        $this->removeOption($option);
365
        $this->hasOption($option)->shouldReturn(false);
366
    }
367
368
    function it_initializes_creation_date_by_default(): void
369
    {
370
        $this->getCreatedAt()->shouldHaveType(\DateTimeInterface::class);
371
    }
372
373
    function its_creation_date_is_mutable(\DateTime $creationDate): void
374
    {
375
        $this->setCreatedAt($creationDate);
376
        $this->getCreatedAt()->shouldReturn($creationDate);
377
    }
378
379
    function it_has_no_last_update_date_by_default(): void
380
    {
381
        $this->getUpdatedAt()->shouldReturn(null);
382
    }
383
384
    function its_last_update_date_is_mutable(\DateTime $updateDate): void
385
    {
386
        $this->setUpdatedAt($updateDate);
387
        $this->getUpdatedAt()->shouldReturn($updateDate);
388
    }
389
390
    function it_is_enabled_by_default(): void
391
    {
392
        $this->shouldBeEnabled();
393
    }
394
395
    function it_is_toggleable(): void
396
    {
397
        $this->disable();
398
        $this->shouldNotBeEnabled();
399
400
        $this->enable();
401
        $this->shouldBeEnabled();
402
    }
403
404
    function it_adds_association(ProductAssociationInterface $association): void
405
    {
406
        $association->setOwner($this)->shouldBeCalled();
407
        $this->addAssociation($association);
408
409
        $this->hasAssociation($association)->shouldReturn(true);
410
    }
411
412
    function it_allows_to_remove_association(ProductAssociationInterface $association): void
413
    {
414
        $association->setOwner($this)->shouldBeCalled();
415
        $association->setOwner(null)->shouldBeCalled();
416
417
        $this->addAssociation($association);
418
        $this->removeAssociation($association);
419
420
        $this->hasAssociation($association)->shouldReturn(false);
421
    }
422
423
    function it_is_simple_if_it_has_one_variant_and_no_options(ProductVariantInterface $variant): void
424
    {
425
        $variant->setProduct($this)->shouldBeCalled();
426
        $this->addVariant($variant);
427
428
        $this->isSimple()->shouldReturn(true);
429
        $this->isConfigurable()->shouldReturn(false);
430
    }
431
432
    function it_is_configurable_if_it_has_at_least_two_variants(
433
        ProductVariantInterface $firstVariant,
434
        ProductVariantInterface $secondVariant
435
    ): void {
436
        $firstVariant->setProduct($this)->shouldBeCalled();
437
        $this->addVariant($firstVariant);
438
        $secondVariant->setProduct($this)->shouldBeCalled();
439
        $this->addVariant($secondVariant);
440
441
        $this->isConfigurable()->shouldReturn(true);
442
        $this->isSimple()->shouldReturn(false);
443
    }
444
445
    function it_is_configurable_if_it_has_one_variant_and_at_least_one_option(
446
        ProductOptionInterface $option,
447
        ProductVariantInterface $variant
448
    ): void {
449
        $variant->setProduct($this)->shouldBeCalled();
450
        $this->addVariant($variant);
451
        $this->addOption($option);
452
453
        $this->isConfigurable()->shouldReturn(true);
454
        $this->isSimple()->shouldReturn(false);
455
    }
456
}
457