Magento 2 Documentation  2.3
Documentation for Magento 2 CMS v2.3 (December 2018)
CustomOptionPriceModifier.php
Go to the documentation of this file.
1 <?php
6 declare(strict_types=1);
7 
9 
13 
18 {
22  private $resource;
23 
27  private $metadataPool;
28 
32  private $columnValueExpressionFactory;
33 
37  private $dataHelper;
38 
42  private $connectionName;
43 
47  private $isPriceGlobalFlag;
48 
52  private $connection;
53 
57  private $tableStrategy;
58 
67  public function __construct(
68  \Magento\Framework\App\ResourceConnection $resource,
69  \Magento\Framework\EntityManager\MetadataPool $metadataPool,
70  \Magento\Framework\DB\Sql\ColumnValueExpressionFactory $columnValueExpressionFactory,
71  \Magento\Catalog\Helper\Data $dataHelper,
72  \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy,
73  $connectionName = 'indexer'
74  ) {
75  $this->resource = $resource;
76  $this->metadataPool = $metadataPool;
77  $this->connectionName = $connectionName;
78  $this->columnValueExpressionFactory = $columnValueExpressionFactory;
79  $this->dataHelper = $dataHelper;
80  $this->tableStrategy = $tableStrategy;
81  }
82 
92  public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void
93  {
94  // no need to run all queries if current products have no custom options
95  if (!$this->checkIfCustomOptionsExist($priceTable)) {
96  return;
97  }
98 
99  $connection = $this->getConnection();
100  $finalPriceTable = $priceTable->getTableName();
101 
102  $coaTable = $this->getCustomOptionAggregateTable();
103  $this->prepareCustomOptionAggregateTable();
104 
105  $copTable = $this->getCustomOptionPriceTable();
106  $this->prepareCustomOptionPriceTable();
107 
108  $select = $this->getSelectForOptionsWithMultipleValues($finalPriceTable);
109  $query = $select->insertFromSelect($coaTable);
110  $connection->query($query);
111 
112  $select = $this->getSelectForOptionsWithOneValue($finalPriceTable);
113  $query = $select->insertFromSelect($coaTable);
114  $connection->query($query);
115 
116  $select = $this->getSelectAggregated($coaTable);
117  $query = $select->insertFromSelect($copTable);
118  $connection->query($query);
119 
120  // update tmp price index with prices from custom options (from previous aggregated table)
121  $select = $this->getSelectForUpdate($copTable);
122  $query = $select->crossUpdateFromSelect(['i' => $finalPriceTable]);
123  $connection->query($query);
124 
125  $connection->delete($coaTable);
126  $connection->delete($copTable);
127  }
128 
134  private function checkIfCustomOptionsExist(IndexTableStructure $priceTable): bool
135  {
136  $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
137 
138  $select = $this->getConnection()
139  ->select()
140  ->from(
141  ['i' => $priceTable->getTableName()],
142  ['entity_id']
143  )->join(
144  ['e' => $this->getTable('catalog_product_entity')],
145  'e.entity_id = i.entity_id',
146  []
147  )->join(
148  ['o' => $this->getTable('catalog_product_option')],
149  'o.product_id = e.' . $metadata->getLinkField(),
150  ['option_id']
151  );
152 
153  return !empty($this->getConnection()->fetchRow($select));
154  }
155 
159  private function getConnection()
160  {
161  if (null === $this->connection) {
162  $this->connection = $this->resource->getConnection($this->connectionName);
163  }
164 
165  return $this->connection;
166  }
167 
175  private function getSelectForOptionsWithMultipleValues(string $sourceTable): Select
176  {
177  $connection = $this->resource->getConnection($this->connectionName);
178  $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
179 
180  $select = $connection->select()
181  ->from(
182  ['i' => $sourceTable],
183  ['entity_id', 'customer_group_id', 'website_id']
184  )->join(
185  ['e' => $this->getTable('catalog_product_entity')],
186  'e.entity_id = i.entity_id',
187  []
188  )->join(
189  ['cwd' => $this->getTable('catalog_product_index_website')],
190  'i.website_id = cwd.website_id',
191  []
192  )->join(
193  ['o' => $this->getTable('catalog_product_option')],
194  'o.product_id = e.' . $metadata->getLinkField(),
195  ['option_id']
196  )->join(
197  ['ot' => $this->getTable('catalog_product_option_type_value')],
198  'ot.option_id = o.option_id',
199  []
200  )->join(
201  ['otpd' => $this->getTable('catalog_product_option_type_price')],
202  'otpd.option_type_id = ot.option_type_id AND otpd.store_id = 0',
203  []
204  )->group(
205  ['i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id']
206  );
207 
208  if ($this->isPriceGlobal()) {
209  $optPriceType = 'otpd.price_type';
210  $optPriceValue = 'otpd.price';
211  } else {
212  $select->joinLeft(
213  ['otps' => $this->getTable('catalog_product_option_type_price')],
214  'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cwd.default_store_id',
215  []
216  );
217 
218  $optPriceType = $connection->getCheckSql(
219  'otps.option_type_price_id > 0',
220  'otps.price_type',
221  'otpd.price_type'
222  );
223  $optPriceValue = $connection->getCheckSql('otps.option_type_price_id > 0', 'otps.price', 'otpd.price');
224  }
225 
226  $minPriceRound = $this->columnValueExpressionFactory
227  ->create([
228  'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
229  ]);
230  $minPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound);
231  $minPriceMin = $this->columnValueExpressionFactory
232  ->create([
233  'expression' => "MIN({$minPriceExpr})"
234  ]);
235  $minPrice = $connection->getCheckSql("MIN(o.is_require) = 1", $minPriceMin, '0');
236 
237  $tierPriceRound = $this->columnValueExpressionFactory
238  ->create([
239  'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)"
240  ]);
241  $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound);
242  $tierPriceMin = $this->columnValueExpressionFactory
243  ->create([
244  'expression' => "MIN({$tierPriceExpr})"
245  ]);
246  $tierPriceValue = $connection->getCheckSql("MIN(o.is_require) > 0", $tierPriceMin, 0);
247  $tierPrice = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", $tierPriceValue, "NULL");
248 
249  $maxPriceRound = $this->columnValueExpressionFactory
250  ->create([
251  'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
252  ]);
253  $maxPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $maxPriceRound);
254  $maxPrice = $connection->getCheckSql(
255  "(MIN(o.type)='radio' OR MIN(o.type)='drop_down')",
256  "MAX({$maxPriceExpr})",
257  "SUM({$maxPriceExpr})"
258  );
259 
260  $select->columns(
261  [
262  'min_price' => $minPrice,
263  'max_price' => $maxPrice,
264  'tier_price' => $tierPrice,
265  ]
266  );
267 
268  return $select;
269  }
270 
278  private function getSelectForOptionsWithOneValue(string $sourceTable): Select
279  {
280  $connection = $this->resource->getConnection($this->connectionName);
281  $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
282 
283  $select = $connection->select()
284  ->from(
285  ['i' => $sourceTable],
286  ['entity_id', 'customer_group_id', 'website_id']
287  )->join(
288  ['e' => $this->getTable('catalog_product_entity')],
289  'e.entity_id = i.entity_id',
290  []
291  )->join(
292  ['cwd' => $this->getTable('catalog_product_index_website')],
293  'i.website_id = cwd.website_id',
294  []
295  )->join(
296  ['o' => $this->getTable('catalog_product_option')],
297  'o.product_id = e.' . $metadata->getLinkField(),
298  ['option_id']
299  )->join(
300  ['opd' => $this->getTable('catalog_product_option_price')],
301  'opd.option_id = o.option_id AND opd.store_id = 0',
302  []
303  );
304 
305  if ($this->isPriceGlobal()) {
306  $optPriceType = 'opd.price_type';
307  $optPriceValue = 'opd.price';
308  } else {
309  $select->joinLeft(
310  ['ops' => $this->getTable('catalog_product_option_price')],
311  'ops.option_id = opd.option_id AND ops.store_id = cwd.default_store_id',
312  []
313  );
314 
315  $optPriceType = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price_type', 'opd.price_type');
316  $optPriceValue = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price', 'opd.price');
317  }
318 
319  $minPriceRound = $this->columnValueExpressionFactory
320  ->create([
321  'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
322  ]);
323  $priceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound);
324  $minPrice = $connection->getCheckSql("{$priceExpr} > 0 AND o.is_require = 1", $priceExpr, 0);
325 
326  $maxPrice = $priceExpr;
327 
328  $tierPriceRound = $this->columnValueExpressionFactory
329  ->create([
330  'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)"
331  ]);
332  $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound);
333  $tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require = 1", $tierPriceExpr, 0);
334  $tierPrice = $connection->getCheckSql("i.tier_price IS NOT NULL", $tierPriceValue, "NULL");
335 
336  $select->columns(
337  [
338  'min_price' => $minPrice,
339  'max_price' => $maxPrice,
340  'tier_price' => $tierPrice,
341  ]
342  );
343 
344  return $select;
345  }
346 
353  private function getSelectAggregated(string $sourceTable): Select
354  {
355  $connection = $this->resource->getConnection($this->connectionName);
356 
357  $select = $connection->select()
358  ->from(
359  [$sourceTable],
360  [
361  'entity_id',
362  'customer_group_id',
363  'website_id',
364  'min_price' => 'SUM(min_price)',
365  'max_price' => 'SUM(max_price)',
366  'tier_price' => 'SUM(tier_price)',
367  ]
368  )->group(
369  ['entity_id', 'customer_group_id', 'website_id']
370  );
371 
372  return $select;
373  }
374 
379  private function getSelectForUpdate(string $sourceTable): Select
380  {
381  $connection = $this->resource->getConnection($this->connectionName);
382 
383  $select = $connection->select()->join(
384  ['io' => $sourceTable],
385  'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' .
386  ' AND i.website_id = io.website_id',
387  []
388  );
389  $select->columns(
390  [
391  'min_price' => new ColumnValueExpression('i.min_price + io.min_price'),
392  'max_price' => new ColumnValueExpression('i.max_price + io.max_price'),
393  'tier_price' => $connection->getCheckSql(
394  'i.tier_price IS NOT NULL',
395  'i.tier_price + io.tier_price',
396  'NULL'
397  ),
398  ]
399  );
400 
401  return $select;
402  }
403 
408  private function getTable(string $tableName): string
409  {
410  return $this->resource->getTableName($tableName, $this->connectionName);
411  }
412 
416  private function isPriceGlobal(): bool
417  {
418  if ($this->isPriceGlobalFlag === null) {
419  $this->isPriceGlobalFlag = $this->dataHelper->isPriceGlobal();
420  }
421 
422  return $this->isPriceGlobalFlag;
423  }
424 
430  private function getCustomOptionAggregateTable(): string
431  {
432  return $this->tableStrategy->getTableName('catalog_product_index_price_opt_agr');
433  }
434 
440  private function getCustomOptionPriceTable(): string
441  {
442  return $this->tableStrategy->getTableName('catalog_product_index_price_opt');
443  }
444 
450  private function prepareCustomOptionAggregateTable()
451  {
452  $this->getConnection()->delete($this->getCustomOptionAggregateTable());
453  }
454 
460  private function prepareCustomOptionPriceTable()
461  {
462  $this->getConnection()->delete($this->getCustomOptionPriceTable());
463  }
464 }
$tableName
Definition: trigger.php:13
$resource
Definition: bulk.php:12
__construct(\Magento\Framework\App\ResourceConnection $resource, \Magento\Framework\EntityManager\MetadataPool $metadataPool, \Magento\Framework\DB\Sql\ColumnValueExpressionFactory $columnValueExpressionFactory, \Magento\Catalog\Helper\Data $dataHelper, \Magento\Framework\Indexer\Table\StrategyInterface $tableStrategy, $connectionName='indexer')