Completed
Push — 22.x ( d7197a...bcdba3 )
by Tim
01:45
created

BundleOptionObserver::hasValue()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 1
nop 1
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\StoreViewCodes;
24
use TechDivision\Import\Utils\BackendTypeKeys;
25
use TechDivision\Import\Observers\StateDetectorInterface;
26
use TechDivision\Import\Observers\AttributeLoaderInterface;
27
use TechDivision\Import\Observers\DynamicAttributeObserverInterface;
28
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
29
use TechDivision\Import\Product\Bundle\Utils\ColumnKeys;
30
use TechDivision\Import\Product\Bundle\Utils\MemberNames;
31
use TechDivision\Import\Product\Bundle\Utils\EntityTypeCodes;
32
use TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface;
33
34
/**
35
 * Oberserver that provides functionality for the bundle option replace operation.
36
 *
37
 * @author    Tim Wagner <[email protected]>
38
 * @copyright 2016 TechDivision GmbH <[email protected]>
39
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
40
 * @link      https://github.com/techdivision/import-product-bundle
41
 * @link      http://www.techdivision.com
42
 */
43
class BundleOptionObserver extends AbstractProductImportObserver implements DynamicAttributeObserverInterface
44
{
45
46
    /**
47
     * The product bundle processor instance.
48
     *
49
     * @var \TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface
50
     */
51
    protected $productBundleProcessor;
52
53
    /**
54
     * The attribute loader instance.
55
     *
56
     * @var \TechDivision\Import\Observers\AttributeLoaderInterface
57
     */
58
    protected $attributeLoader;
59
60
    /**
61
     * Initialize the "dymanmic" columns.
62
     *
63
     * @var array
64
     */
65
    protected $columns = array(MemberNames::POSITION => array(ColumnKeys::BUNDLE_VALUE_POSITION, BackendTypeKeys::BACKEND_TYPE_INT));
66
67
    /**
68
     * Array with virtual column name mappings (this is a temporary
69
     * solution till techdivision/import#179 as been implemented).
70
     *
71
     * @var array
72
     * @todo https://github.com/techdivision/import/issues/179
73
     */
74
    protected $virtualMapping = array(MemberNames::POSITION => ColumnKeys::BUNDLE_VALUE_POSITION);
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\StateDetectorInterface|null                   $stateDetector          The state detector instance
82
     */
83 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...
84
        ProductBundleProcessorInterface $productBundleProcessor,
85
        AttributeLoaderInterface $attributeLoader = null,
86
        StateDetectorInterface $stateDetector = null
87
    ) {
88
89
        // initialize the product bundle processor and the attribute loader instance
90
        $this->productBundleProcessor = $productBundleProcessor;
91
        $this->attributeLoader = $attributeLoader;
92
93
        // pass the state detector to the parent method
94
        parent::__construct($stateDetector);
95
    }
96
97
    /**
98
     * Return's the product bundle processor instance.
99
     *
100
     * @return \TechDivision\Import\Product\Bundle\Services\ProductBundleProcessorInterface The product bundle processor instance
101
     */
102
    protected function getProductBundleProcessor()
103
    {
104
        return $this->productBundleProcessor;
105
    }
106
107
    /**
108
     * Query whether or not a value for the column with the passed name exists.
109
     *
110
     * @param string $name The column name to query for a valid value
111
     *
112
     * @return boolean TRUE if the value is set, else FALSE
113
     * @todo https://github.com/techdivision/import/issues/179
114
     */
115
    public function hasValue($name)
116
    {
117
        return parent::hasValue(isset($this->virtualMapping[$name]) ? $this->virtualMapping[$name] : $name);
118
    }
119
120
    /**
121
     * Process the observer's business logic.
122
     *
123
     * @return array The processed row
124
     */
125
    protected function process()
126
    {
127
128
        // prepare the store view code
129
        $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...
130
131
        // return immediately if we're have no store view code set
132
        if (StoreViewCodes::ADMIN !== $this->getStoreViewCode(StoreViewCodes::ADMIN)) {
133
            return;
134
        }
135
136
        // load the name and the parent SKU
137
        $name = $this->getValue(ColumnKeys::BUNDLE_VALUE_NAME);
138
        $parentSku = $this->getValue(ColumnKeys::BUNDLE_PARENT_SKU);
139
140
        // query whether or not the option has already been created for the parent SKU
141
        if ($this->exists($parentSku, $name)) {
142
            return;
143
        }
144
145
        // load and persist the product bundle option
146
        $optionId = $this->persistProductBundleOption($this->initializeBundleOption($this->prepareDynamicAttributes()));
147
148
        // store the parent SKU => name mapping
149
        $this->addParentSkuNameMapping(array($parentSku => array($name => $optionId)));
150
    }
151
152
    /**
153
     * Appends the dynamic attributes to the static ones and returns them.
154
     *
155
     * @return array The array with all available attributes
156
     */
157
    protected function prepareDynamicAttributes() : array
158
    {
159
        return array_merge($this->prepareAttributes(), $this->attributeLoader ? $this->attributeLoader->load($this, $this->columns) : array());
160
    }
161
162
    /**
163
     * Prepare the attributes of the entity that has to be persisted.
164
     *
165
     * @return array The prepared attributes
166
     */
167
    protected function prepareAttributes()
168
    {
169
170
        try {
171
            // load and map the parent SKU
172
            $parentId = $this->mapSku($this->getValue(ColumnKeys::BUNDLE_PARENT_SKU));
173
        } catch (\Exception $e) {
174
            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...
175
        }
176
177
        // extract the parent/child ID as well as type and position
178
        $type = $this->getValue(ColumnKeys::BUNDLE_VALUE_TYPE);
179
        $required = $this->getValue(ColumnKeys::BUNDLE_VALUE_REQUIRED);
180
181
        // return the prepared product
182
        return $this->initializeEntity(
183
            $this->loadRawEntity(
184
                array(
185
                    MemberNames::PARENT_ID => $parentId,
186
                    MemberNames::REQUIRED  => $required,
187
                    MemberNames::TYPE      => $type
188
                )
189
            )
190
        );
191
    }
192
193
    /**
194
     * Load's and return's a raw entity without primary key but the mandatory members only and nulled values.
195
     *
196
     * @param array $data An array with data that will be used to initialize the raw entity with
197
     *
198
     * @return array The initialized entity
199
     */
200
    protected function loadRawEntity(array $data = array())
201
    {
202
        return $this->getProductBundleProcessor()->loadRawEntity(EntityTypeCodes::CATALOG_PRODUCT_BUNDLE_OPTION, $data);
203
    }
204
205
    /**
206
     * Initialize the bundle option with the passed attributes and returns an instance.
207
     *
208
     * @param array $attr The bundle option attributes
209
     *
210
     * @return array The initialized bundle option
211
     */
212
    protected function initializeBundleOption(array $attr)
213
    {
214
        return $attr;
215
    }
216
217
    /**
218
     * Reset the position counter to 1.
219
     *
220
     * @return void
221
     * @deprecated Since 22.0.0
222
     */
223
    protected function resetPositionCounter()
224
    {
225
        $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...
226
    }
227
228
    /**
229
     * Add's the passed mapping to the subject.
230
     *
231
     * @param array $mapping The mapping to add
232
     *
233
     * @return void
234
     */
235
    protected function addParentSkuNameMapping($mapping = array())
236
    {
237
        $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...
238
    }
239
240
    /**
241
     * Query whether or not the option for the passed parent SKU and name has already been created.
242
     *
243
     * @param string $parentSku The parent SKU to query for
244
     * @param string $name      The option name to query for
245
     *
246
     * @return boolean TRUE if the option already exists, else FALSE
247
     */
248
    protected function exists($parentSku, $name)
249
    {
250
        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...
251
    }
252
253
    /**
254
     * Return the entity ID for the passed SKU.
255
     *
256
     * @param string $sku The SKU to return the entity ID for
257
     *
258
     * @return integer The mapped entity ID
259
     * @throws \Exception Is thrown if the SKU is not mapped yet
260
     */
261
    protected function mapSku($sku)
262
    {
263
        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...
264
    }
265
266
    /**
267
     * Persist's the passed product bundle option data and return's the ID.
268
     *
269
     * @param array $productBundleOption The product bundle option data to persist
270
     *
271
     * @return string The ID of the persisted entity
272
     */
273
    protected function persistProductBundleOption($productBundleOption)
274
    {
275
        return $this->getProductBundleProcessor()->persistProductBundleOption($productBundleOption);
276
    }
277
}
278