Completed
Pull Request — master (#392)
by Christian
03:03
created

Lifecycle   C

Complexity

Total Complexity 64

Size/Duplication

Total Lines 431
Duplicated Lines 15.31 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 2
Bugs 0 Features 0
Metric Value
dl 66
loc 431
rs 5.8364
c 2
b 0
f 0
wmc 64
lcom 1
cbo 7

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 14 14 1
A getSubscribedEvents() 0 12 1
B onPreUpdate() 0 38 6
A onUpdateOrder() 0 16 3
A onDeleteArticle() 0 5 1
A onDeleteDetail() 0 13 3
A onUpdateArticle() 0 6 1
C handleChange() 0 57 14
C onPersistDetail() 0 39 7
A onDeleteShop() 0 15 3
B generateChangesForDetail() 21 21 5
B generateChangesForArticle() 20 20 5
A isAutoUpdateEnabled() 0 4 2
A updateOrderStatus() 0 16 4
A updatePaymentStatus() 0 12 2
A generateChangeForPaymentStatus() 0 8 2
A hasPriceType() 11 11 4

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Lifecycle often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Lifecycle, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * (c) shopware AG <[email protected]>
4
 * For the full copyright and license information, please view the LICENSE
5
 * file that was distributed with this source code.
6
 */
7
8
namespace ShopwarePlugins\Connect\Subscribers;
9
10
use Enlight\Event\SubscriberInterface;
11
use Shopware\Connect\SDK;
12
use Shopware\Connect\Struct\PaymentStatus;
13
use Shopware\Components\Model\ModelManager;
14
use Shopware\CustomModels\Connect\Attribute;
15
use ShopwarePlugins\Connect\Components\Config;
16
use ShopwarePlugins\Connect\Components\Helper;
17
use ShopwarePlugins\Connect\Components\Utils;
18
use ShopwarePlugins\Connect\Components\ConnectExport;
19
use Shopware\Models\Order\Order;
20
21
/**
22
 * Handles article lifecycle events in order to automatically update/delete products to/from connect
23
 *
24
 * Class Lifecycle
25
 * @package ShopwarePlugins\Connect\Subscribers
26
 */
27
class Lifecycle implements SubscriberInterface
28
{
29
    /**
30
     * @var \Shopware\Components\Model\ModelManager
31
     */
32
    private $manager;
33
34
    /**
35
     * @var Helper
36
     */
37
    private $helper;
38
    /**
39
     * @var SDK
40
     */
41
    private $sdk;
42
43
    /**
44
     * @var Config
45
     */
46
    private $config;
47
48
    /**
49
     * @var ConnectExport
50
     */
51
    private $connectExport;
52
53
    /**
54
     * @var int
55
     */
56
    private $autoUpdateProducts;
57
58
    /**
59
     * Lifecycle constructor.
60
     *
61
     * @param ModelManager $modelManager
62
     * @param Helper $helper
63
     * @param SDK $sdk
64
     * @param Config $config
65
     * @param ConnectExport $connectExport
66
     */
67 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...
68
        ModelManager $modelManager,
69
        Helper $helper,
70
        SDK $sdk,
71
        Config $config,
72
        ConnectExport $connectExport
73
    ) {
74
        $this->manager = $modelManager;
75
        $this->helper = $helper;
76
        $this->sdk = $sdk;
77
        $this->config = $config;
78
        $this->connectExport = $connectExport;
79
        $this->autoUpdateProducts = $this->config->getConfig('autoUpdateProducts', 1);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->autoUpdateProducts is correct as $this->config->getConfig('autoUpdateProducts', 1) (which targets ShopwarePlugins\Connect\...nts\Config::getConfig()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
80
    }
81
82
    /**
83
     * {@inheritdoc}
84
     */
85
    public static function getSubscribedEvents()
86
    {
87
        return [
88
            'Shopware\Models\Article\Article::preUpdate' => 'onPreUpdate',
89
            'Shopware\Models\Article\Article::postPersist' => 'onUpdateArticle',
90
            'Shopware\Models\Article\Detail::postPersist' => 'onPersistDetail',
91
            'Shopware\Models\Article\Article::preRemove' => 'onDeleteArticle',
92
            'Shopware\Models\Article\Detail::preRemove' => 'onDeleteDetail',
93
            'Shopware\Models\Order\Order::postUpdate' => 'onUpdateOrder',
94
            'Shopware\Models\Shop\Shop::preRemove' => 'onDeleteShop',
95
        ];
96
    }
97
98
    public function onPreUpdate(\Enlight_Event_EventArgs $eventArgs)
99
    {
100
        /** @var \Shopware\Models\Article\Article $entity */
101
        $entity = $eventArgs->get('entity');
102
        $db = Shopware()->Db();
103
104
        // Check if entity is a connect product
105
        $attribute = $this->helper->getConnectAttributeByModel($entity);
106
        if (!$attribute) {
107
            return;
108
        }
109
110
        // if article is not exported to Connect
111
        // don't need to generate changes
112
        if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
113
            return;
114
        }
115
116
        if (!$this->hasPriceType()) {
117
            return;
118
        }
119
120
        $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($entity);
121
122
        // If product propertyGroup is changed we need to store the old one,
123
        // because product property value are still not changed and
124
        // this will generate wrong Connect changes
125
        if ($changeSet['propertyGroup']) {
126
            $filterGroupId = $db->fetchOne(
127
                'SELECT filtergroupID FROM s_articles WHERE id = ?', [$entity->getId()]
128
            );
129
130
            $db->executeUpdate(
131
                'UPDATE `s_articles_attributes` SET `connect_property_group` = ? WHERE `articledetailsID` = ?',
132
                [$filterGroupId, $entity->getMainDetail()->getId()]
133
            );
134
        }
135
    }
136
137
    /**
138
     * @param \Enlight_Event_EventArgs $eventArgs
139
     */
140
    public function onUpdateOrder(\Enlight_Event_EventArgs $eventArgs)
141
    {
142
        /** @var \Shopware\Models\Order\Order $order */
143
        $order = $eventArgs->get('entity');
144
145
        // Compute the changeset and return, if orderStatus did not change
146
        $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($order);
147
148
        if (isset($changeSet['paymentStatus'])) {
149
            $this->updatePaymentStatus($order);
150
        }
151
152
        if (isset($changeSet['orderStatus'])) {
153
            $this->updateOrderStatus($order);
154
        }
155
    }
156
157
    /**
158
     * Callback function to delete an product from connect
159
     * after it is going to be deleted locally
160
     *
161
     * @param \Enlight_Event_EventArgs $eventArgs
162
     */
163
    public function onDeleteArticle(\Enlight_Event_EventArgs $eventArgs)
164
    {
165
        $entity = $eventArgs->get('entity');
166
        $this->connectExport->setDeleteStatusForVariants($entity);
167
    }
168
169
    /**
170
     * Callback function to delete product detail from connect
171
     * after it is going to be deleted locally
172
     *
173
     * @param \Enlight_Event_EventArgs $eventArgs
174
     */
175
    public function onDeleteDetail(\Enlight_Event_EventArgs $eventArgs)
176
    {
177
        /** @var \Shopware\Models\Article\Detail $entity */
178
        $entity = $eventArgs->get('entity');
179
        if ($entity->getKind() !== 1) {
180
            $attribute = $this->helper->getConnectAttributeByModel($entity);
181
            if (!$this->helper->isProductExported($attribute)) {
182
                return;
183
            }
184
            $this->sdk->recordDelete($attribute->getSourceId());
185
            $this->connectExport->updateConnectItemsStatus([$attribute->getSourceId()], Attribute::STATUS_DELETE);
186
        }
187
    }
188
189
    /**
190
     * Callback method to update changed connect products
191
     *
192
     * @param \Enlight_Event_EventArgs $eventArgs
193
     */
194
    public function onUpdateArticle(\Enlight_Event_EventArgs $eventArgs)
195
    {
196
        $entity = $eventArgs->get('entity');
197
198
        $this->handleChange($entity);
199
    }
200
201
    /**
202
     * Generate changes for Article or Detail if necessary
203
     *
204
     * @param \Shopware\Models\Article\Article | \Shopware\Models\Article\Detail $entity
205
     */
206
    public function handleChange($entity)
207
    {
208
        if (!$entity instanceof \Shopware\Models\Article\Article
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Article does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
209
            && !$entity instanceof \Shopware\Models\Article\Detail
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
210
        ) {
211
            return;
212
        }
213
214
        $id = $entity->getId();
215
        $className = get_class($entity);
216
        $model = $this->manager->getRepository($className)->find($id);
217
        // Check if we have a valid model
218
        if (!$model) {
219
            return;
220
        }
221
222
        // Check if entity is a connect product
223
        $attribute = $this->helper->getConnectAttributeByModel($model);
224
        if (!$attribute) {
225
            return;
226
        }
227
228
        // if article is not exported to Connect
229
        // or at least one article detail from same article is not exported
230
        // don't need to generate changes
231
        if (!$this->helper->isProductExported($attribute) || !empty($attribute->getShopId())) {
232
            if (!$this->helper->hasExportedVariants($attribute)) {
233
                return;
234
            }
235
        }
236
237
        if (!$this->hasPriceType()) {
238
            return;
239
        }
240
241
        $forceExport = false;
242
        if ($entity instanceof \Shopware\Models\Article\Detail) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
243
            $changeSet = $this->manager->getUnitOfWork()->getEntityChangeSet($entity);
244
            // if detail number has been changed
245
            // sc plugin must generate & sync the change immediately
246
            if (array_key_exists('number', $changeSet)) {
247
                $forceExport = true;
248
            }
249
        }
250
251
        // Mark the product for connect update
252
        try {
253
            if ($model instanceof \Shopware\Models\Article\Detail) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Detail does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
254
                $this->generateChangesForDetail($model, $forceExport);
255
            } elseif ($model instanceof \Shopware\Models\Article\Article) {
0 ignored issues
show
Bug introduced by
The class Shopware\Models\Article\Article does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
256
                $this->generateChangesForArticle($model, $forceExport);
257
            }
258
        } catch (\Exception $e) {
259
            // If the update fails due to missing requirements
260
            // (e.g. category assignment), continue without error
261
        }
262
    }
263
264
    /**
265
     * Callback method to insert new article details in Connect system
266
     * Used when article is exported and after that variants are generated
267
     *
268
     * @param \Enlight_Event_EventArgs $eventArgs
269
     */
270
    public function onPersistDetail(\Enlight_Event_EventArgs $eventArgs)
271
    {
272
        /** @var \Shopware\Models\Article\Detail $detail */
273
        $detail = $eventArgs->get('entity');
274
275
        /** @var \Shopware\Models\Article\Article $article */
276
        $article = $detail->getArticle();
277
        $articleAttribute = $this->helper->getConnectAttributeByModel($article);
278
        if (!$articleAttribute) {
279
            return;
280
        }
281
282
        // if article is not exported to Connect
283
        // don't need to generate changes
284
        if (!$this->helper->isProductExported($articleAttribute) || !empty($articleAttribute->getShopId())) {
285
            return;
286
        }
287
288
        if (!$this->hasPriceType()) {
289
            return;
290
        }
291
292
        // Mark the article detail for connect export
293
        try {
294
            $this->helper->getOrCreateConnectAttributeByModel($detail);
295
            $forceExport = false;
296
            $changeSet = $eventArgs->get('entityManager')->getUnitOfWork()->getEntityChangeSet($detail);
297
            // if detail number has been changed
298
            // sc plugin must generate & sync the change immediately
299
            if (array_key_exists('number', $changeSet)) {
300
                $forceExport = true;
301
            }
302
303
            $this->generateChangesForDetail($detail, $forceExport);
304
        } catch (\Exception $e) {
305
            // If the update fails due to missing requirements
306
            // (e.g. category assignment), continue without error
307
        }
308
    }
309
310
    /**
311
     * Callback function to shop from export languages
312
     *
313
     * @param \Enlight_Event_EventArgs $eventArgs
314
     */
315
    public function onDeleteShop(\Enlight_Event_EventArgs $eventArgs)
316
    {
317
        /** @var \Shopware\Models\Shop\Shop $shop */
318
        $shop = $eventArgs->get('entity');
319
        $shopId = $shop->getId();
320
        $exportLanguages = $this->config->getConfig('exportLanguages');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $exportLanguages is correct as $this->config->getConfig('exportLanguages') (which targets ShopwarePlugins\Connect\...nts\Config::getConfig()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
321
        $exportLanguages = $exportLanguages ?: [];
322
323
        if (!in_array($shopId, $exportLanguages)) {
324
            return;
325
        }
326
327
        $exportLanguages = array_splice($exportLanguages, array_search($shopId, $exportLanguages), 1);
328
        $this->config->setConfig('exportLanguages', $exportLanguages, null, 'export');
329
    }
330
331
    /**
332
     * @param \Shopware\Models\Article\Detail $detail
333
     * @param bool $force
334
     */
335 View Code Duplication
    private function generateChangesForDetail(\Shopware\Models\Article\Detail $detail, $force = false)
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...
336
    {
337
        $attribute = $this->helper->getConnectAttributeByModel($detail);
338
        if (!$detail->getActive() && $this->config->getConfig('excludeInactiveProducts')) {
339
            $this->connectExport->syncDeleteDetail($detail);
340
341
            return;
342
        }
343
344
        if ($this->isAutoUpdateEnabled($force)) {
345
            $this->connectExport->export(
346
                [$attribute->getSourceId()], null
347
            );
348
        } elseif ($this->autoUpdateProducts == Config::UPDATE_CRON_JOB) {
349
            $this->manager->getConnection()->update(
350
                's_plugin_connect_items',
351
                ['cron_update' => 1],
352
                ['article_detail_id' => $detail->getId()]
353
            );
354
        }
355
    }
356
357
    /**
358
     * @param \Shopware\Models\Article\Article $article
359
     * @param bool $force
360
     */
361 View Code Duplication
    private function generateChangesForArticle(\Shopware\Models\Article\Article $article, $force = false)
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...
362
    {
363
        if (!$article->getActive() && $this->config->getConfig('excludeInactiveProducts')) {
364
            $this->connectExport->setDeleteStatusForVariants($article);
365
366
            return;
367
        }
368
369
        if ($this->isAutoUpdateEnabled($force)) {
370
            $sourceIds = $this->helper->getSourceIdsFromArticleId($article->getId());
371
372
            $this->connectExport->export($sourceIds, null);
373
        } elseif ($this->autoUpdateProducts == Config::UPDATE_CRON_JOB) {
374
            $this->manager->getConnection()->update(
375
                's_plugin_connect_items',
376
                ['cron_update' => 1],
377
                ['article_id' => $article->getId()]
378
            );
379
        }
380
    }
381
382
    /**
383
     * @param bool $force
384
     * @return bool
385
     */
386
    private function isAutoUpdateEnabled($force)
387
    {
388
        return $this->autoUpdateProducts == Config::UPDATE_AUTO || $force === true;
389
    }
390
391
    /**
392
     * Sends the new order status when supplier change it
393
     *
394
     * @param Order $order
395
     */
396
    private function updateOrderStatus(Order $order)
397
    {
398
        $attribute = $order->getAttribute();
399
        if (!$attribute || !$attribute->getConnectShopId()) {
400
            return;
401
        }
402
403
        $orderStatusMapper = new Utils\OrderStatusMapper();
404
        $orderStatus = $orderStatusMapper->getOrderStatusStructFromOrder($order);
405
406
        try {
407
            $this->sdk->updateOrderStatus($orderStatus);
408
        } catch (\Exception $e) {
409
            // if sn is not available, proceed without exception
410
        }
411
    }
412
413
    /**
414
     * Sends the new payment status when merchant change it
415
     *
416
     * @param Order $order
417
     */
418
    private function updatePaymentStatus(Order $order)
419
    {
420
        $orderUtil = new Utils\ConnectOrderUtil();
421
        if (!$orderUtil->hasLocalOrderConnectProducts($order->getId())) {
422
            return;
423
        }
424
425
        $paymentStatusMapper = new Utils\OrderPaymentStatusMapper();
426
        $paymentStatus = $paymentStatusMapper->getPaymentStatus($order);
427
428
        $this->generateChangeForPaymentStatus($paymentStatus);
429
    }
430
431
    /**
432
     * @param PaymentStatus $paymentStatus
433
     */
434
    private function generateChangeForPaymentStatus(PaymentStatus $paymentStatus)
435
    {
436
        try {
437
            $this->sdk->updatePaymentStatus($paymentStatus);
438
        } catch (\Exception $e) {
439
            // if sn is not available, proceed without exception
440
        }
441
    }
442
443
    /**
444
     * @return bool
445
     */
446 View Code Duplication
    private function hasPriceType()
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...
447
    {
448
        if ($this->sdk->getPriceType() === SDK::PRICE_TYPE_PURCHASE
449
            || $this->sdk->getPriceType() === SDK::PRICE_TYPE_RETAIL
450
            || $this->sdk->getPriceType() === SDK::PRICE_TYPE_BOTH
451
        ) {
452
            return true;
453
        }
454
455
        return false;
456
    }
457
}
458