Complex classes like ProductToShop 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 ProductToShop, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 44 | class ProductToShop implements ProductToShopBase |
||
| 45 | { |
||
| 46 | /** |
||
| 47 | * @var Helper |
||
| 48 | */ |
||
| 49 | private $helper; |
||
| 50 | |||
| 51 | /** |
||
| 52 | * @var ModelManager |
||
| 53 | */ |
||
| 54 | private $manager; |
||
| 55 | |||
| 56 | /** |
||
| 57 | * @var \ShopwarePlugins\Connect\Components\Config |
||
| 58 | */ |
||
| 59 | private $config; |
||
| 60 | |||
| 61 | /** |
||
| 62 | * @var ImageImport |
||
| 63 | */ |
||
| 64 | private $imageImport; |
||
| 65 | |||
| 66 | /** |
||
| 67 | * @var \ShopwarePlugins\Connect\Components\VariantConfigurator |
||
| 68 | */ |
||
| 69 | private $variantConfigurator; |
||
| 70 | |||
| 71 | /** |
||
| 72 | * @var MarketplaceGateway |
||
| 73 | */ |
||
| 74 | private $marketplaceGateway; |
||
| 75 | |||
| 76 | /** |
||
| 77 | * @var ProductTranslationsGateway |
||
| 78 | */ |
||
| 79 | private $productTranslationsGateway; |
||
| 80 | |||
| 81 | /** |
||
| 82 | * @var \Shopware\Models\Shop\Repository |
||
| 83 | */ |
||
| 84 | private $shopRepository; |
||
| 85 | |||
| 86 | private $localeRepository; |
||
| 87 | |||
| 88 | /** |
||
| 89 | * @var CategoryResolver |
||
| 90 | */ |
||
| 91 | private $categoryResolver; |
||
| 92 | |||
| 93 | /** |
||
| 94 | * @var \Shopware\Connect\Gateway |
||
| 95 | */ |
||
| 96 | private $connectGateway; |
||
| 97 | |||
| 98 | /** |
||
| 99 | * @var \Enlight_Event_EventManager |
||
| 100 | */ |
||
| 101 | private $eventManager; |
||
| 102 | |||
| 103 | /** |
||
| 104 | * @param Helper $helper |
||
| 105 | * @param ModelManager $manager |
||
| 106 | * @param ImageImport $imageImport |
||
| 107 | * @param \ShopwarePlugins\Connect\Components\Config $config |
||
| 108 | * @param VariantConfigurator $variantConfigurator |
||
| 109 | * @param \ShopwarePlugins\Connect\Components\Marketplace\MarketplaceGateway $marketplaceGateway |
||
| 110 | * @param ProductTranslationsGateway $productTranslationsGateway |
||
| 111 | * @param CategoryResolver $categoryResolver |
||
| 112 | * @param Gateway $connectGateway |
||
| 113 | * @param \Enlight_Event_EventManager $eventManager |
||
| 114 | */ |
||
| 115 | public function __construct( |
||
| 116 | Helper $helper, |
||
| 117 | ModelManager $manager, |
||
| 118 | ImageImport $imageImport, |
||
| 119 | Config $config, |
||
| 120 | VariantConfigurator $variantConfigurator, |
||
| 121 | MarketplaceGateway $marketplaceGateway, |
||
| 122 | ProductTranslationsGateway $productTranslationsGateway, |
||
| 123 | CategoryResolver $categoryResolver, |
||
| 124 | Gateway $connectGateway, |
||
| 125 | \Enlight_Event_EventManager $eventManager |
||
| 126 | ) { |
||
| 127 | $this->helper = $helper; |
||
| 128 | $this->manager = $manager; |
||
| 129 | $this->config = $config; |
||
| 130 | $this->imageImport = $imageImport; |
||
| 131 | $this->variantConfigurator = $variantConfigurator; |
||
| 132 | $this->marketplaceGateway = $marketplaceGateway; |
||
| 133 | $this->productTranslationsGateway = $productTranslationsGateway; |
||
| 134 | $this->categoryResolver = $categoryResolver; |
||
| 135 | $this->connectGateway = $connectGateway; |
||
| 136 | $this->eventManager = $eventManager; |
||
| 137 | } |
||
| 138 | |||
| 139 | /** |
||
| 140 | * Start transaction |
||
| 141 | * |
||
| 142 | * Starts a transaction, which includes all insertOrUpdate and delete |
||
| 143 | * operations, as well as the revision updates. |
||
| 144 | * |
||
| 145 | * @return void |
||
| 146 | */ |
||
| 147 | public function startTransaction() |
||
| 148 | { |
||
| 149 | $this->manager->getConnection()->beginTransaction(); |
||
| 150 | } |
||
| 151 | |||
| 152 | /** |
||
| 153 | * Commit transaction |
||
| 154 | * |
||
| 155 | * Commits the transactions, once all operations are queued. |
||
| 156 | * |
||
| 157 | * @return void |
||
| 158 | */ |
||
| 159 | public function commit() |
||
| 160 | { |
||
| 161 | $this->manager->getConnection()->commit(); |
||
| 162 | } |
||
| 163 | |||
| 164 | /** |
||
| 165 | * Import or update given product |
||
| 166 | * |
||
| 167 | * Store product in your shop database as an external product. The |
||
| 168 | * associated sourceId |
||
| 169 | * |
||
| 170 | * @param Product $product |
||
| 171 | */ |
||
| 172 | public function insertOrUpdate(Product $product) |
||
| 173 | { |
||
| 174 | /** @var Product $product */ |
||
| 175 | $product = $this->eventManager->filter( |
||
| 176 | 'Connect_ProductToShop_InsertOrUpdate_Before', |
||
| 177 | $product |
||
| 178 | ); |
||
| 179 | |||
| 180 | // todo@dn: Set dummy values and make product inactive |
||
| 181 | if (empty($product->title) || empty($product->vendor)) { |
||
| 182 | return; |
||
| 183 | } |
||
| 184 | |||
| 185 | $detail = $this->helper->getArticleDetailModelByProduct($product); |
||
| 186 | $detail = $this->eventManager->filter( |
||
| 187 | 'Connect_Merchant_Get_Article_Detail_After', |
||
| 188 | $detail, |
||
| 189 | [ |
||
| 190 | 'product' => $product, |
||
| 191 | 'subject' => $this |
||
| 192 | ] |
||
| 193 | ); |
||
| 194 | |||
| 195 | $isMainVariant = false; |
||
| 196 | |||
| 197 | if ($detail === null) { |
||
| 198 | $active = $this->config->getConfig('activateProductsAutomatically', false) ? true : false; |
||
| 199 | if ($product->groupId > 0) { |
||
| 200 | $model = $this->helper->getArticleByRemoteProduct($product); |
||
| 201 | if (!$model instanceof \Shopware\Models\Article\Article) { |
||
|
|
|||
| 202 | $model = $this->helper->createProductModel($product); |
||
| 203 | $model->setActive($active); |
||
| 204 | $isMainVariant = true; |
||
| 205 | } |
||
| 206 | } else { |
||
| 207 | $model = $this->helper->getConnectArticleModel($product->sourceId, $product->shopId); |
||
| 208 | if (!$model instanceof \Shopware\Models\Article\Article) { |
||
| 209 | $model = $this->helper->createProductModel($product); |
||
| 210 | $model->setActive($active); |
||
| 211 | } |
||
| 212 | } |
||
| 213 | |||
| 214 | $detail = new DetailModel(); |
||
| 215 | $detail->setActive($model->getActive()); |
||
| 216 | |||
| 217 | $detail->setArticle($model); |
||
| 218 | if (!empty($product->variant)) { |
||
| 219 | $this->variantConfigurator->configureVariantAttributes($product, $detail); |
||
| 220 | } |
||
| 221 | |||
| 222 | $categories = $this->categoryResolver->resolve($product->categories); |
||
| 223 | $model->setCategories($categories); |
||
| 224 | } else { |
||
| 225 | $model = $detail->getArticle(); |
||
| 226 | // fix for isMainVariant flag |
||
| 227 | // in connect attribute table |
||
| 228 | $mainDetail = $model->getMainDetail(); |
||
| 229 | if ($detail->getId() === $mainDetail->getId()) { |
||
| 230 | $isMainVariant = true; |
||
| 231 | } |
||
| 232 | } |
||
| 233 | |||
| 234 | if (!empty($product->sku)) { |
||
| 235 | $detail->setNumber('SC-' . $product->shopId . '-' . $product->sku); |
||
| 236 | } else { |
||
| 237 | $detail->setNumber('SC-' . $product->shopId . '-' . $product->sourceId); |
||
| 238 | } |
||
| 239 | |||
| 240 | $connectAttribute = $this->helper->getConnectAttributeByModel($detail) ?: new ConnectAttribute; |
||
| 241 | // configure main variant and groupId |
||
| 242 | if ($isMainVariant === true) { |
||
| 243 | $connectAttribute->setIsMainVariant(true); |
||
| 244 | } |
||
| 245 | $connectAttribute->setGroupId($product->groupId); |
||
| 246 | |||
| 247 | $detailAttribute = $detail->getAttribute(); |
||
| 248 | if (!$detailAttribute) { |
||
| 249 | $detailAttribute = new AttributeModel(); |
||
| 250 | $detail->setAttribute($detailAttribute); |
||
| 251 | $detailAttribute->setArticle($model); |
||
| 252 | } |
||
| 253 | |||
| 254 | list($updateFields, $flag) = $this->getUpdateFields($model, $detail, $connectAttribute, $product); |
||
| 255 | /* |
||
| 256 | * Make sure, that the following properties are set for |
||
| 257 | * - new products |
||
| 258 | * - products that have been configured to receive these updates |
||
| 259 | */ |
||
| 260 | if ($updateFields['name']) { |
||
| 261 | $model->setName($product->title); |
||
| 262 | } |
||
| 263 | if ($updateFields['shortDescription']) { |
||
| 264 | $model->setDescription($product->shortDescription); |
||
| 265 | } |
||
| 266 | if ($updateFields['longDescription']) { |
||
| 267 | $model->setDescriptionLong($product->longDescription); |
||
| 268 | } |
||
| 269 | |||
| 270 | if ($updateFields['additionalDescription']) { |
||
| 271 | $detailAttribute->setConnectProductDescription($product->additionalDescription); |
||
| 272 | } |
||
| 273 | |||
| 274 | if ($product->vat !== null) { |
||
| 275 | $repo = $this->manager->getRepository('Shopware\Models\Tax\Tax'); |
||
| 276 | $tax = round($product->vat * 100, 2); |
||
| 277 | /** @var \Shopware\Models\Tax\Tax $tax */ |
||
| 278 | $tax = $repo->findOneBy(['tax' => $tax]); |
||
| 279 | $model->setTax($tax); |
||
| 280 | } |
||
| 281 | |||
| 282 | if ($product->vendor !== null) { |
||
| 283 | $repo = $this->manager->getRepository('Shopware\Models\Article\Supplier'); |
||
| 284 | $supplier = $repo->findOneBy(['name' => $product->vendor]); |
||
| 285 | if ($supplier === null) { |
||
| 286 | $supplier = $this->createSupplier($product->vendor); |
||
| 287 | } |
||
| 288 | $model->setSupplier($supplier); |
||
| 289 | } |
||
| 290 | |||
| 291 | //set product properties |
||
| 292 | $this->applyProductProperties($model, $product); |
||
| 293 | |||
| 294 | // apply marketplace attributes |
||
| 295 | $detailAttribute = $this->applyMarketplaceAttributes($detailAttribute, $product); |
||
| 296 | |||
| 297 | $connectAttribute->setShopId($product->shopId); |
||
| 298 | $connectAttribute->setSourceId($product->sourceId); |
||
| 299 | $connectAttribute->setExportStatus(null); |
||
| 300 | $connectAttribute->setPurchasePrice($product->purchasePrice); |
||
| 301 | $connectAttribute->setFixedPrice($product->fixedPrice); |
||
| 302 | $connectAttribute->setStream($product->stream); |
||
| 303 | |||
| 304 | // store product categories to connect attribute |
||
| 305 | $connectAttribute->setCategory($product->categories); |
||
| 306 | |||
| 307 | $connectAttribute->setLastUpdateFlag($flag); |
||
| 308 | // store purchasePriceHash and offerValidUntil |
||
| 309 | $connectAttribute->setPurchasePriceHash($product->purchasePriceHash); |
||
| 310 | $connectAttribute->setOfferValidUntil($product->offerValidUntil); |
||
| 311 | |||
| 312 | $detail->setInStock($product->availability); |
||
| 313 | $detail->setEan($product->ean); |
||
| 314 | $detail->setShippingTime($product->deliveryWorkDays); |
||
| 315 | $releaseDate = new \DateTime(); |
||
| 316 | $releaseDate->setTimestamp($product->deliveryDate); |
||
| 317 | $detail->setReleaseDate($releaseDate); |
||
| 318 | $detail->setMinPurchase($product->minPurchaseQuantity); |
||
| 319 | |||
| 320 | // some shops have feature "sell not in stock", |
||
| 321 | // then end customer should be able to by the product with stock = 0 |
||
| 322 | $shopConfiguration = $this->connectGateway->getShopConfiguration($product->shopId); |
||
| 323 | if ($shopConfiguration && $shopConfiguration->sellNotInStock) { |
||
| 324 | $model->setLastStock(false); |
||
| 325 | } else { |
||
| 326 | $model->setLastStock(true); |
||
| 327 | } |
||
| 328 | |||
| 329 | // if connect product has unit |
||
| 330 | // find local unit with units mapping |
||
| 331 | // and add to detail model |
||
| 332 | if (array_key_exists('unit', $product->attributes) && $product->attributes['unit']) { |
||
| 333 | $detailAttribute->setConnectRemoteUnit($product->attributes['unit']); |
||
| 334 | if ($this->config->getConfig($product->attributes['unit']) == null) { |
||
| 335 | $this->config->setConfig($product->attributes['unit'], '', null, 'units'); |
||
| 336 | } |
||
| 337 | |||
| 338 | /** @var \ShopwarePlugins\Connect\Components\Utils\UnitMapper $unitMapper */ |
||
| 339 | $unitMapper = new UnitMapper($this->config, $this->manager); |
||
| 340 | |||
| 341 | $shopwareUnit = $unitMapper->getShopwareUnit($product->attributes['unit']); |
||
| 342 | |||
| 343 | /** @var \Shopware\Models\Article\Unit $unit */ |
||
| 344 | $unit = $this->helper->getUnit($shopwareUnit); |
||
| 345 | $detail->setUnit($unit); |
||
| 346 | $detail->setPurchaseUnit($product->attributes['quantity']); |
||
| 347 | $detail->setReferenceUnit($product->attributes['ref_quantity']); |
||
| 348 | } else { |
||
| 349 | $detail->setUnit(null); |
||
| 350 | $detail->setPurchaseUnit(null); |
||
| 351 | $detail->setReferenceUnit(null); |
||
| 352 | } |
||
| 353 | |||
| 354 | // set dimension |
||
| 355 | if (array_key_exists('dimension', $product->attributes) && $product->attributes['dimension']) { |
||
| 356 | $dimension = explode('x', $product->attributes['dimension']); |
||
| 357 | $detail->setLen($dimension[0]); |
||
| 358 | $detail->setWidth($dimension[1]); |
||
| 359 | $detail->setHeight($dimension[2]); |
||
| 360 | } else { |
||
| 361 | $detail->setLen(null); |
||
| 362 | $detail->setWidth(null); |
||
| 363 | $detail->setHeight(null); |
||
| 364 | } |
||
| 365 | |||
| 366 | // set weight |
||
| 367 | if (array_key_exists('weight', $product->attributes) && $product->attributes['weight']) { |
||
| 368 | $detail->setWeight($product->attributes['weight']); |
||
| 369 | } |
||
| 370 | |||
| 371 | // Whenever a product is updated, store a json encoded list of all fields that are updated optionally |
||
| 372 | // This way a customer will be able to apply the most recent changes any time later |
||
| 373 | $connectAttribute->setLastUpdate(json_encode([ |
||
| 374 | 'shortDescription' => $product->shortDescription, |
||
| 375 | 'longDescription' => $product->longDescription, |
||
| 376 | 'additionalDescription' => $product->additionalDescription, |
||
| 377 | 'purchasePrice' => $product->purchasePrice, |
||
| 378 | 'image' => $product->images, |
||
| 379 | 'variantImages' => $product->variantImages, |
||
| 380 | 'price' => $product->price * ($product->vat + 1), |
||
| 381 | 'name' => $product->title, |
||
| 382 | 'vat' => $product->vat |
||
| 383 | ])); |
||
| 384 | |||
| 385 | if ($model->getMainDetail() === null) { |
||
| 386 | $model->setMainDetail($detail); |
||
| 387 | } |
||
| 388 | |||
| 389 | if ($detail->getAttribute() === null) { |
||
| 390 | $detail->setAttribute($detailAttribute); |
||
| 391 | $detailAttribute->setArticle($model); |
||
| 392 | } |
||
| 393 | |||
| 394 | $connectAttribute->setArticle($model); |
||
| 395 | $connectAttribute->setArticleDetail($detail); |
||
| 396 | |||
| 397 | $this->eventManager->notify( |
||
| 398 | 'Connect_Merchant_Saving_ArticleAttribute_Before', |
||
| 399 | [ |
||
| 400 | 'subject' => $this, |
||
| 401 | 'connectAttribute' => $connectAttribute |
||
| 402 | ] |
||
| 403 | ); |
||
| 404 | |||
| 405 | $this->manager->persist($connectAttribute); |
||
| 406 | |||
| 407 | $this->manager->persist($detail); |
||
| 408 | |||
| 409 | // some articles from connect have long sourceId |
||
| 410 | // like OXID articles. They use md5 hash, but it is not supported |
||
| 411 | // in shopware. |
||
| 412 | if (strlen($detail->getNumber()) > 30) { |
||
| 413 | $detail->setNumber('SC-' . $product->shopId . '-' . $detail->getId()); |
||
| 414 | |||
| 415 | $this->manager->persist($detail); |
||
| 416 | $this->manager->flush($detail); |
||
| 417 | } |
||
| 418 | |||
| 419 | $this->manager->flush(); |
||
| 420 | |||
| 421 | $defaultCustomerGroup = $this->helper->getDefaultCustomerGroup(); |
||
| 422 | // Only set prices, if fixedPrice is active or price updates are configured |
||
| 423 | if (count($detail->getPrices()) == 0 || $connectAttribute->getFixedPrice() || $updateFields['price']) { |
||
| 424 | $this->setPrice($model, $detail, $product); |
||
| 425 | } |
||
| 426 | // If the price is not being update, update the purchasePrice anyway |
||
| 427 | $this->setPurchasePrice($detail, $product->purchasePrice, $defaultCustomerGroup); |
||
| 428 | |||
| 429 | $this->manager->clear(); |
||
| 430 | |||
| 431 | $this->addArticleTranslations($model, $product); |
||
| 432 | |||
| 433 | //clear cache for that article |
||
| 434 | $this->helper->clearArticleCache($model->getId()); |
||
| 435 | |||
| 436 | if ($updateFields['image']) { |
||
| 437 | // Reload the model in order to not to work an the already flushed model |
||
| 438 | $model = $this->helper->getArticleModelByProduct($product); |
||
| 439 | // import only global images for article |
||
| 440 | $this->imageImport->importImagesForArticle(array_diff($product->images, $product->variantImages), $model); |
||
| 441 | // Reload the article detail model in order to not to work an the already flushed model |
||
| 442 | $detail = $this->helper->getArticleDetailModelByProduct($product); |
||
| 443 | // import only specific images for variant |
||
| 444 | $this->imageImport->importImagesForDetail($product->variantImages, $detail); |
||
| 445 | } |
||
| 446 | $this->categoryResolver->storeRemoteCategories($product->categories, $model->getId()); |
||
| 447 | |||
| 448 | $this->eventManager->notify( |
||
| 449 | 'Connect_ProductToShop_InsertOrUpdate_After', |
||
| 450 | [ |
||
| 451 | 'connectProduct' => $product, |
||
| 452 | 'shopArticleDetail' => $detail |
||
| 453 | ] |
||
| 454 | ); |
||
| 455 | |||
| 456 | $stream = $this->getOrCreateStream($product); |
||
| 457 | $this->addProductToStream($stream, $model); |
||
| 458 | } |
||
| 459 | |||
| 460 | /** |
||
| 461 | * @param ProductModel $article |
||
| 462 | * @param Product $product |
||
| 463 | */ |
||
| 464 | private function applyProductProperties(ProductModel $article, Product $product) |
||
| 465 | { |
||
| 466 | if (empty($product->properties)) { |
||
| 467 | return; |
||
| 468 | } |
||
| 469 | |||
| 470 | /** @var Property $firstProperty */ |
||
| 471 | $firstProperty = reset($product->properties); |
||
| 472 | $groupRepo = $this->manager->getRepository(PropertyGroup::class); |
||
| 473 | $group = $groupRepo->findOneBy(['name' => $firstProperty->groupName]); |
||
| 474 | |||
| 475 | if (!$group) { |
||
| 476 | $group = new PropertyGroup(); |
||
| 477 | $group->setName($firstProperty->groupName); |
||
| 478 | $group->setComparable($firstProperty->comparable); |
||
| 479 | $group->setSortMode($firstProperty->sortMode); |
||
| 480 | $group->setPosition($firstProperty->groupPosition); |
||
| 481 | |||
| 482 | $attribute = new \Shopware\Models\Attribute\PropertyGroup(); |
||
| 483 | $attribute->setPropertyGroup($group); |
||
| 484 | $attribute->setConnectIsRemote(true); |
||
| 485 | $group->setAttribute($attribute); |
||
| 486 | |||
| 487 | $this->manager->persist($attribute); |
||
| 488 | $this->manager->persist($group); |
||
| 489 | $this->manager->flush(); |
||
| 490 | } |
||
| 491 | |||
| 492 | $propertyValues = $article->getPropertyValues(); |
||
| 493 | $propertyValues->clear(); |
||
| 494 | $this->manager->persist($article); |
||
| 495 | $this->manager->flush(); |
||
| 496 | |||
| 497 | $article->setPropertyGroup($group); |
||
| 498 | |||
| 499 | $optionRepo = $this->manager->getRepository(PropertyOption::class); |
||
| 500 | $valueRepo = $this->manager->getRepository(PropertyValue::class); |
||
| 501 | |||
| 502 | foreach ($product->properties as $property) { |
||
| 503 | $option = $optionRepo->findOneBy(['name' => $property->option]); |
||
| 504 | $optionExists = $option instanceof PropertyOption; |
||
| 505 | if (!$option) { |
||
| 506 | $option = new PropertyOption(); |
||
| 507 | $option->setName($property->option); |
||
| 508 | $option->setFilterable($property->filterable); |
||
| 509 | |||
| 510 | $attribute = new \Shopware\Models\Attribute\PropertyOption(); |
||
| 511 | $attribute->setPropertyOption($option); |
||
| 512 | $attribute->setConnectIsRemote(true); |
||
| 513 | $option->setAttribute($attribute); |
||
| 514 | |||
| 515 | $this->manager->persist($option); |
||
| 516 | $this->manager->flush($option); |
||
| 517 | } |
||
| 518 | |||
| 519 | if (!$optionExists || !$value = $valueRepo->findOneBy(['option' => $option, 'value' => $property->value])) { |
||
| 520 | $value = new PropertyValue($option, $property->value); |
||
| 521 | $value->setPosition($property->valuePosition); |
||
| 522 | |||
| 523 | $attribute = new \Shopware\Models\Attribute\PropertyValue(); |
||
| 524 | $attribute->setPropertyValue($value); |
||
| 525 | $attribute->setConnectIsRemote(true); |
||
| 526 | $value->setAttribute($attribute); |
||
| 527 | |||
| 528 | $this->manager->persist($value); |
||
| 529 | } |
||
| 530 | |||
| 531 | if (!$propertyValues->contains($value)) { |
||
| 532 | //add only new values |
||
| 533 | $propertyValues->add($value); |
||
| 534 | } |
||
| 535 | |||
| 536 | $filters = [ |
||
| 537 | ['property' => 'options.name', 'expression' => '=', 'value' => $property->option], |
||
| 538 | ['property' => 'groups.name', 'expression' => '=', 'value' => $property->groupName], |
||
| 539 | ]; |
||
| 540 | |||
| 541 | $query = $groupRepo->getPropertyRelationQuery($filters, null, 1, 0); |
||
| 542 | $relation = $query->getOneOrNullResult(); |
||
| 543 | |||
| 544 | if (!$relation) { |
||
| 545 | $group->addOption($option); |
||
| 546 | $this->manager->persist($group); |
||
| 547 | $this->manager->flush($group); |
||
| 548 | } |
||
| 549 | } |
||
| 550 | |||
| 551 | $article->setPropertyValues($propertyValues); |
||
| 552 | |||
| 553 | $this->manager->persist($article); |
||
| 554 | $this->manager->flush(); |
||
| 555 | } |
||
| 556 | |||
| 557 | /** |
||
| 558 | * @param Product $product |
||
| 559 | * @return ProductStream |
||
| 560 | */ |
||
| 561 | private function getOrCreateStream(Product $product) |
||
| 562 | { |
||
| 563 | /** @var ProductStreamRepository $repo */ |
||
| 564 | $repo = $this->manager->getRepository(ProductStreamAttribute::class); |
||
| 565 | $stream = $repo->findConnectByName($product->stream); |
||
| 566 | |||
| 567 | if (!$stream) { |
||
| 568 | $stream = new ProductStream(); |
||
| 569 | $stream->setName($product->stream); |
||
| 570 | $stream->setType(ProductStreamService::STATIC_STREAM); |
||
| 571 | $stream->setSorting(json_encode( |
||
| 572 | [ReleaseDateSorting::class => ['direction' => 'desc']] |
||
| 573 | )); |
||
| 574 | |||
| 575 | //add attributes |
||
| 576 | $attribute = new \Shopware\Models\Attribute\ProductStream(); |
||
| 577 | $attribute->setProductStream($stream); |
||
| 578 | $attribute->setConnectIsRemote(true); |
||
| 579 | $stream->setAttribute($attribute); |
||
| 580 | |||
| 581 | $this->manager->persist($attribute); |
||
| 582 | $this->manager->persist($stream); |
||
| 583 | $this->manager->flush(); |
||
| 584 | } |
||
| 585 | |||
| 586 | return $stream; |
||
| 587 | } |
||
| 588 | |||
| 589 | /** |
||
| 590 | * @param ProductStream $stream |
||
| 591 | * @param ProductModel $article |
||
| 592 | * @throws \Doctrine\DBAL\DBALException |
||
| 593 | */ |
||
| 594 | private function addProductToStream(ProductStream $stream, ProductModel $article) |
||
| 595 | { |
||
| 596 | $conn = $this->manager->getConnection(); |
||
| 597 | $sql = 'INSERT INTO `s_product_streams_selection` (`stream_id`, `article_id`) |
||
| 598 | VALUES (:streamId, :articleId) |
||
| 599 | ON DUPLICATE KEY UPDATE stream_id = :streamId, article_id = :articleId'; |
||
| 600 | $stmt = $conn->prepare($sql); |
||
| 601 | $stmt->execute([':streamId' => $stream->getId(), ':articleId' => $article->getId()]); |
||
| 602 | } |
||
| 603 | |||
| 604 | /** |
||
| 605 | * Set detail purchase price with plain SQL |
||
| 606 | * Entity usage throws exception when error handlers are disabled |
||
| 607 | * |
||
| 608 | * @param ProductModel $article |
||
| 609 | * @param DetailModel $detail |
||
| 610 | * @param Product $product |
||
| 611 | * @throws \Doctrine\DBAL\DBALException |
||
| 612 | */ |
||
| 613 | private function setPrice(ProductModel $article, DetailModel $detail, Product $product) |
||
| 614 | { |
||
| 615 | // set price via plain SQL because shopware throws exception |
||
| 616 | // undefined index: key when error handler is disabled |
||
| 617 | $customerGroup = $this->helper->getDefaultCustomerGroup(); |
||
| 618 | |||
| 619 | if (!empty($product->priceRanges)) { |
||
| 620 | $this->setPriceRange($article, $detail, $product->priceRanges, $customerGroup); |
||
| 621 | |||
| 622 | return; |
||
| 623 | } |
||
| 624 | |||
| 625 | $id = $this->manager->getConnection()->fetchColumn( |
||
| 626 | 'SELECT id FROM `s_articles_prices` |
||
| 627 | WHERE `pricegroup` = ? AND `from` = ? AND `to` = ? AND `articleID` = ? AND `articledetailsID` = ?', |
||
| 628 | [$customerGroup->getKey(), 1, 'beliebig', $article->getId(), $detail->getId()] |
||
| 629 | ); |
||
| 630 | |||
| 631 | // todo@sb: test update prices |
||
| 632 | if ($id > 0) { |
||
| 633 | $this->manager->getConnection()->executeQuery( |
||
| 634 | 'UPDATE `s_articles_prices` SET `price` = ?, `baseprice` = ? WHERE `id` = ?', |
||
| 635 | [$product->price, $product->purchasePrice, $id] |
||
| 636 | ); |
||
| 637 | } else { |
||
| 638 | $this->manager->getConnection()->executeQuery( |
||
| 639 | 'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`, `baseprice`) |
||
| 640 | VALUES (?, 1, "beliebig", ?, ?, ?, ?);', |
||
| 641 | [$customerGroup->getKey(), $article->getId(), $detail->getId(), $product->price, $product->purchasePrice] |
||
| 642 | ); |
||
| 643 | } |
||
| 644 | } |
||
| 645 | |||
| 646 | /** |
||
| 647 | * @param ProductModel $article |
||
| 648 | * @param DetailModel $detail |
||
| 649 | * @param array $priceRanges |
||
| 650 | * @param Group $group |
||
| 651 | * @throws \Doctrine\DBAL\ConnectionException |
||
| 652 | * @throws \Exception |
||
| 653 | */ |
||
| 654 | private function setPriceRange(ProductModel $article, DetailModel $detail, array $priceRanges, Group $group) |
||
| 655 | { |
||
| 656 | $this->manager->getConnection()->beginTransaction(); |
||
| 657 | |||
| 658 | try { |
||
| 659 | // We always delete the prices, |
||
| 660 | // because we can not know which record is update |
||
| 661 | $this->manager->getConnection()->executeQuery( |
||
| 662 | 'DELETE FROM `s_articles_prices` WHERE `articleID` = ? AND `articledetailsID` = ?', |
||
| 663 | [$article->getId(), $detail->getId()] |
||
| 664 | ); |
||
| 665 | |||
| 666 | /** @var PriceRange $priceRange */ |
||
| 667 | foreach ($priceRanges as $priceRange) { |
||
| 668 | $priceTo = $priceRange->to == PriceRange::ANY ? 'beliebig' : $priceRange->to; |
||
| 669 | |||
| 670 | //todo: maybe batch insert if possible? |
||
| 671 | $this->manager->getConnection()->executeQuery( |
||
| 672 | 'INSERT INTO `s_articles_prices`(`pricegroup`, `from`, `to`, `articleID`, `articledetailsID`, `price`) |
||
| 673 | VALUES (?, ?, ?, ?, ?, ?);', |
||
| 674 | [ |
||
| 675 | $group->getKey(), |
||
| 676 | $priceRange->from, |
||
| 677 | $priceTo, |
||
| 678 | $article->getId(), |
||
| 679 | $detail->getId(), |
||
| 680 | $priceRange->price |
||
| 681 | ] |
||
| 682 | ); |
||
| 683 | } |
||
| 684 | $this->manager->getConnection()->commit(); |
||
| 685 | } catch (\Exception $e) { |
||
| 686 | $this->manager->getConnection()->rollBack(); |
||
| 687 | throw new \Exception($e->getMessage()); |
||
| 688 | } |
||
| 689 | } |
||
| 690 | |||
| 691 | /** |
||
| 692 | * Adds translation record for given article |
||
| 693 | * |
||
| 694 | * @param ProductModel $article |
||
| 695 | * @param Product $sdkProduct |
||
| 696 | */ |
||
| 697 | private function addArticleTranslations(ProductModel $article, Product $sdkProduct) |
||
| 698 | { |
||
| 699 | /** @var \Shopware\Connect\Struct\Translation $translation */ |
||
| 700 | foreach ($sdkProduct->translations as $key => $translation) { |
||
| 701 | /** @var \Shopware\Models\Shop\Locale $locale */ |
||
| 702 | $locale = $this->getLocaleRepository()->findOneBy(['locale' => LocaleMapper::getShopwareLocale($key)]); |
||
| 703 | /** @var \Shopware\Models\Shop\Shop $shop */ |
||
| 704 | $shop = $this->getShopRepository()->findOneBy(['locale' => $locale]); |
||
| 705 | if (!$shop) { |
||
| 706 | continue; |
||
| 707 | } |
||
| 708 | |||
| 709 | $this->productTranslationsGateway->addArticleTranslation($translation, $article->getId(), $shop->getId()); |
||
| 710 | } |
||
| 711 | } |
||
| 712 | |||
| 713 | /** |
||
| 714 | * dsadsa |
||
| 715 | * @return \Shopware\Components\Model\ModelRepository |
||
| 716 | */ |
||
| 717 | private function getLocaleRepository() |
||
| 725 | |||
| 726 | private function getShopRepository() |
||
| 734 | |||
| 735 | /** |
||
| 736 | * Delete product or product variant with given shopId and sourceId. |
||
| 737 | * |
||
| 738 | * Only the combination of both identifies a product uniquely. Do NOT |
||
| 739 | * delete products just by their sourceId. |
||
| 740 | * |
||
| 741 | * You might receive delete requests for products, which are not available |
||
| 742 | * in your shop. Just ignore them. |
||
| 743 | * |
||
| 744 | * @param string $shopId |
||
| 745 | * @param string $sourceId |
||
| 746 | * @return void |
||
| 747 | */ |
||
| 748 | public function delete($shopId, $sourceId) |
||
| 749 | { |
||
| 750 | $detail = $this->helper->getArticleDetailModelByProduct(new Product([ |
||
| 751 | 'shopId' => $shopId, |
||
| 752 | 'sourceId' => $sourceId, |
||
| 753 | ])); |
||
| 754 | if ($detail === null) { |
||
| 755 | return; |
||
| 756 | } |
||
| 757 | |||
| 758 | |||
| 759 | $this->eventManager->notify( |
||
| 760 | 'Connect_Merchant_Delete_Product_Before', |
||
| 761 | [ |
||
| 762 | 'subject' => $this, |
||
| 763 | 'articleDetail' => $detail |
||
| 764 | ] |
||
| 765 | ); |
||
| 766 | |||
| 767 | |||
| 768 | $article = $detail->getArticle(); |
||
| 769 | $isOnlyOneVariant = false; |
||
| 770 | if (count($article->getDetails()) === 1) { |
||
| 771 | $isOnlyOneVariant = true; |
||
| 772 | } |
||
| 773 | |||
| 774 | // Not sure why, but the Attribute can be NULL |
||
| 775 | $attribute = $this->helper->getConnectAttributeByModel($detail); |
||
| 776 | if ($attribute) { |
||
| 777 | $this->manager->remove($attribute); |
||
| 778 | } |
||
| 779 | |||
| 780 | // if removed variant is main variant |
||
| 781 | // find first variant which is not main and mark it |
||
| 782 | if ($detail->getKind() === 1) { |
||
| 783 | /** @var \Shopware\Models\Article\Detail $variant */ |
||
| 784 | foreach ($article->getDetails() as $variant) { |
||
| 785 | if ($variant->getId() != $detail->getId()) { |
||
| 786 | $variant->setKind(1); |
||
| 787 | $article->setMainDetail($variant); |
||
| 788 | $this->manager->persist($article); |
||
| 789 | $this->manager->persist($variant); |
||
| 790 | break; |
||
| 791 | } |
||
| 792 | } |
||
| 793 | } |
||
| 794 | |||
| 795 | $this->manager->remove($detail); |
||
| 796 | if ($isOnlyOneVariant === true) { |
||
| 797 | $article->getDetails()->clear(); |
||
| 798 | $this->manager->remove($article); |
||
| 799 | } |
||
| 800 | |||
| 801 | $this->manager->flush(); |
||
| 802 | $this->manager->clear(); |
||
| 803 | } |
||
| 804 | |||
| 805 | /** |
||
| 806 | * Get array of update info for the known fields |
||
| 807 | * |
||
| 808 | * @param $model |
||
| 809 | * @param $detail |
||
| 810 | * @param $attribute |
||
| 811 | * @param $product |
||
| 812 | * @return array |
||
| 813 | */ |
||
| 814 | public function getUpdateFields($model, $detail, $attribute, $product) |
||
| 815 | { |
||
| 816 | // This also defines the flags of these fields |
||
| 817 | $fields = $this->helper->getUpdateFlags(); |
||
| 818 | $flagsByName = array_flip($fields); |
||
| 819 | |||
| 820 | $flag = 0; |
||
| 821 | $output = []; |
||
| 822 | foreach ($fields as $key => $field) { |
||
| 823 | // Don't handle the imageInitialImport flag |
||
| 824 | if ($field == 'imageInitialImport') { |
||
| 825 | continue; |
||
| 826 | } |
||
| 827 | |||
| 828 | // If this is a new product |
||
| 829 | if (!$model->getId() && $field == 'image' && !$this->config->getConfig('importImagesOnFirstImport', false)) { |
||
| 830 | $output[$field] = false; |
||
| 831 | $flag |= $flagsByName['imageInitialImport']; |
||
| 832 | continue; |
||
| 833 | } |
||
| 834 | |||
| 835 | $updateAllowed = $this->isFieldUpdateAllowed($field, $model, $attribute); |
||
| 836 | $output[$field] = $updateAllowed; |
||
| 837 | if (!$updateAllowed && $this->hasFieldChanged($field, $model, $detail, $product)) { |
||
| 838 | $flag |= $key; |
||
| 839 | } |
||
| 840 | } |
||
| 841 | |||
| 842 | return [$output, $flag]; |
||
| 843 | } |
||
| 844 | |||
| 845 | /** |
||
| 846 | * Determine if a given field has changed |
||
| 847 | * |
||
| 848 | * @param $field |
||
| 849 | * @param ProductModel $model |
||
| 850 | * @param DetailModel $detail |
||
| 851 | * @param Product $product |
||
| 852 | * @return bool |
||
| 853 | */ |
||
| 854 | public function hasFieldChanged($field, ProductModel $model, DetailModel $detail, Product $product) |
||
| 855 | { |
||
| 856 | switch ($field) { |
||
| 857 | case 'shortDescription': |
||
| 858 | return $model->getDescription() != $product->shortDescription; |
||
| 859 | case 'longDescription': |
||
| 860 | return $model->getDescriptionLong() != $product->longDescription; |
||
| 861 | case 'additionalDescription': |
||
| 862 | return $detail->getAttribute()->getConnectProductDescription() != $product->additionalDescription; |
||
| 863 | case 'name': |
||
| 864 | return $model->getName() != $product->title; |
||
| 865 | case 'image': |
||
| 866 | return count($model->getImages()) != count($product->images); |
||
| 867 | case 'price': |
||
| 868 | $prices = $detail->getPrices(); |
||
| 869 | if (empty($prices)) { |
||
| 870 | return true; |
||
| 871 | } |
||
| 872 | $price = $prices->first(); |
||
| 873 | if (!$price) { |
||
| 874 | return true; |
||
| 875 | } |
||
| 876 | |||
| 877 | return $prices->first()->getPrice() != $product->price; |
||
| 878 | } |
||
| 879 | |||
| 880 | throw new \InvalidArgumentException('Unrecognized field'); |
||
| 881 | } |
||
| 882 | |||
| 883 | /** |
||
| 884 | * Helper method to determine if a given $fields may/must be updated. |
||
| 885 | * This method will check for the model->id in order to determine, if it is a new entity. Therefore |
||
| 886 | * this method cannot be used after the model in question was already flushed. |
||
| 887 | * |
||
| 888 | * @param $field |
||
| 889 | * @param $model ProductModel |
||
| 890 | * @param $attribute ConnectAttribute |
||
| 891 | * @throws \RuntimeException |
||
| 892 | * @return bool|null |
||
| 893 | */ |
||
| 894 | public function isFieldUpdateAllowed($field, ProductModel $model, ConnectAttribute $attribute) |
||
| 895 | { |
||
| 896 | $allowed = [ |
||
| 897 | 'ShortDescription', |
||
| 898 | 'LongDescription', |
||
| 899 | 'AdditionalDescription', |
||
| 900 | 'Image', |
||
| 901 | 'Price', |
||
| 902 | 'Name', |
||
| 903 | ]; |
||
| 904 | |||
| 905 | // Always allow updates for new models |
||
| 906 | if (!$model->getId()) { |
||
| 907 | return true; |
||
| 908 | } |
||
| 909 | |||
| 910 | $field = ucfirst($field); |
||
| 911 | $attributeGetter = 'getUpdate' . $field; |
||
| 912 | $configName = 'overwriteProduct' . $field; |
||
| 913 | |||
| 914 | if (!in_array($field, $allowed)) { |
||
| 915 | throw new \RuntimeException("Unknown update field {$field}"); |
||
| 916 | } |
||
| 917 | |||
| 918 | $attributeValue = $attribute->$attributeGetter(); |
||
| 919 | |||
| 920 | |||
| 921 | |||
| 922 | // If the value is 'null' or 'inherit', the behaviour will be inherited from the global configuration |
||
| 923 | // Once we have a supplier based configuration, we need to take it into account here |
||
| 924 | if ($attributeValue == null || $attributeValue == 'inherit') { |
||
| 925 | return $this->config->getConfig($configName, true); |
||
| 926 | } |
||
| 927 | |||
| 928 | return $attributeValue == 'overwrite'; |
||
| 929 | } |
||
| 930 | |||
| 931 | /** |
||
| 932 | * Read product attributes mapping and set to shopware attribute model |
||
| 933 | * |
||
| 934 | * @param AttributeModel $detailAttribute |
||
| 935 | * @param Product $product |
||
| 936 | * @return AttributeModel |
||
| 937 | */ |
||
| 938 | private function applyMarketplaceAttributes(AttributeModel $detailAttribute, Product $product) |
||
| 953 | |||
| 954 | /** |
||
| 955 | * @param $vendor |
||
| 956 | * @return Supplier |
||
| 957 | */ |
||
| 958 | private function createSupplier($vendor) |
||
| 959 | { |
||
| 960 | $supplier = new Supplier(); |
||
| 961 | |||
| 962 | if (is_array($vendor)) { |
||
| 963 | $supplier->setName($vendor['name']); |
||
| 964 | $supplier->setDescription($vendor['description']); |
||
| 965 | if (array_key_exists('url', $vendor) && $vendor['url']) { |
||
| 966 | $supplier->setLink($vendor['url']); |
||
| 967 | } |
||
| 986 | |||
| 987 | /** |
||
| 988 | * Set detail purchase price with plain SQL |
||
| 989 | * Entity usage throws exception when error handlers are disabled |
||
| 990 | * |
||
| 991 | * @param DetailModel $detail |
||
| 992 | * @param float $purchasePrice |
||
| 993 | * @param Group $defaultGroup |
||
| 994 | * @throws \Doctrine\DBAL\DBALException |
||
| 995 | */ |
||
| 996 | private function setPurchasePrice(DetailModel $detail, $purchasePrice, Group $defaultGroup) |
||
| 1024 | |||
| 1025 | public function update($shopId, $sourceId, ProductUpdate $product) |
||
| 1077 | |||
| 1078 | public function changeAvailability($shopId, $sourceId, $availability) |
||
| 1102 | |||
| 1103 | /** |
||
| 1104 | * @inheritDoc |
||
| 1105 | */ |
||
| 1106 | public function makeMainVariant($shopId, $sourceId, $groupId) |
||
| 1139 | } |
||
| 1140 |
This error could be the result of:
1. Missing dependencies
PHP Analyzer uses your
composer.jsonfile (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects thecomposer.jsonto 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
requireorrequire-devsection?2. Missing use statement
PHP does not complain about undefined classes in
ìnstanceofchecks. For example, the following PHP code will work perfectly fine:If you have not tested against this specific condition, such errors might go unnoticed.