Files
pta/app/Models/SchemaModel.php
2026-08-20 13:22:49 +02:00

149 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace App\Models;
/**
* Contrôle de conformité du schéma, joué à CHAQUE requête par app/bootstrap.php.
*
* Ce garde-fou existe parce que les migrations sont des fichiers SQL appliqués
* par un service séparé : rien ne garantit que la base en face du code soit à
* jour. Plutôt que de laisser l'application échouer plus tard sur une erreur SQL
* cryptique au milieu d'un écran, on refuse de démarrer avec un message qui
* nomme précisément ce qui manque et quel fichier jouer.
*
* CONSÉQUENCE POUR LE DÉVELOPPEUR : ajouter une table ou une colonne obligatoire
* impose de compléter les listes ci-dessous EN MÊME TEMPS que la migration.
* Oublier de le faire ne casse rien tout de suite — c'est justement le piège :
* l'application démarrera sur une base incomplète.
*
* Le coût est de trois requêtes sur information_schema par requête HTTP,
* accepté au regard du volume d'utilisateurs de l'application.
*/
final class SchemaModel extends BaseModel
{
/**
* Vérifie tables, colonnes puis vues d'accès.
*
* Seules les colonnes AJOUTÉES PAR MIGRATION sont listées, pas l'intégralité
* du schéma : ce sont les seules susceptibles de manquer sur une base créée
* à partir de database/pta_prod_schema_mariadb.sql, qui ne contient que le
* socle métier.
*
* @throws \RuntimeException Au premier manque constaté, avec le détail et
* le fichier SQL à exécuter.
*/
public function assertCurrent(): void
{
$requiredTables = [
'agent',
'structure',
'agent_structure',
'motif_planning',
'semaine',
'planning',
'creneau_horaire',
'quota_agent_annuel',
'modele_semaine_agent',
'modele_semaine_creneau',
'structure_couverture_horaire',
'periode_vacance',
'poste_agent',
'pta_annuel',
'agent_structure_souhait',
'agent_contrainte',
'equipe_pta',
'equipe_pta_membre',
'structure_creneau_reference',
];
$placeholders = implode(',', array_fill(0, count($requiredTables), '?'));
$stmt = $this->pdo->prepare(
"SELECT TABLE_NAME
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ($placeholders)"
);
$stmt->execute($requiredTables);
$existing = $stmt->fetchAll(\PDO::FETCH_COLUMN);
$missingTables = array_values(array_diff($requiredTables, $existing));
if ($missingTables) {
throw new \RuntimeException(
'Base PTA incomplète : table(s) manquante(s) : '
. implode(', ', $missingTables)
. '. Exécutez database/install_complet.sql ou les migrations nécessaires.'
);
}
$requiredColumns = [
'creneau_horaire' => ['id_motif'],
'planning' => ['date_validation'],
'agent' => ['email', 'telephone', 'adresse', 'est_diplome', 'diplome_libelle', 'id_structure', 'id_poste', 'type_contrat', 'date_debut_contrat', 'date_fin_contrat', 'droit_enveloppe_formation', 'commentaire_pta'],
'structure' => ['type_affectation'],
];
foreach ($requiredColumns as $table => $columns) {
$columnPlaceholders = implode(',', array_fill(0, count($columns), '?'));
$params = array_merge([$table], $columns);
$columnStmt = $this->pdo->prepare(
"SELECT COLUMN_NAME
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME = ?
AND COLUMN_NAME IN ($columnPlaceholders)"
);
$columnStmt->execute($params);
$existingColumns = $columnStmt->fetchAll(\PDO::FETCH_COLUMN);
$missingColumns = array_values(array_diff($columns, $existingColumns));
if ($missingColumns) {
throw new \RuntimeException(sprintf(
'Base PTA incomplète : colonne(s) manquante(s) dans %s : %s.',
$table,
implode(', ', $missingColumns)
));
}
}
$this->assertAccessViews();
}
/**
* Les vues d'accès portent l'intégralité des règles de droits. Si elles
* manquent, l'application doit refuser de démarrer plutôt que de tourner
* sans contrôle de périmètre.
*/
private function assertAccessViews(): void
{
$requiredViews = [
'v_acces_role_page',
'v_acces_role_permission',
'v_acces_identite_agent',
'v_acces_identite_structure',
'v_acces_agent_par_structure',
'v_acces_perimetre_creneau',
'v_acces_perimetre_planning',
];
$placeholders = implode(',', array_fill(0, count($requiredViews), '?'));
$stmt = $this->pdo->prepare(
"SELECT TABLE_NAME
FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = DATABASE()
AND TABLE_NAME IN ($placeholders)"
);
$stmt->execute($requiredViews);
$missing = array_values(array_diff($requiredViews, $stmt->fetchAll(\PDO::FETCH_COLUMN)));
if ($missing) {
throw new \RuntimeException(
'Contrôle d’accès indisponible : vue(s) manquante(s) : '
. implode(', ', $missing)
. '. Exécutez database/migration_v7_8_securite_acces.sql.'
);
}
}
}