Completed
Push — 22.x ( bcdba3...51a4b7 )
by Tim
01:41
created

BundleOptionObserver::mergeEntity()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 8

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 8
loc 8
ccs 0
cts 8
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 3
crap 6
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Bundle\Observers\BundleOptionObserver
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-product-bundle
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Bundle\Observers;
22
23
use TechDivision\Import\Utils\EntityStatus;
24
use TechDivision\Import\Utils\StoreViewCodes;
25
use TechDivision\Import\Utils\BackendTypeKeys;
26
use TechDivision\Import\Observers\StateDetectorInterface;
27
use TechDivision\Import\Observers\AttributeLoaderInterface;
28
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
29
use TechDivision\Import\Observers\EntityMergers\EntityMergerInterface;
30
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
31
use TechDivision\Import\Product\Bundle\Utils\ColumnKeys;
32
use TechDivision\Import\Product\Bundle\Utils\MemberNames;
33
use TechDivision\Import\Product\Bundle\Utils\EntityTypeCodes;
34
use TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface;
35
36
/**
37
 * Oberserver that provides functionality for the bundle option replace operation.
38
 *
39
 * @author    Tim Wagner <[email protected]>
40
 * @copyright 2016 TechDivision GmbH <[email protected]>
41
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
42
 * @link      https://github.com/techdivision/import-product-bundle
43
 * @link      http://www.techdivision.com
44
 */
45
class BundleOptionObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface
46
{
47
48
    /**
49
     * The product bundle processor instance.
50
     *
51
     * @var \TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface
52
     */
53
    protected $productBundleProcessor;
54
55
    /**
56
     * The attribute loader instance.
57
     *
58
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
59
     */
60
    protected $attributeLoader;
61
62
    /**
63
     * The entity merger instance.
64
     *
65
     * @var \TechDivision\Import\Observers\EntityMergers\EntityMergerInterface
66
     */
67
    protected $entityMerger;
68
69
    /**
70
     * Initialize the "dymanmic" columns.
71
     *
72
     * @var array
73
     */
74
    protected $columns = array(MemberNames::POSITION => array(ColumnKeys::BUNDLE_VALUE_POSITION, BackendTypeKeys::BACKEND_TYPE_INT));
75
76
    /**
77
     * Initialize the observer with the passed product bundle processor instance.
78
     *
79
     * @param \TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface $productBundleProcessor The product bundle processor instance
80
     * @param \TechDivision\Import\Observers\AttributeLoaderInterface|null                 $attributeLoader        The attribute loader instance
81
     * @param \TechDivision\Import\Observers\EntityMergers\EntityMergerInterface           $entityMerger           The entity merger instance
82
     * @param \TechDivision\Import\Observers\StateDetectorInterface|null                   $stateDetector          The state detector instance
83
     */
84 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...
85
        ProductBundleProcessorInterface $productBundleProcessor,
86
        AttributeLoaderInterface $attributeLoader = null,
87
        EntityMergerInterface $entityMerger = null,
88
        StateDetectorInterface $stateDetector = null
89
    ) {
90
91
        // initialize the product bundle processor, the attribute loader and entity merger instance
92
        $this->productBundleProcessor = $productBundleProcessor;
93
        $this->attributeLoader = $attributeLoader;
94
        $this->entityMerger = $entityMerger;
95
96
        // pass the state detector to the parent method
97
        parent::__construct($stateDetector);
98
    }
99
100
    /**
101
     * Return's the product bundle processor instance.
102
     *
103
     * @return \TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface The product bundle processor instance
104
     */
105
    protected function getProductBundleProcessor()
106
    {
107
        return $this->productBundleProcessor;
108
    }
109
110
    /**
111
     * Process the observer's business logic.
112
     *
113
     * @return array The processed row
114
     */
115
    protected function process()
116
    {
117
118
        // prepare the store view code
119
        $this->prepareStoreViewCode($this->getRow());
0 ignored issues
show
Unused Code introduced by
The call to BundleOptionObserver::prepareStoreViewCode() has too many arguments starting with $this->getRow().

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
120
121
        // return immediately if we're have no store view code set
122
        if (StoreViewCodes::ADMIN !== $this->getStoreViewCode(StoreViewCodes::ADMIN)) {
123
            return;
124
        }
125
126
        // load the name and the parent SKU
127
        $name = $this->getValue(ColumnKeys::BUNDLE_VALUE_NAME);
128
        $parentSku = $this->getValue(ColumnKeys::BUNDLE_PARENT_SKU);
129
130
        // query whether or not the option has already been created for the parent SKU
131
        if ($this->exists($parentSku, $name)) {
132
            return;
133
        }
134
135
        // load and persist the product bundle option
136
        $optionId = $this->persistProductBundleOption($this->initializeBundleOption($this->prepareDynamicAttributes()));
137
138
        // store the parent SKU => name mapping
139
        $this->addParentSkuNameMapping(array($parentSku => array($name => $optionId)));
140
    }
141
142
    /**
143
     * Appends the dynamic attributes to the static ones and returns them.
144
     *
145
     * @return array The array with all available attributes
146
     */
147
    protected function prepareDynamicAttributes() : array
148
    {
149
        return array_merge($this->prepareAttributes(), $this->attributeLoader ? $this->attributeLoader->load($this, $this->columns) : array());
150
    }
151
152
    /**
153
     * Prepare the attributes of the entity that has to be persisted.
154
     *
155
     * @return array The prepared attributes
156
     */
157
    protected function prepareAttributes()
158
    {
159
160
        try {
161
            // load and map the parent SKU
162
            $parentId = $this->mapSku($this->getValue(ColumnKeys::BUNDLE_PARENT_SKU));
163
        } catch (\Exception $e) {
164
            throw $this->wrapException(array(ColumnKeys::BUNDLE_PARENT_SKU), $e);
0 ignored issues
show
Documentation introduced by
array(\TechDivision\Impo...eys::BUNDLE_PARENT_SKU) is of type array<integer,?>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
165
        }
166
167
        // extract the parent/child ID as well as type and position
168
        $type = $this->getValue(ColumnKeys::BUNDLE_VALUE_TYPE);
169
        $required = $this->getValue(ColumnKeys::BUNDLE_VALUE_REQUIRED);
170
171
        // return the prepared product
172
        return $this->initializeEntity(
173
            $this->loadRawEntity(
174
                array(
175
                    MemberNames::PARENT_ID => $parentId,
176
                    MemberNames::REQUIRED  => $required,
177
                    MemberNames::TYPE      => $type
178
                )
179
            )
180
        );
181
    }
182
183
    /**
184
     * Merge's and return's the entity with the passed attributes and set's the
185
     * passed status.
186
     *
187
     * @param array       $entity        The entity to merge the attributes into
188
     * @param array       $attr          The attributes to be merged
189
     * @param string|null $changeSetName The change set name to use
190
     *
191
     * @return array The merged entity
192
     * @todo https://github.com/techdivision/import/issues/179
193
     */
194 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...
195
    {
196
        return array_merge(
197
            $entity,
198
            $this->entityMerger ? $this->entityMerger->merge($this, $entity, $attr) : $attr,
199
            array(EntityStatus::MEMBER_NAME => $this->detectState($entity, $attr, $changeSetName))
200
        );
201
    }
202
203
    /**
204
     * Load's and return's a raw entity without primary key but the mandatory members only and nulled values.
205
     *
206
     * @param array $data An array with data that will be used to initialize the raw entity with
207
     *
208
     * @return array The initialized entity
209
     */
210
    protected function loadRawEntity(array $data = array())
211
    {
212
        return $this->getProductBundleProcessor()->loadRawEntity(EntityTypeCodes::CATALOG_PRODUCT_BUNDLE_OPTION, $data);
213
    }
214
215
    /**
216
     * Initialize the bundle option with the passed attributes and returns an instance.
217
     *
218
     * @param array $attr The bundle option attributes
219
     *
220
     * @return array The initialized bundle option
221
     */
222
    protected function initializeBundleOption(array $attr)
223
    {
224
        return $attr;
225
    }
226
227
    /**
228
     * Reset the position counter to 1.
229
     *
230
     * @return void
231
     * @deprecated Since 22.0.0
232
     */
233
    protected function resetPositionCounter()
234
    {
235
        $this->getSubject()->resetPositionCounter();
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 resetPositionCounter() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\Subjects\BundleSubject.

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...
236
    }
237
238
    /**
239
     * Add's the passed mapping to the subject.
240
     *
241
     * @param array $mapping The mapping to add
242
     *
243
     * @return void
244
     */
245
    protected function addParentSkuNameMapping($mapping = array())
246
    {
247
        $this->getSubject()->addParentSkuNameMapping($mapping);
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 addParentSkuNameMapping() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\Subjects\BundleSubject.

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...
248
    }
249
250
    /**
251
     * Query whether or not the option for the passed parent SKU and name has already been created.
252
     *
253
     * @param string $parentSku The parent SKU to query for
254
     * @param string $name      The option name to query for
255
     *
256
     * @return boolean TRUE if the option already exists, else FALSE
257
     */
258
    protected function exists($parentSku, $name)
259
    {
260
        return $this->getSubject()->exists($parentSku, $name);
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 exists() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\Subjects\BundleSubject.

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
     * Return the entity ID for the passed SKU.
265
     *
266
     * @param string $sku The SKU to return the entity ID for
267
     *
268
     * @return integer The mapped entity ID
269
     * @throws \Exception Is thrown if the SKU is not mapped yet
270
     */
271
    protected function mapSku($sku)
272
    {
273
        return $this->getSubject()->mapSkuToEntityId($sku);
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 mapSkuToEntityId() does only exist in the following implementations of said interface: TechDivision\Import\Prod...\Subjects\BundleSubject, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\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...
274
    }
275
276
    /**
277
     * Persist's the passed product bundle option data and return's the ID.
278
     *
279
     * @param array $productBundleOption The product bundle option data to persist
280
     *
281
     * @return string The ID of the persisted entity
282
     */
283
    protected function persistProductBundleOption($productBundleOption)
284
    {
285
        return $this->getProductBundleProcessor()->persistProductBundleOption($productBundleOption);
286
    }
287
}
288