resolveSqlPath(); if ($path === null) { return self::FAILURE; } $cfg = config('database.connections.'.config('database.default')); if (($cfg['driver'] ?? '') !== 'mysql') { $this->error('فقط mysql پشتیبانی می‌شود.'); return self::FAILURE; } $host = (string) ($cfg['host'] ?? '127.0.0.1'); $port = (string) ($cfg['port'] ?? '3306'); $user = (string) ($cfg['username'] ?? ''); $pass = (string) ($cfg['password'] ?? ''); $defaultDb = (string) ($cfg['database'] ?? ''); if ($user === '') { $this->error('DB_USERNAME در .env خالی است.'); return self::FAILURE; } $databases = $this->argument('database'); if ($databases === [] || $databases === null) { if ($defaultDb === '') { $this->error('نام DB ندهید → DB_DATABASE را در .env بگذارید یا آرگومان بدهید.'); return self::FAILURE; } $databases = [$defaultDb]; } $mysql = $this->findMysqlBinary(); if ($mysql === null) { $this->error('mysql client پیدا نشد. نصب کنید یا اجرا کنید: bash database/sql/run_deploy_settings.sh'); return self::FAILURE; } $sql = file_get_contents($path); if ($sql === false || trim($sql) === '') { $this->error("فایل SQL خالی یا غیرقابل خواندن: {$path}"); return self::FAILURE; } $extraPath = database_path('sql/settings_menu_and_show_to_user.sql'); if (is_file($extraPath)) { $extraSql = file_get_contents($extraPath); if ($extraSql !== false && trim($extraSql) !== '') { $sql .= "\n".$extraSql; } } foreach ($databases as $db) { $db = trim((string) $db); if ($db === '') { continue; } $this->info("==> {$path} → {$db} @ {$host}:{$port}"); $process = new Process([ $mysql, '-h', $host, '-P', $port, '-u', $user, '--default-character-set=utf8mb4', $db, ], null, ['MYSQL_PWD' => $pass]); $process->setInput($sql); $process->setTimeout(null); $process->run(function ($type, $buffer) { $line = trim($buffer); if ($line === '') { return; } if ($type === Process::ERR) { $this->error($line); } else { $this->line($line); } }); if (! $process->isSuccessful()) { $this->error("خطا روی دیتابیس: {$db}"); return self::FAILURE; } $this->syncRuntimeCanEdit($db); $this->info(" done: {$db}"); } $this->info('==> all done'); return self::SUCCESS; } private function resolveSqlPath(): ?string { if ($this->option('preserve')) { $relative = 'database/sql/deploy_settings_const_preserve_value_sync_can_edit.sql'; } else { $relative = $this->option('file') ?: 'database/sql/deploy_settings_const.sql'; } $path = str_starts_with($relative, '/') ? $relative : base_path($relative); if (! is_file($path)) { $this->error("فایل SQL پیدا نشد: {$path}"); return null; } return $path; } private function syncRuntimeCanEdit(string $database): void { try { $sync = app(SettingsRuntimeCanEditSync::class); $sync->useDatabase($database); $updated = $sync->sync(); $this->line(" can_edit sync: {$updated} row(s) updated"); $status = $sync->statusMap(); if ($status !== []) { $preview = collect($status) ->map(fn ($canEdit, $key) => "{$key}={$canEdit}") ->implode(', '); $this->line(" can_edit sample: {$preview}"); } $violations = $sync->violations(); if ($violations !== []) { $this->warn(' WARN: runtime keys still can_edit≠1: '.implode(', ', array_keys($violations))); } $advancedViolations = $sync->advancedViolations(); if ($advancedViolations !== []) { $this->warn(' WARN: advanced keys still can_edit≠0: '.implode(', ', array_keys($advancedViolations))); } $fee = \App\Models\Setting::query() ->where('key', 'fee_deduct_from_gold') ->first(['key', 'can_edit']); if ($fee) { $this->line(" check fee_deduct_from_gold can_edit={$fee->can_edit}"); } } catch (\Throwable $e) { $this->warn(' runtime can_edit sync skipped: '.$e->getMessage()); } } private function findMysqlBinary(): ?string { $process = new Process(['command', '-v', 'mysql']); $process->run(); if ($process->isSuccessful()) { $bin = trim($process->getOutput()); if ($bin !== '') { return $bin; } } foreach (['/usr/bin/mysql', '/usr/local/bin/mysql'] as $candidate) { if (is_executable($candidate)) { return $candidate; } } return null; } }