Amateur Radio · Arras · Hauts-de-France
QRV · 27.710 MHz
SFI 99 · KP 0 · MUF 26.5 MHz · Sp-E · Arras — °C
--:--:--
🎨 Thème :
11m
SSN 58
A-idx 3
26KW133 27480
14EK/D34 27490
14FDX/CH62068 27410
14EK/D33 27460
47/14SD152 27525
108RC/EMT 27445
14RC362 27565
161AT/GG 27590
26KW133 27480
14EK/D34 27490
14FDX/CH62068 27410
14EK/D33 27460
47/14SD152 27525
108RC/EMT 27445
14RC362 27565
161AT/GG 27590
Tutoriel Technique

Construire son log DX en PHP et SQLite

Un log DX est la mémoire vivante de vos contacts sur 27 MHz. Avec PHP et SQLite, vous pouvez créer une solution légère, rapide et totalement maîtrisée — intégrée à votre site sans dépendance externe.

✍️ 14RC185 Johan 📅 2026 💻 PHP · SQLite · ADIF ⏱️ 8 min de lecture

🗄️ 1. Pourquoi SQLite plutôt que MySQL ?

L'original de ce tutoriel utilisait MySQL. Sur mon propre site, j'utilise SQLite — et pour un log DX personnel, c'est le meilleur choix. Voici pourquoi.

📁
Un seul fichier
La base de données entière tient dans un fichier .db. Sauvegarde = copier un fichier. Migration = déplacer un fichier. Pas de serveur MySQL à configurer ou maintenir.
Légèreté et rapidité
Pour un log personnel de quelques milliers de contacts, SQLite est plus rapide que MySQL. Pas de connexion réseau, pas de processus serveur — accès direct au fichier.
🔒
Sécurité simplifiée
Pas de port SQL exposé, pas de mot de passe de base de données à gérer. Le fichier .db doit juste être placé hors de la racine web ou protégé par .htaccess.
🔄
Compatible PHP natif
SQLite3 est inclus dans PHP de base — aucune extension à installer. Fonctionne immédiatement sur tous les hébergeurs mutualisés dont LWS.

Si votre hébergeur impose MySQL ou si vous prévoyez un volume très important de données partagées entre plusieurs utilisateurs, MySQL reste pertinent. Pour un log DX personnel, SQLite est largement suffisant et bien plus simple.

📋 2. Structure de la base de données

Voici la structure SQLite utilisée sur ce site pour le log DX. Elle couvre tous les champs nécessaires à un échange eQSL valide et à un export ADIF complet.

SQL — Création de la table
CREATE TABLE IF NOT EXISTS activites (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  date_qso    TEXT    NOT NULL,          -- Format : YYYY-MM-DD
  heure_qso   TEXT    NOT NULL,          -- Format : HH:MM UTC
  indicatif   TEXT    NOT NULL,          -- Ex : 14RC185
  frequence   TEXT    NOT NULL,          -- Ex : 27.555
  mode        TEXT    NOT NULL DEFAULT 'USB', -- USB, LSB, AM, FM
  report      TEXT,                      -- Ex : 59, 57
  pays        TEXT,                      -- Ex : France, Italie
  commentaire TEXT,
  date_ajout  TEXT    DEFAULT (datetime('now'))
);

Protection du fichier DB : placez votre fichier .db dans un dossier non accessible depuis le web (ex: /admin/activites.db) ou ajoutez une règle .htaccess pour bloquer l'accès direct :

.htaccess — protection du dossier
<FilesMatch "\.db$">
  Require all denied
</FilesMatch>

🔌 3. Connexion PHP à la base SQLite

Deux approches sont possibles en PHP : la classe SQLite3 native ou PDO avec le driver SQLite. J'utilise SQLite3 natif sur ce site — plus simple et sans dépendance.

PHP — Connexion SQLite3
// Connexion en lecture/écriture
$db = new SQLite3(__DIR__ . '/admin/activites.db');
$db->enableExceptions(true);

// Connexion en lecture seule (pour les pages publiques)
$db = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);

Toujours utiliser SQLITE3_OPEN_READONLY sur les pages publiques — un visiteur ne doit jamais pouvoir déclencher une écriture en base depuis une page accessible sur le web.

🧰 4. Formulaire d'ajout d'un contact

Ce formulaire HTML sert à saisir un nouveau QSO depuis l'interface d'administration. Il ne doit jamais être accessible depuis l'extérieur.

HTML — Formulaire de saisie
<form method="post" action="ajout-log.php">
  <label>Date (UTC) :
    <input type="date" name="date_qso" required>
  </label>
  <label>Heure UTC :
    <input type="time" name="heure_qso" required>
  </label>
  <label>Indicatif :
    <input type="text" name="indicatif" placeholder="14RC185" required>
  </label>
  <label>Fréquence (MHz) :
    <input type="text" name="frequence" placeholder="27.555">
  </label>
  <label>Mode :
    <select name="mode">
      <option value="USB">USB</option>
      <option value="LSB">LSB</option>
      <option value="AM">AM</option>
      <option value="FM">FM</option>
    </select>
  </label>
  <label>Report RS :
    <input type="text" name="report" placeholder="59">
  </label>
  <label>Pays :
    <input type="text" name="pays" placeholder="France">
  </label>
  <button type="submit">Enregistrer le QSO</button>
</form>

💾 5. Traitement PHP de l'insertion

Le traitement doit toujours utiliser des requêtes préparées pour éviter les injections SQL — même avec SQLite.

PHP — ajout-log.php
<?php
// Vérification basique d'authentification admin
session_start();
if (empty($_SESSION['admin'])) {
    header('Location: /admin/login.php');
    exit;
}

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    header('Location: /admin/log.php');
    exit;
}

$db = new SQLite3(__DIR__ . '/../admin/activites.db');
$db->enableExceptions(true);

$stmt = $db->prepare("
    INSERT INTO activites (date_qso, heure_qso, indicatif, frequence, mode, report, pays, commentaire)
    VALUES (:date, :heure, :indicatif, :freq, :mode, :report, :pays, :comment)
");

$stmt->bindValue(':date',     trim($_POST['date_qso']   ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':heure',    trim($_POST['heure_qso']  ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':indicatif',trim($_POST['indicatif']  ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':freq',     trim($_POST['frequence']  ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':mode',     trim($_POST['mode']       ?? 'USB'), SQLITE3_TEXT);
$stmt->bindValue(':report',   trim($_POST['report']     ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':pays',     trim($_POST['pays']       ?? ''), SQLITE3_TEXT);
$stmt->bindValue(':comment',  trim($_POST['commentaire']?? ''), SQLITE3_TEXT);

$stmt->execute();

header('Location: /admin/log.php?ajout=ok');
exit;
?>

📄 6. Affichage public du log

Pour la page publique, on lit en lecture seule et on échappe toutes les sorties.

PHP — Lecture et affichage
<?php
$db  = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);
$res = $db->query("SELECT * FROM activites ORDER BY date_qso DESC, heure_qso DESC LIMIT 50");

while ($row = $res->fetchArray(SQLITE3_ASSOC)):
?>
<tr>
  <td><?= htmlspecialchars($row['date_qso'])    ?></td>
  <td><?= htmlspecialchars($row['heure_qso'])   ?> UTC</td>
  <td><?= htmlspecialchars($row['indicatif'])   ?></td>
  <td><?= htmlspecialchars($row['frequence'])   ?> MHz</td>
  <td><?= htmlspecialchars($row['mode'])        ?></td>
  <td><?= htmlspecialchars($row['report'])      ?></td>
</tr>
<?php endwhile; ?>

📤 7. Export ADIF pour les plateformes eQSL

Le format ADIF (.ADI) est le standard universel d'échange de logs entre logiciels et plateformes. Voici comment générer un fichier ADIF depuis votre base SQLite.

PHP — Génération export ADIF
<?php
// Forcer le téléchargement du fichier
header('Content-Type: text/plain; charset=UTF-8');
header('Content-Disposition: attachment; filename="log-dx-' . date('Ymd') . '.adi"');

$db  = new SQLite3(__DIR__ . '/admin/activites.db', SQLITE3_OPEN_READONLY);
$res = $db->query("SELECT * FROM activites ORDER BY date_qso ASC");

// En-tête ADIF
echo "<ADIF_VER:5>3.1.0\n";
echo "<PROGRAMID:8>RadioDX\n";
echo "<EOH>\n\n";

// Chaque contact
while ($row = $res->fetchArray(SQLITE3_ASSOC)) {
    $call  = strtoupper(trim($row['indicatif']));
    $date  = str_replace('-', '', $row['date_qso']);  // YYYYMMDD
    $time  = str_replace(':', '', $row['heure_qso']); // HHMM
    $freq  = $row['frequence'];
    $mode  = strtoupper($row['mode']);
    $rst   = $row['report'] ?: '59';

    echo "<CALL:" .   strlen($call)  . ">$call ";
    echo "<QSO_DATE:" . strlen($date) . ">$date ";
    echo "<TIME_ON:"  . strlen($time) . ">$time ";
    echo "<FREQ:"     . strlen($freq) . ">$freq ";
    echo "<MODE:"     . strlen($mode) . ">$mode ";
    echo "<RST_SENT:" . strlen($rst)  . ">$rst ";
    echo "<EOR>\n";
}
exit;
?>

Ce script est à placer dans votre administration (jamais en public). Il génère un fichier .ADI téléchargeable directement compatible avec Log11DX, ClusterDX et toutes les plateformes eQSL standardisées.

🔒 8. Bonnes pratiques de sécurité

Requêtes préparées obligatoires : n'interpolez jamais directement les données $_POST dans vos requêtes SQL. Utilisez toujours bindValue() ou bindParam(). Une injection SQL sur un log DX peut effacer des années d'historique.

Échappement des sorties : toujours utiliser htmlspecialchars() sur toutes les données affichées en HTML. Sans ça, un indicatif contenant <script> peut exécuter du JavaScript arbitraire chez vos visiteurs (XSS).

Administration protégée : les scripts d'ajout, de modification et d'export doivent être dans un dossier admin protégé par session PHP ou .htaccess. Ne jamais exposer ces formulaires sur le web public.

Sauvegarde régulière : copiez votre fichier .db sur un support externe une fois par semaine minimum. Voir le dossier archivage longue durée pour les bonnes pratiques complètes.