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

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