select( 'variant.id AS variant_id, o2.id AS option_id, (g.title || \' : \' || o3.title) AS title' ) ->from('variant') ->leftJoin('option_to_variant o', 'variant.id = o.variant_id') ->leftJoin('option o2', 'o.option_id = o2.id') ->leftJoin('option_lang o3', 'o2.id = o3.option_id') ->leftJoin("group", 'o2.group_id = "group".id') ->leftJoin('group_lang g', '"group".id = g.group_id') ->where( [ 'g.language_id' => Language::getCurrent()->id, ] ) ->andWhere( [ 'o3.language_id' => Language::getCurrent()->id, ] ) ->andWhere( [ 'product_id' => $productId, ] ); $result = $query->all(); foreach ($result as $value) { if (!isset($this->data[ $value[ 'option_id' ] ])) { $this->data[ $value[ 'option_id' ] ] = $value[ 'title' ]; } if (!isset($this->bitMap[ $value[ 'variant_id' ] ])) { $this->bitMap[ $value[ 'variant_id' ] ] = count($this->bitMap); } if (isset($this->optionVariants[ $value[ 'option_id' ] ])) { $this->optionVariants[ $value[ 'option_id' ] ] += ( 2 ** $this->bitMap[ $value[ 'variant_id' ] ] ); } else { $this->optionVariants[ $value[ 'option_id' ] ] = ( 2 ** $this->bitMap[ $value[ 'variant_id' ] ] ); } } } public function getData(): array { return $this->data; } public function setProductOptions($options) { if (empty($options)) { $this->newProductOptions = []; } else { $this->newProductOptions = $options; } } public function setVariantOptions($variantId, $options) { if (empty($options)) { $this->newVariantOptions[ $variantId ] = []; } else { $this->newVariantOptions[ $variantId ] = $options; } } public function save() { $this->insertData($this->prepareData()); $this->updateVariantOptions(); } public function getProductOptions(): array { $allVariantsMask = ( 2 ** count($this->bitMap) ) - 1; $result = []; foreach ($this->optionVariants as $option_id => $bitmask) { if ($bitmask === $allVariantsMask) { $result[] = $option_id; } } return $result; } public function getVariantOptions(int $variantId): array { if (empty($this->bitMap) or !isset($this->bitMap[ $variantId ])) { return []; } $allVariantsMask = ( 2 ** count($this->bitMap) ) - 1; $variantBit = 2 ** ( $this->bitMap[ $variantId ] ); $result = []; foreach ($this->optionVariants as $option_id => $bitMask) { if (( $bitMask !== $allVariantsMask ) && ( ( $bitMask & $variantBit ) !== 0 )) { $result[] = $option_id; } } return $result; } protected function insertData(array $data): bool { $transaction = \Yii::$app->db->beginTransaction(); try { \Yii::$app->db->createCommand() ->delete( 'option_to_variant', [ 'variant_id' => array_keys($this->newVariantOptions), ] ) ->execute(); \Yii::$app->db->createCommand() ->batchInsert( 'option_to_variant', [ 'variant_id', 'option_id', ], $data ) ->execute(); $transaction->commit(); return true; } catch (\Exception $e) { $transaction->rollBack(); throw $e; } catch (\Throwable $e) { $transaction->rollBack(); throw $e; } } protected function prepareData(): array { $variantToOptionInsert = []; foreach ($this->newVariantOptions as $variant_id => $options) { foreach ($options as $option) { $variantToOptionInsert[] = [ $variant_id, $option, ]; } foreach ($this->newProductOptions as $option) { $variantToOptionInsert[] = [ $variant_id, $option, ]; } } return $variantToOptionInsert; } protected function updateVariantOptions() { $query = ( new Query() )->select('*') ->from('option_to_variant') ->leftJoin('option o', 'option_to_variant.option_id = o.id') ->where( [ 'variant_id' => array_keys($this->newVariantOptions), ] ) ->orderBy('variant_id'); $rows = $query->all(); $prev = null; $collection = []; foreach ($rows as $row) { $current = $row[ 'variant_id' ]; if (( $prev !== null ) && ( $prev !== $current )) { $this->flushCollection($collection, $prev); $collection = []; } $collection[ $row[ 'group_id' ] ][] = $row[ 'id' ]; $prev = $current; } $this->flushCollection($collection, $prev); $this->flushQuery(); } protected function flushCollection(array $collection, $prev) { $arrays = []; $first = true; foreach ($collection as $group) { if ($first) { foreach ($group as $option) { $arrays[] = [ $option ]; } $first = false; } else { $newArrays = []; foreach ($arrays as $a) { foreach ($group as $b) { $newChunk = array_merge($a, [ $b ]); $newArrays[] = $newChunk; } } $arrays = $newArrays; } } foreach ($arrays as $array) { $this->toInsert[] = [ $prev, 'array[' . implode(',', $array) . ']', ]; } } protected function flushQuery() { $values = []; foreach ($this->toInsert as $value) { $values[] = '(' . implode(',', $value) . ')'; } $insertString = implode(',', $values); try { \Yii::$app->db->createCommand() ->delete( 'variant_options', [ 'variant_id' => array_keys($this->newVariantOptions), ] ) ->execute(); $command = \Yii::$app->db->createCommand( 'INSERT INTO "variant_options" ("variant_id", "options") VALUES ' . $insertString ); $command->execute(); } catch (Exception $exception) { \Yii::error('Variant options query failed'); } } }