Completed
Push — date-time-interface ( 768b2d )
by Kamil
37:14
created

Product   D

Complexity

Total Complexity 59

Size/Duplication

Total Lines 432
Duplicated Lines 0 %

Coupling/Cohesion

Components 5
Dependencies 6

Importance

Changes 0
Metric Value
wmc 59
c 0
b 0
f 0
lcom 5
cbo 6
dl 0
loc 432
rs 4.5454

43 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
A __toString() 0 4 1
A getId() 0 4 1
A getCode() 0 4 1
A setCode() 0 4 1
A getName() 0 4 1
A setName() 0 4 1
A getSlug() 0 4 1
A setSlug() 0 4 1
A getDescription() 0 4 1
A setDescription() 0 4 1
A getMetaKeywords() 0 4 1
A setMetaKeywords() 0 4 1
A getMetaDescription() 0 4 1
A setMetaDescription() 0 4 1
A isAvailable() 0 5 4
A getAvailableOn() 0 4 1
A setAvailableOn() 0 4 1
A getAvailableUntil() 0 4 1
A setAvailableUntil() 0 4 1
A getAttributes() 0 4 1
A addAttribute() 0 7 2
A removeAttribute() 0 7 2
A hasAttribute() 0 4 1
A hasAttributeByCode() 0 10 3
A getAttributeByCode() 0 10 3
A hasVariants() 0 4 1
A getVariants() 0 4 1
A getAvailableVariants() 0 6 1
A addVariant() 0 7 2
A removeVariant() 0 7 2
A hasVariant() 0 4 1
A hasOptions() 0 4 1
A getOptions() 0 4 1
A addOption() 0 6 2
A removeOption() 0 6 2
A hasOption() 0 4 1
A getAssociations() 0 4 1
A addAssociation() 0 7 2
A removeAssociation() 0 7 2
A hasAssociation() 0 4 1
A isSimple() 0 4 2
A isConfigurable() 0 4 1

How to fix   Complexity   

Complex Class

Complex classes like Product 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 Product, 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
namespace Sylius\Component\Product\Model;
13
14
use Doctrine\Common\Collections\ArrayCollection;
15
use Doctrine\Common\Collections\Collection;
16
use Sylius\Component\Attribute\Model\AttributeValueInterface;
17
use Sylius\Component\Resource\Model\TimestampableTrait;
18
use Sylius\Component\Resource\Model\ToggleableTrait;
19
use Sylius\Component\Resource\Model\TranslatableTrait;
20
21
/**
22
 * @author Paweł Jędrzejewski <[email protected]>
23
 * @author Gonzalo Vilaseca <[email protected]>
24
 */
25
class Product implements ProductInterface
26
{
27
    use TimestampableTrait, ToggleableTrait;
28
    use TranslatableTrait {
29
        __construct as private initializeTranslationsCollection;
30
    }
31
32
    /**
33
     * @var mixed
34
     */
35
    protected $id;
36
37
    /**
38
     * @var string
39
     */
40
    protected $code;
41
42
    /**
43
     * @var \DateTime
44
     */
45
    protected $availableOn;
46
47
    /**
48
     * @var \DateTime
49
     */
50
    protected $availableUntil;
51
52
    /**
53
     * @var Collection|AttributeValueInterface[]
54
     */
55
    protected $attributes;
56
57
    /**
58
     * @var Collection|ProductVariantInterface[]
59
     */
60
    protected $variants;
61
62
    /**
63
     * @var Collection|ProductOptionInterface[]
64
     */
65
    protected $options;
66
67
    /**
68
     * @var Collection|ProductAssociationInterface[]
69
     */
70
    protected $associations;
71
72
    public function __construct()
73
    {
74
        $this->initializeTranslationsCollection();
75
76
        $this->availableOn = new \DateTime();
77
        $this->attributes = new ArrayCollection();
78
        $this->associations = new ArrayCollection();
79
        $this->variants = new ArrayCollection();
80
        $this->options = new ArrayCollection();
81
        $this->createdAt = new \DateTime();
82
    }
83
84
    /**
85
     * @return string
86
     */
87
    public function __toString()
88
    {
89
        return $this->getName();
90
    }
91
92
    /**
93
     * {@inheritdoc}
94
     */
95
    public function getId()
96
    {
97
        return $this->id;
98
    }
99
100
    /**
101
     * @return string
102
     */
103
    public function getCode()
104
    {
105
        return $this->code;
106
    }
107
108
    /**
109
     * @param string $code
110
     */
111
    public function setCode($code)
112
    {
113
        $this->code = $code;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119
    public function getName()
120
    {
121
        return $this->translate()->getName();
122
    }
123
124
    /**
125
     * {@inheritdoc}
126
     */
127
    public function setName($name)
128
    {
129
        $this->translate()->setName($name);
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function getSlug()
136
    {
137
        return $this->translate()->getSlug();
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function setSlug($slug = null)
144
    {
145
        $this->translate()->setSlug($slug);
146
    }
147
148
    /**
149
     * {@inheritdoc}
150
     */
151
    public function getDescription()
152
    {
153
        return $this->translate()->getDescription();
154
    }
155
156
    /**
157
     * {@inheritdoc}
158
     */
159
    public function setDescription($description)
160
    {
161
        $this->translate()->setDescription($description);
162
    }
163
164
    /**
165
     * {@inheritdoc}
166
     */
167
    public function getMetaKeywords()
168
    {
169
        return $this->translate()->getMetaKeywords();
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175
    public function setMetaKeywords($metaKeywords)
176
    {
177
        $this->translate()->setMetaKeywords($metaKeywords);
178
    }
179
180
    /**
181
     * {@inheritdoc}
182
     */
183
    public function getMetaDescription()
184
    {
185
        return $this->translate()->getMetaDescription();
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     */
191
    public function setMetaDescription($metaDescription)
192
    {
193
        $this->translate()->setMetaDescription($metaDescription);
194
    }
195
196
    /**
197
     * {@inheritdoc}
198
     */
199
    public function isAvailable()
200
    {
201
        return (null === $this->availableOn || new \DateTimeImmutable() >= $this->availableOn)
202
            && (null === $this->availableUntil || new \DateTimeImmutable() <= $this->availableUntil);
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function getAvailableOn()
209
    {
210
        return $this->availableOn;
211
    }
212
213
    /**
214
     * {@inheritdoc}
215
     */
216
    public function setAvailableOn(\DateTimeInterface $availableOn = null)
217
    {
218
        $this->availableOn = $availableOn;
0 ignored issues
show
Documentation Bug introduced by
It seems like $availableOn can also be of type object<DateTimeInterface>. However, the property $availableOn is declared as type object<DateTime>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224
    public function getAvailableUntil()
225
    {
226
        return $this->availableUntil;
227
    }
228
229
    /**
230
     * {@inheritdoc}
231
     */
232
    public function setAvailableUntil(\DateTimeInterface $availableUntil = null)
233
    {
234
        $this->availableUntil = $availableUntil;
0 ignored issues
show
Documentation Bug introduced by
It seems like $availableUntil can also be of type object<DateTimeInterface>. However, the property $availableUntil is declared as type object<DateTime>. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function getAttributes()
241
    {
242
        return $this->attributes;
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248
    public function addAttribute(AttributeValueInterface $attribute)
249
    {
250
        if (!$this->hasAttribute($attribute)) {
251
            $attribute->setProduct($this);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Attribu...AttributeValueInterface as the method setProduct() does only exist in the following implementations of said interface: Sylius\Component\Product...l\ProductAttributeValue.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
252
            $this->attributes->add($attribute);
253
        }
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259
    public function removeAttribute(AttributeValueInterface $attribute)
260
    {
261
        if ($this->hasAttribute($attribute)) {
262
            $this->attributes->removeElement($attribute);
263
            $attribute->setProduct(null);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Attribu...AttributeValueInterface as the method setProduct() does only exist in the following implementations of said interface: Sylius\Component\Product...l\ProductAttributeValue.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
264
        }
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function hasAttribute(AttributeValueInterface $attribute)
271
    {
272
        return $this->attributes->contains($attribute);
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278
    public function hasAttributeByCode($attributeCode)
279
    {
280
        foreach ($this->attributes as $attribute) {
281
            if ($attribute->getAttribute()->getCode() === $attributeCode) {
282
                return true;
283
            }
284
        }
285
286
        return false;
287
    }
288
289
    /**
290
     * {@inheritdoc}
291
     */
292
    public function getAttributeByCode($attributeCode)
293
    {
294
        foreach ($this->attributes as $attribute) {
295
            if ($attributeCode === $attribute->getAttribute()->getCode()) {
296
                return $attribute;
297
            }
298
        }
299
300
        return null;
301
    }
302
303
    /**
304
     * {@inheritdoc}
305
     */
306
    public function hasVariants()
307
    {
308
        return !$this->getVariants()->isEmpty();
309
    }
310
311
    /**
312
     * {@inheritdoc}
313
     */
314
    public function getVariants()
315
    {
316
        return $this->variants;
317
    }
318
319
    /**
320
     * {@inheritdoc}
321
     */
322
    public function getAvailableVariants()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
323
    {
324
        return $this->variants->filter(function (ProductVariantInterface $variant) {
325
            return $variant->isAvailable();
326
        });
327
    }
328
329
    /**
330
     * {@inheritdoc}
331
     */
332
    public function addVariant(ProductVariantInterface $variant)
333
    {
334
        if (!$this->hasVariant($variant)) {
335
            $variant->setProduct($this);
336
            $this->variants->add($variant);
337
        }
338
    }
339
340
    /**
341
     * {@inheritdoc}
342
     */
343
    public function removeVariant(ProductVariantInterface $variant)
344
    {
345
        if ($this->hasVariant($variant)) {
346
            $variant->setProduct(null);
347
            $this->variants->removeElement($variant);
348
        }
349
    }
350
351
    /**
352
     * {@inheritdoc}
353
     */
354
    public function hasVariant(ProductVariantInterface $variant)
355
    {
356
        return $this->variants->contains($variant);
357
    }
358
359
    /**
360
     * {@inheritdoc}
361
     */
362
    public function hasOptions()
363
    {
364
        return !$this->options->isEmpty();
365
    }
366
367
    /**
368
     * {@inheritdoc}
369
     */
370
    public function getOptions()
371
    {
372
        return $this->options;
373
    }
374
375
    /**
376
     * {@inheritdoc}
377
     */
378
    public function addOption(ProductOptionInterface $option)
379
    {
380
        if (!$this->hasOption($option)) {
381
            $this->options->add($option);
382
        }
383
    }
384
385
    /**
386
     * {@inheritdoc}
387
     */
388
    public function removeOption(ProductOptionInterface $option)
389
    {
390
        if ($this->hasOption($option)) {
391
            $this->options->removeElement($option);
392
        }
393
    }
394
395
    /**
396
     * {@inheritdoc}
397
     */
398
    public function hasOption(ProductOptionInterface $option)
399
    {
400
        return $this->options->contains($option);
401
    }
402
403
    /**
404
     * {@inheritdoc}
405
     */
406
    public function getAssociations()
407
    {
408
        return $this->associations;
409
    }
410
411
    /**
412
     * {@inheritdoc}
413
     */
414
    public function addAssociation(ProductAssociationInterface $association)
415
    {
416
        if (!$this->hasAssociation($association)) {
417
            $this->associations->add($association);
418
            $association->setOwner($this);
419
        }
420
    }
421
422
    /**
423
     * {@inheritdoc}
424
     */
425
    public function removeAssociation(ProductAssociationInterface $association)
426
    {
427
        if ($this->hasAssociation($association)) {
428
            $association->setOwner(null);
429
            $this->associations->removeElement($association);
430
        }
431
    }
432
433
    /**
434
     * {@inheritdoc}
435
     */
436
    public function hasAssociation(ProductAssociationInterface $association)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
437
    {
438
        return $this->associations->contains($association);
439
    }
440
441
    /**
442
     * {@inheritdoc}
443
     */
444
    public function isSimple()
445
    {
446
        return 1 === $this->variants->count() && !$this->hasOptions();
447
    }
448
449
    /**
450
     * {@inheritdoc}
451
     */
452
    public function isConfigurable()
453
    {
454
        return !$this->isSimple();
455
    }
456
}
457