Feat: Restructuring MigrateImport, Dispatching a per product job for import
This commit is contained in:
@@ -8,15 +8,16 @@ use Lunar\Models\ProductVariant;
|
|||||||
* Generates a SKU for every ProductVariant missing one — extracted out of
|
* Generates a SKU for every ProductVariant missing one — extracted out of
|
||||||
* Command\BackfillMissingSkusCommand (which becomes a thin CLI wrapper
|
* Command\BackfillMissingSkusCommand (which becomes a thin CLI wrapper
|
||||||
* around this, keeping --dry-run/progress-bar concerns out of the
|
* around this, keeping --dry-run/progress-bar concerns out of the
|
||||||
* reusable logic) so MigrateImport\RunMigrateImportJob can also call it
|
* reusable logic) so MigrateImport\Shopify\Services\ShopifyExportImporter can
|
||||||
* directly, right after a Shopify import, with no CLI concerns at all.
|
* also call it directly, once every product job in its import batch has
|
||||||
|
* finished (see that class's own import()), with no CLI concerns at all.
|
||||||
*
|
*
|
||||||
* Format is "SKU-P{product_id}-V{variant_id}": deterministic and
|
* Format is "SKU-P{product_id}-V{variant_id}": deterministic and
|
||||||
* guaranteed unique without a uniqueness check, since product_id/
|
* guaranteed unique without a uniqueness check, since product_id/
|
||||||
* variant_id already are. Only variants with a null `sku` are touched —
|
* variant_id already are. Only variants with a null `sku` are touched —
|
||||||
* not an importer bug when one shows up after a Shopify import, the
|
* not an importer bug when one shows up after a Shopify import, the
|
||||||
* source CSV rows genuinely had no `Variant SKU` value (see
|
* source CSV rows genuinely had no `Variant SKU` value (see
|
||||||
* MigrateImport\Shopify\ShopifyExportImporter).
|
* MigrateImport\Shopify\Services\ShopifyExportImporter).
|
||||||
*/
|
*/
|
||||||
class SkuBackfillService
|
class SkuBackfillService
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use Modules\Core\Catalog\Services\SkuBackfillService;
|
|||||||
* CLI wrapper (--dry-run, a progress bar) around Catalog\Services\
|
* CLI wrapper (--dry-run, a progress bar) around Catalog\Services\
|
||||||
* SkuBackfillService — see that class's own docblock for the actual
|
* SkuBackfillService — see that class's own docblock for the actual
|
||||||
* backfill logic, also called automatically after a Shopify import (see
|
* backfill logic, also called automatically after a Shopify import (see
|
||||||
* MigrateImport\RunMigrateImportJob).
|
* MigrateImport\Jobs\RunMigrateImportJob).
|
||||||
*/
|
*/
|
||||||
class BackfillMissingSkusCommand extends Command
|
class BackfillMissingSkusCommand extends Command
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ namespace Modules\Core\Command;
|
|||||||
|
|
||||||
use Illuminate\Console\Command;
|
use Illuminate\Console\Command;
|
||||||
use Lunar\Models\Language;
|
use Lunar\Models\Language;
|
||||||
use Modules\Core\MigrateImport\ImportSpec;
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
use Modules\Core\MigrateImport\RunMigrateImportJob;
|
use Modules\Core\MigrateImport\Jobs\RunMigrateImportJob;
|
||||||
|
|
||||||
class MigrateImportCommand extends Command
|
class MigrateImportCommand extends Command
|
||||||
{
|
{
|
||||||
@@ -67,7 +67,7 @@ class MigrateImportCommand extends Command
|
|||||||
// (HTML)/etc. column per row, no per-locale columns at all — so
|
// (HTML)/etc. column per row, no per-locale columns at all — so
|
||||||
// its text is necessarily written in exactly one language, and
|
// its text is necessarily written in exactly one language, and
|
||||||
// there is no reliable way to detect which one from the file
|
// there is no reliable way to detect which one from the file
|
||||||
// itself. Modules\Core\MigrateImport\ImportLocale::code() used to
|
// itself. Modules\Core\MigrateImport\Services\ImportLocale::code() used to
|
||||||
// (as its former name, DefaultLocale, admits) assume it always
|
// (as its former name, DefaultLocale, admits) assume it always
|
||||||
// matched this store's own Lunar\Models\
|
// matched this store's own Lunar\Models\
|
||||||
// Language::getDefault(), which is often wrong (a store's default
|
// Language::getDefault(), which is often wrong (a store's default
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ class WipeCatalogCommand extends Command
|
|||||||
* for the skipped products) whose 'image' ImportMapping rows then
|
* for the skipped products) whose 'image' ImportMapping rows then
|
||||||
* caused a LATER Shopify re-import to silently reuse those now-
|
* caused a LATER Shopify re-import to silently reuse those now-
|
||||||
* orphaned Media objects instead of importing fresh ones — see
|
* orphaned Media objects instead of importing fresh ones — see
|
||||||
* MigrateImport\Shopify\ShopifyExportImporter::resolveOrImportImage()'s
|
* MigrateImport\Shopify\Services\ShopifyExportImporter::resolveOrImportImage()'s
|
||||||
* own docblock for that half of the same incident. Always re-querying
|
* own docblock for that half of the same incident. Always re-querying
|
||||||
* the first N remaining rows (never advancing an id cursor) guarantees
|
* the first N remaining rows (never advancing an id cursor) guarantees
|
||||||
* every product is actually visited exactly once, however many are
|
* every product is actually visited exactly once, however many are
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Core\MigrateImport\Contracts;
|
||||||
|
|
||||||
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
|
|
||||||
|
interface Importer
|
||||||
|
{
|
||||||
|
public function import(ImportSpec $spec): void;
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport;
|
namespace Modules\Core\MigrateImport\DTOs;
|
||||||
|
|
||||||
class ImportSpec
|
class ImportSpec
|
||||||
{
|
{
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport;
|
|
||||||
|
|
||||||
interface Importer
|
|
||||||
{
|
|
||||||
public function import(ImportSpec $spec): void;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Core\MigrateImport\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
|
use Modules\Core\MigrateImport\Services\ImporterFactory;
|
||||||
|
|
||||||
|
class RunMigrateImportJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public readonly ImportSpec $spec,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just hands off to the right Importer and returns — for Shopify,
|
||||||
|
* that importer dispatches a job batch instead of importing inline
|
||||||
|
* (see Shopify\ShopifyExportImporter::import()) and this job's own
|
||||||
|
* work is done the moment that batch is queued, well before the
|
||||||
|
* batch's jobs actually run. The SKU backfill that used to happen
|
||||||
|
* right here, after import() returned, now happens in that batch's
|
||||||
|
* own then() callback instead — running it here would fire before a
|
||||||
|
* single product had actually been imported.
|
||||||
|
*/
|
||||||
|
public function handle(): void
|
||||||
|
{
|
||||||
|
Log::info('Import started', ['source' => $this->spec->source, 'type' => $this->spec->type, 'file' => $this->spec->filePath]);
|
||||||
|
|
||||||
|
$importer = ImporterFactory::make($this->spec);
|
||||||
|
$importer->import($this->spec);
|
||||||
|
|
||||||
|
Log::info('Import job complete', ['source' => $this->spec->source]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
namespace Modules\Core\MigrateImport\JudgeMe\Resolvers;
|
namespace Modules\Core\MigrateImport\JudgeMe\Resolvers;
|
||||||
|
|
||||||
use Lunar\Models\Product;
|
use Lunar\Models\Product;
|
||||||
use Lunar\Models\Url;
|
|
||||||
|
|
||||||
class ProductResolver
|
class ProductResolver
|
||||||
{
|
{
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport\JudgeMe;
|
namespace Modules\Core\MigrateImport\JudgeMe\Services;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
+17
-4
@@ -1,12 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport\JudgeMe;
|
namespace Modules\Core\MigrateImport\JudgeMe\Services;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Modules\Core\MigrateImport\Importer;
|
use Modules\Core\MigrateImport\Contracts\Importer;
|
||||||
use Modules\Core\MigrateImport\ImportSpec;
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
use Modules\Core\MigrateImport\JudgeMe\Resolvers\ProductResolver;
|
use Modules\Core\MigrateImport\JudgeMe\Resolvers\ProductResolver;
|
||||||
use Modules\Core\MigrateImport\Models\ImportMapping;
|
use Modules\Core\MigrateImport\Models\ImportMapping;
|
||||||
use Modules\Core\Review\Models\ProductReview;
|
use Modules\Core\Review\Models\ProductReview;
|
||||||
@@ -22,9 +22,22 @@ class JudgeMeExportImporter implements Importer
|
|||||||
|
|
||||||
public function import(ImportSpec $spec): void
|
public function import(ImportSpec $spec): void
|
||||||
{
|
{
|
||||||
foreach ($this->csvReader->read($spec->filePath) as $row) {
|
$rows = $this->csvReader->read($spec->filePath);
|
||||||
|
$total = count($rows);
|
||||||
|
$imported = 0;
|
||||||
|
|
||||||
|
Log::info('JudgeMe import: starting', ['total' => $total]);
|
||||||
|
|
||||||
|
foreach ($rows as $row) {
|
||||||
$this->importReview($row);
|
$this->importReview($row);
|
||||||
|
$imported++;
|
||||||
|
|
||||||
|
if ($imported % 100 === 0) {
|
||||||
|
Log::info('JudgeMe import: progress', ['imported' => $imported, 'total' => $total]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('JudgeMe import: finished', ['imported' => $imported, 'total' => $total]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function importReview(array $row): void
|
private function importReview(array $row): void
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport;
|
|
||||||
|
|
||||||
use Illuminate\Bus\Queueable;
|
|
||||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
||||||
use Illuminate\Foundation\Bus\Dispatchable;
|
|
||||||
use Illuminate\Queue\InteractsWithQueue;
|
|
||||||
use Illuminate\Queue\SerializesModels;
|
|
||||||
use Modules\Core\Catalog\Services\SkuBackfillService;
|
|
||||||
|
|
||||||
class RunMigrateImportJob implements ShouldQueue
|
|
||||||
{
|
|
||||||
use Dispatchable;
|
|
||||||
use InteractsWithQueue;
|
|
||||||
use Queueable;
|
|
||||||
use SerializesModels;
|
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public readonly ImportSpec $spec,
|
|
||||||
) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public function handle(SkuBackfillService $skuBackfill): void
|
|
||||||
{
|
|
||||||
$importer = ImporterFactory::make($this->spec);
|
|
||||||
$importer->import($this->spec);
|
|
||||||
|
|
||||||
// Only Shopify's importer creates ProductVariant rows at all (see
|
|
||||||
// Shopify\ShopifyExportImporter) — JudgeMe never touches products,
|
|
||||||
// so running this for that source would just be a guaranteed
|
|
||||||
// no-op query every time. Source CSV rows genuinely can have no
|
|
||||||
// `Variant SKU` value; see SkuBackfillService's own docblock for
|
|
||||||
// why that's synthesized rather than treated as an importer bug.
|
|
||||||
if ($this->spec->source === 'shopify') {
|
|
||||||
$skuBackfill->backfill();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport;
|
namespace Modules\Core\MigrateImport\Services;
|
||||||
|
|
||||||
use Lunar\Models\Language;
|
use Lunar\Models\Language;
|
||||||
|
|
||||||
+5
-3
@@ -1,10 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport;
|
namespace Modules\Core\MigrateImport\Services;
|
||||||
|
|
||||||
use InvalidArgumentException;
|
use InvalidArgumentException;
|
||||||
use Modules\Core\MigrateImport\JudgeMe\JudgeMeExportImporter;
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
use Modules\Core\MigrateImport\Shopify\ShopifyExportImporter;
|
use Modules\Core\MigrateImport\Contracts\Importer;
|
||||||
|
use Modules\Core\MigrateImport\JudgeMe\Services\JudgeMeExportImporter;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\Services\ShopifyExportImporter;
|
||||||
|
|
||||||
class ImporterFactory
|
class ImporterFactory
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport\Shopify;
|
namespace Modules\Core\MigrateImport\Shopify\DTOs;
|
||||||
|
|
||||||
class ProductGroup
|
class ProductGroup
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\Core\MigrateImport\Shopify\Jobs;
|
||||||
|
|
||||||
|
use Illuminate\Bus\Batchable;
|
||||||
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
|
use Illuminate\Foundation\Bus\Dispatchable;
|
||||||
|
use Illuminate\Queue\InteractsWithQueue;
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
use Lunar\Models\CollectionGroup;
|
||||||
|
use Lunar\Models\Currency;
|
||||||
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\DTOs\ProductGroup;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\Services\ShopifyExportImporter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One product per job — ShopifyExportImporter::import() used to loop over
|
||||||
|
* every ProductGroup inline, inside RunMigrateImportJob's own single
|
||||||
|
* process. A large export (hundreds of products, each with variants,
|
||||||
|
* media downloads and conversions) grew that one process's memory past
|
||||||
|
* queue:work's --memory limit (see docker-compose.yml), which kills the
|
||||||
|
* worker mid-job; the container then restarts and the WHOLE import
|
||||||
|
* re-runs from row one, never actually finishing. Splitting into one
|
||||||
|
* job per product means memory resets between jobs (a fresh worker
|
||||||
|
* process picks up each one), and a restart only repeats whichever
|
||||||
|
* single product was in flight — ImportMapping's own per-handle
|
||||||
|
* resolve()/record() already makes re-importing the same product cheap
|
||||||
|
* and idempotent.
|
||||||
|
*/
|
||||||
|
class ImportShopifyProductJob implements ShouldQueue
|
||||||
|
{
|
||||||
|
use Batchable;
|
||||||
|
use Dispatchable;
|
||||||
|
use InteractsWithQueue;
|
||||||
|
use Queueable;
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public int $tries = 3;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private readonly ProductGroup $group,
|
||||||
|
private readonly string $imagesPath,
|
||||||
|
private readonly CollectionGroup $collectionGroup,
|
||||||
|
private readonly Currency $currency,
|
||||||
|
private readonly ?string $locale,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ShopifyExportImporter $importer): void
|
||||||
|
{
|
||||||
|
if ($this->locale !== null) {
|
||||||
|
ImportLocale::set($this->locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$importer->importProduct($this->group, $this->imagesPath, $this->collectionGroup, $this->currency);
|
||||||
|
} finally {
|
||||||
|
// A queue worker process outlives a single job — this must
|
||||||
|
// not leak into whichever product the same worker picks up
|
||||||
|
// next.
|
||||||
|
ImportLocale::reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ use Lunar\FieldTypes\TranslatedText;
|
|||||||
use Lunar\Models\Collection;
|
use Lunar\Models\Collection;
|
||||||
use Lunar\Models\CollectionGroup;
|
use Lunar\Models\CollectionGroup;
|
||||||
use Lunar\Models\Product;
|
use Lunar\Models\Product;
|
||||||
use Modules\Core\MigrateImport\ImportLocale;
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
|
|
||||||
class CollectionResolver
|
class CollectionResolver
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Lunar\Models\Attribute;
|
|||||||
use Lunar\Models\AttributeGroup;
|
use Lunar\Models\AttributeGroup;
|
||||||
use Lunar\Models\Product;
|
use Lunar\Models\Product;
|
||||||
use Lunar\Models\ProductType;
|
use Lunar\Models\ProductType;
|
||||||
use Modules\Core\MigrateImport\ImportLocale;
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
|
|
||||||
class ImportAttributeResolver
|
class ImportAttributeResolver
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use Lunar\FieldTypes\Number;
|
|||||||
use Lunar\FieldTypes\Text;
|
use Lunar\FieldTypes\Text;
|
||||||
use Lunar\FieldTypes\TranslatedText;
|
use Lunar\FieldTypes\TranslatedText;
|
||||||
use Lunar\Models\ProductType;
|
use Lunar\Models\ProductType;
|
||||||
use Modules\Core\MigrateImport\ImportLocale;
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
|
|
||||||
class ProductAttributeResolver
|
class ProductAttributeResolver
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ namespace Modules\Core\MigrateImport\Shopify\Resolvers;
|
|||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Lunar\Models\ProductOption;
|
use Lunar\Models\ProductOption;
|
||||||
use Lunar\Models\ProductOptionValue;
|
use Lunar\Models\ProductOptionValue;
|
||||||
use Modules\Core\MigrateImport\ImportLocale;
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
|
|
||||||
class ProductOptionResolver
|
class ProductOptionResolver
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport\Shopify;
|
namespace Modules\Core\MigrateImport\Shopify\Services;
|
||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\DTOs\ProductGroup;
|
||||||
|
|
||||||
class ShopifyCsvReader
|
class ShopifyCsvReader
|
||||||
{
|
{
|
||||||
+48
-24
@@ -1,9 +1,10 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Modules\Core\MigrateImport\Shopify;
|
namespace Modules\Core\MigrateImport\Shopify\Services;
|
||||||
|
|
||||||
use Lunar\Models\TaxClass;
|
use Lunar\Models\TaxClass;
|
||||||
use Lunar\Models\ProductOption;
|
use Lunar\Models\ProductOption;
|
||||||
|
use Illuminate\Support\Facades\Bus;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
use Lunar\Models\Collection;
|
use Lunar\Models\Collection;
|
||||||
use Lunar\Models\CollectionGroup;
|
use Lunar\Models\CollectionGroup;
|
||||||
@@ -12,10 +13,13 @@ use Lunar\Models\Language;
|
|||||||
use Lunar\Models\Product;
|
use Lunar\Models\Product;
|
||||||
use Lunar\Models\ProductVariant;
|
use Lunar\Models\ProductVariant;
|
||||||
use Lunar\Models\Url;
|
use Lunar\Models\Url;
|
||||||
use Modules\Core\MigrateImport\ImportLocale;
|
use Modules\Core\Catalog\Services\SkuBackfillService;
|
||||||
use Modules\Core\MigrateImport\ImportSpec;
|
use Modules\Core\MigrateImport\Services\ImportLocale;
|
||||||
use Modules\Core\MigrateImport\Importer;
|
use Modules\Core\MigrateImport\DTOs\ImportSpec;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\DTOs\ProductGroup;
|
||||||
|
use Modules\Core\MigrateImport\Contracts\Importer;
|
||||||
use Modules\Core\MigrateImport\Models\ImportMapping;
|
use Modules\Core\MigrateImport\Models\ImportMapping;
|
||||||
|
use Modules\Core\MigrateImport\Shopify\Jobs\ImportShopifyProductJob;
|
||||||
use Modules\Core\MigrateImport\Shopify\Resolvers\AssetResolver;
|
use Modules\Core\MigrateImport\Shopify\Resolvers\AssetResolver;
|
||||||
use Modules\Core\MigrateImport\Shopify\Resolvers\BrandResolver;
|
use Modules\Core\MigrateImport\Shopify\Resolvers\BrandResolver;
|
||||||
use Modules\Core\MigrateImport\Shopify\Resolvers\CollectionResolver;
|
use Modules\Core\MigrateImport\Shopify\Resolvers\CollectionResolver;
|
||||||
@@ -47,32 +51,52 @@ class ShopifyExportImporter implements Importer
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dispatches one ImportShopifyProductJob per ProductGroup instead of
|
||||||
|
* importing them inline — see that job's own docblock for why (a
|
||||||
|
* single process holding every group in memory for the whole run
|
||||||
|
* kept exceeding queue:work's --memory limit on a large export,
|
||||||
|
* which kills the worker mid-run and restarts the entire import from
|
||||||
|
* scratch). Bus::batch()'s then() is what SkuBackfillService used to
|
||||||
|
* run right after this loop — now deferred until every product job
|
||||||
|
* in the batch has actually finished, since dispatching a batch
|
||||||
|
* itself returns immediately.
|
||||||
|
*/
|
||||||
public function import(ImportSpec $spec): void
|
public function import(ImportSpec $spec): void
|
||||||
{
|
{
|
||||||
if ($spec->locale !== null) {
|
$groups = $this->csvReader->read($spec->filePath);
|
||||||
ImportLocale::set($spec->locale);
|
$total = count($groups);
|
||||||
}
|
$imagesPath = dirname($spec->filePath).'/files';
|
||||||
|
$collectionGroup = CollectionGroup::firstOrCreate(
|
||||||
|
['handle' => 'shopify'],
|
||||||
|
['name' => 'Shopify'],
|
||||||
|
);
|
||||||
|
$currency = Currency::getDefault();
|
||||||
|
|
||||||
try {
|
Log::info('Shopify import: dispatching product jobs', ['total' => $total]);
|
||||||
$groups = $this->csvReader->read($spec->filePath);
|
|
||||||
$imagesPath = dirname($spec->filePath).'/files';
|
|
||||||
$collectionGroup = CollectionGroup::firstOrCreate(
|
|
||||||
['handle' => 'shopify'],
|
|
||||||
['name' => 'Shopify'],
|
|
||||||
);
|
|
||||||
$currency = Currency::getDefault();
|
|
||||||
|
|
||||||
foreach ($groups as $group) {
|
$jobs = collect($groups)->map(fn (ProductGroup $group) => new ImportShopifyProductJob(
|
||||||
$this->importProduct($group, $imagesPath, $collectionGroup, $currency);
|
$group,
|
||||||
}
|
$imagesPath,
|
||||||
} finally {
|
$collectionGroup,
|
||||||
// A queue worker process outlives a single import run — this
|
$currency,
|
||||||
// must not leak into whatever's imported next.
|
$spec->locale,
|
||||||
ImportLocale::reset();
|
))->all();
|
||||||
}
|
|
||||||
|
Bus::batch($jobs)
|
||||||
|
->name("Shopify import: {$spec->filePath}")
|
||||||
|
->then(function () use ($total) {
|
||||||
|
Log::info('Shopify import: all product jobs finished, backfilling missing SKUs', ['total' => $total]);
|
||||||
|
app(SkuBackfillService::class)->backfill();
|
||||||
|
Log::info('Shopify import: finished', ['total' => $total]);
|
||||||
|
})
|
||||||
|
->catch(function ($batch, $e) {
|
||||||
|
Log::error('Shopify import: batch failed', ['error' => $e->getMessage()]);
|
||||||
|
})
|
||||||
|
->dispatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function importProduct(
|
public function importProduct(
|
||||||
ProductGroup $group,
|
ProductGroup $group,
|
||||||
string $imagesPath,
|
string $imagesPath,
|
||||||
CollectionGroup $collectionGroup,
|
CollectionGroup $collectionGroup,
|
||||||
Reference in New Issue
Block a user