Completed
Push — pac-232-import-product-variant... ( fce0e7 )
by
unknown
06:04
created

getProductBunchProcessor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * TechDivision\Import\Product\Variant\Observers\CleanUpVariantProductRelationObserver
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    Martin Eisenführer <[email protected]>
15
 * @copyright 2020 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-media
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Variant\Observers;
22
23
use TechDivision\Import\Observers\StateDetectorInterface;
24
use TechDivision\Import\Product\Link\Utils\ProductTypes;
25
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
26
use TechDivision\Import\Product\Services\ProductBunchProcessorInterface;
27
use TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface;
28
use TechDivision\Import\Product\Variant\Utils\ColumnKeys;
29
use TechDivision\Import\Product\Variant\Utils\ConfigurationKeys;
30
use TechDivision\Import\Product\Variant\Utils\MemberNames;
31
32
/**
33
 * Observer that cleaned up a product's media gallery information.
34
 *
35
 * @author    Martin Eisenführer <[email protected]>
36
 * @copyright 2020 TechDivision GmbH <[email protected]>
37
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
38
 * @link      https://github.com/techdivision/import-product-media
39
 * @link      http://www.techdivision.com
40
 */
41
class CleanUpVariantProductRelationObserver extends AbstractProductImportObserver
42
{
43
44
    /**
45
     * The product media processor instance.
46
     *
47
     * @var \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface
48
     */
49
    protected $productVariantProcessor;
50
51
    /**
52
     * @var ProductBunchProcessorInterface
53
     */
54
    private $productBunchProcessor;
55
56
    /**
57
     * Initialize the observer with the passed product media processor instance.
58
     *
59
     * @param \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface $productVariantProcessor The product media processor instance
60
     * @param ProductBunchProcessorInterface                                                 $productBunchProcessor   The product bunch processor instance
61
     * @param StateDetectorInterface|null                                                    $stateDetector           The state detector instance to use
62
     */
63
    public function __construct(
64
        ProductVariantProcessorInterface $productVariantProcessor,
65
        ProductBunchProcessorInterface $productBunchProcessor,
66
        StateDetectorInterface $stateDetector = null
67
    ) {
68
        parent::__construct($stateDetector);
69
        $this->productVariantProcessor = $productVariantProcessor;
70
        $this->productBunchProcessor = $productBunchProcessor;
71
        $this->stateDetector = $stateDetector;
72
    }
73
74
    /**
75
     * Return's the product media processor instance.
76
     *
77
     * @return \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface The product media
78
     *     processor instance
79
     */
80
    protected function getProductVariantProcessor()
81
    {
82
        return $this->productVariantProcessor;
83
    }
84
85
    /**
86
     * Return's the product bunch processor instance.
87
     *
88
     * @return \TechDivision\Import\Product\Services\ProductBunchProcessorInterface The product bunch processor instance
89
     */
90
    public function getProductBunchProcessor()
91
    {
92
        return $this->productBunchProcessor;
93
    }
94
95
    /**
96
     * Process the observer's business logic.
97
     *
98
     * @return void
99
     * @throws \Exception
100
     */
101
    protected function process()
102
    {
103
        // query whether or not we've found a configurable product
104
        if ($this->getValue(ColumnKeys::PRODUCT_TYPE) !== ProductTypes::CONFIGURABLE) {
105
            return;
106
        }
107
108
        // query whether or not the media gallery has to be cleaned up
109
        if (($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_VARIANTS)
110
            && $this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::CLEAN_UP_VARIANTS))
111
        ) {
112
            $this->findOldVariantsAndCleanUp();
113
114
            // log a message that the images has been cleaned-up
115
            $this->getSubject()
116
                ->getSystemLogger()
117
                ->debug(
118
                    $this->getSubject()->appendExceptionSuffix(
119
                        sprintf(
120
                            'Successfully clean up variants for product with SKU "%s"',
121
                            $this->getValue(ColumnKeys::SKU)
122
                        )
123
                    )
124
                );
125
        }
126
    }
127
128
    /**
129
     * Return's the primary key of the product to load.
130
     *
131
     * @param array $product product array like from ProductBunchProcessorInterface::loadProduct
132
     * @return integer The primary key of the product
133
     */
134
    protected function getPrimaryKey(array $product)
135
    {
136
        return isset($product[\TechDivision\Import\Product\Utils\MemberNames::ENTITY_ID])
137
            ? $product[\TechDivision\Import\Product\Utils\MemberNames::ENTITY_ID] : null;
138
    }
139
140
141
    /**
142
     * Search for variants in the artefact in check differenz to database.
143
     * Remove entries in DB that not exist in artefact
144
     *
145
     * @return void
146
     * @throws \Exception
147
     */
148
    protected function findOldVariantsAndCleanUp()
149
    {
150
        $artefacts = $this->getSubject()->getArtefacts();
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 getArtefacts() does only exist in the following implementations of said interface: TechDivision\Import\Plugins\ExportableSubjectImpl, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Subjects\ExportableTraitImpl.

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...
151
        if (isset($artefacts[ProductVariantObserver::ARTEFACT_TYPE]) === null) {
152
            return;
153
        }
154
155
        $actualVariants = [];
156
        $actualAttributes = [];
157
        foreach ($artefacts[ProductVariantObserver::ARTEFACT_TYPE] as $allVariants) {
158
            foreach ($allVariants as $variantData) {
159
                $parentId = null;
160
                $childId = null;
161
                try {
162
                    // try to load and map the parent ID
163
                    $product = $this->getProductBunchProcessor()
164
                        ->loadProduct($variantData[ColumnKeys::VARIANT_PARENT_SKU]);
165
                    $parentId = $this->getPrimaryKey($product);
166
                } catch (\Exception $e) {
167
                    throw $this->wrapException(array(ColumnKeys::VARIANT_PARENT_SKU), $e);
0 ignored issues
show
Documentation introduced by
array(\TechDivision\Impo...ys::VARIANT_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...
168
                }
169
170
                try {
171
                    // try to load and map the child ID
172
                    $product =
173
                        $this->getProductBunchProcessor()->loadProduct($variantData[ColumnKeys::VARIANT_CHILD_SKU]);
174
                    $childId = $this->getPrimaryKey($product);
175
                } catch (\Exception $e) {
176
                    throw $this->wrapException(array(ColumnKeys::VARIANT_CHILD_SKU), $e);
0 ignored issues
show
Documentation introduced by
array(\TechDivision\Impo...eys::VARIANT_CHILD_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...
177
                }
178
                if ($parentId && $childId) {
179
                    $actualVariants[$parentId][$childId] = $childId;
180
                    $actualAttributes[$parentId][$variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE]] =
181
                        $variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE];
182
                }
183
            }
184
        }
185
186
        // delete not exists import variants from database
187
        $this->cleanUpVariantChildren($actualVariants);
188
        $this->cleanUpVariantAttributes($actualAttributes);
189
    }
190
191
    /**
192
     * Delete not exists import variants from database
193
     *
194
     * @param array $actualVariants array of variants
195
     * @return void
196
     * @throws \Exception
197
     */
198
    protected function cleanUpVariantChildren(array $actualVariants)
199
    {
200
        $parentSku = $this->getValue(ColumnKeys::SKU);
201
        foreach ($actualVariants as $parentProductId => $childData) {
202
            // load the existing variant entities for the product with the given SKU
203 View Code Duplication
            foreach ($this->getProductVariantProcessor()
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
204
                         ->loadProductSuperLinksFromParent($parentProductId) as $existingVariantChildren) {
205
                if (in_array($existingVariantChildren[MemberNames::PRODUCT_ID], $childData)) {
206
                    continue;
207
                }
208
209
                try {
210
                    // remove the old variantes from the database
211
                    $this->getProductVariantProcessor()
212
                        ->deleteProductSuperLink(array(MemberNames::LINK_ID => $existingVariantChildren[MemberNames::LINK_ID]));
213
214
                    // log a debug message that the image has been removed
215
                    $this->getSubject()
216
                        ->getSystemLogger()
217
                        ->info(
218
                            $this->getSubject()->appendExceptionSuffix(
219
                                sprintf(
220
                                    'Successfully clean up variants for product with SKU "%s"',
221
                                    $parentSku
222
                                )
223
                            )
224
                        );
225
                } catch (\Exception $e) {
226
                    // log a warning if debug mode has been enabled and the file is NOT available
227
                    if ($this->getSubject()->isDebugMode()) {
228
                        $this->getSubject()
229
                            ->getSystemLogger()
230
                            ->critical($this->getSubject()->appendExceptionSuffix($e->getMessage()));
231
                    } else {
232
                        throw $e;
233
                    }
234
                }
235
            }
236
        }
237
    }
238
239
    /**
240
     * Delete not exists import variants from database
241
     *
242
     * @param array $actualAttributes array of actual attributes
243
     * @return void
244
     * @throws \Exception
245
     */
246
    protected function cleanUpVariantAttributes(array $actualAttributes)
247
    {
248
        $parentSku = $this->getValue(ColumnKeys::SKU);
249
        $allProductAttributes = $this->getSubject()->getAttributes();
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 getAttributes() does only exist in the following implementations of said interface: TechDivision\Import\Observers\EntitySubjectImpl, TechDivision\Import\Prod...\AbstractProductSubject, TechDivision\Import\Product\Subjects\BunchSubject, TechDivision\Import\Prod...Subjects\VariantSubject, TechDivision\Import\Subjects\AbstractEavSubject, TechDivision\Import\Subjects\ValidatorSubject.

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...
250
        foreach ($actualAttributes as $parentProductId => $attributeCodes) {
251
            // search and collect attribute ID from $attributeCode
252
            $attributeFromParentProduct = [];
253
            foreach ($attributeCodes as $attributeCode) {
254
                if (isset($allProductAttributes[$attributeCode])) {
255
                    $attributeFromParentProduct[] = $allProductAttributes[$attributeCode][MemberNames::ATTRIBUTE_ID];
256
                }
257
            }
258
            // load the existing super attributes for the product with the given SKU
259 View Code Duplication
            foreach ($this->getProductVariantProcessor()
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
260
                         ->loadProductSuperAttributesFromProduct($parentProductId) as $existingSuperAttribute) {
261
                if (in_array($existingSuperAttribute[MemberNames::ATTRIBUTE_ID], $attributeFromParentProduct)) {
262
                    continue;
263
                }
264
265
                try {
266
                    // remove the old super attributes from the database
267
                    $this->getProductVariantProcessor()
268
                        ->deleteProductSuperAttribute(array(MemberNames::PRODUCT_SUPER_ATTRIBUTE_ID => $existingSuperAttribute[MemberNames::PRODUCT_SUPER_ATTRIBUTE_ID]));
269
270
                    // log a debug message that the image has been removed
271
                    $this->getSubject()
272
                        ->getSystemLogger()
273
                        ->info(
274
                            $this->getSubject()->appendExceptionSuffix(
275
                                sprintf(
276
                                    'Successfully clean up attributes for product with SKU "%s"',
277
                                    $parentSku
278
                                )
279
                            )
280
                        );
281
                } catch (\Exception $e) {
282
                    // log a warning if debug mode has been enabled and the file is NOT available
283
                    if ($this->getSubject()->isDebugMode()) {
284
                        $this->getSubject()
285
                            ->getSystemLogger()
286
                            ->critical($this->getSubject()->appendExceptionSuffix($e->getMessage()));
287
                    } else {
288
                        throw $e;
289
                    }
290
                }
291
            }
292
        }
293
    }
294
}
295