Feat: Updates to Product Indexer, Shopify Importer, Adding cascade delete on reviews

This commit is contained in:
2026-09-03 12:26:00 +03:00
parent 448da869a9
commit f43f72f633
4 changed files with 123 additions and 10 deletions
@@ -25,6 +25,7 @@ use Modules\Core\MigrateImport\Shopify\Resolvers\ProductOptionResolver;
use Modules\Core\MigrateImport\Shopify\Resolvers\ProductTypeResolver;
use Modules\Core\MigrateImport\Shopify\Resolvers\TagResolver;
use Modules\Core\MigrateImport\Shopify\Resolvers\TaxClassResolver;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
class ShopifyExportImporter implements Importer
{
@@ -113,7 +114,7 @@ class ShopifyExportImporter implements Importer
$options = $this->attachOptions($product, $row);
foreach ($group->variantRows as $index => $variantRow) {
$this->importVariant($product, $group->handle, $index, $variantRow, $taxClass, $currency, $options);
$this->importVariant($product, $group->handle, $index, $variantRow, $taxClass, $currency, $options, $imagesPath);
}
foreach ($group->imageRows as $index => $imageRow) {
@@ -151,6 +152,7 @@ class ShopifyExportImporter implements Importer
TaxClass $taxClass,
Currency $currency,
array $options,
string $imagesPath,
): void {
$externalId = "{$handle}#{$index}";
$existing = ImportMapping::resolve(self::SOURCE, 'variant', $externalId);
@@ -182,6 +184,26 @@ class ShopifyExportImporter implements Importer
: null;
$this->priceResolver->resolve($variant, $currency, $price, $comparePrice);
// Shopify's own "Variant Image" column — the one image a variant picker
// actually swaps to when that variant is selected — distinct from the
// product's full gallery (imageRows below). Often the same file as one
// of the product's own image rows, sometimes not yet imported at all
// (e.g. a variant-only image never listed as its own image row) — either
// way resolveOrImportImage() handles both via the same Image Src dedup
// key, so whichever of importVariant()/importImage() runs first for a
// given src does the actual import.
$variantImageSrc = trim((string) ($row['Variant Image'] ?? ''));
if ($variantImageSrc !== '') {
$media = $this->resolveOrImportImage($product, $handle, $variantImageSrc, 1, $imagesPath);
if ($media) {
$variant->images()->syncWithoutDetaching([
$media->id => ['primary' => true, 'position' => 1],
]);
}
}
}
private function importImage(
@@ -191,29 +213,54 @@ class ShopifyExportImporter implements Importer
array $row,
string $imagesPath,
): void {
$externalId = $row['Image Src'] ?: "{$handle}#image-{$index}";
$position = (int) ($row['Image Position'] ?? $index + 1);
if (ImportMapping::resolve(self::SOURCE, 'image', $externalId)) {
return;
$this->resolveOrImportImage($product, $handle, $row['Image Src'], $position, $imagesPath);
}
/**
* Resolves the Media already imported for $imageSrc (recorded under
* source_type 'image', keyed by Image Src — the same URL Shopify repeats
* across a product's own image rows and any variant's "Variant Image"
* column), importing it via AssetResolver if this is the first time this
* src has been seen. Shared by importImage() (product gallery) and
* importVariant() (variant-specific image) so the same physical file is
* never uploaded to Spatie MediaLibrary twice just because Shopify's flat
* CSV format repeats the URL on multiple rows.
*/
private function resolveOrImportImage(
Product $product,
string $handle,
string $imageSrc,
int $position,
string $imagesPath,
): ?Media {
$externalId = $imageSrc ?: "{$handle}#image-{$position}";
$existing = ImportMapping::resolve(self::SOURCE, 'image', $externalId);
if ($existing instanceof Media) {
return $existing;
}
$localFile = $this->findLocalFile($imagesPath, $row['Image Src']);
$localFile = $this->findLocalFile($imagesPath, $imageSrc);
if ($localFile === null) {
Log::warning('Shopify import: image file not found', [
'handle' => $handle,
'image_src' => $row['Image Src'],
'image_src' => $imageSrc,
]);
return;
return null;
}
$media = $this->assetResolver->resolve($product, $localFile, $position);
if ($media) {
ImportMapping::record(self::SOURCE, 'image', $externalId, $product);
ImportMapping::record(self::SOURCE, 'image', $externalId, $media);
}
return $media;
}
private function findLocalFile(string $imagesPath, string $imageSrc): ?string