Completed
Push — master ( 0891d0...500833 )
by Paweł
48:22 queued 28:23
created

Product   F

Complexity

Total Complexity 67

Size/Duplication

Total Lines 498
Duplicated Lines 0 %

Coupling/Cohesion

Components 4
Dependencies 12

Importance

Changes 0
Metric Value
wmc 67
lcom 4
cbo 12
dl 0
loc 498
rs 3.0612
c 0
b 0
f 0

46 Methods

Rating   Name   Duplication   Size   Complexity  
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 4 1
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 __construct() 0 11 1
A getAttributesByLocale() 0 15 2
A addAttribute() 0 13 2
A removeAttribute() 0 13 2
A hasAttribute() 0 4 1
B hasAttributeByCodeAndLocale() 0 15 5
B getAttributeByCodeAndLocale() 0 15 5
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
A createTranslation() 0 4 1
A getAttributeInDifferentLocale() 0 14 4

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 Webmozart\Assert\Assert;
17
use Sylius\Component\Attribute\Model\AttributeValueInterface;
18
use Sylius\Component\Resource\Model\TimestampableTrait;
19
use Sylius\Component\Resource\Model\ToggleableTrait;
20
use Sylius\Component\Resource\Model\TranslatableTrait;
21
22
/**
23
 * @author Paweł Jędrzejewski <[email protected]>
24
 * @author Gonzalo Vilaseca <[email protected]>
25
 */
26
class Product implements ProductInterface
27
{
28
    use TimestampableTrait, ToggleableTrait;
29
    use TranslatableTrait {
30
        __construct as private initializeTranslationsCollection;
31
    }
32
33
    /**
34
     * @var mixed
35
     */
36
    protected $id;
37
38
    /**
39
     * @var string
40
     */
41
    protected $code;
42
43
    /**
44
     * @var \DateTime
45
     */
46
    protected $availableOn;
47
48
    /**
49
     * @var \DateTime
50
     */
51
    protected $availableUntil;
52
53
    /**
54
     * @var Collection|AttributeValueInterface[]
55
     */
56
    protected $attributes;
57
58
    /**
59
     * @var Collection|ProductVariantInterface[]
60
     */
61
    protected $variants;
62
63
    /**
64
     * @var Collection|ProductOptionInterface[]
65
     */
66
    protected $options;
67
68
    /**
69
     * @var Collection|ProductAssociationInterface[]
70
     */
71
    protected $associations;
72
73
    public function __construct()
74
    {
75
        $this->initializeTranslationsCollection();
76
77
        $this->createdAt = new \DateTime();
78
        $this->availableOn = new \DateTime();
79
        $this->attributes = new ArrayCollection();
80
        $this->associations = new ArrayCollection();
81
        $this->variants = new ArrayCollection();
82
        $this->options = new ArrayCollection();
83
    }
84
85
    /**
86
     * @return string
87
     */
88
    public function __toString()
89
    {
90
        return $this->getName();
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function getId()
97
    {
98
        return $this->id;
99
    }
100
101
    /**
102
     * @return string
103
     */
104
    public function getCode()
105
    {
106
        return $this->code;
107
    }
108
109
    /**
110
     * @param string $code
111
     */
112
    public function setCode($code)
113
    {
114
        $this->code = $code;
115
    }
116
117
    /**
118
     * {@inheritdoc}
119
     */
120
    public function getName()
121
    {
122
        return $this->getTranslation()->getName();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getName() does only exist in the following implementations of said interface: Sylius\Component\Attribu...el\AttributeTranslation, Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Payment...aymentMethodTranslation, Sylius\Component\Product...ociationTypeTranslation, Sylius\Component\Product...uctAttributeTranslation, Sylius\Component\Product...roductOptionTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Product...oductVariantTranslation, Sylius\Component\Shippin...ippingMethodTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
123
    }
124
125
    /**
126
     * {@inheritdoc}
127
     */
128
    public function setName($name)
129
    {
130
        $this->getTranslation()->setName($name);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method setName() does only exist in the following implementations of said interface: Sylius\Component\Attribu...el\AttributeTranslation, Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Payment...aymentMethodTranslation, Sylius\Component\Product...ociationTypeTranslation, Sylius\Component\Product...uctAttributeTranslation, Sylius\Component\Product...roductOptionTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Product...oductVariantTranslation, Sylius\Component\Shippin...ippingMethodTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function getSlug()
137
    {
138
        return $this->getTranslation()->getSlug();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getSlug() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
139
    }
140
141
    /**
142
     * {@inheritdoc}
143
     */
144
    public function setSlug($slug = null)
145
    {
146
        $this->getTranslation()->setSlug($slug);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method setSlug() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     */
152
    public function getDescription()
153
    {
154
        return $this->getTranslation()->getDescription();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getDescription() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Payment...aymentMethodTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Shippin...ippingMethodTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
155
    }
156
157
    /**
158
     * {@inheritdoc}
159
     */
160
    public function setDescription($description)
161
    {
162
        $this->getTranslation()->setDescription($description);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method setDescription() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Payment...aymentMethodTranslation, Sylius\Component\Product\Model\ProductTranslation, Sylius\Component\Shippin...ippingMethodTranslation, Sylius\Component\Taxonomy\Model\TaxonTranslation.

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...
163
    }
164
165
    /**
166
     * {@inheritdoc}
167
     */
168
    public function getMetaKeywords()
169
    {
170
        return $this->getTranslation()->getMetaKeywords();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getMetaKeywords() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation.

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...
171
    }
172
173
    /**
174
     * {@inheritdoc}
175
     */
176
    public function setMetaKeywords($metaKeywords)
177
    {
178
        $this->getTranslation()->setMetaKeywords($metaKeywords);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method setMetaKeywords() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation.

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...
179
    }
180
181
    /**
182
     * {@inheritdoc}
183
     */
184
    public function getMetaDescription()
185
    {
186
        return $this->getTranslation()->getMetaDescription();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method getMetaDescription() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation.

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...
187
    }
188
189
    /**
190
     * {@inheritdoc}
191
     */
192
    public function setMetaDescription($metaDescription)
193
    {
194
        $this->getTranslation()->setMetaDescription($metaDescription);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sylius\Component\Resourc...el\TranslationInterface as the method setMetaDescription() does only exist in the following implementations of said interface: Sylius\Component\Core\Model\ProductTranslation, Sylius\Component\Product\Model\ProductTranslation.

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...
195
    }
196
197
    /**
198
     * {@inheritdoc}
199
     */
200
    public function isAvailable()
201
    {
202
        return (new DateRange($this->availableOn, $this->availableUntil))->isInRange();
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(\DateTime $availableOn = null)
217
    {
218
        $this->availableOn = $availableOn;
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(\DateTime $availableUntil = null)
233
    {
234
        $this->availableUntil = $availableUntil;
235
    }
236
237
    /**
238
     * {@inheritdoc}
239
     */
240
    public function getAttributes()
241
    {
242
        return $this->attributes;
243
    }
244
245
    /**
246
     * {@inheritdoc}
247
     */
248
    public function getAttributesByLocale($localeCode, $fallbackLocaleCode)
249
    {
250
        $attributes = $this->attributes->filter(
251
            function (ProductAttributeValueInterface $attribute) use ($fallbackLocaleCode) {
252
                return $attribute->getLocaleCode() === $fallbackLocaleCode;
253
            }
254
        );
255
256
        $attributesWithFallback = [];
257
        foreach ($attributes as $attribute) {
258
            $attributesWithFallback[] = $this->getAttributeInDifferentLocale($attribute, $localeCode);
259
        }
260
261
        return $attributesWithFallback;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $attributesWithFallback; (array) is incompatible with the return type declared by the interface Sylius\Component\Attribu...::getAttributesByLocale of type Doctrine\Common\Collecti...tributeValueInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
262
    }
263
264
    /**
265
     * {@inheritdoc}
266
     */
267
    public function addAttribute(AttributeValueInterface $attribute)
268
    {
269
        Assert::isInstanceOf(
270
            $attribute,
271
            ProductAttributeValueInterface::class,
272
            'Attribute objects added to a Product object have to implement ProductAttributeValueInterface'
273
        );
274
275
        if (!$this->hasAttribute($attribute)) {
276
            $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...
277
            $this->attributes->add($attribute);
278
        }
279
    }
280
281
    /**
282
     * {@inheritdoc}
283
     */
284
    public function removeAttribute(AttributeValueInterface $attribute)
285
    {
286
        Assert::isInstanceOf(
287
            $attribute,
288
            ProductAttributeValueInterface::class,
289
            'Attribute objects removed from a Product object have to implement ProductAttributeValueInterface'
290
        );
291
292
        if ($this->hasAttribute($attribute)) {
293
            $this->attributes->removeElement($attribute);
294
            $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...
295
        }
296
    }
297
298
    /**
299
     * {@inheritdoc}
300
     */
301
    public function hasAttribute(AttributeValueInterface $attribute)
302
    {
303
        return $this->attributes->contains($attribute);
304
    }
305
306
    /**
307
     * {@inheritdoc}
308
     */
309
    public function hasAttributeByCodeAndLocale($attributeCode, $localeCode = null)
310
    {
311
        if (null === $localeCode) {
312
            $localeCode = $this->getTranslation()->getLocale();
313
        }
314
315
        foreach ($this->attributes as $attribute) {
316
            if ($attribute->getAttribute()->getCode() === $attributeCode &&
317
                $attribute->getLocaleCode() === $localeCode) {
318
                return true;
319
            }
320
        }
321
322
        return false;
323
    }
324
325
    /**
326
     * {@inheritdoc}
327
     */
328
    public function getAttributeByCodeAndLocale($attributeCode, $localeCode = null)
329
    {
330
        if (null === $localeCode) {
331
            $localeCode = $this->getTranslation()->getLocale();
332
        }
333
334
        foreach ($this->attributes as $attribute) {
335
            if ($attribute->getAttribute()->getCode() === $attributeCode &&
336
                $attribute->getLocaleCode() === $localeCode) {
337
                return $attribute;
338
            }
339
        }
340
341
        return null;
342
    }
343
344
    /**
345
     * {@inheritdoc}
346
     */
347
    public function hasVariants()
348
    {
349
        return !$this->getVariants()->isEmpty();
350
    }
351
352
    /**
353
     * {@inheritdoc}
354
     */
355
    public function getVariants()
356
    {
357
        return $this->variants;
358
    }
359
360
    /**
361
     * {@inheritdoc}
362
     */
363
    public function getAvailableVariants()
364
    {
365
        return $this->variants->filter(function (ProductVariantInterface $variant) {
366
            return $variant->isAvailable();
367
        });
368
    }
369
370
    /**
371
     * {@inheritdoc}
372
     */
373
    public function addVariant(ProductVariantInterface $variant)
374
    {
375
        if (!$this->hasVariant($variant)) {
376
            $variant->setProduct($this);
377
            $this->variants->add($variant);
378
        }
379
    }
380
381
    /**
382
     * {@inheritdoc}
383
     */
384
    public function removeVariant(ProductVariantInterface $variant)
385
    {
386
        if ($this->hasVariant($variant)) {
387
            $variant->setProduct(null);
388
            $this->variants->removeElement($variant);
389
        }
390
    }
391
392
    /**
393
     * {@inheritdoc}
394
     */
395
    public function hasVariant(ProductVariantInterface $variant)
396
    {
397
        return $this->variants->contains($variant);
398
    }
399
400
    /**
401
     * {@inheritdoc}
402
     */
403
    public function hasOptions()
404
    {
405
        return !$this->options->isEmpty();
406
    }
407
408
    /**
409
     * {@inheritdoc}
410
     */
411
    public function getOptions()
412
    {
413
        return $this->options;
414
    }
415
416
    /**
417
     * {@inheritdoc}
418
     */
419
    public function addOption(ProductOptionInterface $option)
420
    {
421
        if (!$this->hasOption($option)) {
422
            $this->options->add($option);
423
        }
424
    }
425
426
    /**
427
     * {@inheritdoc}
428
     */
429
    public function removeOption(ProductOptionInterface $option)
430
    {
431
        if ($this->hasOption($option)) {
432
            $this->options->removeElement($option);
433
        }
434
    }
435
436
    /**
437
     * {@inheritdoc}
438
     */
439
    public function hasOption(ProductOptionInterface $option)
440
    {
441
        return $this->options->contains($option);
442
    }
443
444
    /**
445
     * {@inheritdoc}
446
     */
447
    public function getAssociations()
448
    {
449
        return $this->associations;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->associations; of type Doctrine\Common\Collecti...tAssociationInterface[] adds the type Doctrine\Common\Collections\Collection to the return on line 449 which is incompatible with the return type declared by the interface Sylius\Component\Product...erface::getAssociations of type Sylius\Component\Product...tAssociationInterface[].
Loading history...
450
    }
451
452
    /**
453
     * {@inheritdoc}
454
     */
455
    public function addAssociation(ProductAssociationInterface $association)
456
    {
457
        if (!$this->hasAssociation($association)) {
458
            $this->associations->add($association);
459
            $association->setOwner($this);
460
        }
461
    }
462
463
    /**
464
     * {@inheritdoc}
465
     */
466
    public function removeAssociation(ProductAssociationInterface $association)
467
    {
468
        if ($this->hasAssociation($association)) {
469
            $association->setOwner(null);
470
            $this->associations->removeElement($association);
471
        }
472
    }
473
474
    /**
475
     * {@inheritdoc}
476
     */
477
    public function hasAssociation(ProductAssociationInterface $association)
478
    {
479
        return $this->associations->contains($association);
480
    }
481
482
    /**
483
     * {@inheritdoc}
484
     */
485
    public function isSimple()
486
    {
487
        return 1 === $this->variants->count() && !$this->hasOptions();
488
    }
489
490
    /**
491
     * {@inheritdoc}
492
     */
493
    public function isConfigurable()
494
    {
495
        return !$this->isSimple();
496
    }
497
498
    /**
499
     * {@inheritdoc}
500
     */
501
    protected function createTranslation()
502
    {
503
        return new ProductTranslation();
504
    }
505
506
    /**
507
     * {@inheritdoc}
508
     */
509
    private function getAttributeInDifferentLocale(ProductAttributeValueInterface $attributeValue, $localeCode)
510
    {
511
        if (!$this->hasAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode)) {
512
            return $attributeValue;
513
        }
514
515
        $attributeValueInDifferentLocale = $this->getAttributeByCodeAndLocale($attributeValue->getCode(), $localeCode);
516
        if ('' === $attributeValueInDifferentLocale->getValue()
517
            || null === $attributeValueInDifferentLocale->getValue()) {
518
            return $attributeValue;
519
        }
520
521
        return $attributeValueInDifferentLocale;
522
    }
523
}
524