PHP typé de bout en bout.
Svelte pour l'interface.
Un fichier pour déployer.
Un framework PHP léger, sécurisé par défaut, où le PHP reste la source de vérité : les types TypeScript, les routes et le client d'API sont générés depuis vos contrôleurs.
- PHP 8.4+ (testé sur 8.5)
- Svelte 5 + TypeScript + Vite
- 0 dépendance en production
- SQLite / MySQL / PostgreSQL
- CSRF · CSP stricte · libsodium
Pourquoi M-PHP
Types de bout en bout
Une faute de frappe dans un nom de route, un champ de formulaire oublié ou un mauvais type de réponse sont des erreurs de compilation, sans OpenAPI ni serveur Node.
Léger à prendre en main
Un contrôleur, des attributs, une page Svelte. SQLite par défaut : rien à installer pour commencer.
Sécurisé par défaut
Requêtes préparées, CSRF, CSP sans inline, sessions durcies, uploads vérifiés sur le contenu, $fillable obligatoire.
Déploiement trivial
php bin/mphp package pour un zip, php bin/mphp compile pour un unique fichier PHAR. Pas de vendor/ ni de Node en production.
Démarrage
composer install
npm install
cp .env.example .env
php bin/mphp key:generate
php bin/mphp migrate
php bin/mphp dev # PHP http://127.0.0.1:8000 + Vite (rechargement à chaud)
Avec Docker : docker compose up --build puis localhost:8000.
| Dossier | Rôle |
|---|---|
app/Controllers | Contrôleurs ; les routes sont des attributs PHP |
app/Models | Modèles ($fillable obligatoire) |
app/Data | Données envoyées au navigateur, converties en types TypeScript |
app/Requests | Corps de requête validés et typés (FormRequest) |
resources/pages | Une page Svelte par fichier |
database/migrations | Migrations |
public/ | La seule racine web |
core/ | Le framework (Mphp\) |
Concepts
1. Une entrée validée et typée
final readonly class NameInput extends FormRequest
{
public function __construct(
#[Rule('required|string|max:50')]
public string $firstname,
) {}
}
2. Un contrôleur avec ses routes
#[Prefix('/api/names')]
final class NameController
{
#[Put('/{id:\d+}', name: 'names.update')]
public function update(NameInput $input, int $id): NameData // 422 automatique si invalide
{
$name = Name::find($id) ?? throw new HttpException(404, 'Name not found.');
return NameData::fromModel($name->update($input->toArray()));
}
}
3. Générer les types
php bin/mphp types:generate
4. Les utiliser côté Svelte
const updated = await api('names.update', { params: { id: 3 }, body: { firstname: 'Ada' } });
// ^ NameData : route, paramètres, corps et réponse viennent du PHP
const form = new Form('names.store', { firstname: '' });
// form.errors.firstname, form.processing, await form.submit()
Ce que le framework fournit
| Besoin | API |
|---|---|
| Requête / réponse | Request, Response::json/html/redirect/noContent |
| Retour d'action | Response ; tableau/objet → JSON ; chaîne → HTML ; null → 204 |
| Injection | Typez la dépendance dans le constructeur ou l'action |
| Middleware | 'csrf', 'throttle:5,60', ou votre classe |
| Validation | FormRequest + #[Rule], ou Validator::validate() |
| Base de données | $db->table('t')->where('a', '>=', 1)->get() ; Model::create/find/update/delete |
| Chiffrement | Crypto::encrypt/decrypt/sign ; Passwords::hash/verify |
| Upload | Uploader::store($_FILES['f'], 'image') |
| Erreurs | throw new HttpException(404, '…') |
Sécurité par défaut
| Risque | Protection |
|---|---|
| Injection SQL | Requêtes préparées partout, identifiants validés, opérateurs en liste blanche, update/delete sans where refusés |
| CSRF | Jeton synchronisé en session, middleware global (X-CSRF-Token) |
| XSS | Échappement par défaut (e(), Svelte), props en JSON sûr, CSP script-src 'self' sans inline |
| Mass-assignment | $fillable obligatoire sur les modèles |
| Fichiers sensibles | Seul public/ est servi ; .env et storage/ ne sont jamais exposés |
| Uploads | Type détecté sur le contenu, nom aléatoire, taille limitée |
| Chiffrement | libsodium (authentifié), nonce aléatoire, APP_KEY |
| Sessions | HttpOnly, SameSite=Lax, Secure en HTTPS, mode strict |
| Force brute | Middleware throttle sans Redis |
| Fuite d'information | Erreur 500 générique en production, détails seulement avec APP_DEBUG=true |
Barre de debug
Avec APP_DEBUG=true, chaque page HTML reçoit une barre en bas d'écran : statut, route, temps, mémoire et chaque requête SQL avec sa durée. Les requêtes répétées sont signalées (piste N+1). Elle fonctionne sans JavaScript, donc sous la CSP stricte. Les appels d'API reçoivent un en-tête Server-Timing, visible dans l'onglet Réseau du navigateur. En production, rien n'est ajouté.
Déploiement
Zip
php bin/mphp package produit dist/release.zip : front compilé et cache des routes inclus, sans vendor/ ni node_modules/.
PHAR
php bin/mphp compile produit dist/mphp.phar, un seul fichier. php mphp.phar serve suffit ; .env et storage/ restent à côté.
Docker
docker compose up --build : image PHP 8.5 + Apache, front compilé en amont, SQLite ou MySQL en option.
# Zip ou PHAR, sur le serveur
cp .env.example .env # APP_ENV=production, APP_DEBUG=false
php bin/mphp key:generate
php bin/mphp migrate
# racine web = public/ (le .htaccess racine suffit sur un mutualisé)
# PHAR seul
php mphp.phar key:generate && php mphp.phar migrate && php mphp.phar serve --port=8000
Nginx : root /chemin/vers/public; location / { try_files $uri /index.php?$query_string; } puis le bloc PHP-FPM habituel.
Commandes
| Commande | Rôle |
|---|---|
php bin/mphp dev | PHP + Vite ensemble (développement) |
php bin/mphp serve | Serveur PHP seul |
php bin/mphp routes | Liste des routes |
make:controller X · make:model X · make:migration create_x_table | Générateurs |
migrate · migrate:rollback · migrate:status | Base de données |
types:generate | Types TypeScript et helper de routes depuis le PHP |
key:generate | Clé de chiffrement APP_KEY |
build | Front de production + cache des routes |
package · compile | Zip prêt à envoyer · fichier PHAR unique |
composer test · npm run check | Tests Pest · vérification TypeScript/Svelte |