*/ private const CUSTOMER_TABLES = [ 'customer_notes', 'customer_social_accounts', 'customer_password_resets', 'gdpr_data_request', 'wishlist_items', 'wishlist', 'compare_items', 'cart_rule_customers', 'cart_rule_coupon_usage', 'mw_reward_point_customer_sign', 'mw_reward_point_history', 'mw_reward_point_customer', 'mw_growth_value_history', 'mw_growth_value_customer', 'member_log', 'downloadable_link_purchased', 'subscribers_list', 'customers', ]; /** * Historical / catalog rows that keep their snapshot but must not block deleting customers. * * @var list */ private const DETACH_CUSTOMER_ID = [ ['orders', 'customer_id'], ['shipments', 'customer_id'], ['product_reviews', 'customer_id'], ['product_questions', 'customer_id'], ['product_question_answers', 'customer_id'], ['product_question_answer_votes', 'customer_id'], ['cart_rule_coupons', 'customer_id'], ['gift_cards', 'customer_id'], ['gift_card_usage_logs', 'customer_id'], ['addresses', 'customer_id'], ]; public function handle(): int { $dryRun = (bool) $this->option('dry-run'); $tables = $this->existingTables(self::CUSTOMER_TABLES); $this->warn('This clears storefront customers so they can be re-imported.'); $this->line('Kept: customer groups, admins, products, orders (email snapshot stays; customer_id set null).'); $this->line('Removed: customers, customer addresses, newsletters, wishlists, compare, reward/growth balances, tokens.'); $this->newLine(); $rows = $this->countRows($tables); $this->table(['table', 'rows'], collect($rows)->map(fn ($count, $table) => [$table, $count])->values()->all()); $total = array_sum($rows); $this->info("Customer rows that would be truncated: {$total}"); $addressCount = $this->countCustomerAddresses(); if ($addressCount > 0) { $this->comment("addresses (address_type=customer): {$addressCount}"); } $cartCount = $this->countCustomerCarts(); if ($cartCount > 0) { $this->comment("cart (logged-in customers): {$cartCount}"); } $tokenCount = $this->countCustomerTokens(); if ($tokenCount > 0) { $this->comment("personal_access_tokens (customers): {$tokenCount}"); } $detachCounts = $this->countDetachRows(); if ($detachCounts !== []) { $this->newLine(); $this->comment('Historical rows whose customer_id will be set to NULL:'); $this->table(['table', 'rows with customer_id'], collect($detachCounts)->map(fn ($count, $table) => [$table, $count])->values()->all()); } if ($dryRun) { $this->info('Dry run — nothing written.'); return self::SUCCESS; } if (! $this->option('force') && ! $this->confirm('Truncate customer tables now?', false)) { $this->info('Aborted.'); return self::SUCCESS; } $driver = DB::getDriverName(); try { $this->disableForeignKeyChecks($driver); $this->deleteCustomerAddresses(); $this->deleteCustomerCarts(); $this->deleteCustomerTokens(); $this->deleteCustomerVisits(); $this->detachHistoricalCustomerIds(); foreach ($tables as $table) { $this->emptyTable($driver, $table); } } finally { $this->enableForeignKeyChecks($driver); } Cache::forget(self::PROGRESS_KEY); $this->newLine(); $this->info('Customer reset finished. Next:'); $this->line(' php artisan customers:migrate-asteria --reset-progress'); $this->line(' php artisan orders:migrate-asteria --reset-progress # if orders should re-link to new customer ids'); $this->line(' php artisan reviews:migrate-asteria --reset-progress --sync'); return self::SUCCESS; } /** * @param list $tables * @return list */ private function existingTables(array $tables): array { return array_values(array_filter($tables, fn (string $table) => Schema::hasTable($table))); } /** * @param list $tables * @return array */ private function countRows(array $tables): array { $counts = []; foreach ($tables as $table) { $counts[$table] = (int) DB::table($table)->count(); } return $counts; } /** * @return array */ private function countDetachRows(): array { $counts = []; foreach (self::DETACH_CUSTOMER_ID as [$table, $column]) { if (! Schema::hasTable($table) || ! Schema::hasColumn($table, $column)) { continue; } $query = DB::table($table)->whereNotNull($column); if ($table === 'gift_cards') { $query->where($column, '!=', 0); } if ($table === 'addresses' && Schema::hasColumn($table, 'address_type')) { $query->where('address_type', '!=', 'customer'); } $count = (int) $query->count(); if ($count > 0) { $counts[$table] = $count; } } return $counts; } private function countCustomerAddresses(): int { if (! Schema::hasTable('addresses') || ! Schema::hasColumn('addresses', 'address_type')) { return 0; } return (int) DB::table('addresses')->where('address_type', 'customer')->count(); } private function countCustomerCarts(): int { if (! Schema::hasTable('cart') || ! Schema::hasColumn('cart', 'customer_id')) { return 0; } return (int) DB::table('cart')->whereNotNull('customer_id')->count(); } private function countCustomerTokens(): int { if (! Schema::hasTable('personal_access_tokens')) { return 0; } return (int) $this->customerTokenQuery()->count(); } private function deleteCustomerAddresses(): void { if (! Schema::hasTable('addresses') || ! Schema::hasColumn('addresses', 'address_type')) { return; } DB::table('addresses')->where('address_type', 'customer')->delete(); } private function deleteCustomerCarts(): void { if (! Schema::hasTable('cart') || ! Schema::hasColumn('cart', 'customer_id')) { return; } $cartIds = DB::table('cart')->whereNotNull('customer_id')->pluck('id'); if ($cartIds->isEmpty()) { return; } $itemIds = Schema::hasTable('cart_items') ? DB::table('cart_items')->whereIn('cart_id', $cartIds)->pluck('id') : collect(); if ($itemIds->isNotEmpty() && Schema::hasTable('cart_item_inventories')) { DB::table('cart_item_inventories')->whereIn('cart_item_id', $itemIds)->delete(); } if ($itemIds->isNotEmpty()) { DB::table('cart_items')->whereIn('id', $itemIds)->delete(); } if (Schema::hasTable('cart_payment')) { DB::table('cart_payment')->whereIn('cart_id', $cartIds)->delete(); } if (Schema::hasTable('guest_cart_tokens')) { DB::table('guest_cart_tokens')->whereIn('cart_id', $cartIds)->delete(); } $addressIds = collect(); if (Schema::hasTable('addresses') && Schema::hasColumn('addresses', 'cart_id')) { $addressIds = DB::table('addresses')->whereIn('cart_id', $cartIds)->pluck('id'); } if ($addressIds->isNotEmpty() && Schema::hasTable('cart_shipping_rates') && Schema::hasColumn('cart_shipping_rates', 'cart_address_id')) { DB::table('cart_shipping_rates')->whereIn('cart_address_id', $addressIds)->delete(); } if ($addressIds->isNotEmpty()) { DB::table('addresses')->whereIn('id', $addressIds)->delete(); } DB::table('cart')->whereIn('id', $cartIds)->delete(); } private function deleteCustomerTokens(): void { if (! Schema::hasTable('personal_access_tokens')) { return; } $this->customerTokenQuery()->delete(); } private function customerTokenQuery() { return DB::table('personal_access_tokens') ->where('tokenable_type', 'like', '%Customer%'); } private function deleteCustomerVisits(): void { if (! Schema::hasTable('visits') || ! Schema::hasColumn('visits', 'visitable_type')) { return; } DB::table('visits')->where('visitable_type', 'like', '%Customer%')->delete(); } private function detachHistoricalCustomerIds(): void { foreach (self::DETACH_CUSTOMER_ID as [$table, $column]) { if (! Schema::hasTable($table) || ! Schema::hasColumn($table, $column)) { continue; } $query = DB::table($table)->whereNotNull($column); if ($table === 'gift_cards') { $query->where($column, '!=', 0); } $query->update([$column => null]); } } private function emptyTable(string $driver, string $table): void { if ($driver === 'mysql') { DB::statement('TRUNCATE TABLE '.$this->quoteTable($table)); return; } DB::table($table)->delete(); if ($driver === 'sqlite') { DB::table('sqlite_sequence')->where('name', DB::getTablePrefix().$table)->delete(); } } private function quoteTable(string $table): string { $name = DB::getTablePrefix().$table; return '`'.str_replace('`', '``', $name).'`'; } private function disableForeignKeyChecks(string $driver): void { if ($driver === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS=0'); } elseif ($driver === 'sqlite') { DB::statement('PRAGMA foreign_keys = OFF'); } } private function enableForeignKeyChecks(string $driver): void { if ($driver === 'mysql') { DB::statement('SET FOREIGN_KEY_CHECKS=1'); } elseif ($driver === 'sqlite') { DB::statement('PRAGMA foreign_keys = ON'); } } }