Completed
Push — master ( 257940...1c25a6 )
by Fabian
14s queued 12s
created

ProductBuilder::withBackorders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace TddWizard\Fixtures\Catalog;
4
5
use Magento\Catalog\Api\Data\ProductInterface;
6
use Magento\Catalog\Api\Data\ProductWebsiteLinkInterfaceFactory;
7
use Magento\Catalog\Api\ProductRepositoryInterface;
8
use Magento\Catalog\Api\ProductWebsiteLinkRepositoryInterface;
9
use Magento\Catalog\Model\Product;
10
use Magento\Catalog\Model\Product\Attribute\Source\Status;
11
use Magento\Catalog\Model\Product\Visibility;
12
use Magento\CatalogInventory\Api\Data\StockItemInterface;
13
use Magento\CatalogInventory\Api\StockItemRepositoryInterface;
14
use Magento\Framework\ObjectManagerInterface;
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 ProductInterface|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
    public function __construct(
70
        ProductRepositoryInterface $productRepository,
71
        StockItemRepositoryInterface $stockItemRepository,
72
        ProductWebsiteLinkRepositoryInterface $websiteLinkRepository,
73
        ProductWebsiteLinkInterfaceFactory $websiteLinkFactory,
74
        IndexerFactory $indexerFactory,
75
        ProductInterface $product,
76
        array $websiteIds,
77
        array $storeSpecificValues
78
    ) {
79
        $this->productRepository = $productRepository;
80
        $this->websiteLinkRepository = $websiteLinkRepository;
81
        $this->stockItemRepository = $stockItemRepository;
82
        $this->websiteLinkFactory = $websiteLinkFactory;
83
        $this->indexerFactory = $indexerFactory;
84
        $this->product = $product;
85
        $this->websiteIds = $websiteIds;
86
        $this->storeSpecificValues = $storeSpecificValues;
87
    }
88
89
    public function __clone()
90
    {
91
        $this->product = clone $this->product;
92
    }
93
94
    public static function aSimpleProduct(ObjectManagerInterface $objectManager = null): ProductBuilder
95
    {
96
        if ($objectManager === null) {
97
            $objectManager = Bootstrap::getObjectManager();
98
        }
99
        /** @var ProductInterface|Product $product */
100
        $product = $objectManager->create(ProductInterface::class);
101
102
        $product->setTypeId(Product\Type::TYPE_SIMPLE)
103
                ->setAttributeSetId(4)
104
                ->setName('Simple Product')
105
                ->setPrice(10)
106
                ->setVisibility(Visibility::VISIBILITY_BOTH)
107
                ->setStatus(Status::STATUS_ENABLED);
108
        $product->addData(
109
            [
110
                'tax_class_id' => 1,
111
                'description' => 'Description',
112
            ]
113
        );
114
        /** @var StockItemInterface $stockItem */
115
        $stockItem = $objectManager->create(StockItemInterface::class);
116
        $stockItem->setManageStock(true)
117
                  ->setQty(100)
118
                  ->setIsQtyDecimal(false)
119
                  ->setIsInStock(true);
120
        $product->setExtensionAttributes(
121
            $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

121
            $product->getExtensionAttributes()->/** @scrutinizer ignore-call */ setStockItem($stockItem)
Loading history...
122
        );
123
124
        return new static(
125
            $objectManager->create(ProductRepositoryInterface::class),
126
            $objectManager->create(StockItemRepositoryInterface::class),
127
            $objectManager->create(ProductWebsiteLinkRepositoryInterface::class),
128
            $objectManager->create(ProductWebsiteLinkInterfaceFactory::class),
129
            $objectManager->create(IndexerFactory::class),
130
            $product,
131
            [1],
132
            []
133
        );
134
    }
135
136
    public function withData(array $data): ProductBuilder
137
    {
138
        $builder = clone $this;
139
140
        $builder->product->addData($data);
141
142
        return $builder;
143
    }
144
145
    public function withSku(string $sku): ProductBuilder
146
    {
147
        $builder = clone $this;
148
        $builder->product->setSku($sku);
149
        return $builder;
150
    }
151
152
    public function withName(string $name, $storeId = null): ProductBuilder
153
    {
154
        $builder = clone $this;
155
        if ($storeId) {
156
            $builder->storeSpecificValues[$storeId][ProductInterface::NAME] = $name;
157
        } else {
158
            $builder->product->setName($name);
159
        }
160
        return $builder;
161
    }
162
163
    /**
164
     * @param int $status
165
     * @param int|null $storeId Pass store ID to set value for specific store.
166
     *                          Attention: Status is configured per website, will affect all stores of the same website
167
     * @return ProductBuilder
168
     */
169
    public function withStatus(int $status, $storeId = null): ProductBuilder
170
    {
171
        $builder = clone $this;
172
        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...
173
            $builder->storeSpecificValues[$storeId][ProductInterface::STATUS] = $status;
174
        } else {
175
            $builder->product->setStatus($status);
176
        }
177
        return $builder;
178
    }
179
180
    public function withVisibility(int $visibility, $storeId = null): ProductBuilder
181
    {
182
        $builder = clone $this;
183
        if ($storeId) {
184
            $builder->storeSpecificValues[$storeId][ProductInterface::VISIBILITY] = $visibility;
185
        } else {
186
            $builder->product->setVisibility($visibility);
187
        }
188
        return $builder;
189
    }
190
191
    public function withWebsiteIds(array $websiteIds): ProductBuilder
192
    {
193
        $builder = clone $this;
194
        $builder->websiteIds = $websiteIds;
195
        return $builder;
196
    }
197
198
    public function withCategoryIds(array $categoryIds): ProductBuilder
199
    {
200
        $builder = clone $this;
201
        $builder->categoryIds = $categoryIds;
202
        return $builder;
203
    }
204
205
    public function withPrice(float $price): ProductBuilder
206
    {
207
        $builder = clone $this;
208
        $builder->product->setPrice($price);
209
        return $builder;
210
    }
211
212
    public function withTaxClassId($taxClassId): ProductBuilder
213
    {
214
        $builder = clone $this;
215
        $builder->product->setData('tax_class_id', $taxClassId);
216
        return $builder;
217
    }
218
219
    public function withIsInStock(bool $inStock): ProductBuilder
220
    {
221
        $builder = clone $this;
222
        $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

222
        $builder->product->getExtensionAttributes()->/** @scrutinizer ignore-call */ getStockItem()->setIsInStock($inStock);
Loading history...
223
        return $builder;
224
    }
225
226
    public function withStockQty($qty): ProductBuilder
227
    {
228
        $builder = clone $this;
229
        $builder->product->getExtensionAttributes()->getStockItem()->setQty($qty);
230
        return $builder;
231
    }
232
233
    public function withBackorders($backorders) : ProductBuilder
234
    {
235
        $builder = clone $this;
236
        $builder->product->getExtensionAttributes()->getStockItem()->setBackorders($backorders);
237
        return $builder;
238
    }
239
240
    public function withWeight($weight): ProductBuilder
241
    {
242
        $builder = clone $this;
243
        $builder->product->setWeight($weight);
244
        return $builder;
245
    }
246
247
    public function withCustomAttributes(array $values, $storeId = null): ProductBuilder
248
    {
249
        $builder = clone $this;
250
        foreach ($values as $code => $value) {
251
            if ($storeId) {
252
                $builder->storeSpecificValues[$storeId][$code] = $value;
253
            } else {
254
                $builder->product->setCustomAttribute($code, $value);
255
            }
256
        }
257
        return $builder;
258
    }
259
260
    /**
261
     * @return ProductInterface
262
     * @throws \Exception
263
     */
264
    public function build(): ProductInterface
265
    {
266
        try {
267
            $product = $this->createProduct();
268
            $this->indexerFactory->create()->load('cataloginventory_stock')->reindexRow($product->getId());
269
            return $product;
270
        } catch (\Exception $e) {
271
            $e->getPrevious();
272
            if ($this->isTransactionException($e) || $this->isTransactionException($e->getPrevious())) {
273
                throw IndexFailed::becauseInitiallyTriggeredInTransaction($e);
274
            }
275
            throw $e;
276
        }
277
    }
278
279
    /**
280
     * @return ProductInterface
281
     * @throws \Exception
282
     */
283
    private function createProduct(): ProductInterface
284
    {
285
        $builder = clone $this;
286
        if (!$builder->product->getSku()) {
287
            $builder->product->setSku(sha1(uniqid('', true)));
288
        }
289
        $builder->product->setCustomAttribute('url_key', $builder->product->getSku());
290
        $builder->product->setData('category_ids', $builder->categoryIds);
291
        $product = $builder->productRepository->save($builder->product);
292
        foreach ($builder->websiteIds as $websiteId) {
293
            $websiteLink = $builder->websiteLinkFactory->create();
294
            $websiteLink->setWebsiteId($websiteId)->setSku($product->getSku());
295
            $builder->websiteLinkRepository->save($websiteLink);
296
        }
297
        foreach ($builder->storeSpecificValues as $storeId => $values) {
298
            /** @var Product $storeProduct */
299
            $storeProduct = clone $product;
300
            $storeProduct->setStoreId($storeId);
301
            $storeProduct->addData($values);
302
            $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

302
            /** @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...
303
        }
304
        return $product;
305
    }
306
307
    private function isTransactionException($exception): bool
308
    {
309
        if ($exception === null) {
310
            return false;
311
        }
312
        return preg_match(
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match('{plea...xception->getMessage()) returns the type integer which is incompatible with the type-hinted return boolean.
Loading history...
313
            '{please retry transaction|DDL statements are not allowed in transactions}i',
314
            $exception->getMessage()
315
        );
316
    }
317
}
318