Utiliser du PHP en mode offline

PHP est une techno web. Pour l’un de nos clients, nous avons développé un CRM qui est donc accessible de partout dans le monde… à condition d’avoir du réseau !
Que faire quand il faut pouvoir utiliser le même applicatif sur le portable du commercial, mais chez un client sans réseau, et sans avoir à redévelopper une application native ?

Et bien pourquoi ne pas embarquer un environnement PHP directement sur le portable ?
Trop compliqué à installer ? Plus si sûr !

1- Se passer de serveur web

Depuis la version 5.4 de PHP, celui-ci intègre directement un serveur web interne. Il est prévu uniquement pour du développement et gère mal les accès concurrents, mais semble largement suffisant pour un usage local et exclusif sur le poste utilisateur.
http://php.net/manual/fr/features.commandline.webserver.php

Il suffit dont de lancer un

php -S localhost:8000

Première difficulté, la réécriture d’url ne passe plus.
Celle-ci est en effet gérée par un Apache via un fichier .htaccess

La solution : réécrite la réécriture (!) en php à l’aide de preg_match, et passer par un script de routage, par exemple :

if(preg_match('/^\/([a-zA-Z\-]+)\/([^\/^,]*),*([^\/]*)\/([^\/^,]*),([^\/]+)\/*$/', $_SERVER["REQUEST_URI"], $matches)) {
$_REQUEST['lang'] = $matches[1];
$_REQUEST['page'] = $matches[2];
$_REQUEST['pagevar'] = $matches[3];
$_REQUEST['content'] = $matches[4];
$_REQUEST['contentvar'] = $matches[5];
include __DIR__ . '/index.php';
return false;
}

Le serveur sera alors lancé via

 php -S localhost:8000 routing.php

Seconde difficulté, les sessions ne se créent pas où il faut. Et les paramètres par défaut du CLI ne collent pas forcément au besoin.

On va donc utiliser un php.ini custom dans lequel on précisera que les sessions doivent s’enregistrer dans le répertoire local session/
[Session]

 session.save_path = "./session"

Finalement, le serveur sera lancé avec

 php -c ./php.ini -S localhost:8000 routing.php

2- Se passer de serveur de base de données

On s’est passé d’Apache, peut-on aussi se passer de mySQL pour ne plus avoir de serveur ? Et bien oui, grâce à SQLite.
La base sera donc dans un simple fichier, sans serveur. C’est un peu plus lent, mais pour un usage d’une seule personne sans concurrence, nos tests ont montré que c’est très raisonnable.

La première étape consiste donc à migrer la base mySQL existante en SQLite.
Pour cela, on passe par le script mysql2sqlite.sh
https://gist.github.com/esperlu/943776

Petite astuce: il faut prendre une version plus récente dans les commentaires et non directement la version d’origine : celles-ci corrigent des problèmes avec les foreign keys, et autoincrement.
Toutefois, le résultat n’était pas encore 100% utilisable, certains commentaires étaient mal convertis, mais assez rapidement nous avons réussi à générer notre base SQLite.

Grâce à PDO (ainsi que Propel pour notre test), une fois les données converties, le passage de mySQL à SQLite est totalement transparent pour l’applicatif.

3- Un peu de packaging

Pour terminer le prototype et le rendre autonome sous Windows, nous avons packagé le tout dans un répertoire unique.

Nous avons ainsi mis directement les binaires PHP : php.ext et php5.dll dans le répertoire de l’application, en version 32 bits pour maximiser la compatibilité.
php.exe nécessite l’installation des runtime Visual C++ 2012, mais il semblerait que l’installation de la simple DLL msvcr110.dll dans le répertoire soit suffisante.

Pour utiliser SQLite, il faut l’extension correspondante : on garde donc le répertoire ext/  et on précise son emplacement dans le php.ini embarqué.

Pour terminer le tout et permettre un lancement simple par un utilisateur, nous avons finalement mis en place un script JS Windows Scripting Host (WSH)

var WindowStyle_Hidden = 0
var objShell = WScript.CreateObject("WScript.Shell")
var php = objShell.Run("cmd.exe /c start.bat", WindowStyle_Hidden)
var chrome = objShell.Run("chrome.exe --app=http://localhost:8000", 1, true)
var chrome = objShell.Run("taskkill /F /IM php.exe", 0, true)

Il suffit que l’utilisateur double-clique sur ce script JS pour que cela lance le PHP, puis lance un chrome (qui doit préalablement être installé) directement sur la bonne URL en mode « application » (donc sans barre d’url ou autre).
Il a donc l’impression d’utiliser une application native.
Dés qu’il ferme la fenêtre, le JS passe à la ligne suivante qui va couper l’exécution du PHP (cette coupure est un peu violente et pourrait être améliorée).

Conclusion

Au final, notre prototype tient dans un répertoire indépendant qui peut être déplacé d’un poste Windows à l’autre facilement sans avoir besoin d’installation complexe.
Il pourrait être démarré d’une clé USB par exemple.

Il peut-être lancé d’un simple double-clic comme une application native et surtout fonctionne sans aucun réseau.

Prochaines étapes

Pour aller plus loin et finaliser le concept, il faudra bien entendu remettre en place une connexion avec le serveur afin de synchroniser les données, autrement l’application serait fonctionnelle mais peu utile.
Une difficulté sera la gestion des conflits : comme gérer une donnée qui peut-être modifiée depuis plusieurs applications locales déconnectées ?
Dans un premier temps, nous mettrons en place un mécanisme de « lock » : l’utilisateur choisira quand il aura du réseau les données qu’il souhaite pouvoir modifier localement, et il les verrouillera. Les autres ne lui seront accessibles qu’en lecture seule.

Il faudra également améliorer le packaging et mieux gérer les mises à jour applicatives.
Pour cela, une idée serait de convertir le code applicatif dans un fichier PHAR unique, limitant ainsi la tentation pour les utilisateurs de venir jouer avec le code source.
Pour rendre le tout totalement autonome, il serait intéressant également d’embarquer un Chrome, ou peut-être un Chromium directement au sein de l’applicatif, garantissant en plus l’utilisation d’une version unique, ce qui simplifiera la maintenance.
Et pour finir, il faudra également prévoir la création d’un véritable install Windows, capable également de faire des mises à jour propres sans effacer les données, et rajoutant un bel icône de démarrage pour une expérience utilisateur complète.

A suivre…

You may also like...