Completed
Push — 19.x ( 239dba...3864f1 )
by Tim
01:48
created

EntityAttributeObserver::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 13
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 13
loc 13
ccs 0
cts 9
cp 0
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 3
crap 2
1
<?php
2
3
/**
4
 * TechDivision\Import\Attribute\Observers\EntityAttributeObserver
5
 *
6
 * NOTICE OF LICENSE
7
 *
8
 * This source file is subject to the Open Software License (OSL 3.0)
9
 * that is available through the world-wide-web at this URL:
10
 * http://opensource.org/licenses/osl-3.0.php
11
 *
12
 * PHP version 5
13
 *
14
 * @author    Tim Wagner <[email protected]>
15
 * @copyright 2016 TechDivision GmbH <[email protected]>
16
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
17
 * @link      https://github.com/techdivision/import-attribute
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Attribute\Observers;
22
23
use TechDivision\Import\Attribute\Utils\ColumnKeys;
24
use TechDivision\Import\Attribute\Utils\MemberNames;
25
use TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface;
26
use TechDivision\Import\Observers\EntityMergers\EntityMergerInterface;
27
use TechDivision\Import\Observers\StateDetectorInterface;
28
use TechDivision\Import\Utils\EntityStatus;
29
30
/**
31
 * Observer that create's the EAV entity attribute itself.
32
 *
33
 * @author    Tim Wagner <[email protected]>
34
 * @copyright 2016 TechDivision GmbH <[email protected]>
35
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
36
 * @link      https://github.com/techdivision/import-attribute
37
 * @link      http://www.techdivision.com
38
 */
39
class EntityAttributeObserver extends AbstractAttributeImportObserver
40
{
41
42
    /**
43
     * The attribute processor instance.
44
     *
45
     * @var \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface
46
     */
47
    protected $attributeBunchProcessor;
48
49
    /**
50
     * The collection with entity merger instances.
51
     *
52
     * @var \Doctrine\Common\Collections\Collection
53
     */
54
    protected $entityMergers;
55
56
    /**
57
     * Initializes the observer with the passed subject instance.
58
     *
59
     * @param \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface $attributeBunchProcessor The attribute bunch processor instance
60
     * @param \TechDivision\Import\Observers\EntityMergers\EntityMergerInterface       $entityMerger            The entity merger instance
61
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null               $stateDetector           The state detector instance to use
62
     */
63 View Code Duplication
    public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
64
        AttributeBunchProcessorInterface $attributeBunchProcessor,
65
        EntityMergerInterface $entityMerger = null,
66
        StateDetectorInterface $stateDetector = null
67
    ) {
68
69
        // initialize the bunch processor and the entity merger instance
70
        $this->attributeBunchProcessor = $attributeBunchProcessor;
71
        $this->entityMerger = $entityMerger;
0 ignored issues
show
Bug introduced by
The property entityMerger does not seem to exist. Did you mean entityMergers?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
72
73
        // pass the state detector to the parent method
74
        parent::__construct($stateDetector);
75
    }
76
77
    /**
78
     * Process the observer's business logic.
79
     *
80
     * @return void
81
     */
82
    protected function process()
83
    {
84
85
        // query whether or not, we've found a new attribute code => means we've found a new attribute
86
        if ($this->hasBeenProcessed($this->getValue(ColumnKeys::ATTRIBUTE_CODE))) {
87
            return;
88
        }
89
90
        // load the last attribute ID
91
        $this->attributeId = $this->getLastAttributeId();
0 ignored issues
show
Bug introduced by
The property attributeId does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
92
93
        // map the entity type code to the ID
94
        $entityType = $this->getEntityType($this->entityTypeCode = $this->getValue(ColumnKeys::ENTITY_TYPE_CODE));
0 ignored issues
show
Bug introduced by
The property entityTypeCode does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
95
        $this->entityTypeId = $entityType[MemberNames::ENTITY_TYPE_ID];
0 ignored issues
show
Bug introduced by
The property entityTypeId does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
96
97
        // load the sort order for the attribute
98
        $this->sortOrder = $this->getValue(ColumnKeys::SORT_ORDER, 0);
0 ignored issues
show
Bug introduced by
The property sortOrder does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
99
100
        // explode the attribute set + group names
101
        $attributeSetNames = $this->getValue(ColumnKeys::ATTRIBUTE_SET_NAME, array(), array($this, 'explode'));
102
        $attributeGroupNames = $this->getValue(ColumnKeys::ATTRIBUTE_GROUP_NAME, array(), array($this, 'explode'));
103
104
        // make sure we've the same number of attribute sets/gropus
105
        if (sizeof($attributeSetNames) !== sizeof($attributeGroupNames)) {
106
            throw new \Exception(sprintf('Size of attribute names doesn\'t match size of attribute groups'));
107
        }
108
109
        // iterate over the attribute names and create the attribute entities therefore
110
        foreach ($attributeSetNames as $key => $attributeSetName) {
111
            // load the attribute set ID
112
            $attributeSet = $this->getAttributeSetByAttributeSetNameAndEntityTypeCode($attributeSetName, $this->entityTypeCode);
113
114
            // initialize the values to create the attribute entity
115
            $this->attributeSetId = $attributeSet[MemberNames::ATTRIBUTE_SET_ID];
0 ignored issues
show
Bug introduced by
The property attributeSetId does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
116
            $this->attributeSetName = $attributeSetName;
0 ignored issues
show
Bug introduced by
The property attributeSetName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
117
            $this->attributeGroupName = $attributeGroupNames[$key];
0 ignored issues
show
Bug introduced by
The property attributeGroupName does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
118
119
            // prepare the EAV entity attribue values
120
            $entityAttribute = $this->initializeAttribute($this->prepareAttributes());
121
122
            // insert the EAV entity attribute
123
            $this->persistEntityAttribute($entityAttribute);
124
        }
125
    }
126
127
    /**
128
     * Merge's and return's the entity with the passed attributes and set's the
129
     * passed status.
130
     *
131
     * @param array       $entity        The entity to merge the attributes into
132
     * @param array       $attr          The attributes to be merged
133
     * @param string|null $changeSetName The change set name to use
134
     *
135
     * @return array The merged entity
136
     * @todo https://github.com/techdivision/import/issues/179
137
     */
138 View Code Duplication
    protected function mergeEntity(array $entity, array $attr, $changeSetName = null)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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.

Loading history...
139
    {
140
        return array_merge(
141
            $entity,
142
            $this->entityMerger ? $this->entityMerger->merge($this, $entity, $attr) : $attr,
0 ignored issues
show
Bug introduced by
The property entityMerger does not seem to exist. Did you mean entityMergers?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
143
            array(EntityStatus::MEMBER_NAME => $this->detectState($entity, $attr, $changeSetName))
144
        );
145
    }
146
147
    /**
148
     * Prepare the attributes of the entity that has to be persisted.
149
     *
150
     * @return array The prepared attributes
151
     */
152
    protected function prepareAttributes()
153
    {
154
155
        // load the attribute group ID
156
        $attributeGroup = $this->getAttributeGroupByEntityTypeCodeAndAttributeSetNameAndAttributeGroupName(
157
            $this->entityTypeCode,
158
            $this->attributeSetName,
159
            $this->attributeGroupName
160
        );
161
162
        // load the attribute group ID
163
        $attributeGroupId = $attributeGroup[MemberNames::ATTRIBUTE_GROUP_ID];
164
165
        // return the prepared product
166
        return $this->initializeEntity(
167
            array(
168
                MemberNames::ATTRIBUTE_ID       => $this->attributeId,
169
                MemberNames::ENTITY_TYPE_ID     => $this->entityTypeId,
170
                MemberNames::ATTRIBUTE_SET_ID   => $this->attributeSetId,
171
                MemberNames::SORT_ORDER         => $this->sortOrder,
172
                MemberNames::ATTRIBUTE_GROUP_ID => $attributeGroupId
173
            )
174
        );
175
    }
176
177
    /**
178
     * Initialize the attribute with the passed attributes and returns an instance.
179
     *
180
     * @param array $attr The attribute attributes
181
     *
182
     * @return array The initialized attribute
183
     */
184
    protected function initializeAttribute(array $attr)
185
    {
186
        return $attr;
187
    }
188
189
    /**
190
     * Return's the attribute bunch processor instance.
191
     *
192
     * @return \TechDivision\Import\Attribute\Services\AttributeBunchProcessorInterface The attribute bunch processor instance
193
     */
194
    protected function getAttributeBunchProcessor()
195
    {
196
        return $this->attributeBunchProcessor;
197
    }
198
199
    /**
200
     * Queries whether or not the attribute with the passed code has already been processed.
201
     *
202
     * @param string $attributeCode The attribute code to check
203
     *
204
     * @return boolean TRUE if the path has been processed, else FALSE
205
     */
206
    protected function hasBeenProcessed($attributeCode)
207
    {
208
        return $this->getSubject()->hasBeenProcessed($attributeCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method hasBeenProcessed() does only exist in the following implementations of said interface: TechDivision\Import\Attr...rs\AttributeSubjectImpl, TechDivision\Import\Attr...e\Subjects\BunchSubject, TechDivision\Import\Attr...\Subjects\OptionSubject.

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...
209
    }
210
211
    /**
212
     * Return's the ID of the attribute that has been created recently.
213
     *
214
     * @return integer The attribute ID
215
     */
216
    protected function getLastAttributeId()
217
    {
218
        return $this->getSubject()->getLastAttributeId();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getLastAttributeId() does only exist in the following implementations of said interface: TechDivision\Import\Attr...rs\AttributeSubjectImpl, TechDivision\Import\Attr...e\Subjects\BunchSubject.

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...
219
    }
220
221
    /**
222
     * Return's the entity type for the passed code.
223
     *
224
     * @param string $entityTypeCode The entity type code
225
     *
226
     * @return array The requested entity type
227
     * @throws \Exception Is thrown, if the entity type with the passed code is not available
228
     */
229
    protected function getEntityType($entityTypeCode)
230
    {
231
        return $this->getSubject()->getEntityType($entityTypeCode);
0 ignored issues
show
Bug introduced by
The method getEntityType() does not exist on TechDivision\Import\Subjects\SubjectInterface. Did you maybe mean getEntityTypeCode()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
232
    }
233
234
    /**
235
     * Return's the attribute set with the passed attribute set name.
236
     *
237
     * @param string $attributeSetName The name of the requested attribute set
238
     * @param string $entityTypeCode   The entity type code of the requested attribute set
239
     *
240
     * @return array The EAV attribute set
241
     * @throws \Exception Is thrown, if the attribute set with the passed name is not available
242
     */
243
    protected function getAttributeSetByAttributeSetNameAndEntityTypeCode($attributeSetName, $entityTypeCode)
244
    {
245
        return $this->getSubject()->getAttributeSetByAttributeSetNameAndEntityTypeCode($attributeSetName, $entityTypeCode);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getAttributeSetByAttribu...NameAndEntityTypeCode() does only exist in the following implementations of said interface: TechDivision\Import\Attr...e\Subjects\BunchSubject.

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...
246
    }
247
248
    /**
249
     * Return's the attribute group with the passed attribute set/group name.
250
     *
251
     * @param string $entityTypeCode     The entity type code of the requested attribute group
252
     * @param string $attributeSetName   The name of the requested attribute group's attribute set
253
     * @param string $attributeGroupName The name of the requested attribute group
254
     *
255
     * @return array The EAV attribute group
256
     * @throws \Exception Is thrown, if the attribute group with the passed attribute set/group name is not available
257
     */
258
    protected function getAttributeGroupByEntityTypeCodeAndAttributeSetNameAndAttributeGroupName($entityTypeCode, $attributeSetName, $attributeGroupName)
259
    {
260
        return $this->getSubject()->getAttributeGroupByEntityTypeCodeAndAttributeSetNameAndAttributeGroupName($entityTypeCode, $attributeSetName, $attributeGroupName);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface TechDivision\Import\Subjects\SubjectInterface as the method getAttributeGroupByEntit...AndAttributeGroupName() does only exist in the following implementations of said interface: TechDivision\Import\Attr...e\Subjects\BunchSubject.

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...
261
    }
262
263
    /**
264
     * Persist the passed entity attribute.
265
     *
266
     * @param array $entityAttribute The entity attribute to persist
267
     *
268
     * @return void
269
     */
270
    protected function persistEntityAttribute(array $entityAttribute)
271
    {
272
        return $this->getAttributeBunchProcessor()->persistEntityAttribute($entityAttribute);
273
    }
274
}
275