Completed
Pull Request — master (#377)
by Jonas
03:33
created

ImportService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 21
nc 1
nop 10
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Components;
9
10
use Doctrine\DBAL\Connection;
11
use Shopware\Bundle\AttributeBundle\Service\DataPersister;
12
use Shopware\Components\Model\CategoryDenormalization;
13
use ShopwarePlugins\Connect\Components\CategoryResolver\AutoCategoryResolver;
14
use Shopware\Components\Model\ModelManager;
15
use Shopware\Components\MultiEdit\Resource\ResourceInterface;
16
use Shopware\CustomModels\Connect\ProductToRemoteCategoryRepository;
17
use Shopware\CustomModels\Connect\RemoteCategoryRepository;
18
use Shopware\Models\Article\Repository as ArticleRepository;
19
use Shopware\Models\Category\Repository as CategoryRepository;
20
21
class ImportService
22
{
23
    /**
24
     * @var ModelManager
25
     */
26
    private $manager;
27
28
    /**
29
     * @var \Shopware\Components\MultiEdit\Resource\ResourceInterface
30
     */
31
    private $productResource;
32
33
    /**
34
     * @var \Shopware\Models\Category\Repository
35
     */
36
    private $categoryRepository;
37
38
    /**
39
     * @var ArticleRepository
40
     */
41
    private $articleRepository;
42
43
    /**
44
     * @var RemoteCategoryRepository
45
     */
46
    private $remoteCategoryRepository;
47
48
    /**
49
     * @var ProductToRemoteCategoryRepository
50
     */
51
    private $productToRemoteCategoryRepository;
52
53
    /**
54
     * @var CategoryResolver
55
     */
56
    private $autoCategoryResolver;
57
58
    /**
59
     * @var CategoryExtractor
60
     */
61
    private $categoryExtractor;
62
63
    /**
64
     * @var CategoryDenormalization
65
     */
66
    private $categoryDenormalization;
67
68
    /**
69
     * @var DataPersister
70
     */
71
    private $dataPersister;
72
73
    public function __construct(
74
        ModelManager $manager,
75
        ResourceInterface $productResource,
76
        CategoryRepository $categoryRepository,
77
        ArticleRepository$articleRepository,
78
        RemoteCategoryRepository $remoteCategoryRepository,
79
        ProductToRemoteCategoryRepository $productToRemoteCategoryRepository,
80
        AutoCategoryResolver $categoryResolver,
81
        CategoryExtractor $categoryExtractor,
82
        CategoryDenormalization $categoryDenormalization,
83
        DataPersister $dataPersister
84
    ) {
85
        $this->manager = $manager;
86
        $this->productResource = $productResource;
87
        $this->categoryRepository = $categoryRepository;
88
        $this->articleRepository = $articleRepository;
89
        $this->remoteCategoryRepository = $remoteCategoryRepository;
90
        $this->productToRemoteCategoryRepository = $productToRemoteCategoryRepository;
91
        $this->autoCategoryResolver = $categoryResolver;
92
        $this->categoryExtractor = $categoryExtractor;
93
        $this->categoryDenormalization = $categoryDenormalization;
94
        $this->dataPersister = $dataPersister;
95
    }
96
97
    public function findBothArticlesType($categoryId, $query = '', $showOnlyConnectArticles = true, $limit = 10, $offset = 0)
98
    {
99
        if ($categoryId == 0) {
100
            return [];
101
        }
102
103
        return $this->productResource->filter($this->getAst($categoryId, $query, $showOnlyConnectArticles), $offset, $limit);
104
    }
105
106
    /**
107
     * @param $categoryId
108
     * @return bool
109
     */
110
    public function hasCategoryChildren($categoryId)
111
    {
112
        return (bool) $this->categoryRepository->getChildrenCountList($categoryId);
113
    }
114
115
    public function assignCategoryToArticles($categoryId, array $articleIds)
116
    {
117
        $articles = $this->articleRepository->findBy(['id' => $articleIds]);
118
119
        if (empty($articles)) {
120
            throw new \RuntimeException('Invalid article ids');
121
        }
122
123
        /** @var \Shopware\Models\Category\Category $category */
124
        $category = $this->categoryRepository->find($categoryId);
125
        if (!$category) {
126
            throw new \RuntimeException('Invalid category id');
127
        }
128
129
        /** @var \Shopware\Models\Article\Article $article */
130
        foreach ($articles as $article) {
131
            $article->addCategory($category);
132
            $this->manager->persist($article);
133
            /** @var \Shopware\Models\Article\Detail $detail */
134
            foreach ($article->getDetails() as $detail) {
135
                $attribute = $detail->getAttribute();
136
                $attribute->setConnectMappedCategory(true);
137
                $this->manager->persist($attribute);
138
            }
139
        }
140
141
        $this->manager->flush();
142
    }
143
144
    /**
145
     * Unassign all categories from given article ids
146
     * Set connect_mapped_category flag in article
147
     * attributes to NULL
148
     *
149
     * @param array $articleIds
150
     * @throws \Doctrine\DBAL\ConnectionException
151
     * @throws \Exception
152
     */
153
    public function unAssignArticleCategories(array $articleIds)
154
    {
155
        if (!empty($articleIds)) {
156
            // cast all items in $articleIds to int
157
            // before use them in WHERE IN clause
158
            foreach ($articleIds as $key => $articleId) {
159
                $articleIds[$key] = (int) $articleId;
160
            }
161
162
            $connection = $this->manager->getConnection();
163
            $connection->beginTransaction();
164
165
            try {
166
                $attributeStatement = $connection->prepare(
167
                    'UPDATE s_articles_attributes SET connect_mapped_category = NULL WHERE articleID IN (' . implode(', ', $articleIds) . ')'
168
                );
169
                $attributeStatement->execute();
170
171
                $categoriesStatement = $this->manager->getConnection()->prepare('DELETE FROM s_articles_categories WHERE articleID IN (' . implode(', ', $articleIds) . ')');
172
                $categoriesStatement->execute();
173
174
                $categoryLogStatement = $this->manager->getConnection()->prepare('DELETE FROM s_articles_categories_ro WHERE articleID IN (' . implode(', ', $articleIds) . ')');
175
                $categoryLogStatement->execute();
176
                $connection->commit();
177
            } catch (\Exception $e) {
178
                $connection->rollBack();
179
                throw new \Exception($e->getMessage());
180
            }
181
        }
182
    }
183
184
    /**
185
     * Collect remote article ids by given category id
186
     *
187
     * @param int $localCategoryId
188
     * @return array
189
     */
190
    public function findRemoteArticleIdsByCategoryId($localCategoryId)
191
    {
192
        $connection = $this->manager->getConnection();
193
        $sql = 'SELECT sac.articleID
194
            FROM s_articles_categories sac
195
            LEFT JOIN s_articles_attributes saa ON sac.articleID = saa.articleID
196
            WHERE sac.categoryID = :categoryId AND saa.connect_mapped_category = 1';
197
        $rows = $connection->fetchAll($sql, [':categoryId' => $localCategoryId]);
198
199
        return array_map(function ($row) {
200
            return $row['articleID'];
201
        }, $rows);
202
    }
203
204
    /**
205
     * @param array $categoryIds
206
     * @return int
207
     */
208
    public function deactivateLocalCategoriesByIds(array $categoryIds)
209
    {
210
        $builder = $this->manager->getConnection()->createQueryBuilder();
211
        $rowCount = $builder->update('s_categories', 'c')
212
            ->set('c.active', 0)
213
            ->where('c.id IN (:categoryIds)')
214
            ->setParameter('categoryIds', $categoryIds, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
215
            ->execute();
216
217
        return $rowCount;
218
    }
219
220
    /**
221
     * Collect all child categories by given
222
     * remote category key and create same
223
     * categories structure as Shopware Connect structure.
224
     * Find all remote products which belong to these categories
225
     * and assign them.
226
     *
227
     * @param int $localCategoryId
228
     * @param string $remoteCategoryKey
229
     * @param string $remoteCategoryLabel
230
     * @return void
231
     */
232
    public function importRemoteCategory($localCategoryId, $remoteCategoryKey, $remoteCategoryLabel)
233
    {
234
        /** @var \Shopware\Models\Category\Category $localCategory */
235
        $localCategory = $this->categoryRepository->find((int) $localCategoryId);
236
        if (!$localCategory) {
237
            throw new \RuntimeException('Local category not found!');
238
        }
239
240
        /** @var \Shopware\CustomModels\Connect\RemoteCategory $remoteCategory */
241
        $remoteCategory = $this->remoteCategoryRepository->findOneBy(['categoryKey' => $remoteCategoryKey]);
242
        if (!$remoteCategory) {
243
            throw new \RuntimeException('Remote category not found!');
244
        }
245
246
        // collect his child categories and
247
        // generate remote category tree by given remote category
248
        $remoteCategoryChildren = $this->categoryExtractor->getRemoteCategoriesTree($remoteCategoryKey, true);
249
        $remoteCategoryNodes = [
250
            [
251
                'name' => $remoteCategoryLabel,
252
                'categoryId' => $remoteCategoryKey,
253
                'leaf' => empty($remoteCategoryChildren) ? true : false,
254
                'children' => $remoteCategoryChildren,
255
            ]
256
        ];
257
258
        // create same category structure as Shopware Connect structure
259
        $categories = $this->autoCategoryResolver->convertTreeToKeys($remoteCategoryNodes, $localCategory->getId(), false);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class ShopwarePlugins\Connect\...onents\CategoryResolver as the method convertTreeToKeys() does only exist in the following sub-classes of ShopwarePlugins\Connect\...onents\CategoryResolver: ShopwarePlugins\Connect\...er\AutoCategoryResolver. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
260
261
        foreach ($categories as $category) {
262
            $articleIds = $this->productToRemoteCategoryRepository->findArticleIdsByRemoteCategory($category['remoteCategory']);
263
            foreach ($articleIds as $articleId) {
264
                $this->categoryDenormalization->addAssignment($articleId, $category['categoryKey']);
265
                $this->categoryDenormalization->removeAssignment($articleId, $category['parentId']);
266
                $this->manager->getConnection()->executeQuery(
267
                    'INSERT IGNORE INTO `s_articles_categories` (`articleID`, `categoryID`) VALUES (?, ?)',
268
                    [$articleId,  $category['categoryKey']]
269
                );
270
                $this->manager->getConnection()->executeQuery(
271
                    'DELETE FROM `s_articles_categories` WHERE `articleID` = :articleID AND `categoryID` = :categoryID',
272
                    [
273
                        ':articleID' => $articleId,
274
                        ':categoryID' => $category['parentId']
275
                    ]
276
                );
277
                $detailId = $this->manager->getConnection()->fetchColumn(
278
                    'SELECT id FROM `s_articles_details` WHERE `articleID` = :articleID',
279
                    ['articleID' => $articleId]
280
                );
281
                $this->manager->getConnection()->executeQuery(
282
                    'INSERT  INTO `s_articles_attributes` (`articleID`, `articledetailsID`, `connect_mapped_category`) 
283
                        VALUES (?, ?, 1)
284
                        ON DUPLICATE KEY UPDATE `connect_mapped_category` = 1
285
                    ',
286
                    [$articleId,  $detailId]
287
                );
288
            }
289
        }
290
    }
291
292
    /**
293
     * @param array $articleIds
294
     */
295
    public function activateArticles(array $articleIds)
296
    {
297
        $articleBuilder = $this->manager->createQueryBuilder();
298
        $articleBuilder->update('\Shopware\Models\Article\Article', 'a')
299
            ->set('a.active', 1)
300
            ->where('a.id IN (:articleIds)')
301
            ->setParameter(':articleIds', $articleIds, Connection::PARAM_STR_ARRAY);
302
303
        $articleBuilder->getQuery()->execute();
304
305
        $detailBuilder = $this->manager->createQueryBuilder();
306
        $detailBuilder->update('\Shopware\Models\Article\Detail', 'd')
307
            ->set('d.active', 1)
308
            ->where('d.articleId IN (:articleIds)')
309
            ->setParameter(':articleIds', $articleIds, Connection::PARAM_STR_ARRAY);
310
311
        $detailBuilder->getQuery()->execute();
312
    }
313
314
    /**
315
     * Store remote categories in Connect tables
316
     * and add relations between categories and products.
317
     *
318
     * @param array $remoteItems
319
     *
320
     * @throws \Exception
321
     */
322
    public function storeRemoteCategories(array $remoteItems)
323
    {
324
        $connection = $this->manager->getConnection();
325
326
        $connection->beginTransaction();
327
        try {
328
            foreach ($remoteItems as $articleId => $categories) {
329
                foreach ($categories as $categoryKey => $category) {
330
                    $connection->executeQuery(
331
                        'INSERT IGNORE INTO `s_plugin_connect_categories` (`category_key`, `label`) VALUES (?, ?)',
332
                        [$categoryKey, $category]
333
                    );
334
335
                    $connection->executeQuery(
336
                        'INSERT IGNORE INTO `s_plugin_connect_product_to_categories` (`connect_category_id`, `articleID`) VALUES ((SELECT c.id FROM s_plugin_connect_categories c WHERE c.category_key = ?), ?)',
337
                        [$categoryKey, $articleId]
338
                    );
339
                }
340
            }
341
            $connection->commit();
342
        } catch (\Exception $e) {
343
            $connection->rollBack();
344
345
            throw $e;
346
        }
347
    }
348
349
    /**
350
     * Fetch remote (Connect) categories by given article ids
351
     * @param array $articleIds
352
     * @return array
353
     */
354
    public function fetchRemoteCategoriesByArticleIds(array $articleIds)
355
    {
356
        $remoteCategoryIds = [];
357
        while ($currentIdBatch = array_splice($articleIds, 0, 500)) {
358
            $sql = 'SELECT sac.categoryID
359
            FROM s_articles_categories sac
360
            LEFT JOIN s_categories_attributes attr ON sac.categoryID = attr.categoryID
361
            WHERE attr.connect_imported_category = 1 AND sac.articleID IN (' . implode(', ', $currentIdBatch) . ') GROUP BY sac.categoryID';
362
            $rows = $this->manager->getConnection()->fetchAll($sql);
363
364
            $remoteCategoryIds = array_merge($remoteCategoryIds, array_map(function ($row) {
365
                return $row['categoryID'];
366
            }, $rows));
367
        }
368
369
        return array_unique($remoteCategoryIds);
370
    }
371
372
    /**
373
     * Fetch all articles where categories are auto imported
374
     * and there isn't record in s_plugin_connect_product_to_categories for them.
375
     * Returned array contains key = articleId and value = array of categories
376
     *
377
     * @return array
378
     */
379
    public function getArticlesWithAutoImportedCategories()
380
    {
381
        $statement = $this->manager->getConnection()->prepare(
382
            'SELECT b.article_id, b.category
383
            FROM s_plugin_connect_items b
384
            LEFT JOIN s_plugin_connect_product_to_categories a ON b.article_id = a.articleID
385
            WHERE b.shop_id > 0 AND a.connect_category_id IS NULL GROUP BY b.article_id'
386
        );
387
        $statement->execute();
388
389
        $remoteItems = [];
390
        foreach ($statement->fetchAll(\PDO::FETCH_ASSOC) as $item) {
391
            $categories = json_decode($item['category'], true);
392
            if (is_array($categories) && count($categories) > 0) {
393
                $articleId = $item['article_id'];
394
                $remoteItems[$articleId] = $categories;
395
            }
396
        }
397
398
        return $remoteItems;
399
    }
400
401
    /**
402
     * Helper function to create filter values
403
     * @param int $categoryId
404
     * @param bool $showOnlyConnectArticles
405
     * @param string $query
406
     * @return array
407
     */
408
    private function getAst($categoryId, $query = '', $showOnlyConnectArticles = true)
409
    {
410
        $ast = [
411
            [
412
                'type' => 'nullaryOperators',
413
                'token' => 'ISMAIN',
414
            ]
415
        ];
416
417
        if (trim($query) !== '') {
418
            $queryArray = [
419
                [
420
                    'type' => 'boolOperators',
421
                    'token' => 'AND',
422
                ],
423
                [
424
                    'type' => 'subOperators',
425
                    'token' => '(',
426
                ],
427
                [
428
                    'type' => 'attribute',
429
                    'token' => 'ARTICLE.NAME'
430
                ],
431
                [
432
                    'type' => 'binaryOperator',
433
                    'token' => '~'
434
                ],
435
                [
436
                    'type' => 'values',
437
                    'token' => '"' . $query . '"'
438
                ],
439
                [
440
                    'type' => 'boolOperators',
441
                    'token' => 'OR',
442
                ],
443
                [
444
                    'type' => 'attribute',
445
                    'token' => 'SUPPLIER.NAME'
446
                ],
447
                [
448
                    'type' => 'binaryOperator',
449
                    'token' => '~'
450
                ],
451
                [
452
                    'type' => 'values',
453
                    'token' => '"' . $query . '"'
454
                ],
455
                [
456
                    'type' => 'boolOperators',
457
                    'token' => 'OR',
458
                ],
459
                [
460
                    'type' => 'attribute',
461
                    'token' => 'DETAIL.NUMBER'
462
                ],
463
                [
464
                    'type' => 'binaryOperator',
465
                    'token' => '~'
466
                ],
467
                [
468
                    'type' => 'values',
469
                    'token' => '"' . $query . '"'
470
                ],
471
                [
472
                    'type' => 'subOperators',
473
                    'token' => ')',
474
                ]
475
            ];
476
            $ast = array_merge($ast, $queryArray);
477
        }
478
479
        $categoryArray = [
480
            [
481
            'type' => 'boolOperators',
482
            'token' => 'AND',
483
            ],
484
            [
485
                'type' => 'subOperators',
486
                'token' => '(',
487
            ],
488
            [
489
                'type' => 'attribute',
490
                'token' => 'CATEGORY.PATH',
491
            ],
492
            [
493
                'type' => 'binaryOperators',
494
                'token' => '=',
495
            ],
496
            [
497
                'type' => 'values',
498
                'token' => '"%|' . $categoryId . '|%"',
499
            ],
500
            [
501
                'type' => 'boolOperators',
502
                'token' => 'OR',
503
            ],
504
            [
505
                'type' => 'attribute',
506
                'token' => 'CATEGORY.ID',
507
            ],
508
            [
509
                'type' => 'binaryOperators',
510
                'token' => '=',
511
            ],
512
            [
513
                'type' => 'values',
514
                'token' => $categoryId,
515
            ],
516
            [
517
                'type' => 'subOperators',
518
                'token' => ')',
519
            ]
520
        ];
521
522
        $ast = array_merge($ast, $categoryArray);
523
524
        if ($showOnlyConnectArticles === true) {
525
            $ast = array_merge($ast, [
526
                [
527
                    'type' => 'boolOperators',
528
                    'token' => 'AND',
529
                ],
530
                [
531
                    'type' => 'attribute',
532
                    'token' => 'ATTRIBUTE.CONNECTMAPPEDCATEGORY',
533
                ],
534
                [
535
                    'type' => 'binaryOperators',
536
                    'token' => '!=',
537
                ],
538
                [
539
                    'type' => 'values',
540
                    'token' => 'NULL',
541
                ],
542
            ]);
543
        }
544
545
        return $ast;
546
    }
547
}
548