Guides
Créer une page
phpx make:page génère un écran natif dans lib/pages/ (namespace Engine\App\) et un contrôleur backend jumeau dans lib/backend/src/Controller/ (namespace Backend\), puis enregistre les deux routes automatiquement — la route de l'écran dans public/index.php, la route API dans lib/backend/src/Kernel.php.
bin/phpx make:page Profil /profilSans route explicite, elle est dérivée du nom en kebab-case (make:page Profil → /profil). Le fichier généré, prêt à modifier :
<?php
namespace Engine\App;
use Engine\Native\Scaffold;
use Engine\Native\Center;
use Engine\Native\Widget;
use Engine\Native\Text;
use Engine\Native\Tokens;
final class ProfilPage
{
public static function build(float $screenWidth, float $screenHeight): Widget
{
return new Scaffold(
new Center(new Text('ProfilPage', Tokens::TEXT_TITLE, Tokens::ink()->toHex(), bold: true)),
$screenWidth,
$screenHeight,
);
}
}Un écran est une classe finale avec une seule méthode statique : build(float $screenWidth, float $screenHeight): Widget. Pas d'héritage, pas de cycle de vie à implémenter — le contrat entier tient dans l'interface Widget (layout()/paint()), voir plus bas.
Créer une entité
phpx make:entity génère une entité (lib/backend/src/Entity/) et un repository jumeau (lib/backend/src/Repository/) qui crée sa propre table SQLite au premier appel (CREATE TABLE IF NOT EXISTS) et expose find()/save() — à toi d'ajouter tes propres colonnes aux deux méthodes au fur et à mesure que l'entité grandit au-delà d'un simple id.
bin/phpx make:entity ProduitLe repository généré utilise Engine\Database\Database::connection() (package phpnitro/database, Doctrine DBAL) — SQLite local par défaut, ou MySQL/PostgreSQL si DATABASE_URL est défini dans .env, sans changement de code.
Composer un écran avec les widgets natifs
Tout ce qui s'affiche implémente Engine\Native\Widget — un nœud d'un arbre à deux passes, exactement le contrat de RenderObject en Flutter : layout(Constraints): Size propose une taille, paint(Canvas, x, y) empile des commandes de dessin absolues. Le package phpnitro/ui (Engine\Native\*) fournit une cinquantaine de nœuds prêts à composer :
- Mise en page —
Flex::row()/::column(),Stack,Wrap,Padding,Align,Center,SizedBox,Container. - Écran —
Scaffold(appBar/bottomNav/fab/drawer pinnés, le corps défile en dessous),AppBar,BottomNavigation,Drawer,Fab. - Texte —
Text,RichText(multi-styles, wrap réel),TextSpan. - Contrôles —
Button,Checkbox,Toggle,SelectBox,TextField,DatePicker/TimePicker,ConfirmButton,AlertButton. - Listes & cartes —
ListTile,Card,Table,LazyList(liste virtualisée avec fenêtre de préchargement),Reorderable,Dismissible(glisser pour supprimer, 100% côté client). - Média & animation —
Image,ImageCircle,IconCircle,Icon/MaterialIcons,Lottie,VideoPlayer,Animated,Hero,CustomPaint. - Autres —
MapView(osmdroid/OpenStreetMap ou Mapbox/Google si configurés),GestureDetector,Async/AsyncTask,ClientTabs,PageView,Spinner,CircularProgress/ProgressBar,Banner,Divider.
Le catalogue complet documente chacun avec un exemple réel et son constructeur.
Exemple réel — un compteur persistant :
<?php
namespace Engine\App;
use Engine\Native\EdgeInsets;
use Engine\Native\Button;
use Engine\Native\Scaffold;
use Engine\Native\Container;
use Engine\Native\Flex;
use Engine\Native\Widget;
use Engine\Native\Padding;
use Engine\Native\Text;
use Engine\Native\Tokens;
use Engine\Preferences\Preferences;
final class CompteurPage
{
public static function build(float $screenWidth, float $screenHeight): Widget
{
$count = (int) Preferences::get('compteur', '0');
$body = new Padding(
EdgeInsets::all(Tokens::SPACE_XL),
Flex::column([
new Text((string) $count, Tokens::TEXT_DISPLAY, Tokens::ink()->toHex(), bold: true),
new Padding(
EdgeInsets::only(top: Tokens::SPACE_LG),
new Button('Incrémenter', 'increment'),
),
]),
);
return new Scaffold($body, $screenWidth, $screenHeight);
}
}Actions et état
Un widget interactif (Button, Tappable, ListTile...) prend une chaîne $action — pas un callback. Au tap, le client Kotlin fait le hit-test lui-même, appelle onTap(action, ...), qui reconnaît un préfixe connu (navigate:écran, back, tab:écran, toggle:champ, device:...) ou, à défaut, renvoie l'action telle quelle en ?action=... à la prochaine requête. Ton écran lit $_GET['action'] avant de construire son arbre :
// Dans public/index.php, avant de dispatcher vers l'écran :
if ($action === 'increment') {
Preferences::set('compteur', (string) ((int) Preferences::get('compteur', '0') + 1));
}Rien ne persiste entre deux requêtes en dehors de $_SESSION, Engine\Preferences\Preferences (clé-valeur persistant, JSON-encodé, package phpnitro/preferences) ou une vraie table via Engine\Database\Database.
Design tokens
Engine\Native\Tokens centralise l'échelle de design plutôt que des nombres choisis au cas par cas : espacements (SPACE_XS à SPACE_XXL), rayons (RADIUS_SM à RADIUS_PILL), échelle typographique (TEXT_CAPTION à TEXT_DISPLAY) et rôles de couleur (Tokens::ink(), ::inkSecondary(), ::inkMuted(), ::surface(), ::surfaceMuted(), ::border(), chacune renvoyant un Engine\Color avec ->toHex()). Utilise ces constantes plutôt que des valeurs brutes : c'est ce qui garde une app cohérente écran après écran.
phpnitro.yml — paiements, cartes, Firebase
phpnitro.yml, à la racine du projet, déclare le nom/description/version de l'app ainsi que les fournisseurs de paiement/cartes/Firebase utilisés — juste leurs noms, pas où sont leurs clés. Chaque fournisseur a un nom de variable .env fixe, défini une seule fois par le framework (voir paymentGatewayEnvVars()/mapProviderEnvVars() dans bin/phpx) :
name: Mon application
payments: [stripe, fedapay, paypal, razorpay, paydunya]
maps: [mapbox]
firebase: trueLes vraies valeurs vont dans .env sous ces noms fixes (STRIPE_PUBLIC_KEY/STRIPE_SECRET_KEY, MAPBOX_ACCESS_TOKEN, FIREBASE_SERVICE_ACCOUNT_JSON/FIREBASE_PROJECT_ID/FIREBASE_WEB_API_KEY...), rien à redéclarer dans phpnitro.yml. Trois commandes lisent ce fichier et croisent avec .env pour dire ce qui est réellement configuré, sans jamais afficher les clés : phpx payments, phpx maps, phpx firebase. phpx icon régénère l'icône de lancement Android (mipmaps + icône adaptative) depuis la clé icon (PNG carré) et icon_background.
Firebase
FirebaseAuth (connexion des utilisateurs finaux, clé web API client-safe) peut tourner depuis le PHP embarqué sur l'appareil. FirebaseMessaging (envoi de push FCM) et Firestore utilisent un compte de service — un vrai secret serveur — et doivent tourner depuis ton propre backend hébergé, jamais depuis le PHP qui s'exécute sur le téléphone de chaque utilisateur.
Publier sur Android
Un projet scaffoldé ne contient pas le moteur natif comme code source : android/app/build.gradle.kts déclare implementation("com.github.phpnitro:android-engine:v1.0.0"), résolu via JitPack depuis le dépôt github.com/phpnitro/android-engine (miroir en lecture seule du dossier android/engine/ du monorepo du framework) — exactement comme un projet Flutter référence le SDK Flutter sans le copier.
bin/phpx build:android releaseGénère android/app/build/outputs/apk/release/app-release.apk, signé selon android/app/build.gradle.kts/keystore.properties (copie keystore.properties.example et renseigne ton propre keystore avant un vrai build de release). La checklist complète (fiche Play Store, politique de confidentialité, captures) reste à ta charge.