Completed
Push — master ( 00926c...4a3877 )
by Tim
13s queued 11s
created

CleanUpVariantProductRelationObserver::process()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 28
rs 9.472
c 0
b 0
f 0
cc 4
nc 3
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-variant
18
 * @link      http://www.techdivision.com
19
 */
20
21
namespace TechDivision\Import\Product\Variant\Observers;
22
23
use TechDivision\Import\Utils\ProductTypes;
24
use TechDivision\Import\Observers\StateDetectorInterface;
25
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
26
use TechDivision\Import\Product\Variant\Utils\ColumnKeys;
27
use TechDivision\Import\Product\Variant\Utils\MemberNames;
28
use TechDivision\Import\Product\Variant\Utils\ConfigurationKeys;
29
use TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface;
30
31
/**
32
 * Observer that cleaned up a product's media gallery information.
33
 *
34
 * @author    Martin Eisenführer <[email protected]>
35
 * @copyright 2020 TechDivision GmbH <[email protected]>
36
 * @license   http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
37
 * @link      https://github.com/techdivision/import-product-variant
38
 * @link      http://www.techdivision.com
39
 */
40
class CleanUpVariantProductRelationObserver extends AbstractProductImportObserver
41
{
42
43
    /**
44
     * The product variant processor instance.
45
     *
46
     * @var \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface
47
     */
48
    protected $productVariantProcessor;
49
50
    /**
51
     * Initialize the observer with the passed product variant processor instance.
52
     *
53
     * @param \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface $productVariantProcessor The product variant processor instance
54
     * @param StateDetectorInterface|null                                                    $stateDetector           The state detector instance to use
55
     */
56
    public function __construct(
57
        ProductVariantProcessorInterface $productVariantProcessor,
58
        StateDetectorInterface $stateDetector = null
59
    ) {
60
61
        // pass the state detector to the parent constructor
62
        parent::__construct($stateDetector);
63
64
        // initialize the product variant processor instance
65
        $this->productVariantProcessor = $productVariantProcessor;
66
    }
67
68
    /**
69
     * Return's the product variant processor instance.
70
     *
71
     * @return \TechDivision\Import\Product\Variant\Services\ProductVariantProcessorInterface The product variant processor instance
72
     */
73
    protected function getProductVariantProcessor()
74
    {
75
        return $this->productVariantProcessor;
76
    }
77
78
    /**
79
     * Process the observer's business logic.
80
     *
81
     * @return void
82
     * @throws \Exception
83
     */
84
    protected function process()
85
    {
86
87
        // query whether or not we've found a configurable product
88
        if ($this->getValue(ColumnKeys::PRODUCT_TYPE) !== ProductTypes::CONFIGURABLE) {
89
            return;
90
        }
91
92
        // query whether or not the media gallery has to be cleaned up
93
        if ($this->getSubject()->getConfiguration()->hasParam(ConfigurationKeys::CLEAN_UP_VARIANTS) &&
94
            $this->getSubject()->getConfiguration()->getParam(ConfigurationKeys::CLEAN_UP_VARIANTS)
95
        ) {
96
            // clean-up the existing variants
97
            $this->cleanUpVariants();
98
99
            // log a message that the images has been cleaned-up
100
            $this->getSubject()
101
                 ->getSystemLogger()
102
                 ->debug(
103
                     $this->getSubject()->appendExceptionSuffix(
104
                         sprintf(
105
                             'Successfully clean up variants for product with SKU "%s"',
106
                             $this->getValue(ColumnKeys::SKU)
107
                         )
108
                     )
109
                 );
110
        }
111
    }
112
113
    /**
114
     * Search for variants in the artefacts and check for differences in
115
     * the database. Remove entries in DB that not exist in artefact.
116
     *
117
     * @return void
118
     * @throws \Exception Is thrown, if either the variant children und attributes can not be deleted
119
     */
120
    protected function cleanUpVariants()
121
    {
122
123
        // load the available artefacts from the subject
124
        $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...
125
126
        // return, if we do NOT have any variant artefacts
127
        if (!isset($artefacts[ProductVariantObserver::ARTEFACT_TYPE])) {
128
            return;
129
        }
130
131
        // load the entity ID of the parent product
132
        $parentIdForArtefacts = $this->getLastEntityId();
133
134
        // return, if we do NOT have any artefacts for the actual entity ID
135
        if (!isset($artefacts[ProductVariantObserver::ARTEFACT_TYPE][$parentIdForArtefacts])) {
136
            return;
137
        }
138
139
        // initialize the array with the SKUs of
140
        // the child IDs and the attribute codes
141
        $actualVariants = [];
142
        $actualAttributes = [];
143
144
        // load the variant artefacts for the actual entity ID
145
        $allVariants = $artefacts[ProductVariantObserver::ARTEFACT_TYPE][$parentIdForArtefacts];
146
147
        // iterate over the artefacts with the variant data
148
        foreach ($allVariants as $variantData) {
149
            // add the child SKU to the array
150
            $actualVariants[] = $variantData[ColumnKeys::VARIANT_CHILD_SKU];
151
            // add the attribute code to the array
152
            $actualAttributes[$variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE]] =
153
                $variantData[ColumnKeys::VARIANT_ATTRIBUTE_CODE];
154
        }
155
156
        // load the row/entity ID of the parent product
157
        $parentId = $this->getLastPrimaryKey();
158
159
        try {
160
            // delete not exists import variants from database
161
            $this->cleanUpVariantChildren($parentId, $actualVariants);
162
            $this->cleanUpVariantAttributes($parentId, $actualAttributes);
163
        } catch (\Exception $e) {
164
            // log a warning if debug mode has been enabled
165
            if ($this->getSubject()->isDebugMode()) {
166
                $this->getSubject()
167
                     ->getSystemLogger()
168
                     ->critical($this->getSubject()->appendExceptionSuffix($e->getMessage()));
169
            } else {
170
                throw $e;
171
            }
172
        }
173
    }
174
175
    /**
176
     * Delete not exists import variants from database.
177
     *
178
     * @param int   $parentProductId The ID of the parent product
179
     * @param array $childData       The array of variants
180
     *
181
     * @return void
182
     */
183
    protected function cleanUpVariantChildren($parentProductId, array $childData)
184
    {
185
186
        // we don't want delete everything
187
        if (empty($childData)) {
188
            return;
189
        }
190
191
        // load the SKU of the parent product
192
        $parentSku = $this->getValue(ColumnKeys::SKU);
193
194
        // remove the old variantes from the database
195
        $this->getProductVariantProcessor()
196
             ->deleteProductSuperLink(
197
                 array(
198
                     MemberNames::PARENT_ID => $parentProductId,
199
                     MemberNames::SKU       => $childData
200
                 )
201
             );
202
203
        // log a debug message that the image has been removed
204
        $this->getSubject()
205
             ->getSystemLogger()
206
             ->debug(
207
                 $this->getSubject()->appendExceptionSuffix(
208
                     sprintf(
209
                         'Successfully clean up variants for product with SKU "%s" except "%s"',
210
                         $parentSku,
211
                         implode(', ', $childData)
212
                     )
213
                 )
214
             );
215
    }
216
217
    /**
218
     * Delete not exists import variants from database.
219
     *
220
     * @param int   $parentProductId  The ID of the parent product
221
     * @param array $actualAttributes The array of actual attributes
222
     *
223
     * @return void
224
     */
225
    protected function cleanUpVariantAttributes($parentProductId, array $actualAttributes)
226
    {
227
228
        // load the SKU and the attributes from the subject
229
        $parentSku = $this->getValue(ColumnKeys::SKU);
230
        $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...
231
232
        // search and collect attribute ID from $attributeCode
233
        $attributeIdFromParentProduct = [];
234
235
        // prepare the array with the variant
236
        // attribute IDs of the parent product
237
        foreach ($actualAttributes as $attributeCode) {
238
            if (isset($allProductAttributes[$attributeCode])) {
239
                $attributeIdFromParentProduct[] = $allProductAttributes[$attributeCode][MemberNames::ATTRIBUTE_ID];
240
            }
241
        }
242
243
        // we don't want delete everything
244
        if (empty($attributeIdFromParentProduct)) {
245
            return;
246
        }
247
248
        // remove the old super attributes from the database
249
        $this->getProductVariantProcessor()
250
             ->deleteProductSuperAttribute(
251
                 array(
252
                     MemberNames::PRODUCT_ID   => $parentProductId,
253
                     MemberNames::ATTRIBUTE_ID => $attributeIdFromParentProduct
254
                 )
255
             );
256
257
        // log a debug message that the image has been removed
258
        $this->getSubject()
259
             ->getSystemLogger()
260
             ->info(
261
                 $this->getSubject()->appendExceptionSuffix(
262
                     sprintf(
263
                         'Successfully clean up variant attributes for product with SKU "%s"',
264
                         $parentSku
265
                     )
266
                 )
267
             );
268
    }
269
270
    /**
271
     * Return's the PK to create the product => variant relation.
272
     *
273
     * @return integer The PK to create the relation with
274
     */
275
    protected function getLastPrimaryKey()
276
    {
277
        return $this->getLastEntityId();
278
    }
279
}
280