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

getLastPrimaryKey()   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
     * Search for variants in the artefact in check differenz to database.
130
     * Remove entries in DB that not exist in artefact
131
     *
132
     * @return void
133
     * @throws \Exception
134
     */
135
    protected function findOldVariantsAndCleanUp()
136
    {
137
        $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...
138
        if (!isset($artefacts[ProductVariantObserver::ARTEFACT_TYPE])) {
139
            return;
140
        }
141
142
        $parentIdForArtefacts = $this->getLastEntityId();
143
        if (!isset($artefacts[ProductVariantObserver::ARTEFACT_TYPE][$parentIdForArtefacts])) {
144
            return;
145
        }
146
147
        $actualVariants = [];
148
        $actualAttributes = [];
149
        $allVariants = $artefacts[ProductVariantObserver::ARTEFACT_TYPE][$parentIdForArtefacts];
150
        foreach ($allVariants as $variantData) {
151
            $actualVariants[] = $variantData[ColumnKeys::VARIANT_CHILD_SKU];
152
            $actualAttributes[$variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE]] =
153
                $variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE];
154
        }
155
156
        $parentId = $this->getLastPrimaryKey();
157
        // delete not exists import variants from database
158
        $this->cleanUpVariantChildren($parentId, $actualVariants);
159
        $this->cleanUpVariantAttributes($parentId, $actualAttributes);
160
    }
161
162
    /**
163
     * Delete not exists import variants from database
164
     *
165
     * @param int   $parentProductId parent product ID
166
     * @param array $childData       array of variants
167
     * @return void
168
     * @throws \Exception
169
     */
170
    protected function cleanUpVariantChildren($parentProductId, array $childData)
171
    {
172
        // we don't want delete everything
173
        if (empty($childData)) {
174
            return;
175
        }
176
        $parentSku = $this->getValue(ColumnKeys::SKU);
177
        try {
178
            // remove the old variantes from the database
179
            $this->getProductVariantProcessor()
180
                ->deleteProductSuperLink(
181
                    array(
182
                        MemberNames::PARENT_ID => $parentProductId,
183
                        MemberNames::SKU => $childData
184
                    )
185
                );
186
187
            // log a debug message that the image has been removed
188
            $this->getSubject()
189
                ->getSystemLogger()
190
                ->debug(
191
                    $this->getSubject()->appendExceptionSuffix(
192
                        sprintf(
193
                            'Successfully clean up variants for product with SKU "%s" except "%s"',
194
                            $parentSku,
195
                            implode(', ', $childData)
196
                        )
197
                    )
198
                );
199
        } catch (\Exception $e) {
200
            // log a warning if debug mode has been enabled and the file is NOT available
201
            if ($this->getSubject()->isDebugMode()) {
202
                $this->getSubject()
203
                    ->getSystemLogger()
204
                    ->critical($this->getSubject()->appendExceptionSuffix($e->getMessage()));
205
            } else {
206
                throw $e;
207
            }
208
        }
209
    }
210
211
    /**
212
     * Delete not exists import variants from database
213
     *
214
     * @param int   $parentProductId  parent product ID
215
     * @param array $actualAttributes array of actual attributes
216
     * @return void
217
     * @throws \Exception
218
     */
219
    protected function cleanUpVariantAttributes($parentProductId, array $actualAttributes)
220
    {
221
        $parentSku = $this->getValue(ColumnKeys::SKU);
222
        $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...
223
        // search and collect attribute ID from $attributeCode
224
        $attributeIdFromParentProduct = [];
225
        foreach ($actualAttributes as $attributeCode) {
226
            if (isset($allProductAttributes[$attributeCode])) {
227
                $attributeIdFromParentProduct[] = $allProductAttributes[$attributeCode][MemberNames::ATTRIBUTE_ID];
228
            }
229
        }
230
231
        // we don't want delete everything
232
        if (empty($attributeIdFromParentProduct)) {
233
            return;
234
        }
235
236
        try {
237
            // remove the old super attributes from the database
238
            $this->getProductVariantProcessor()
239
                ->deleteProductSuperAttribute(
240
                    array(
241
                        MemberNames::PRODUCT_ID => $parentProductId,
242
                        MemberNames::ATTRIBUTE_ID => $attributeIdFromParentProduct
243
                    )
244
                );
245
246
            // log a debug message that the image has been removed
247
            $this->getSubject()
248
                ->getSystemLogger()
249
                ->info(
250
                    $this->getSubject()->appendExceptionSuffix(
251
                        sprintf(
252
                            'Successfully clean up attributes for product with SKU "%s"',
253
                            $parentSku
254
                        )
255
                    )
256
                );
257
        } catch (\Exception $e) {
258
            // log a warning if debug mode has been enabled and the file is NOT available
259
            if ($this->getSubject()->isDebugMode()) {
260
                $this->getSubject()
261
                    ->getSystemLogger()
262
                    ->critical($this->getSubject()->appendExceptionSuffix($e->getMessage()));
263
            } else {
264
                throw $e;
265
            }
266
        }
267
    }
268
269
    /**
270
     * Return's the PK to create the product => variant relation.
271
     *
272
     * @return integer The PK to create the relation with
273
     */
274
    protected function getLastPrimaryKey()
275
    {
276
        return $this->getLastEntityId();
277
    }
278
}
279