Completed
Push — master ( 938e21...ab9bf2 )
by Fabian
25s
created

ProductBuilder::withPrice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 2
b 0
f 0
nc 1
nop 1
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 10
1
<?php
2
declare(strict_types=1);
3
4
namespace TddWizard\Fixtures\Catalog;
5
6
use Magento\Catalog\Api\Data\ProductInterface;
7
use Magento\Catalog\Api\Data\ProductWebsiteLinkInterfaceFactory;
8
use Magento\Catalog\Api\ProductRepositoryInterface;
9
use Magento\Catalog\Api\ProductWebsiteLinkRepositoryInterface;
10
use Magento\Catalog\Model\Product;
11
use Magento\Catalog\Model\Product\Attribute\Source\Status;
12
use Magento\Catalog\Model\Product\Visibility;
13
use Magento\CatalogInventory\Api\Data\StockItemInterface;
14
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
15
use Magento\Indexer\Model\IndexerFactory;
16
use Magento\TestFramework\Helper\Bootstrap;
0 ignored issues
show
Bug introduced by
The type Magento\TestFramework\Helper\Bootstrap was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
17
18
/**
19
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
20
 * @SuppressWarnings(PHPMD.UnusedPrivateField)
21
 */
22
class ProductBuilder
23
{
24
    /**
25
     * @var ProductRepositoryInterface
26
     */
27
    private $productRepository;
28
29
    /**
30
     * @var StockItemRepositoryInterface
31
     */
32
    private $stockItemRepository;
33
34
    /**
35
     * @var ProductWebsiteLinkRepositoryInterface
36
     */
37
    private $websiteLinkRepository;
38
39
    /**
40
     * @var ProductWebsiteLinkInterfaceFactory
41
     */
42
    private $websiteLinkFactory;
43
44
    /**
45
     * @var IndexerFactory
46
     */
47
    private $indexerFactory;
48
49
    /**
50
     * @var Product
51
     */
52
    protected $product;
53
54
    /**
55
     * @var int[]
56
     */
57
    private $websiteIds;
58
59
    /**
60
     * @var mixed[][]
61
     */
62
    private $storeSpecificValues;
63
64
    /**
65
     * @var int[]
66
     */
67
    private $categoryIds = [];
68
69
    /**
70
     * @param ProductRepositoryInterface $productRepository
71
     * @param StockItemRepositoryInterface $stockItemRepository
72
     * @param ProductWebsiteLinkRepositoryInterface $websiteLinkRepository
73
     * @param ProductWebsiteLinkInterfaceFactory $websiteLinkFactory
74
     * @param IndexerFactory $indexerFactory
75
     * @param Product $product
76
     * @param int[] $websiteIds
77
     * @param mixed[] $storeSpecificValues
78
     */
79 27
    final public function __construct(
80
        ProductRepositoryInterface $productRepository,
81
        StockItemRepositoryInterface $stockItemRepository,
82
        ProductWebsiteLinkRepositoryInterface $websiteLinkRepository,
83
        ProductWebsiteLinkInterfaceFactory $websiteLinkFactory,
84
        IndexerFactory $indexerFactory,
85
        Product $product,
86
        array $websiteIds,
87
        array $storeSpecificValues
88
    ) {
89 27
        $this->productRepository = $productRepository;
90 27
        $this->websiteLinkRepository = $websiteLinkRepository;
91 27
        $this->stockItemRepository = $stockItemRepository;
92 27
        $this->websiteLinkFactory = $websiteLinkFactory;
93 27
        $this->indexerFactory = $indexerFactory;
94 27
        $this->product = $product;
95 27
        $this->websiteIds = $websiteIds;
96 27
        $this->storeSpecificValues = $storeSpecificValues;
97 27
    }
98
99 27
    public function __clone()
100
    {
101 27
        $this->product = clone $this->product;
102 27
    }
103
104 27
    public static function aSimpleProduct(): ProductBuilder
105
    {
106 27
        $objectManager = Bootstrap::getObjectManager();
107
        /** @var Product $product */
108 27
        $product = $objectManager->create(ProductInterface::class);
109
110 27
        $product->setTypeId(Product\Type::TYPE_SIMPLE)
111 27
                ->setAttributeSetId(4)
112 27
                ->setName('Simple Product')
113 27
                ->setPrice(10)
114 27
                ->setVisibility(Visibility::VISIBILITY_BOTH)
115 27
                ->setStatus(Status::STATUS_ENABLED);
116 27
        $product->addData(
117
            [
118 27
                'tax_class_id' => 1,
119
                'description' => 'Description',
120
            ]
121
        );
122
        /** @var StockItemInterface $stockItem */
123 27
        $stockItem = $objectManager->create(StockItemInterface::class);
124 27
        $stockItem->setManageStock(true)
125 27
                  ->setQty(100)
126 27
                  ->setIsQtyDecimal(false)
127 27
                  ->setIsInStock(true);
128 27
        $product->setExtensionAttributes(
129 27
            $product->getExtensionAttributes()->setStockItem($stockItem)
0 ignored issues
show
Bug introduced by
The method setStockItem() does not exist on Magento\Framework\Api\ExtensionAttributesInterface. It seems like you code against a sub-type of Magento\Framework\Api\ExtensionAttributesInterface such as Magento\Ui\Model\Bookmark. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

129
            $product->getExtensionAttributes()->/** @scrutinizer ignore-call */ setStockItem($stockItem)
Loading history...
130
        );
131
132 27
        return new static(
133 27
            $objectManager->create(ProductRepositoryInterface::class),
134 27
            $objectManager->create(StockItemRepositoryInterface::class),
135 27
            $objectManager->create(ProductWebsiteLinkRepositoryInterface::class),
136 27
            $objectManager->create(ProductWebsiteLinkInterfaceFactory::class),
137 27
            $objectManager->create(IndexerFactory::class),
138
            $product,
139 27
            [1],
140 27
            []
141
        );
142
    }
143
144
    /**
145
     * @param mixed[] $data
146
     * @return ProductBuilder
147
     */
148
    public function withData(array $data): ProductBuilder
149
    {
150
        $builder = clone $this;
151
152
        $builder->product->addData($data);
153
154
        return $builder;
155
    }
156
157 7
    public function withSku(string $sku): ProductBuilder
158
    {
159 7
        $builder = clone $this;
160 7
        $builder->product->setSku($sku);
161 7
        return $builder;
162
    }
163
164 2
    public function withName(string $name, int $storeId = null): ProductBuilder
165
    {
166 2
        $builder = clone $this;
167 2
        if ($storeId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $storeId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
168 1
            $builder->storeSpecificValues[$storeId][ProductInterface::NAME] = $name;
169
        } else {
170 2
            $builder->product->setName($name);
171
        }
172 2
        return $builder;
173
    }
174
175
    /**
176
     * @param int $status
177
     * @param int|null $storeId Pass store ID to set value for specific store.
178
     *                          Attention: Status is configured per website, will affect all stores of the same website
179
     * @return ProductBuilder
180
     */
181 2
    public function withStatus(int $status, $storeId = null): ProductBuilder
182
    {
183 2
        $builder = clone $this;
184 2
        if ($storeId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $storeId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
185 1
            $builder->storeSpecificValues[$storeId][ProductInterface::STATUS] = $status;
186
        } else {
187 2
            $builder->product->setStatus($status);
188
        }
189 2
        return $builder;
190
    }
191
192 2
    public function withVisibility(int $visibility, int $storeId = null): ProductBuilder
193
    {
194 2
        $builder = clone $this;
195 2
        if ($storeId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $storeId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
196 1
            $builder->storeSpecificValues[$storeId][ProductInterface::VISIBILITY] = $visibility;
197
        } else {
198 2
            $builder->product->setVisibility($visibility);
199
        }
200 2
        return $builder;
201
    }
202
203
    /**
204
     * @param int[] $websiteIds
205
     * @return ProductBuilder
206
     */
207 2
    public function withWebsiteIds(array $websiteIds): ProductBuilder
208
    {
209 2
        $builder = clone $this;
210 2
        $builder->websiteIds = $websiteIds;
211 2
        return $builder;
212
    }
213
214
    /**
215
     * @param int[] $categoryIds
216
     * @return ProductBuilder
217
     */
218
    public function withCategoryIds(array $categoryIds): ProductBuilder
219
    {
220
        $builder = clone $this;
221
        $builder->categoryIds = $categoryIds;
222
        return $builder;
223
    }
224
225 2
    public function withPrice(float $price): ProductBuilder
226
    {
227 2
        $builder = clone $this;
228 2
        $builder->product->setPrice($price);
229 2
        return $builder;
230
    }
231
232 1
    public function withTaxClassId(int $taxClassId): ProductBuilder
233
    {
234 1
        $builder = clone $this;
235 1
        $builder->product->setData('tax_class_id', $taxClassId);
236 1
        return $builder;
237
    }
238
239 1
    public function withIsInStock(bool $inStock): ProductBuilder
240
    {
241 1
        $builder = clone $this;
242 1
        $builder->product->getExtensionAttributes()->getStockItem()->setIsInStock($inStock);
0 ignored issues
show
Bug introduced by
The method getStockItem() does not exist on Magento\Framework\Api\ExtensionAttributesInterface. It seems like you code against a sub-type of Magento\Framework\Api\ExtensionAttributesInterface such as Magento\Ui\Model\Bookmark. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

242
        $builder->product->getExtensionAttributes()->/** @scrutinizer ignore-call */ getStockItem()->setIsInStock($inStock);
Loading history...
243 1
        return $builder;
244
    }
245
246 1
    public function withStockQty(float $qty): ProductBuilder
247
    {
248 1
        $builder = clone $this;
249 1
        $builder->product->getExtensionAttributes()->getStockItem()->setQty($qty);
250 1
        return $builder;
251
    }
252
253 1
    public function withBackorders(float $backorders) : ProductBuilder
254
    {
255 1
        $builder = clone $this;
256 1
        $builder->product->getExtensionAttributes()->getStockItem()->setBackorders($backorders);
257 1
        return $builder;
258
    }
259
260 1
    public function withWeight(float $weight): ProductBuilder
261
    {
262 1
        $builder = clone $this;
263 1
        $builder->product->setWeight($weight);
264 1
        return $builder;
265
    }
266
267
    /**
268
     * @param mixed[] $values
269
     * @param int|null $storeId
270
     * @return ProductBuilder
271
     */
272 2
    public function withCustomAttributes(array $values, int $storeId = null): ProductBuilder
273
    {
274 2
        $builder = clone $this;
275 2
        foreach ($values as $code => $value) {
276 2
            if ($storeId) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $storeId of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
277 1
                $builder->storeSpecificValues[$storeId][$code] = $value;
278
            } else {
279 2
                $builder->product->setCustomAttribute($code, $value);
280
            }
281
        }
282 2
        return $builder;
283
    }
284
285
    /**
286
     * @return ProductInterface
287
     * @throws \Exception
288
     */
289 27
    public function build(): ProductInterface
290
    {
291
        try {
292 27
            $product = $this->createProduct();
293 26
            $this->indexerFactory->create()->load('cataloginventory_stock')->reindexRow($product->getId());
294 26
            return $product;
295 1
        } catch (\Exception $e) {
296 1
            $e->getPrevious();
297 1
            if (self::isTransactionException($e) || self::isTransactionException($e->getPrevious())) {
298
                throw IndexFailed::becauseInitiallyTriggeredInTransaction($e);
299
            }
300 1
            throw $e;
301
        }
302
    }
303
304
    /**
305
     * @return ProductInterface
306
     * @throws \Exception
307
     */
308 27
    private function createProduct(): ProductInterface
309
    {
310 27
        $builder = clone $this;
311 27
        if (!$builder->product->getSku()) {
312 21
            $builder->product->setSku(sha1(uniqid('', true)));
313
        }
314 27
        $builder->product->setCustomAttribute('url_key', $builder->product->getSku());
315 27
        $builder->product->setData('category_ids', $builder->categoryIds);
316 27
        $product = $builder->productRepository->save($builder->product);
317 27
        foreach ($builder->websiteIds as $websiteId) {
318 27
            $websiteLink = $builder->websiteLinkFactory->create();
319 27
            $websiteLink->setWebsiteId($websiteId)->setSku($product->getSku());
320 27
            $builder->websiteLinkRepository->save($websiteLink);
321
        }
322 26
        foreach ($builder->storeSpecificValues as $storeId => $values) {
323
            /** @var Product $storeProduct */
324 1
            $storeProduct = clone $product;
325 1
            $storeProduct->setStoreId($storeId);
326 1
            $storeProduct->addData($values);
327 1
            $storeProduct->save();
0 ignored issues
show
Deprecated Code introduced by
The function Magento\Framework\Model\AbstractModel::save() has been deprecated: 100.1.0 because entities must not be responsible for their own persistence. Service contracts should persist entities. Use resource model "save" to implement service contract persistence operations. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

327
            /** @scrutinizer ignore-deprecated */ $storeProduct->save();

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
328
        }
329 26
        return $product;
330
    }
331
332
    /**
333
     * @param \Throwable|null $exception
334
     * @return bool
335
     */
336 1
    private static function isTransactionException($exception): bool
337
    {
338 1
        if ($exception === null) {
339
            return false;
340
        }
341 1
        return (bool) preg_match(
342 1
            '{please retry transaction|DDL statements are not allowed in transactions}i',
343 1
            $exception->getMessage()
344
        );
345
    }
346
}
347