Feat: Updating ShopifyExportImporter and WipeCatalogCommand to handle images
This commit is contained in:
@@ -124,17 +124,36 @@ class WipeCatalogCommand extends Command
|
|||||||
* same way regardless. media_variant and product_option_value_
|
* same way regardless. media_variant and product_option_value_
|
||||||
* product_variant DO cascade at the DB level (see their own
|
* product_variant DO cascade at the DB level (see their own
|
||||||
* migrations), so deleting the variant itself is enough for those two.
|
* migrations), so deleting the variant itself is enough for those two.
|
||||||
|
*
|
||||||
|
* Deliberately NOT chunkById() — that re-queries "id > lastSeenId"
|
||||||
|
* every iteration, but deleting rows inside the loop shrinks the
|
||||||
|
* table out from under it: any product whose id fell in a range
|
||||||
|
* chunkById() had already stepped past could be silently skipped and
|
||||||
|
* never actually deleted at all. Caught in practice — the first real
|
||||||
|
* run of this command left orphaned Media rows (Spatie's own
|
||||||
|
* deleteAllMedia(), fired from Product's `deleting` event, never ran
|
||||||
|
* for the skipped products) whose 'image' ImportMapping rows then
|
||||||
|
* caused a LATER Shopify re-import to silently reuse those now-
|
||||||
|
* orphaned Media objects instead of importing fresh ones — see
|
||||||
|
* MigrateImport\Shopify\ShopifyExportImporter::resolveOrImportImage()'s
|
||||||
|
* own docblock for that half of the same incident. Always re-querying
|
||||||
|
* the first N remaining rows (never advancing an id cursor) guarantees
|
||||||
|
* every product is actually visited exactly once, however many are
|
||||||
|
* deleted out from under the query as it goes.
|
||||||
*/
|
*/
|
||||||
private function wipe(): void
|
private function wipe(): void
|
||||||
{
|
{
|
||||||
ImportMapping::where('source_type', 'product')->delete();
|
ImportMapping::whereIn('source_type', ['product', 'variant', 'image'])->delete();
|
||||||
ImportMapping::where('source_type', 'variant')->delete();
|
|
||||||
|
while (true) {
|
||||||
|
$products = Product::with(['variants', 'associations', 'inverseAssociations'])
|
||||||
|
->limit(100)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
if ($products->isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// Model-by-model, not a bulk query — see class docblock on why
|
|
||||||
// this must go through Eloquent for Spatie's media cleanup to
|
|
||||||
// fire on both Product and ProductVariant.
|
|
||||||
Product::with(['variants', 'associations', 'inverseAssociations'])
|
|
||||||
->chunkById(100, function ($products) {
|
|
||||||
foreach ($products as $product) {
|
foreach ($products as $product) {
|
||||||
$product->associations()->delete();
|
$product->associations()->delete();
|
||||||
$product->inverseAssociations()->delete();
|
$product->inverseAssociations()->delete();
|
||||||
@@ -147,7 +166,7 @@ class WipeCatalogCommand extends Command
|
|||||||
|
|
||||||
$product->delete();
|
$product->delete();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
Product::removeAllFromSearch();
|
Product::removeAllFromSearch();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -245,7 +245,23 @@ class ShopifyExportImporter implements Importer
|
|||||||
|
|
||||||
$existing = ImportMapping::resolve(self::SOURCE, 'image', $externalId);
|
$existing = ImportMapping::resolve(self::SOURCE, 'image', $externalId);
|
||||||
|
|
||||||
if ($existing instanceof Media) {
|
// ImportMapping is a durable record of "we already imported this,"
|
||||||
|
// but the Media row it points at can go stale — e.g. Command\
|
||||||
|
// WipeCatalogCommand deletes every Product (media included, via
|
||||||
|
// Spatie's own model-delete cleanup) without knowing this mapping
|
||||||
|
// exists, since the mapping ISN'T scoped to a single Product to
|
||||||
|
// clean up alongside it. Re-running an import afterward used to
|
||||||
|
// trust the cached Media object unconditionally — it still existed
|
||||||
|
// as a PHP object even though its underlying row (and file) were
|
||||||
|
// long gone, so every re-imported product silently got zero
|
||||||
|
// media, no error, no warning. Falls through to a fresh import
|
||||||
|
// whenever the mapping doesn't resolve to a real, still-attached
|
||||||
|
// Media row.
|
||||||
|
if ($existing instanceof Media
|
||||||
|
&& Media::whereKey($existing->getKey())->exists()
|
||||||
|
&& $existing->model_type === $product->getMorphClass()
|
||||||
|
&& (int) $existing->model_id === $product->id
|
||||||
|
) {
|
||||||
return $existing;
|
return $existing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user