154 lines
7.5 KiB
PHP
154 lines
7.5 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Console\Commands;
|
||
|
|
|
||
|
|
use Illuminate\Console\Command;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
use Illuminate\Support\Facades\Hash;
|
||
|
|
use Illuminate\Support\Str;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Jeu de données de charge (bloc B, CLAUDE.md : « 500 classes, 20 000
|
||
|
|
* élèves ») pour mesurer l'effet des index avec EXPLAIN sur un volume
|
||
|
|
* réaliste. Jamais en production : efface et recrée des données, et
|
||
|
|
* 20 000 lignes sur un serveur de production gênerait les vrais profs
|
||
|
|
* pendant la mesure. À lancer contre une base locale ou jetable.
|
||
|
|
*
|
||
|
|
* Insertions en lot (DB::table()->insert() par paquets), pas par Eloquent
|
||
|
|
* un par un : 20 000 `Hash::make()` individuels prendraient des minutes.
|
||
|
|
*/
|
||
|
|
class GenererJeuDeChargeTest extends Command
|
||
|
|
{
|
||
|
|
protected $signature = 'charge:generer {--classes=500} {--eleves-par-classe=40}';
|
||
|
|
|
||
|
|
protected $description = 'Remplit la base avec un jeu de données de charge réaliste (jamais en production)';
|
||
|
|
|
||
|
|
public function handle(): int
|
||
|
|
{
|
||
|
|
if ($this->laravel->environment('production')) {
|
||
|
|
$this->error('Jamais en production : lance ceci contre une base locale ou jetable.');
|
||
|
|
|
||
|
|
return self::FAILURE;
|
||
|
|
}
|
||
|
|
|
||
|
|
$nombreClasses = (int) $this->option('classes');
|
||
|
|
$elevesParClasse = (int) $this->option('eleves-par-classe');
|
||
|
|
$maintenant = now();
|
||
|
|
$codeSecretHache = Hash::make('123456'); // un seul hachage, réutilisé pour toutes les lignes
|
||
|
|
|
||
|
|
$this->info("Génération de {$nombreClasses} classes, {$elevesParClasse} élève(s) chacune…");
|
||
|
|
|
||
|
|
$etablissementIds = $this->inserer('etablissements', range(1, 100), fn (int $i) => [
|
||
|
|
'nom' => "Établissement de charge {$i}", 'ville' => 'Test', 'uai' => "CHARGE{$i}",
|
||
|
|
'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$profIds = $this->inserer('users', range(1, (int) ceil($nombreClasses / 2)), fn (int $i) => [
|
||
|
|
'name' => "Prof de charge {$i}", 'email' => "prof-charge-{$i}@charge.test",
|
||
|
|
'password' => $codeSecretHache, 'etablissement_id' => $etablissementIds[$i % count($etablissementIds)],
|
||
|
|
'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$classeIds = $this->inserer('classes', range(1, $nombreClasses), fn (int $i) => [
|
||
|
|
'etablissement_id' => $etablissementIds[$i % count($etablissementIds)],
|
||
|
|
'user_id' => $profIds[$i % count($profIds)],
|
||
|
|
'nom' => "Classe de charge {$i}", 'code_classe' => Str::upper(Str::random(5)).$i,
|
||
|
|
'archivee_a' => null, 'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$labId = DB::table('labs')->first()->id ?? DB::table('labs')->insertGetId([
|
||
|
|
'identifiant' => 'charge-test', 'titre' => 'Lab de charge', 'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
|
||
|
|
$compteurEleve = 0;
|
||
|
|
$this->withProgressBar($classeIds, function (int $classeId) use (&$compteurEleve, $elevesParClasse, $maintenant, $codeSecretHache, $labId) {
|
||
|
|
$eleveIds = $this->inserer('eleves', range(1, $elevesParClasse), function (int $i) use (&$compteurEleve, $classeId, $maintenant, $codeSecretHache) {
|
||
|
|
$compteurEleve++;
|
||
|
|
|
||
|
|
return [
|
||
|
|
'classe_id' => $classeId, 'prenom' => "Élève{$compteurEleve}", 'nom' => 'Charge',
|
||
|
|
'identifiant' => "eleve-charge-{$compteurEleve}", 'code_secret' => $codeSecretHache,
|
||
|
|
'derniere_connexion_a' => $maintenant->copy()->subDays(random_int(0, 30)),
|
||
|
|
'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
];
|
||
|
|
});
|
||
|
|
|
||
|
|
// 3 séances par classe : deux terminées (historique), une sur dix encore en cours (activité réelle).
|
||
|
|
$enCours = random_int(1, 10) === 1;
|
||
|
|
$statuts = [$enCours ? 'en_cours' : 'terminee', 'terminee', 'terminee'];
|
||
|
|
foreach ($statuts as $indice => $statut) {
|
||
|
|
$demarreeA = $maintenant->copy()->subDays(random_int(1, 90));
|
||
|
|
$seanceId = DB::table('seances')->insertGetId([
|
||
|
|
'classe_id' => $classeId, 'lab_id' => $labId, 'statut' => $statut,
|
||
|
|
'demarree_a' => $demarreeA, 'terminee_a' => $statut === 'terminee' ? $demarreeA->copy()->addMinutes(45) : null,
|
||
|
|
'mode_examen' => false, 'created_at' => $demarreeA, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// La moitié des élèves de la classe a une session de lab sur cette séance, avec sa correction.
|
||
|
|
$participants = array_slice($eleveIds, 0, (int) (count($eleveIds) / 2));
|
||
|
|
$labSessions = [];
|
||
|
|
foreach ($participants as $eleveId) {
|
||
|
|
$labSessions[] = [
|
||
|
|
'seance_id' => $seanceId, 'eleve_id' => $eleveId, 'lab_id' => $labId,
|
||
|
|
'etat' => $statut === 'terminee' ? 'termine' : 'actif',
|
||
|
|
'created_at' => $demarreeA, 'updated_at' => $maintenant,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
if ($labSessions !== []) {
|
||
|
|
DB::table('lab_sessions')->insert($labSessions);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2 devoirs par classe, rendus par un peu plus d'un tiers des élèves.
|
||
|
|
foreach ([1, 2] as $n) {
|
||
|
|
$devoirId = DB::table('devoirs')->insertGetId([
|
||
|
|
'classe_id' => $classeId, 'titre' => "Devoir {$n}", 'consigne' => 'Consigne de charge.',
|
||
|
|
'echeance' => $maintenant->copy()->addDays(random_int(-30, 30)),
|
||
|
|
'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
]);
|
||
|
|
$rendus = [];
|
||
|
|
foreach (array_slice($eleveIds, 0, (int) (count($eleveIds) / 3)) as $eleveId) {
|
||
|
|
$rendus[] = [
|
||
|
|
'devoir_id' => $devoirId, 'eleve_id' => $eleveId, 'reponse' => 'Réponse de charge.',
|
||
|
|
'note' => random_int(5, 20), 'rendu_a' => $maintenant, 'created_at' => $maintenant, 'updated_at' => $maintenant,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
if ($rendus !== []) {
|
||
|
|
DB::table('rendus_devoirs')->insert($rendus);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
});
|
||
|
|
$this->newLine(2);
|
||
|
|
|
||
|
|
$this->info('Terminé : '.DB::table('classes')->count().' classes, '.DB::table('eleves')->count().' élèves, '
|
||
|
|
.DB::table('seances')->count().' séances, '.DB::table('lab_sessions')->count().' sessions de lab, '
|
||
|
|
.DB::table('devoirs')->count().' devoirs, '.DB::table('rendus_devoirs')->count().' rendus.');
|
||
|
|
|
||
|
|
return self::SUCCESS;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Insère en lots de 500 et renvoie les ids créés, dans l'ordre.
|
||
|
|
*
|
||
|
|
* @param array<int, int> $indices
|
||
|
|
* @param callable(int): array<string, mixed> $fabrique
|
||
|
|
* @return array<int, int>
|
||
|
|
*/
|
||
|
|
private function inserer(string $table, array $indices, callable $fabrique): array
|
||
|
|
{
|
||
|
|
$ids = [];
|
||
|
|
|
||
|
|
foreach (array_chunk($indices, 500) as $paquet) {
|
||
|
|
$lignes = array_map($fabrique, $paquet);
|
||
|
|
DB::table($table)->insert($lignes);
|
||
|
|
// insert() en lot : LAST_INSERT_ID() renvoie l'id du PREMIER rang
|
||
|
|
// inséré par l'instruction (comportement MySQL/MariaDB documenté),
|
||
|
|
// les suivants sont consécutifs (innodb_autoinc_lock_mode par défaut).
|
||
|
|
$premier = (int) DB::getPdo()->lastInsertId();
|
||
|
|
$ids = array_merge($ids, range($premier, $premier + count($lignes) - 1));
|
||
|
|
}
|
||
|
|
|
||
|
|
return $ids;
|
||
|
|
}
|
||
|
|
}
|