Files
ms1inscription-v5/php/inc_fx_static_sync.php
stephan a8274dd640 Update key column references and enhance error handling in static synchronization functions
This commit modifies key column references in the `fxStaticSyncTableDefinitions` function to align with updated database schema identifiers. Additionally, it introduces a new utility function, `fxStaticSyncKeyColumnsValid`, to validate key columns before executing queries, improving error handling in the `fxStaticSyncFetchRowsByKey` and `fxStaticSyncLoadKeyedRows` functions. The changes aim to enhance the robustness of the synchronization process and provide clearer error feedback during data operations.
2026-07-06 15:34:20 -04:00

882 lines
28 KiB
PHP

<?php
require_once __DIR__ . '/inc_fx_static_sync_config.php';
/**
* Environnements comparables — configs depuis inc_settings.php uniquement.
* Source : $arrConDatabasePreProd (dev préprod sur serveur dev).
*/
function fxStaticSyncEnvironmentDefinitions() {
global $arrConDatabasePreProd, $arrConDatabaseProd;
global $arrConDatabaseClientPreProd, $arrConDatabaseClientProd;
$tabEnvs = array(
'dev_preprod' => array(
'label' => 'Dev préprod',
'config' => $arrConDatabasePreProd,
'is_source' => true,
),
'dev_prod' => array(
'label' => 'Dev prod',
'config' => $arrConDatabaseProd,
'is_source' => false,
),
);
if (!empty($arrConDatabaseClientPreProd)) {
$tabEnvs['client_preprod'] = array(
'label' => 'Client préprod',
'config' => $arrConDatabaseClientPreProd,
'is_source' => false,
);
}
if (!empty($arrConDatabaseClientProd)) {
$tabEnvs['client_prod'] = array(
'label' => 'Client prod',
'config' => $arrConDatabaseClientProd,
'is_source' => false,
);
}
return $tabEnvs;
}
/** Connexion MySQL avec timeout court, sans die() — pour BD client additionnelles. */
class clsMysqlStaticSync extends clsMysql {
function __construct($arrDatabase = array()) {
$this->last_id = 0;
$this->affected_rows = 0;
$this->con = null;
if (empty($arrDatabase['host']) || empty($arrDatabase['db'])) {
return;
}
$mysqli = mysqli_init();
if (!$mysqli) {
return;
}
mysqli_options($mysqli, MYSQLI_OPT_CONNECT_TIMEOUT, 3);
if (!@mysqli_real_connect(
$mysqli,
$arrDatabase['host'],
$arrDatabase['user'],
$arrDatabase['pass'],
$arrDatabase['db'],
3306
)) {
return;
}
// Pas de mysqli_set_charset ici — charset serveur / table legacy (latin1, utf8, etc.).
$this->con = $mysqli;
}
}
function fxStaticSyncConnect($arrConfig) {
if (empty($arrConfig['host']) || empty($arrConfig['db'])) {
return null;
}
$obj = new clsMysqlStaticSync($arrConfig);
return !empty($obj->con) ? $obj : null;
}
/**
* Dev : réutilise objDatabase* de inc_fonctions.php.
* Client : $arrConDatabaseClient* de inc_settings.php (si définis pour l'environnement).
*/
function fxStaticSyncOpenConnections() {
global $objDatabasePreProd, $objDatabaseProd;
global $arrConDatabaseClientPreProd, $arrConDatabaseClientProd;
$tabConnections = array(
'dev_preprod' => !empty($objDatabasePreProd) ? $objDatabasePreProd : null,
'dev_prod' => !empty($objDatabaseProd) ? $objDatabaseProd : null,
);
if (!empty($arrConDatabaseClientPreProd)) {
$tabConnections['client_preprod'] = fxStaticSyncConnect($arrConDatabaseClientPreProd);
}
if (!empty($arrConDatabaseClientProd)) {
$tabConnections['client_prod'] = fxStaticSyncConnect($arrConDatabaseClientProd);
}
return $tabConnections;
}
function fxStaticSyncTableExists($objDb, $strTable) {
if (!$objDb) {
return false;
}
$sql = "SHOW TABLES LIKE '" . $objDb->fxEscape($strTable) . "'";
$row = $objDb->fxGetRow($sql);
return !empty($row);
}
function fxStaticSyncRowBusinessKey(array $row, array $tabKeyCols) {
$tabParts = array();
foreach ($tabKeyCols as $strCol) {
$tabParts[] = isset($row[$strCol]) ? (string)$row[$strCol] : '';
}
return implode("\0", $tabParts);
}
/**
* Colonnes communes source/cible pour compare et copy (alignés).
* missing_in_target / extra_in_target : écarts de structure (hors ignore_cols).
*/
function fxStaticSyncResolveCompareColumns($objSource, $objTarget, $strTable, array $tabIgnoreCols) {
$tabSourceCols = fxStaticSyncGetTableColumnNames($objSource, $strTable);
$tabTargetCols = fxStaticSyncGetTableColumnNames($objTarget, $strTable);
$tabCommon = array();
foreach ($tabSourceCols as $strCol) {
if (in_array($strCol, $tabTargetCols, true)) {
$tabCommon[] = $strCol;
}
}
$tabCompareCols = array();
foreach ($tabCommon as $strCol) {
if (!in_array($strCol, $tabIgnoreCols, true)) {
$tabCompareCols[] = $strCol;
}
}
$tabMissingInTarget = array();
foreach ($tabSourceCols as $strCol) {
if (!in_array($strCol, $tabTargetCols, true) && !in_array($strCol, $tabIgnoreCols, true)) {
$tabMissingInTarget[] = $strCol;
}
}
$tabExtraInTarget = array();
foreach ($tabTargetCols as $strCol) {
if (!in_array($strCol, $tabSourceCols, true) && !in_array($strCol, $tabIgnoreCols, true)) {
$tabExtraInTarget[] = $strCol;
}
}
return array(
'copy_cols' => $tabCommon,
'compare_cols' => $tabCompareCols,
'missing_in_target' => $tabMissingInTarget,
'extra_in_target' => $tabExtraInTarget,
);
}
function fxStaticSyncSchemasAligned(array $tabSchema) {
return count($tabSchema['missing_in_target']) === 0
&& count($tabSchema['extra_in_target']) === 0;
}
function fxStaticSyncFormatSchemaDiff(array $tabSchema) {
$tabParts = array();
if (!empty($tabSchema['missing_in_target'])) {
$tabParts[] = 'absentes en cible : ' . implode(', ', $tabSchema['missing_in_target']);
}
if (!empty($tabSchema['extra_in_target'])) {
$tabParts[] = 'en trop en cible : ' . implode(', ', $tabSchema['extra_in_target']);
}
return implode(' · ', $tabParts);
}
function fxStaticSyncNormalizeCellValue($val) {
if ($val === null) {
return null;
}
return (string)$val;
}
function fxStaticSyncSqlOrderByKeys(array $tabKeys) {
$tabParts = array();
foreach ($tabKeys as $strCol) {
$tabParts[] = '`' . $strCol . '`';
}
return implode(',', $tabParts);
}
function fxStaticSyncGetTableRowCount($objDb, $strTable) {
if (!$objDb || !fxStaticSyncTableExists($objDb, $strTable)) {
return 0;
}
return (int)$objDb->fxGetVar('SELECT COUNT(*) FROM `' . $strTable . '`');
}
function fxStaticSyncKeyColumnsValid($objDb, $strTable, array $tabKeys) {
if (count($tabKeys) === 0) {
return false;
}
$tabCols = fxStaticSyncGetTableColumnNames($objDb, $strTable);
foreach ($tabKeys as $strCol) {
if (!in_array($strCol, $tabCols, true)) {
return false;
}
}
return true;
}
/**
* Charge les lignes — une entrée par clé métier (dernière gagne, ordre stable si ORDER BY possible).
*/
function fxStaticSyncFetchRowsByKey($objDb, array $tabDef) {
$strTable = $tabDef['table'];
$tabKeys = $tabDef['keys'];
if (!fxStaticSyncTableExists($objDb, $strTable)) {
return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => null);
}
if (!fxStaticSyncKeyColumnsValid($objDb, $strTable, $tabKeys)) {
return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'bad_keys');
}
$sql = 'SELECT * FROM `' . $strTable . '` ORDER BY ' . fxStaticSyncSqlOrderByKeys($tabKeys);
$tabResults = $objDb->fxGetResults($sql);
$tabKeyed = array();
$intDupes = 0;
$intPhysical = 0;
if (!is_array($tabResults)) {
return array('rows' => array(), 'duplicate_count' => 0, 'physical_count' => 0, 'error' => 'query_failed');
}
foreach ($tabResults as $row) {
if (!is_array($row)) {
continue;
}
$intPhysical++;
$strKey = fxStaticSyncRowBusinessKey($row, $tabKeys);
if (isset($tabKeyed[$strKey])) {
$intDupes++;
}
$tabKeyed[$strKey] = $row;
}
return array(
'rows' => $tabKeyed,
'duplicate_count' => $intDupes,
'physical_count' => $intPhysical,
'error' => null,
);
}
function fxStaticSyncRowContentHash(array $row, array $tabCompareCols) {
$tabData = array();
foreach ($tabCompareCols as $strCol) {
$tabData[$strCol] = array_key_exists($strCol, $row)
? fxStaticSyncNormalizeCellValue($row[$strCol])
: null;
}
ksort($tabData);
return md5(json_encode($tabData, JSON_UNESCAPED_UNICODE));
}
function fxStaticSyncLoadKeyedRows($objDb, array $tabDef, array $tabCompareCols = null) {
$tabKeys = $tabDef['keys'];
$tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array();
if (!fxStaticSyncTableExists($objDb, $tabDef['table'])) {
return array('error' => 'missing_table', 'rows' => array(), 'count' => 0, 'physical_count' => 0);
}
$tabFetch = fxStaticSyncFetchRowsByKey($objDb, $tabDef);
if ($tabFetch['error'] === 'bad_keys') {
return array('error' => 'bad_keys', 'rows' => array(), 'count' => 0, 'physical_count' => 0);
}
if ($tabFetch['error'] === 'query_failed') {
return array('error' => 'query_failed', 'rows' => array(), 'count' => 0, 'physical_count' => 0);
}
$tabKeyed = array();
if ($tabCompareCols === null) {
$tabCompareCols = array();
if (count($tabFetch['rows']) > 0) {
$row = reset($tabFetch['rows']);
foreach (array_keys($row) as $strCol) {
if (!in_array($strCol, $tabIgnore, true)) {
$tabCompareCols[] = $strCol;
}
}
}
}
foreach ($tabFetch['rows'] as $strKey => $row) {
$tabKeyed[$strKey] = array(
'hash' => fxStaticSyncRowContentHash($row, $tabCompareCols),
'row' => $row,
);
}
return array(
'error' => null,
'rows' => $tabKeyed,
'count' => count($tabKeyed),
'physical_count' => $tabFetch['physical_count'],
'duplicate_count' => $tabFetch['duplicate_count'],
);
}
/** Sync d'écriture interdite vers client prod (comparaison seule). */
function fxStaticSyncWriteAllowed($strEnvId) {
return $strEnvId !== 'client_prod';
}
function fxStaticSyncFinalizeCompareResult(array $tabCmp) {
if (in_array($tabCmp['status'], array('missing_table', 'schema_mismatch', 'no_connection'), true)) {
return $tabCmp;
}
$intMissingKeys = count($tabCmp['missing_keys']);
$intExtraKeys = count($tabCmp['extra_keys']);
$intDiffKeys = count($tabCmp['diff_keys']);
if ($intMissingKeys === 0 && $intExtraKeys === 0 && $intDiffKeys === 0) {
$tabCmp['status'] = 'ok';
return $tabCmp;
}
$intDelta = (int)$tabCmp['target_count'] - (int)$tabCmp['source_count'];
if ($intDelta < 0) {
$tabCmp['status'] = 'missing_rows';
} elseif ($intDelta > 0) {
$tabCmp['status'] = 'extra_rows';
} else {
$tabCmp['status'] = 'content_diff';
}
return $tabCmp;
}
/**
* @return array status: ok|content_diff|missing_rows|extra_rows|missing_table|schema_mismatch|no_connection|error
*/
function fxStaticSyncCompareTable(array $tabSourceData, array $tabTargetData, array $tabSchemaInfo = array()) {
if (!empty($tabTargetData['error']) && $tabTargetData['error'] === 'missing_table') {
return array(
'status' => 'missing_table',
'source_count' => $tabSourceData['count'],
'target_count' => 0,
'missing_keys' => array(),
'extra_keys' => array(),
'diff_keys' => array(),
);
}
$tabSourceRows = $tabSourceData['rows'];
$tabTargetRows = $tabTargetData['rows'];
$tabMissing = array();
$tabDiff = array();
foreach ($tabSourceRows as $strKey => $tabRow) {
if (!isset($tabTargetRows[$strKey])) {
$tabMissing[] = $strKey;
continue;
}
if ($tabTargetRows[$strKey]['hash'] !== $tabRow['hash']) {
$tabDiff[] = $strKey;
}
}
$tabExtra = array();
foreach ($tabTargetRows as $strKey => $tabRow) {
if (!isset($tabSourceRows[$strKey])) {
$tabExtra[] = $strKey;
}
}
return fxStaticSyncFinalizeCompareResult(array(
'status' => 'pending',
'source_count' => $tabSourceData['count'],
'target_count' => $tabTargetData['count'],
'source_physical' => isset($tabSourceData['physical_count']) ? $tabSourceData['physical_count'] : $tabSourceData['count'],
'target_physical' => isset($tabTargetData['physical_count']) ? $tabTargetData['physical_count'] : $tabTargetData['count'],
'missing_keys' => $tabMissing,
'extra_keys' => $tabExtra,
'diff_keys' => $tabDiff,
'schema_missing_cols' => isset($tabSchemaInfo['missing_in_target']) ? $tabSchemaInfo['missing_in_target'] : array(),
'schema_extra_cols' => isset($tabSchemaInfo['extra_in_target']) ? $tabSchemaInfo['extra_in_target'] : array(),
));
}
function fxStaticSyncDbLastError($objDb) {
if (!$objDb || empty($objDb->con)) {
return '';
}
return mysqli_error($objDb->con);
}
/**
* Exécute une requête d'écriture — erreur remontée dans le message flash (fxQuery n'echo pas).
*/
function fxStaticSyncExecQuery($objDb, $strSql) {
if (!$objDb) {
return array('ok' => false, 'error' => 'Connexion BD invalide.');
}
$blnOk = $objDb->fxQuery($strSql);
if ($blnOk === false) {
return array(
'ok' => false,
'error' => fxStaticSyncDbLastError($objDb) ?: 'Erreur SQL inconnue.',
);
}
return array('ok' => true, 'error' => null);
}
function fxStaticSyncGetTableColumnNames($objDb, $strTable) {
if (!$objDb) {
return array();
}
$tabCols = $objDb->fxGetResults('SHOW COLUMNS FROM `' . $strTable . '`');
$tabNames = array();
if (!is_array($tabCols)) {
return array();
}
foreach ($tabCols as $row) {
if (is_array($row) && !empty($row['Field'])) {
$tabNames[] = $row['Field'];
}
}
return $tabNames;
}
function fxStaticSyncGetTableEngine($objDb, $strTable) {
if (!$objDb) {
return '';
}
$row = $objDb->fxGetRow(
"SELECT ENGINE FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = '" . $objDb->fxEscape($strTable) . "'"
);
return !empty($row['ENGINE']) ? strtoupper($row['ENGINE']) : '';
}
function fxStaticSyncCopyTable($objSource, $objTarget, $strTable, array $tabDef = null) {
if (!$objSource || !$objTarget) {
return array('ok' => false, 'message' => 'Connexion BD invalide.');
}
if (!fxStaticSyncTableExists($objSource, $strTable)) {
return array('ok' => false, 'message' => 'Table absente dans la source.');
}
if (!fxStaticSyncTableExists($objTarget, $strTable)) {
return array('ok' => false, 'message' => 'Table absente dans la cible — exécuter le DDL d\'abord.');
}
if (!$tabDef) {
return array('ok' => false, 'message' => 'Table non configurée.');
}
$tabIgnore = ($tabDef && isset($tabDef['ignore_cols'])) ? $tabDef['ignore_cols'] : array();
$tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $strTable, $tabIgnore);
if (!fxStaticSyncSchemasAligned($tabSchema)) {
return array(
'ok' => false,
'message' => 'Structures non alignées — '
. fxStaticSyncFormatSchemaDiff($tabSchema)
. '. Aligner les structures (Navicat) avant toute sync. Aucune donnée modifiée.',
);
}
$tabCopyCols = $tabSchema['copy_cols'];
if (count($tabCopyCols) === 0) {
$tabFetchCheck = fxStaticSyncFetchRowsByKey($objSource, $tabDef);
if (count($tabFetchCheck['rows']) > 0) {
return array(
'ok' => false,
'message' => 'Aucune colonne commune entre source et cible.',
);
}
}
$tabFetch = fxStaticSyncFetchRowsByKey($objSource, $tabDef);
$tabRowsToCopy = $tabFetch['rows'];
$intDupes = $tabFetch['duplicate_count'];
// Préparer les INSERT avant toute écriture destructive (évite une table vide si échec).
$tabInsertBatches = array();
$intCopied = count($tabRowsToCopy);
if (count($tabCopyCols) > 0 && count($tabRowsToCopy) > 0) {
$strColList = '`' . implode('`,`', $tabCopyCols) . '`';
$intBatchSize = 50;
$tabBatch = array();
foreach ($tabRowsToCopy as $row) {
if (!is_array($row)) {
continue;
}
$tabVals = array();
foreach ($tabCopyCols as $strCol) {
if (!array_key_exists($strCol, $row) || $row[$strCol] === null) {
$tabVals[] = 'NULL';
} else {
$tabVals[] = "'" . $objTarget->fxEscape($row[$strCol]) . "'";
}
}
$tabBatch[] = '(' . implode(',', $tabVals) . ')';
if (count($tabBatch) >= $intBatchSize) {
$tabInsertBatches[] = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch);
$tabBatch = array();
}
}
if (count($tabBatch) > 0) {
$tabInsertBatches[] = 'INSERT INTO `' . $strTable . '` (' . $strColList . ') VALUES ' . implode(',', $tabBatch);
}
}
$blnTransactional = (fxStaticSyncGetTableEngine($objTarget, $strTable) === 'INNODB');
$blnFkDisabled = false;
$tabFkOff = fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=0');
if (!$tabFkOff['ok']) {
return array('ok' => false, 'message' => 'FK checks : ' . $tabFkOff['error']);
}
$blnFkDisabled = true;
if ($blnTransactional) {
$tabTx = fxStaticSyncExecQuery($objTarget, 'START TRANSACTION');
if (!$tabTx['ok']) {
fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1');
return array('ok' => false, 'message' => 'Transaction : ' . $tabTx['error']);
}
}
// DELETE plutôt que TRUNCATE : compatible FK (InnoDB) avec checks désactivés.
$tabDelete = fxStaticSyncExecQuery($objTarget, 'DELETE FROM `' . $strTable . '`');
if (!$tabDelete['ok']) {
if ($blnTransactional) {
fxStaticSyncExecQuery($objTarget, 'ROLLBACK');
}
if ($blnFkDisabled) {
fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1');
}
return array('ok' => false, 'message' => 'Vidage table : ' . $tabDelete['error']);
}
foreach ($tabInsertBatches as $strSql) {
$tabInsert = fxStaticSyncExecQuery($objTarget, $strSql);
if (!$tabInsert['ok']) {
if ($blnTransactional) {
fxStaticSyncExecQuery($objTarget, 'ROLLBACK');
} else {
return array(
'ok' => false,
'message' => 'Insertion : ' . $tabInsert['error']
. ' — la table cible peut être vide (MyISAM, pas de rollback).',
);
}
if ($blnFkDisabled) {
fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1');
}
return array('ok' => false, 'message' => 'Insertion : ' . $tabInsert['error'] . ' (rollback effectué).');
}
}
if ($blnTransactional) {
$tabCommit = fxStaticSyncExecQuery($objTarget, 'COMMIT');
if (!$tabCommit['ok']) {
fxStaticSyncExecQuery($objTarget, 'ROLLBACK');
if ($blnFkDisabled) {
fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1');
}
return array('ok' => false, 'message' => 'Commit : ' . $tabCommit['error']);
}
}
if ($blnFkDisabled) {
fxStaticSyncExecQuery($objTarget, 'SET FOREIGN_KEY_CHECKS=1');
}
$strMessage = $intCopied . ' ligne(s) copiée(s).';
if ($intDupes > 0) {
$strMessage .= ' (' . $intDupes . ' doublon(s) ignoré(s) en source — même clé métier.)';
}
return array(
'ok' => true,
'message' => $strMessage,
'rows' => $intCopied,
);
}
function fxStaticSyncStatusLabel($strStatus, array $tabCmp = null) {
switch ($strStatus) {
case 'ok':
return 'Données alignées';
case 'missing_table':
return 'Table absente';
case 'schema_mismatch':
return 'Structures non alignées';
case 'no_connection':
return 'Connexion impossible';
case 'error':
return 'Erreur lecture';
}
if (is_array($tabCmp)) {
$intDelta = (int)$tabCmp['target_count'] - (int)$tabCmp['source_count'];
if ($intDelta < 0) {
return 'Il en manque ' . abs($intDelta);
}
if ($intDelta > 0) {
return 'Il y en a ' . $intDelta . ' en trop';
}
return 'Contenu différent';
}
switch ($strStatus) {
case 'content_diff':
return 'Contenu différent';
case 'missing_rows':
return 'Il en manque';
case 'extra_rows':
return 'Il y en a en trop';
default:
return 'Erreur';
}
}
function fxStaticSyncStatusClass($strStatus) {
switch ($strStatus) {
case 'ok':
return 'ss-ok';
case 'content_diff':
case 'missing_rows':
return 'ss-warn';
case 'extra_rows':
return 'ss-bad';
case 'missing_table':
case 'no_connection':
return 'ss-muted';
case 'schema_mismatch':
return 'ss-schema';
case 'error':
return 'ss-bad';
default:
return 'ss-bad';
}
}
function fxStaticSyncFormatKeyPreview($strKey) {
$strKey = str_replace("\0", ' · ', $strKey);
if (strlen($strKey) > 120) {
return substr($strKey, 0, 117) . '…';
}
return $strKey;
}
function fxStaticSyncEnsureCsrfToken() {
if (empty($_SESSION['ms1_static_sync_csrf'])) {
$_SESSION['ms1_static_sync_csrf'] = bin2hex(random_bytes(16));
}
return $_SESSION['ms1_static_sync_csrf'];
}
function fxStaticSyncValidateCsrf($strToken) {
return !empty($_SESSION['ms1_static_sync_csrf'])
&& hash_equals($_SESSION['ms1_static_sync_csrf'], (string)$strToken);
}
function fxStaticSyncFindTableDef($strTable) {
foreach (fxStaticSyncTableDefinitions() as $tabDef) {
if ($tabDef['table'] === $strTable) {
return $tabDef;
}
}
return null;
}
function fxStaticSyncFindEnvDef($strEnvId) {
$tabEnvs = fxStaticSyncEnvironmentDefinitions();
return isset($tabEnvs[$strEnvId]) ? $tabEnvs[$strEnvId] : null;
}
function fxStaticSyncCompareEnvPair($objSource, $objTarget, array $tabDef) {
$tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array();
if (!fxStaticSyncTableExists($objTarget, $tabDef['table'])) {
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef);
return array(
'status' => 'missing_table',
'source_count' => $tabSourceData['count'],
'target_count' => 0,
'missing_keys' => array(),
'extra_keys' => array(),
'diff_keys' => array(),
'schema_missing_cols' => array(),
'schema_extra_cols' => array(),
);
}
$tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $tabDef['table'], $tabIgnore);
if (!fxStaticSyncSchemasAligned($tabSchema)) {
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef);
$tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef);
return array(
'status' => 'schema_mismatch',
'source_count' => $tabSourceData['count'],
'target_count' => $tabTargetData['count'],
'missing_keys' => array(),
'extra_keys' => array(),
'diff_keys' => array(),
'schema_missing_cols' => $tabSchema['missing_in_target'],
'schema_extra_cols' => $tabSchema['extra_in_target'],
);
}
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef, $tabSchema['compare_cols']);
$tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef, $tabSchema['compare_cols']);
if (!empty($tabSourceData['error']) || !empty($tabTargetData['error'])) {
return array(
'status' => 'error',
'source_count' => isset($tabSourceData['count']) ? $tabSourceData['count'] : 0,
'target_count' => isset($tabTargetData['count']) ? $tabTargetData['count'] : 0,
'missing_keys' => array(),
'extra_keys' => array(),
'diff_keys' => array(),
'schema_missing_cols' => array(),
'schema_extra_cols' => array(),
);
}
return fxStaticSyncCompareTable($tabSourceData, $tabTargetData, $tabSchema);
}
function fxStaticSyncBuildReport($tabConnections) {
$tabDefs = fxStaticSyncTableDefinitions();
$tabEnvs = fxStaticSyncEnvironmentDefinitions();
$objSource = $tabConnections['dev_preprod'];
$tabReport = array();
foreach ($tabDefs as $tabDef) {
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef);
$tabRow = array(
'def' => $tabDef,
'source_count' => $tabSourceData['count'],
'source_physical' => $tabSourceData['physical_count'],
'source_dupes' => $tabSourceData['duplicate_count'],
'envs' => array(),
);
foreach ($tabEnvs as $strEnvId => $tabEnv) {
if (!empty($tabEnv['is_source'])) {
continue;
}
$objTarget = isset($tabConnections[$strEnvId]) ? $tabConnections[$strEnvId] : null;
if (!$objTarget) {
$tabRow['envs'][$strEnvId] = array(
'status' => 'no_connection',
'source_count' => $tabSourceData['count'],
'target_count' => 0,
'missing_keys' => array(),
'extra_keys' => array(),
'diff_keys' => array(),
'schema_missing_cols' => array(),
'schema_extra_cols' => array(),
);
continue;
}
$tabRow['envs'][$strEnvId] = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef);
}
$tabReport[] = $tabRow;
}
return $tabReport;
}
function fxStaticSyncGetDiffDetail($strTable, $strEnvId, $tabConnections) {
$tabDef = fxStaticSyncFindTableDef($strTable);
$tabEnv = fxStaticSyncFindEnvDef($strEnvId);
if (!$tabDef || !$tabEnv || !empty($tabEnv['is_source'])) {
return null;
}
$objSource = $tabConnections['dev_preprod'];
$objTarget = $tabConnections[$strEnvId];
if (!$objSource || !$objTarget) {
return null;
}
$tabCompare = fxStaticSyncCompareEnvPair($objSource, $objTarget, $tabDef);
$tabIgnore = isset($tabDef['ignore_cols']) ? $tabDef['ignore_cols'] : array();
$tabSchema = fxStaticSyncResolveCompareColumns($objSource, $objTarget, $tabDef['table'], $tabIgnore);
$tabSourceData = fxStaticSyncLoadKeyedRows($objSource, $tabDef, $tabSchema['compare_cols']);
$tabTargetData = fxStaticSyncLoadKeyedRows($objTarget, $tabDef, $tabSchema['compare_cols']);
$tabDetail = array(
'compare' => $tabCompare,
'samples' => array(),
);
$tabKeysToShow = array_slice(
array_merge($tabCompare['missing_keys'], $tabCompare['diff_keys'], $tabCompare['extra_keys']),
0,
30
);
foreach ($tabKeysToShow as $strKey) {
$tabSample = array(
'key' => $strKey,
'type' => in_array($strKey, $tabCompare['extra_keys'], true) ? 'extra'
: (in_array($strKey, $tabCompare['missing_keys'], true) ? 'missing' : 'diff'),
'source' => isset($tabSourceData['rows'][$strKey]['row']) ? $tabSourceData['rows'][$strKey]['row'] : null,
'target' => isset($tabTargetData['rows'][$strKey]['row']) ? $tabTargetData['rows'][$strKey]['row'] : null,
);
if ($tabDef['table'] === 'info' && $tabSample['type'] === 'diff') {
$tabSample['source_texte'] = isset($tabSample['source']['info_texte']) ? $tabSample['source']['info_texte'] : '';
$tabSample['target_texte'] = isset($tabSample['target']['info_texte']) ? $tabSample['target']['info_texte'] : '';
}
$tabDetail['samples'][] = $tabSample;
}
return $tabDetail;
}