Tu as raison. Je n'ai rien compris.
La perfection, c'est ça :
Architecture SSG avec PHP
Objectif
Créer une architecture de génération de site statique avec PHP pur, sans framework.
Principe fondamental
- Chaque dossier métier contient un mini-programme PHP (process.php)
- L'intelligence est centralisée dans un dossier /libs
- Les fichiers générés (HTML, JSON, JS) sont servis statiquement
- Performance maximale car pas d'exécution PHP à chaque requête
Structure du projet
/votre-projet/
├── index.php
├── /libs/
│ └── ContentGenerator.php
├── /images/
│ ├── photo1.jpg
│ ├── photo2.png
│ └── process.php
├── /documents/
│ ├── data1.csv
│ ├── data2.csv
│ └── process.php
├── /api/
│ ├── users.csv
│ └── process.php
└── /public/
├── gallery.html
├── data.json
├── users.json
└── app.js
Code complet
1. Librairie centrale : /libs/ContentGenerator.php
<?php
class ContentGenerator {
public static function generateGallery($sourceDir, $outputFile) {
$images = glob($sourceDir . '/*.{jpg,jpeg,png,gif}', GLOB_BRACE);
$html = "<!DOCTYPE html>\n";
$html .= "<html>\n<head>\n";
$html .= "<title>Galerie d'images</title>\n";
$html .= "<style>
.gallery { display: flex; flex-wrap: wrap; gap: 10px; }
.gallery img { width: 200px; height: 150px; object-fit: cover; }
</style>\n";
$html .= "</head>\n<body>\n";
$html .= "<h1>Galerie</h1>\n";
$html .= "<div class='gallery'>\n";
foreach ($images as $image) {
$filename = basename($image);
$html .= " <img src='/$sourceDir/$filename' alt='$filename'>\n";
}
$html .= "</div>\n</body>\n</html>";
file_put_contents($outputFile, $html);
return count($images) . " images intégrées dans " . basename($outputFile);
}
public static function generateJson($sourceDir, $pattern, $outputFile, $callback) {
$files = glob($sourceDir . '/' . $pattern);
$data = [];
foreach ($files as $file) {
$data[] = $callback($file);
}
file_put_contents($outputFile, json_encode($data, JSON_PRETTY_PRINT));
return count($data) . " éléments exportés dans " . basename($outputFile);
}
public static function generateJs($data, $outputFile, $varName = 'APP_DATA') {
$js = "// Fichier généré automatiquement le " . date('Y-m-d H:i:s') . "\n";
$js .= "const $varName = " . json_encode($data, JSON_PRETTY_PRINT) . ";\n";
$js .= "console.log('$varName chargé avec ' + Object.keys($varName).length + ' éléments');\n";
file_put_contents($outputFile, $js);
return "Fichier JS généré : " . basename($outputFile);
}
}
2. Mini-programme : /images/process.php
<?php
require_once __DIR__ . '/../libs/ContentGenerator.php';
$result = ContentGenerator::generateGallery(
__DIR__,
__DIR__ . '/../public/gallery.html'
);
echo $result;
3. Mini-programme : /documents/process.php
<?php
require_once __DIR__ . '/../libs/ContentGenerator.php';
$result = ContentGenerator::generateJson(
__DIR__,
'*.csv',
__DIR__ . '/../public/data.json',
function($file) {
$rows = array_map('str_getcsv', file($file));
$headers = array_shift($rows);
$data = [];
foreach ($rows as $row) {
$data[] = array_combine($headers, $row);
}
return $data;
}
);
echo $result;
4. Mini-programme : /api/process.php
<?php
require_once __DIR__ . '/../libs/ContentGenerator.php';
$users = [
['id' => 1, 'name' => 'Alice', 'email' => 'alice@example.com'],
['id' => 2, 'name' => 'Bob', 'email' => 'bob@example.com']
];
$result = ContentGenerator::generateJson(
__DIR__,
'*.csv',
__DIR__ . '/../public/users.json',
function($file) use ($users) {
return $users;
}
);
echo $result;
5. Index principal : /index.php
<?php
if (isset($_GET['regenerate'])) {
echo "<h2>Régénération en cours...</h2>";
include __DIR__ . '/images/process.php';
include __DIR__ . '/documents/process.php';
include __DIR__ . '/api/process.php';
echo "<p>✅ Tous les contenus ont été régénérés</p>";
}
$page = $_GET['page'] ?? 'home';
switch ($page) {
case 'gallery':
include __DIR__ . '/public/gallery.html';
break;
case 'api':
header('Content-Type: application/json');
readfile(__DIR__ . '/public/users.json');
break;
case 'data':
header('Content-Type: application/json');
readfile(__DIR__ . '/public/data.json');
break;
default:
echo "<h1>Accueil</h1>";
echo "<ul>
<li><a href='?page=gallery'>Galerie d'images</a></li>
<li><a href='?page=data'>Données CSV en JSON</a></li>
<li><a href='?page=api'>API utilisateurs</a></li>
<li><a href='?regenerate=1'>Régénérer tout le contenu</a></li>
</ul>";
}
Utilisation depuis votre site
Page HTML classique
<iframe src="/public/gallery.html"></iframe>
<a href="/public/gallery.html">Voir la galerie</a>
Appel AJAX pour récupérer du JSON
fetch('/public/users.json')
.then(response => response.json())
.then(users => {
console.log('Utilisateurs chargés :', users);
});
fetch('/public/data.json')
.then(response => response.json())
.then(data => {
console.log('Données CSV :', data);
});
Fichier JS intégré dans votre site
<script src="/public/app.js"></script>
<script>
console.log(APP_DATA);
</script>
Optimisations et bonnes pratiques
Traitement uniquement sur demande
Mauvaise pratique (à éviter) :
include __DIR__ . '/images/process.php'; // Traite à chaque chargement de page !
include __DIR__ . '/documents/process.php';
Bonne pratique :
if (isset($_GET['process_images'])) {
include __DIR__ . '/images/process.php';
}
if (isset($_GET['process_csv'])) {
include __DIR__ . '/documents/process.php';
}
Traitement asynchrone pour les opérations lourdes
if (isset($_GET['process'])) {
exec('php ' . __DIR__ . '/images/process.php > /dev/null 2>&1 &');
echo "Traitement lancé en arrière-plan";
}
Limiter le nombre d'éléments traités
public static function processImages($directory, $limit = 10) {
$files = glob($directory . '/*.{jpg,jpeg,png,gif}', GLOB_BRACE);
$files = array_slice($files, 0, $limit);
foreach ($files as $file) {
// Traitement...
}
}
Mettre en cache les résultats
public static function processImages($directory) {
$cacheFile = $directory . '/.processed_cache.json';
if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < 3600)) {
return json_decode(file_get_contents($cacheFile), true);
}
$results = [];
foreach ($files as $file) {
$results[] = self::resizeImage($file, 800, 600);
}
file_put_contents($cacheFile, json_encode($results));
return $results;
}
Éviter les exécutions simultanées (verrou)
if (!file_exists(__DIR__ . '/.lock')) {
file_put_contents(__DIR__ . '/.lock', time());
require_once __DIR__ . '/../libs/ContentGenerator.php';
$result = ContentGenerator::processImages(__DIR__);
unlink(__DIR__ . '/.lock');
echo $result;
} else {
echo "Traitement déjà en cours...";
}
Cron / Tâche planifiée
# Toutes les 5 minutes
*/5 * * * * php /chemin/projet/images/process.php
# Toutes les heures pour les documents
0 * * * * php /chemin/projet/documents/process.php
# Tous les jours à minuit pour tout régénérer
0 0 * * * php /chemin/projet/index.php?regenerate=1
Aller plus loin (Expert niveau 2)
Watcher automatique
$watchedDirs = ['/images', '/documents', '/api'];
while (true) {
foreach ($watchedDirs as $dir) {
if (hasChanged($dir)) {
include __DIR__ . $dir . '/process.php';
echo "✅ Régénéré : $dir\n";
}
}
sleep(5);
}
function hasChanged($dir) {
$hashFile = __DIR__ . $dir . '/.hash';
$currentHash = md5_file(__DIR__ . $dir);
if (!file_exists($hashFile)) {
file_put_contents($hashFile, $currentHash);
return true;
}
$oldHash = file_get_contents($hashFile);
if ($oldHash !== $currentHash) {
file_put_contents($hashFile, $currentHash);
return true;
}
return false;
}
Versionnement des assets
public static function generateWithVersion($file, $content) {
$version = md5($content);
$versionedFile = str_replace('.', ".$version.", $file);
file_put_contents($versionedFile, $content);
return $versionedFile;
}
Incremental build
public static function incrementalBuild($sourceDir, $outputDir) {
$hashFile = $outputDir . '/.hash';
$currentHash = md5_file($sourceDir);
if (file_exists($hashFile) && file_get_contents($hashFile) === $currentHash) {
return "✅ Rien à régénérer";
}
file_put_contents($hashFile, $currentHash);
return "🔄 Régénéré";
}
Interface d'administration
// admin.php
?>
<!DOCTYPE html>
<html>
<head>
<title>Administration des contenus</title>
<style>
body { font-family: Arial; padding: 20px; }
button { padding: 10px 20px; margin: 5px; cursor: pointer; }
.success { color: green; }
</style>
</head>
<body>
<h1>Administration des contenus</h1>
<form action="regenerate.php" method="post">
<button type="submit" name="type" value="images">🔁 Régénérer la galerie</button>
<button type="submit" name="type" value="documents">🔁 Régénérer les données</button>
<button type="submit" name="type" value="api">🔁 Régénérer l'API</button>
<button type="submit" name="type" value="all" style="background: #4CAF50; color: white;">🔄 Tout régénérer</button>
</form>
<?php if (isset($_GET['status'])): ?>
<p class="success">✅ <?= htmlspecialchars($_GET['status']) ?></p>
<?php endif; ?>
</body>
</html>
// regenerate.php
$type = $_POST['type'] ?? '';
switch ($type) {
case 'images':
include __DIR__ . '/images/process.php';
$status = "Galerie régénérée";
break;
case 'documents':
include __DIR__ . '/documents/process.php';
$status = "Données régénérées";
break;
case 'api':
include __DIR__ . '/api/process.php';
$status = "API régénérée";
break;
case 'all':
include __DIR__ . '/images/process.php';
include __DIR__ . '/documents/process.php';
include __DIR__ . '/api/process.php';
$status = "Tout a été régénéré";
break;
default:
$status = "Action inconnue";
}
header('Location: admin.php?status=' . urlencode($status));
Le secret : DIR
Chaque mini-programme utilise __DIR__ qui donne automatiquement le chemin de son propre dossier :
| Fichier | __DIR__ donne |
|---|---|
| /images/process.php | /votre-projet/images |
| /documents/process.php | /votre-projet/documents |
| /api/process.php | /votre-projet/api |
Plus jamais de problèmes de chemins relatifs.
Avantages de cette architecture
- Pas de souci de chemins
- Code ultra-léger
- Intelligence centralisée
- Facile à maintenir
- Autonome
- Extensible
- Performance
- Cache intégré
- Découplage
- Scalabilité
- Sécurité
- Flexibilité