>> */ public function esporta(): array { return [ self::TABELLA_MAPPE => $this->righe(self::TABELLA_MAPPE), self::TABELLA_VERSIONI => $this->righe(self::TABELLA_VERSIONI), ]; } /** * Controlla la struttura di piantine.json prima che il ripristino tocchi * qualunque cosa. Null e' ammesso: e' l'archivio di formato 1, che le * piantine non le conteneva. */ public function valida(?array $dati): void { if ($dati === null) { return; } foreach ([self::TABELLA_MAPPE, self::TABELLA_VERSIONI] as $tabella) { if (! isset($dati[$tabella]) || ! is_array($dati[$tabella]) || ! array_is_list($dati[$tabella])) { throw new RuntimeException('Archivio non valido: in piantine.json manca l\'elenco "' . $tabella . '".'); } foreach ($dati[$tabella] as $posizione => $riga) { if (! is_array($riga) || ! isset($riga['id']) || ! is_numeric($riga['id'])) { throw new RuntimeException(sprintf( 'Archivio non valido: la riga %d di "%s" in piantine.json non e\' leggibile.', $posizione + 1, $tabella )); } } } } /** * Sostituisce il contenuto delle due tabelle con quello del backup. * * Va chiamato dentro la transazione del ripristino e *dopo* l'import XML: * servono i territori gia' ricreati per ritrovare i collegamenti. DELETE e * non TRUNCATE: su MariaDB TRUNCATE e' DDL e fa commit implicito, cioe' * chiuderebbe a meta' la transazione che deve poter annullare tutto. * * Con $dati null (archivio di formato 1) le tabelle si svuotano soltanto. * Tenere le righe dell'installazione corrente vorrebbe dire piantine che * citano immagini del disco appena sostituito — file che non ci sono piu' * — accanto ai dati di un'altra epoca: proprio lo stato misto che il * ripristino deve evitare. Lo storico, del resto, lo svuotava gia' la * cascata sulla cancellazione dei territori. * * @return array{mappe: int, versioni: int, issues: list} */ public function ripristina(?array $dati): array { DB::table(self::TABELLA_VERSIONI)->delete(); DB::table(self::TABELLA_MAPPE)->delete(); $esito = ['mappe' => 0, 'versioni' => 0, 'issues' => []]; if ($dati === null) { return $esito; } // withTrashed implicito: il query builder non conosce i soft delete, e // una piantina collegata a un territorio nel cestino deve restarlo. $territori = DB::table('territori')->pluck('id', 'numero') ->mapWithKeys(fn ($id, $numero) => [trim((string) $numero) => (int) $id]) ->all(); $colonneMappe = array_flip(Schema::getColumnListing(self::TABELLA_MAPPE)); $collegati = []; foreach ($dati[self::TABELLA_MAPPE] as $riga) { $numero = trim((string) ($riga['territorio_numero'] ?? '')); $territorioId = null; if (($riga['territorio_id'] ?? null) !== null) { $territorioId = $territori[$numero] ?? null; // territorio_id e' unico: due piantine sullo stesso numero // (possibile solo se il backup aveva territori con numero // ripetuto, che l'import scarta) non possono restare collegate // entrambe. if ($territorioId !== null && isset($collegati[$territorioId])) { $territorioId = null; } if ($territorioId === null) { // Il disegno non si butta: resta scollegato, con il numero // come etichetta per riconoscerlo. if (trim((string) ($riga['nome'] ?? '')) === '' && $numero !== '') { $riga['nome'] = 'Terr. ' . $numero; } $esito['issues'][] = $this->problema('piantina', $riga, 'Territorio ' . ($numero !== '' ? $numero : 'sconosciuto') . ' non trovato: piantina ripristinata scollegata'); } else { $collegati[$territorioId] = true; } } $riga['territorio_id'] = $territorioId; DB::table(self::TABELLA_MAPPE)->insert(array_intersect_key($riga, $colonneMappe)); $esito['mappe']++; } $colonneVersioni = array_flip(Schema::getColumnListing(self::TABELLA_VERSIONI)); foreach ($dati[self::TABELLA_VERSIONI] as $riga) { $numero = trim((string) ($riga['territorio_numero'] ?? '')); $territorioId = $territori[$numero] ?? null; // Una versione senza territorio non e' ripristinabile da nessuna // schermata (e la colonna non ammette NULL): si scarta, e lo si dice. if ($territorioId === null) { $esito['issues'][] = $this->problema('versione PDF', $riga, 'Territorio ' . ($numero !== '' ? $numero : 'sconosciuto') . ' non trovato: versione non ripristinata'); continue; } $riga['territorio_id'] = $territorioId; DB::table(self::TABELLA_VERSIONI)->insert(array_intersect_key($riga, $colonneVersioni)); $esito['versioni']++; } return $esito; } private function righe(string $tabella): array { return DB::table($tabella) ->leftJoin('territori', 'territori.id', '=', $tabella . '.territorio_id') ->orderBy($tabella . '.id') ->get([$tabella . '.*', 'territori.numero as territorio_numero']) ->map(fn (object $riga) => (array) $riga) ->all(); } /** Stessa forma delle segnalazioni dell'import XML, per mostrarle insieme. */ private function problema(string $entita, array $riga, string $motivo): array { return ['entity' => $entita, 'legacy_id' => (string) ($riga['id'] ?? ''), 'reason' => $motivo]; } }