EavAttributeJoiner::joinScopeable()   B
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 56

Duplication

Lines 9
Ratio 16.07 %

Importance

Changes 0
Metric Value
dl 9
loc 56
rs 8.9599
c 0
b 0
f 0
cc 3
nc 3
nop 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types = 1);
4
5
/**
6
 * File: EavAttributeJoiner.php
7
 *
8
 * @author Bartosz Kubicki [email protected]>
9
 * @copyright Copyright (C) 2018 Lizard Media (http://lizardmedia.pl)
10
 */
11
12
namespace LizardMedia\ProductAttachment\Model\ResourceModel\Db\Collection;
13
14
use \Magento\Eav\Api\AttributeRepositoryInterface;
15
use \Magento\Eav\Model\Entity\Type as EntityTypeModel;
16
use \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
17
use \Magento\Store\Model\StoreManagerInterface;
18
use \Magento\Store\Model\Store;
19
use \Psr\Log\LoggerInterface;
20
21
/** Class can be used to join attributes to non-eav collection */
22
/**
23
 * When attribute is joined it has key in collection based on pattern 'entityTypeCode_attributeCode'.
24
 * If attribute can have scoped value, than method `joinScopeable()` with parameter `storeId` parameter should be used.
25
 * Then value for default scope 0 will be joined with key 'entityTypeCode_attributeCode_default'.
26
 *
27
 * In case of joining multiple attributes from the same entity every join has unique aliases for tables.
28
 * They are returned as array from function and can be used in further operations on collection.
29
 *
30
 *
31
 * In case of joining attribute basing on already joined field argument `$mainTable` may be useful, to start operation
32
 * from not very first table.
33
 *
34
 * In case of some not standard basic entity fields, argument `$entityForeignKeyName` may be used.
35
 */
36
37
/**
38
 * Class EavAttributeJoiner
39
 * @package LizardMedia\ProductAttachments\Model\ResourceModel\Db\Collection
40
 */
41
class EavAttributeJoiner
42
{
43
    /**
44
     * @string
45
     */
46
    const VALUE_FIELD = 'value';
47
48
49
    /**
50
     * @var int
51
     */
52
    private $aliasIndex;
53
54
55
    /**
56
     * @var array
57
     */
58
    private $joinReturnData = [];
59
60
61
    /**
62
     * @var \Magento\Eav\Model\Entity\Type
63
     */
64
    private $entityType;
65
66
67
    /**
68
     * @var string
69
     */
70
    private $entityTable;
71
72
73
    /**
74
     * @var \Magento\Framework\Model\ResourceModel\AbstractResource
75
     */
76
    private $entityBasicModel;
77
78
79
    /**
80
     * @var int
81
     */
82
    private $entityBasicModelIdField;
83
84
85
    /**
86
     * @var \Magento\Eav\Api\Data\AttributeInterface
87
     */
88
    private $attributeModel;
89
90
91
    /**
92
     * @var string
93
     */
94
    private $backendType;
95
96
97
    /** Dependencies */
98
99
    /**
100
     * @var \Magento\Eav\Api\AttributeRepositoryInterface
101
     */
102
    private $attributeRepository;
103
104
105
    /**
106
     * @var \Magento\Eav\Model\Entity\Type
107
     */
108
    private $entityTypeModel;
109
110
111
    /**
112
     * @var \Magento\Store\Model\StoreManagerInterface
113
     */
114
    private $storeManager;
115
116
117
    /**
118
     * @var \Psr\Log\LoggerInterface
119
     */
120
    private $logger;
121
122
123
    /**
124
     * @param \Magento\Eav\Api\AttributeRepositoryInterface $attributeRepository
125
     * @param \Magento\Eav\Model\Entity\Type $entityTypeModel
126
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
127
     * @param \Psr\Log\LoggerInterface $logger
128
     */
129
    public function __construct(
130
        AttributeRepositoryInterface $attributeRepository,
131
        EntityTypeModel $entityTypeModel,
132
        StoreManagerInterface $storeManager,
133
        LoggerInterface $logger
134
    ) {
135
        $this->attributeRepository = $attributeRepository;
136
        $this->entityTypeModel = $entityTypeModel;
137
        $this->storeManager = $storeManager;
138
        $this->logger = $logger;
139
    }
140
141
142
    /**
143
     * @param \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection
144
     * @param string $foreignKeyName
145
     * @param string $entityTypeCode
146
     * @param mixed string | array $attributes
147
     * @param string $entityAlternativeKeyName
148
     * @param string $mainTable
149
     *
150
     * @throws \Magento\Framework\Exception\NoSuchEntityException
151
     * @throws \Magento\Framework\Exception\LocalizedException
152
     *
153
     * @return array
154
     */
155
    public function join(
156
        AbstractCollection $collection,
157
        string $foreignKeyName,
158
        string $entityTypeCode,
159
        $attributes,
160
        string $entityAlternativeKeyName = '',
161
        string $mainTable = 'main_table'
162
    ) : array {
163
        $this->prepareEntityInformation($collection, $entityTypeCode, $entityAlternativeKeyName);
164
165
        $attributesTable = $this->convertAttributesArgument($attributes);
166
167
        foreach ($attributesTable as $attributeCode) {
168
            $this->aliasIndex++;
169
            $entityTableAlias = $this->generateAlias($this->entityTable);
170
            $this->prepareAttributeInformation($entityTypeCode, $attributeCode);
171
172
            if (!$this->attributeModel->getBackend()->isStatic()) {
173
                $typeTable = $this->buildTypeTableName();
174
                $typeTableAlias = $this->generateAlias($typeTable);
175
176
                $collection->getSelect()->joinLeft(
177
                    [$typeTableAlias => $typeTable],
178
                    $mainTable . '.' . $foreignKeyName . ' = ' . $typeTableAlias . '.' . $this->entityBasicModelIdField
179
                    . ' AND ' . $typeTableAlias . '.attribute_id = ' . $this->attributeModel->getAttributeId(),
180
                    [$entityTypeCode . '_' . $attributeCode => $typeTableAlias . '.' . self::VALUE_FIELD]
181
                );
182
183
                $this->addRecordToJoinReturnData($entityTypeCode, $attributeCode, $typeTableAlias, self::VALUE_FIELD);
184 View Code Duplication
            } else {
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...
185
                $collection->getSelect()->joinLeft(
186
                    [$entityTableAlias => $this->entityTable],
187
                    $mainTable . '.' . $foreignKeyName . ' = ' . $entityTableAlias . '.' . $this->entityBasicModelIdField,
188
                    [$entityTypeCode . '_' . $attributeCode => $attributeCode]
189
                );
190
191
                $this->addRecordToJoinReturnData($entityTypeCode, $attributeCode, $entityTableAlias, $attributeCode);
192
            }
193
        }
194
195
        return $this->joinReturnData;
196
    }
197
198
199
    /**
200
     * @param \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection
201
     * @param string $foreignKeyName
202
     * @param string $entityTypeCode
203
     * @param mixed string | array $attributes
204
     * @param int $storeId
205
     * @param string $entityAlternativeKeyName
206
     * @param string $mainTable
207
     *
208
     * @throws \Magento\Framework\Exception\NoSuchEntityException
209
     * @throws \Magento\Framework\Exception\LocalizedException
210
     *
211
     * @return array
212
     */
213
    public function joinScopeable(
214
        AbstractCollection $collection,
215
        string $foreignKeyName,
216
        string $entityTypeCode,
217
        $attributes,
218
        int $storeId = 0,
219
        string $entityAlternativeKeyName = '',
220
        string $mainTable = 'main_table'
221
    ) : array {
222
        $this->prepareEntityInformation($collection, $entityTypeCode, $entityAlternativeKeyName);
223
224
        $attributesTable = $this->convertAttributesArgument($attributes);
225
226
        foreach ($attributesTable as $attributeCode) {
227
            $this->aliasIndex++;
228
            $entityTableAlias = $this->generateAlias($this->entityTable);
229
            $this->prepareAttributeInformation($entityTypeCode, $attributeCode);
230
231
            if (!$this->attributeModel->getBackend()->isStatic()) {
232
                $typeTable = $this->buildTypeTableName();
233
                $typeTableAlias = $this->generateAlias($typeTable);
234
                $typeTableAliasDefault = $this->generateAlias($typeTable, true);
235
236
                $collection->getSelect()->joinLeft(
237
                    [$typeTableAlias => $typeTable],
238
                    $mainTable . '.' . $foreignKeyName . ' = ' . $typeTableAlias . '.' . $this->entityBasicModelIdField
239
                    . ' AND ' . $typeTableAlias . '.attribute_id = ' . $this->attributeModel->getAttributeId()
240
                    . ' AND ' . $typeTableAlias . '.store_id = ' . $storeId,
241
                    [$entityTypeCode . '_' . $attributeCode => $typeTableAlias . '.' . self::VALUE_FIELD]
242
                )->joinLeft(
243
                    [$typeTableAliasDefault => $typeTable],
244
                    $mainTable . '.' . $foreignKeyName . ' = ' . $typeTableAliasDefault . '.' . $this->entityBasicModelIdField
245
                    . ' AND ' . $typeTableAliasDefault . '.attribute_id = ' . $this->attributeModel->getAttributeId()
246
                    . ' AND ' . $typeTableAliasDefault . '.store_id = ' . Store::DEFAULT_STORE_ID,
247
                    [
248
                        $entityTypeCode . '_' . $attributeCode . '_default'
249
                        => $typeTableAliasDefault . '.' . self::VALUE_FIELD
250
                    ]
251
                );
252
253
254
                $this->addRecordToJoinReturnData($entityTypeCode, $attributeCode, $typeTableAlias, self::VALUE_FIELD);
255
                $this->addRecordToJoinReturnData($entityTypeCode, $attributeCode, $typeTableAliasDefault, self::VALUE_FIELD);
256 View Code Duplication
            } else {
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...
257
                $collection->getSelect()->joinLeft(
258
                    [$entityTableAlias => $this->entityTable],
259
                    $mainTable . '.' . $foreignKeyName . ' = ' . $entityTableAlias . '.' . $this->entityBasicModelIdField,
260
                    [$entityTypeCode . '_' . $attributeCode => $attributeCode]
261
                );
262
263
                $this->addRecordToJoinReturnData($entityTypeCode, $attributeCode, $entityTableAlias, $attributeCode);
264
            }
265
        }
266
267
        return $this->joinReturnData;
268
    }
269
270
271
    /**
272
     * @param \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection
273
     * @param string $entityTypeCode
274
     * @param string $entityAlternativeKeyName
275
     *
276
     * @return void
277
     */
278
    private function prepareEntityInformation(
279
        AbstractCollection $collection,
280
        string $entityTypeCode,
281
        string $entityAlternativeKeyName = ''
282
    ) {
283
        $this->entityType = $this->entityTypeModel->loadByCode($entityTypeCode);
284
        $this->entityTable = $collection->getTable($this->entityType->getEntityTable());
285
        $this->entityBasicModel = $this->entityType->getEntity();
286
        $this->entityBasicModelIdField = $this->entityBasicModel->getEntityIdField();
287
288
        if ($entityAlternativeKeyName) {
289
            $this->entityBasicModelIdField = $entityAlternativeKeyName;
0 ignored issues
show
Documentation Bug introduced by
The property $entityBasicModelIdField was declared of type integer, but $entityAlternativeKeyName is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
290
        }
291
    }
292
293
294
    /**
295
     * @param mixed string | array $attributes
296
     *
297
     * @return array $attributesTable
298
     */
299
    private function convertAttributesArgument($attributes) : array
300
    {
301
        if (!is_array($attributes)) {
302
            $attributesTable[] = $attributes;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$attributesTable was never initialized. Although not strictly required by PHP, it is generally a good practice to add $attributesTable = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
303
        } else {
304
            $attributesTable = $attributes;
305
        }
306
307
308
        return $attributesTable;
309
    }
310
311
312
    /**
313
     * @param string $entityTypeCode
314
     * @param string $attributeCode
315
     *
316
     * @throws \Magento\Framework\Exception\NoSuchEntityException
317
     *
318
     * @return void
319
     */
320
    private function prepareAttributeInformation(string $entityTypeCode, string $attributeCode)
321
    {
322
        $this->attributeModel = $this->getAttributeByCode($entityTypeCode, $attributeCode);
323
        $this->backendType = $this->attributeModel->getBackendType();
324
    }
325
326
327
    /**
328
     * @param string $entityTypeCode
329
     * @param string $attributeCode
330
     *
331
     * @throws \Magento\Framework\Exception\NoSuchEntityException
332
     *
333
     * @return \Magento\Eav\Api\Data\AttributeInterface
334
     */
335
    private function getAttributeByCode($entityTypeCode, $attributeCode)
336
    {
337
        return $this->attributeRepository->get($entityTypeCode, $attributeCode);
338
    }
339
340
341
    /**
342
     * @return string
343
     */
344
    private function buildTypeTableName()
345
    {
346
        return $this->entityTable . '_' . $this->backendType;
347
    }
348
349
350
    /**
351
     * @param string $tableName
352
     * @param bool $useDefaultSuffix
353
     *
354
     * @return string
355
     */
356
    private function generateAlias(string $tableName, bool $useDefaultSuffix = false) : string
357
    {
358
        $alias = 'table_' . $this->aliasIndex . '_' . $tableName;
359
360
        if ($useDefaultSuffix === true) {
361
            $alias .= '_default';
362
        }
363
364
        return $alias;
365
    }
366
367
368
    /**
369
     * @param string $entityTypeCode
370
     * @param string $attributeCode
371
     * @param string $tableAlias
372
     * @param string $fieldName
373
     *
374
     * @return void
375
     */
376
    private function addRecordToJoinReturnData(
377
        string $entityTypeCode,
378
        string $attributeCode,
379
        string $tableAlias,
380
        string $fieldName
381
    ) {
382
        $this->joinReturnData[$entityTypeCode . '_' . $attributeCode] =
383
            ['table' => $tableAlias, 'field' => $fieldName];
384
    }
385
}
386