diff --git a/src/Command/DataExportCommand.php b/src/Command/DataExportCommand.php index 591a4808..a19eb602 100644 --- a/src/Command/DataExportCommand.php +++ b/src/Command/DataExportCommand.php @@ -154,6 +154,9 @@ public function execute(InputInterface $input, OutputInterface $output): int unlink(stream_get_meta_data($this->file)['uri']); } + $peakMemory = memory_get_peak_usage(true) / 1024 / 1024; + $output->writeln(sprintf('Peak Memory Usage: %.2f MB', $peakMemory)); + return 0; } @@ -228,3 +231,4 @@ private function createFile(string $exportType, string $salesChannelId) return $this->file; } } +// diff --git a/src/Export/Data/Entity/ProductEntity.php b/src/Export/Data/Entity/ProductEntity.php index ab4a3dd1..9e2373f9 100644 --- a/src/Export/Data/Entity/ProductEntity.php +++ b/src/Export/Data/Entity/ProductEntity.php @@ -96,10 +96,12 @@ public function toArray(): array { $cachedProductFieldNames = array_map(fn (FieldInterface $field) => $field->getName(), iterator_to_array($this->cachedProductFields)); $fields = array_filter($this->productFields, fn (FieldInterface $productField) => !in_array($productField->getName(), $cachedProductFieldNames)); - $isVariant = $this->product->getId() !== $this->product->getParentId() && isset($this->parent); - $defaultFields = [ + $isVariant = $this->product->getParentId() !== null; + $resolvedParent = $this->parent ?? $this->product->getParent(); + + $defaultFields = [ 'ProductNumber' => $this->product->getProductNumber(), - 'Master' => $isVariant ? $this->parent->getProductNumber() : $this->product->getProductNumber(), + 'Master' => ($isVariant && $resolvedParent) ? $resolvedParent->getProductNumber() : $this->product->getProductNumber(), 'Name' => (string) $this->product->getTranslation('name'), 'FilterAttributes' => $this->getFilterAttributes(), 'CustomFields' => $this->getCustomFields(), @@ -109,9 +111,10 @@ public function toArray(): array $fields, fn (array $fields, FieldInterface $field): array => array_merge( $fields, - [$field->getName() => ($this->getAdditionalCache($field->getName()) ?? $field->getValue($isVariant ? $this->parent : $this->product))] + [$field->getName() => ($this->getAdditionalCache($field->getName()) ?? $field->getValue($isVariant && $resolvedParent ? $resolvedParent : $this->product))] ), $defaultFields ); } } +// diff --git a/src/Export/Data/Factory/ProductEntityFactory.php b/src/Export/Data/Factory/ProductEntityFactory.php index 1528f2db..44a01079 100644 --- a/src/Export/Data/Factory/ProductEntityFactory.php +++ b/src/Export/Data/Factory/ProductEntityFactory.php @@ -37,22 +37,24 @@ public function handle(Entity $entity): bool } /** - * @param Entity $entity - * @param string $producedType - * - * @return ProductEntity[]|iterable - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * @param SalesChannelProductEntity $entity */ public function createEntities(Entity $entity, string $producedType = ProductEntity::class): iterable { - // @todo use spread operator? $fields = array_merge($this->fieldsProvider->getFields($producedType), $this->currencyFieldsProvider->getCurrencyFields()); - $parent = new $producedType($entity, new \ArrayIterator($fields), new \ArrayIterator()); - if ($entity->getChildCount()) { - yield from $entity->getChildren()->map(fn ( - SalesChannelProductEntity $child) => new VariantEntity($child, $parent->toArray(), $this->propertyFormatter, iterator_to_array($this->variantFields))); + + if ($entity->getParentId() !== null) { + $pseudoParent = new $producedType($entity, new \ArrayIterator($fields), new \ArrayIterator()); + + yield new VariantEntity( + $entity, + $pseudoParent->toArray(), + $this->propertyFormatter, + iterator_to_array($this->variantFields) + ); + } else { + yield new $producedType($entity, new \ArrayIterator($fields), new \ArrayIterator()); } - yield $parent; } } +// diff --git a/src/Export/ExportProducts.php b/src/Export/ExportProducts.php index 974e7551..0cb70697 100644 --- a/src/Export/ExportProducts.php +++ b/src/Export/ExportProducts.php @@ -6,7 +6,6 @@ use Omikron\FactFinder\Shopware6\Export\Data\Entity\ProductEntity as ExportProductEntity; use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria; -use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter; use Shopware\Core\System\SalesChannel\Entity\SalesChannelRepository; use Shopware\Core\System\SalesChannel\SalesChannelContext; @@ -25,12 +24,59 @@ public function __construct(SalesChannelRepository $productRepository, array $cu public function getByContext(SalesChannelContext $context, int $batchSize = 100): iterable { - $criteria = $this->getCriteria($batchSize); - $products = $this->productRepository->search($criteria, $context); - while ($products->count()) { - yield from $products; - $criteria->setOffset($criteria->getOffset() + $criteria->getLimit()); + $offset = 0; + + while (true) { + $criteria = $this->getCriteria($batchSize, $offset); $products = $this->productRepository->search($criteria, $context); + + if ($products->count() === 0) { + break; + } + + $parentIds = []; + // 1. Zbieramy unikalne ID rodziców z bieżącej paczki + foreach ($products->getElements() as $product) { + if ($product->getParentId() !== null) { + $parentIds[$product->getParentId()] = true; + } + } + + $parents = []; + if (!empty($parentIds)) { + $parentCriteria = new Criteria(array_keys($parentIds)); + $parentCriteria->addAssociation('categories'); + $parentCriteria->addAssociation('categoriesRo'); + $parents = $this->productRepository->search($parentCriteria, $context)->getElements(); + } + + foreach ($products->getElements() as $product) { + if ($product->getParentId() !== null && isset($parents[$product->getParentId()])) { + $product->setParent($parents[$product->getParentId()]); + } + + yield $product; + } + + $products->clear(); + unset($products, $criteria); + gc_collect_cycles(); + + // --- DEBUG PAMIĘCI START --- + // Przeliczamy bajty na megabajty dla czytelności + $memoryUsageMB = memory_get_usage(true) / 1024 / 1024; + $peakMemoryMB = memory_get_peak_usage(true) / 1024 / 1024; + + echo sprintf( + "[%s] Offset: %d | Memory: %.2f MB | Peak: %.2f MB\n", + date('H:i:s'), + $offset, + $memoryUsageMB, + $peakMemoryMB + ); + // --- DEBUG PAMIĘCI END --- + + $offset += $batchSize; } } @@ -39,25 +85,38 @@ public function getProducedExportEntityType(): string return ExportProductEntity::class; } - private function getCriteria(int $batchSize): Criteria + private function getCriteria(int $batchSize, int $offset): Criteria { $criteria = new Criteria(); $criteria->setLimit($batchSize); + $criteria->setOffset($offset); + $criteria->setTotalCountMode(Criteria::TOTAL_COUNT_MODE_NONE); + $criteria->addAssociation('categories'); $criteria->addAssociation('categoriesRo'); - $criteria->addAssociation('children.options.group'); $criteria->addAssociation('manufacturer'); - $criteria->addAssociation('properties'); - $criteria->addAssociation('customFields'); $criteria->addAssociation('properties.group'); - $criteria->addAssociation('seoUrls'); + $criteria->addAssociation('options.group'); $criteria->addAssociation('media'); - $criteria->addAssociation('children.cover.media'); + $criteria->addAssociation('cover.media'); + $criteria->addAssociation('seoUrls'); + $criteria->addAssociation('customFields'); + + $criteria->addAssociation('configuratorSettings.option.group'); + foreach ($this->customAssociations as $association) { $criteria->addAssociation($association); } - $criteria->addFilter(new EqualsFilter('parentId', null)); + +// $criteria->addFilter(new \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter( +// \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter::CONNECTION_OR, +// [ +// new \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter('parentId', null), +// new \Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter('parent.active', true) +// ] +// )); return $criteria; } } +// diff --git a/src/Export/Feed.php b/src/Export/Feed.php index b9047885..37fad7fa 100644 --- a/src/Export/Feed.php +++ b/src/Export/Feed.php @@ -29,9 +29,16 @@ public function generate(StreamInterface $stream, array $columns): void $stream->addEntity($columns); $emptyRecord = array_combine($columns, array_fill(0, count($columns), '')); + $i = 0; + foreach ($this->getEntities() as $entity) { $entityData = array_merge($emptyRecord, array_intersect_key($entity->toArray(), $emptyRecord)); $stream->addEntity($this->prepare($entityData)); + + unset($entity, $entityData); + if (++$i % 500 === 0) { + gc_collect_cycles(); + } } } @@ -47,3 +54,4 @@ private function prepare(array $data): array return array_map([$this->filter, 'filterValue'], $data); } } +// diff --git a/src/Export/Field/CategoryPath.php b/src/Export/Field/CategoryPath.php index 7f61cbd4..a1e24c6a 100644 --- a/src/Export/Field/CategoryPath.php +++ b/src/Export/Field/CategoryPath.php @@ -61,6 +61,7 @@ private function getCategories(Entity $entity): CategoryCollection return new CategoryCollection([$entity]); } - return $entity->getCategories(); + return $entity->getCategories() ?? new CategoryCollection(); } } +// diff --git a/src/Export/Field/FilterAttributes.php b/src/Export/Field/FilterAttributes.php index 3f977795..5799d209 100644 --- a/src/Export/Field/FilterAttributes.php +++ b/src/Export/Field/FilterAttributes.php @@ -29,16 +29,32 @@ public function getName(): string /** * @param Product $entity - * - * @return string */ public function getValue(Entity $entity): string { - $attributes = $entity->getChildren()->reduce( - fn (array $result, Product $child): array => $result + array_map($this->propertyFormatter, $child->getOptions()->getElements()), - array_map($this->propertyFormatter, $this->applyPropertyGroupsFilter($entity)) - ); - return $attributes ? '|' . implode('|', array_values($attributes)) . '|' : ''; + $properties = $this->applyPropertyGroupsFilter($entity); + $attributes = $properties ? array_map($this->propertyFormatter, $properties) : []; + + if ($entity->getParentId() !== null) { + $options = $entity->getOptions() ? $entity->getOptions()->getElements() : []; + $attributes = array_merge($attributes, array_map($this->propertyFormatter, $options)); + } else { + $configuratorSettings = $entity->getConfiguratorSettings(); + + if ($configuratorSettings) { + $options = []; + + foreach ($configuratorSettings as $setting) { + if ($setting->getOption()) { + $options[] = $setting->getOption(); + } + } + + $attributes = array_merge($attributes, array_map($this->propertyFormatter, $options)); + } + } + + return $attributes ? '|' . implode('|', array_unique(array_values($attributes))) . '|' : ''; } public function getCompatibleEntityTypes(): array @@ -51,10 +67,12 @@ private function applyPropertyGroupsFilter(Product $product): array $disabledProperties = $this->exportSettings->getDisabledPropertyGroups(); if (!$disabledProperties) { - return $product->getProperties()->getElements(); + return $product->getProperties() ? $product->getProperties()->getElements() : []; } + return $product->getProperties() - ->filter(fn (PropertyGroupOptionEntity $option): bool => !in_array($option->getGroupId(), $disabledProperties)) - ->getElements(); + ->filter(fn (PropertyGroupOptionEntity $option): bool => !in_array($option->getGroupId(), $disabledProperties)) + ->getElements(); } } +// diff --git a/src/Export/Stream/ConsoleOutput.php b/src/Export/Stream/ConsoleOutput.php index 6536d05c..e9cf142a 100644 --- a/src/Export/Stream/ConsoleOutput.php +++ b/src/Export/Stream/ConsoleOutput.php @@ -23,7 +23,7 @@ public function addEntity(array $entity): void { $this->fileResource = $this->fileResource ?? new File('php://output', 'w'); ob_start(); - $this->fileResource->fputcsv($entity, $this->delimiter); + $this->fileResource->fputcsv($entity, $this->delimiter, '"', '\\'); $this->output->writeln(rtrim(ob_get_clean())); } } diff --git a/src/Export/Stream/CsvFile.php b/src/Export/Stream/CsvFile.php index fc64b218..d54cc117 100644 --- a/src/Export/Stream/CsvFile.php +++ b/src/Export/Stream/CsvFile.php @@ -23,6 +23,6 @@ public function __construct($fileResource, string $delimiter = ';') public function addEntity(array $entity): void { - fputcsv($this->fileResource, $entity, $this->delimiter); + fputcsv($this->fileResource, $entity, $this->delimiter, '"', '\\'); } } diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index dc677f45..93a37a8e 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -45,7 +45,7 @@ ProductNumber - children.media + cover.media