5.5 KiB
F3 Simple Blog
Blog simple avec Fat-Free Framework.
Structure
project/
├── config.local.ini # Surcharges locales (gitignored)
├── app/
│ ├── config.ini # Routes et variables F3
│ ├── bootstrap.php # Initialisation (DB, session, cache, erreurs)
│ ├── Controllers/
│ ├── Helpers/ # Fonctions utilitaires (App.php, Error.php)
│ ├── Models/ # DB\SQL\Mapper (Post, Media, User)
│ ├── Services/ # MarkdownService
│ └── Views/
├── db/
│ └── app.sqlite
├── logs/
│ ├── app.log
│ └── php-error.log
├── public/
│ ├── assets/ # Sources CSS/JS (servis minifiés via /min/@file)
│ └── uploads/
│ └── media/ # Images publiées (JPG conservé, PNG/WebP normalisés en PNG)
└── tmp/
├── cache/ # Cache F3 (pages publiques + assets minifiés)
└── uploads/ # Transit Web::receive() — nettoyé après chaque upload
Philosophie des dossiers runtime
Le projet sépare les données persistantes du runtime jetable :
tmp/= runtime temporaire, recréabledb/= base SQLite persistantelogs/= logs persistantspublic/uploads/media/= médias publiés et persistants
Autrement dit, tmp/ peut être vidé sans perte métier. Les données à sauvegarder restent hors de tmp/.
Fonctionnalités F3 utilisées
- Routage nommé —
config.ini [routes], filtrealiasdans les templates,reroute('@route')dans les contrôleurs - Cache HTTP + serveur — TTL déclarés directement dans
[routes],Cache::reset('.url')à la mutation - Assets minifiés —
Web::minify()viaAssetController(GET /min/@file) - Upload —
Web::receive()avec contrôle de taille, puis validation MIME/dimensions côté modèle - Images — normalisation des médias via GD (
JPGconservé,PNG/WebPconvertis enPNGpour préserver la transparence) - Markdown —
Markdown::instance()->convert()+ reconstruction DOM en liste blanche - Slugs —
Web::instance()->slug() - Session —
$f3->set('JAR', …), hooksbeforeRoute()sur les contrôleurs protégés, token CSRF créé seulement sur les vues qui contiennent des formulaires - Logging —
Logde F3 avec fallbackfile_put_contents - ORM —
DB\SQL\Mapper:paginate(),copyfrom(),cast(),find()
Prérequis
Développement local
- PHP 8.3+
- Composer
- Extensions PHP :
pdo_sqlite,dom,gd,mbstring
Déploiement Docker
- Docker
- Docker Compose
Configuration
Les paramètres par défaut sont dans app/config.ini.
Pour surcharger localement ou en production :
cp config.local.ini.example config.local.ini
Réglages minimums conseillés en production :
[globals]
app.env=prod
app.timezone=Europe/Paris
Le fichier config.local.ini sert uniquement aux surcharges d'environnement. Les chemins runtime restent les mêmes partout :
tmp/cache/pour le cache F3 et les assets minifiéstmp/uploads/pour les fichiers temporaires d'upload
Développement local
composer install
cp config.local.ini.example config.local.ini
php scripts/install.php
php -S 127.0.0.1:8080 -t public
Ouvre ensuite http://127.0.0.1:8080.
Créer un compte admin :
php scripts/create-admin.php admin
# mot de passe : 10 caractères minimum
Déploiement avec Docker
cp config.local.ini.example config.local.ini
# édite config.local.ini (app.env=prod, app.timezone, etc.)
docker compose up -d --build
Docker ne monte que les dossiers persistants (db/, logs/, public/uploads/media/) et laisse tmp/ dans le conteneur pour qu'il reste réellement éphémère.
Si config.local.ini n'existe pas, le conteneur démarre avec les valeurs par défaut de app/config.ini.
Le service écoute sur http://127.0.0.1:8888.
Créer un compte admin :
docker compose exec app php scripts/create-admin.php admin
# mot de passe : 10 caractères minimum
Cache public et navigation
Les pages publiques (/ et /posts/@slug) restent cacheables parce que leur rendu n'accède ni à la session, ni au token CSRF. La navigation publique affiche donc un lien statique vers la connexion / l'administration, tandis que les vues d'administration restent session-aware.
Médias et limites d'upload
- Formats acceptés à l'entrée :
JPG,PNG,WebP - Taille max du fichier reçu :
10 Mo - Dimensions max :
8000 × 8000 px - Limite de surface :
40 mégapixels - Sortie publiée :
JPGpour les sources JPEG,PNGpour les sources PNG/WebP
La médiathèque admin est paginée et le picker dans l'éditeur charge seulement les images les plus récentes pour éviter de charger toute la bibliothèque en mémoire à chaque formulaire.
Reverse proxy Caddy
Caddy sur le même hôte
blog.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8888
}
Caddy dans Docker
Si Caddy tourne aussi dans Docker, place-le sur le même réseau que app et cible directement le service :
blog.example.com {
encode zstd gzip
reverse_proxy app:80
}
Données à sauvegarder
db/— base SQLitepublic/uploads/media/— imageslogs/— optionneltmp/— non persistant, recréable
Mise à jour
docker compose up -d --build
Logs
- Applicatifs :
logs/app.log - PHP :
logs/php-error.log - Apache (conteneur) :
docker compose logs -f app