Symfony Messenger et rabbitmq

Symfony 4 apporte un nouveau composant qui va nous permettre de brancher notre application sur un, ou des, brokers de messages. Grâce à ce composant, on va pouvoir accélérer notre application en traitant de façon asynchrone tout ce qui n’est pas strictement nécessaire à l’affichage de nos pages. Ce module maintenu par la Core Team Symfony va à terme remplacer les bundles existants. 

Use case

Un exemple couramment utilisé pour expliquer pourquoi c’est nécessaire d’utiliser ce genre de technique est le suivant:

Un utilisateur passe une commande sur mon site de e-commerce. Il a payé sa commande et attend juste le message de confirmation. Si je traite tout en synchrone je vais devoir réaliser plusieurs traitements avant de lui rendre la main:

  1. Changer le statut de sa commande
  2. Mettre à jour les stocks et invalider les caches qui correspondent
  3. Envoyer un email de confirmation au client
  4. Prévenir le service de préparation de cette nouvelle commande
  5. Envoyer des metrics pour suivre le volume des ventes

Sur les 5 tâches citées, le client n’a besoin que de la première de façon immédiate. Les autres peuvent se faire dans les secondes ou minutes qui suivent. 

Au-delà du simple point de performance il y a aussi le problème de responsabilité unique. Si toute cette logique est dans le contrôleur, vous allez devoir gérer tout un ensemble de règles disparates. Le contrôleur va forcement devenir inmaintenable au bout d’un moment.

AMQP

Pour pouvoir différer les autres tâches, il va falloir que je pousse des messages dans des queues (type rabbitmq, SQS) et que des workers viennent les consommer pour réaliser les traitements.

Sur cet exemple, de Microsoft, on voit bien le principe d’un broker de message. Un ou plusieurs producteurs (senders) envoient des messages dans une queue et ils sont dépilés par un ou plusieurs consommateurs (receivers).

Dans le meilleur des mondes, vous n’avez qu’une technologie de service de queue et peut-être même qu’un seul serveur à interroger. Dans ce cas, c’est assez simple et vous utilisez sûrement déjà un client php pour le faire.

Mais si vous êtes à cheval sur plusieurs technos/ serveurs, c’est vite un casse-tête car il va falloir gérer plusieurs protocoles et serializers.

Symfony Messenger

Le composant Messenger va vous permettre de gérer les problèmes de techno et de serveurs grâce à de la configuration YML. Dans votre implémentation vous n’aurez pas à vous soucier de comment va partir votre message, ni de comment le sérializer. Le composant est compatible avec les brokers de messages AMQP (la plupart).

  • Message : Un objet PHP serializable
  • Bus : Le composant qui va s’occuper de la communication avec le queue manager et d’appliquer les middlewares que l’on aura pu enregistrer (logs, debug,…)
  • Handler : La classe qui va recevoir un message à exécuter. C’est cette classe qui va tenir la logique métier.
  • Receiver : Déserialize le message reçu via le bus et le transmet au bon handler.
  • Sender : Sérialize le message et le transmet au queue manager via le bus.

Nous allons appliquer ce principe pour le cas de l’envoi du mail de confirmation de commande. Il faudra répéter le pattern pour chacun des autres types d’action à effectuer.

Mise en application

Installation du composant

Le composant s’installe à l’aide de composer via la commande :

composer require symfony/messenger

Symfony flex va s’occuper automatiquement d’enregistrer le bundle et créer le fichier de configuration par défaut.  Le composant vient avec son intégration à la Symfony toolbar et au profiler. Il est donc possible de voir en détail les messages dispatchés lors d’un hit.

Configuration du composant

Je vais prendre le cas d’un rabbitmq. Il va falloir veiller à installer et activer l’extension php amqp.

Une fois que c’est fait, nous pouvons éditer le fichier de configuration de messenger pour y ajouter nos transports.

parameters:
messenger.transport.default_serialization_context: {groups: [messenger]}
framework:
messenger:
transports:
# les Data Source Name pour mes différents transports
amqp_mailer: '%env(MESSENGER_TRANSPORT_DSN_MAILER)%'
amqp_default: '%env(MESSENGER_TRANSPORT_DSN)%'
routing:
# En fonction du type de message on peut router le message
# pour ma part j'utilise des interface
'App\Message\Interfaces\Mailer': amqp_mailer
# Tout ce qui n'a pas matché avance passera ici
'*': amqp_default

Dans votre fichier .env à la racine de votre dépôt, il faudra ajouter les différents Data Source Name.

###> symfony/messenger ###
MESSENGER_TRANSPORT_DSN_MAILER=amqp://LOGIN:PASSWORD@HOST:5672/%2f/mails
MESSENGER_TRANSPORT_DSN=amqp://LOGIN:PASSWORD@HOST:5672/%2f/default
###< symfony/messenger ###
view raw .env hosted with ❤ by GitHub

Création de notre message

Le message est un simple objet php.

namespace App\Message\Interfaces;
interface Message
{
}
view raw message hosted with ❤ by GitHub
L’interface par défaut de mes messages (Optionnel mais pratique à l’usage)
<?php
namespace App\Message\Interfaces;
interface Mailer extends Message
{
}
view raw mailer hosted with ❤ by GitHub
L’interface des objets de type mail
<?php
namespace App\Message;
use App\Message\Interfaces\Mailer;
class ConfirmCommandMailer implements Mailer
{
/**
* @var int
*/
protected $commandId;
/**
* ConfirmCommandMailer constructor.
* @param int $commandId
*/
public function __construct(int $commandId)
{
$this->commandId = $commandId;
}
/**
* @return int
*/
public function getCommandId() :int
{
return $this->commandId;
}
}

Dans mon message j’envoie les id des différentes entités. Selon vos besoins vous pouvez directement mettre les entités.

Envoie du message dans le broker

Maintenant que nous avons notre objet de message nous allons pouvoir le pousser.

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Messenger\MessageBusInterface;
use App\Message\ConfirmCommandMailer;
class CommandController extends Controller
{
/**
* @var MessageBusInterface
*/
private $bus;
/**
* CommandController constructor.
*/
public function __construct(MessageBusInterface $bus)
{
$this->bus = $bus;
}
/**
* @Route("/customer/{idCustomer}/command/{idCommand}/confirm", name="confirm_command", methods={"POST"})
* @param $name
*/
public function index($idCustomer, $idCommand)
{
// check des params
// changement du statut de la commande
//…
// on envoie notre message dans le broker
$this->bus->dispatch(new ConfirmCommandMailer($idCommand));
// rendu du tpl
}
}

Traitement du message

Pour le moment, votre application ne fonctionnera pas car Symfony Messenger refusera de prendre en compte un message dont il ne connait pas le handler.

<?php
namespace App\Message\Handler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\MessageBusInterface;
use App\Message\ConfirmCommandMailer;
use \Swift_Mailer;
/**
* Class ConfirmCommandMailerHandler
*/
class ConfirmCommandMailerHandler
{
/**
* @var Swift_Mailer
*/
protected $mailer;
/**
* @var EntityManagerInterface
*/
protected $entityManager;
/**
* ConfirmCommandMailerHandler constructor.
* @param \Swift_Mailer $mailer
* @param EntityManagerInterface $entityManager
*/
public function __construct(Swift_Mailer $mailer, EntityManagerInterface $entityManager)
{
$this->mailer = $mailer;
$this->entityManager = $entityManager;
}
/**
* @param ConfirmCommandMailer $message
*/
public function __invoke(ConfirmCommandMailer $message)
{
$command = $this->entityManager->getRepository(Command::class)->find($message->getCommandId());
// verifier que les données sont cohérantes
$message = (new \Swift_Message('Commande confirmée'))
->setFrom('no-reply@example.com')
->setTo($command->getCustomer()->getEmail())
->setBody(
"Commande #" . $command->getId() . " confirmée"
)
;
$this->mailer->send($message);
}
}

Il faut maintenant enregistrer notre handler dans le container avec le tag ‘messenger.message_handler’.

App\Message\Handler\ConfirmCommandMailerHandler:
tags: ['messenger.message_handler']
view raw service.yml hosted with ❤ by GitHub

À partir de ce moment, Symfony va vous autoriser à dispatcher les messages ConfirmCommandMailer. Pour cet exemple, j’ai utilisé  Swift Mailer mais libre à vous d’utiliser une autre librairie.

Le composant va utiliser la reflection PHP pour détecter le handler qui doit être utilisé pour un message. Il va regarder le type du paramètre passé à la fonction __invoke. 

Lancer le worker

Maintenant que l’on a tout ce qu’il nous faut, il ne reste plus qu’à lancer notre consommateur. Il faudra lancer au minimum autant de workers que de channels.

bin/console messenger:consume-messages amqp_mailer

Il y a pas mal d’options disponibles pour limiter la durée de vie du daemon, la mémoire allouée, le temps de pause entre chaque message traité… Pour ma part je lance toujours au moins deux consommateurs pour un type de message et je fais en sorte qu’ils se tuent automatiquement tous les n messages traités.

Voici une démonstration sous forme de GIF de ce que l’on vient de faire. Pour simplifier la démonstration, j’ai fait une seconde commande Symfony qui pousse notre message dans la queue.

En production

En production il faut automatiser le lancement du daemon et le relancer en cas de crash. Pour ce faire vous pouvez utiliser supervisor avec la config suivante :

[program:amqp_mailer]
command=php /symfony/bin/console messenger:consume-messages amqp_mailer
startsecs = 0
stdout_logfile=/tmp/supervisord-amqp.log
stdout_logfile_maxbytes=10MB
view raw gistfile1.txt hosted with ❤ by GitHub

Généralement je fais tourner les workers dans des conteners docker dans un cluster swarm. De cette manière je peux gérer le nombre de consommateurs par type de message à la volée. Voici un exemple de Docker file qui peut faire tourner un worker symfony messenger. Je l’ai fait pour un projet perso, il n’est donc pas parfaitement optimisé pour de la vraie prod. 

FROM php:7.2
# Composer
RUN php -r "copy('https://getcomposer.org/installer&#39;, 'composer-setup.php');" \
&& php -r "if (hash_file('SHA384', 'composer-setup.php') === '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
&& php composer-setup.php –install-dir=/bin –filename=composer \
&& php -r "unlink('composer-setup.php');"
# amqp
RUN apt-get update && apt-get install -y librabbitmq-dev libssh-dev \
&& docker-php-ext-install opcache bcmath sockets \
&& pecl install amqp \
&& docker-php-ext-enable amqp
# core tools
RUN apt-get install -y git-core
# php exts
RUN docker-php-ext-install pdo pdo_mysql zip mbstring
RUN mkdir /app
WORKDIR /app
env QUEUE_NAME amqp_default
ENTRYPOINT ["sh", "-c", "/app/bin/console messenger:consume-messages ${QUEUE_NAME} –limit=100 –time-limit=900 -n"]
Exemple de docker file simple pour un worker Symfony messenger

Et voici comment l’utiliser dans le cadre d’un docker compose :

version: '3'
services:
worker_mailer:
image: ma-super-image-de-worker:latest
environment:
– QUEUE_NAME=amqp_mailer
volumes:
– "/PATH/VERS/MON/APP/SF:/app:rw"
deploy:
mode: replicated
replicas: 2

Vous pouvez maintenant faire un docker-compose up ou le lancer sur un cluster swarm via la commande docker stack deploy.

Limitation

Pour le moment il n’y a pas de solution « out of the box » pour gérer le re-jeux des messages en cas d’erreur. C’est à vous de catcher les erreurs et soit de les pousser dans une autre queue, d’écrire un log, une métrique, ou de les stocker en base pour les identifier au besoin. C’est une lacune assez importante de la librairie qui devrait être corrigée.

Les problèmes possibles

Que veux dire l’erreur : Attempted to load class « AMQPConnection » from the global namespace.
Did you forget a « use » statement?

Ce message vous signale que vous n’avez pas l’extension amqp d’activée sur votre machine. Il faut installer et activer l’extension php-amqp.

Comment résoudre l’erreur: [ErrorException] Declaration of SymfonyFlexParallelDownloader::getRemoteContents($originUrl, $fileUrl, $context) should be compatible with ComposerUtilRemoteFilesystem::getRemoteContents($originUrl, $fileUrl, $context, ?array & $responseHeaders = NULL)

Il faut exécuter composer update –no-plugins pour mettre Symfony flex à jour.

Comment corriger l’erreur : [SymfonyComponentMessengerExceptionNoHandlerForMessageException]
No handler for message « AppMessageConfirmCommandMailer ».

Il faut veiller à deux points pour trouver la source de cette erreur. Premièrement que vous avez bel et bien créé un handler avec une méthode __invoke qui prend un objet de type AppMessageConfirmCommandMailer en premier et unique paramètre. Et dans un second temps que vous avez bien ajouté votre handler dans votre fichier services.yml avec le tag « messenger.message_handler »

Cocktailand – Ajouter du cache HTTP dans mon symfony

C’est quoi un ESI ?

Les ESI ou Edge Side Includes sont un balisage supporté par Varnish qui permet de gérer des temps de cache différents pour des blocs de la même page.

Dans le cadre de Cocktailand, certains blocs sont actualisés régulièrement comme le « Cocktail du jour » mais d’autres ne changent quasiment jamais comme la liste des catégories.

Voici donc le découpage que j’ai fait sur la page principale. Pour la barre de menu, c’est bien évidemment le contenu du méga menu que j’ai voulu mettre en évidence.

Il est donc intéressant de ne pas avoir à invalider toute la page lorsque le cocktail du jour est changé. Le second avantage en termes de performance est que les blocs peuvent être utilisés sur différentes pages. Cela signifie qu’un ESI présent sur toutes les pages du site ne sera généré qu’une seule fois. Lors des autres appels, Varnish utilisera son cache.

Configuration de varnish

Pour Cocktailand, la configuration de varnish est assez simple car je ne fais pas de purge et parce qu’il n’y a pas d’espace connecté sur le site.

Voici la configuration que j’ai:

vcl 4.0;
import std;
backend default {
// le hostname du nginx dans ma stack
.host = "front";
.port = "80";
}
sub vcl_recv {
set req.http.Surrogate-Capability = "abc=ESI/1.0";
unset req.http.Cookie;
}
//Ensuite, ce block est appelé après la réception des headers de réponse.
//Nous supprimons le header et activons les ESI
sub vcl_backend_response {
if (beresp.http.Surrogate-Control ~ "ESI/1.0") {
unset beresp.http.Surrogate-Control;
set beresp.do_esi = true;
}
if (bereq.url ~ "\.(jpe?g|png|gif|pdf|tiff?|css|js|ttf|woff2?|otf|eot|svg)$") {
set beresp.ttl = std.duration(beresp.http.age+"s",0s) + 24h;
}
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
view raw varnish vcl hosted with ❤ by GitHub

La mise en place dans Symfony 4

ESI

Le support des ESI dans Symfony est intégré nativement dans le framework.

Dans le fichier config/packages/framework.yaml il faut activer les ESIs.

framework:
esi: { enabled: true }
fragments: { path: /_fragment }
view raw framework.yaml hosted with ❤ by GitHub

Toutes vos routes qui servent des ESI devront commencer par _fragment.

Dans vos vues twig, vous avez des helpers à disposition pour poser vos tags.

{{ render_esi(url('popular_cocktails')) }}
view raw render_esi hosted with ❤ by GitHub

Cache http

En utilisant le package sensio/framework-extra-bundle on peut gérer le cache sur les controller avec des annotations.

/**
* @Route("/cocktail/recette/{name}-{id}", name="cocktail_detail", requirements={"id"="\d+", "name"="[0-9a-z-]+"})
* @Cache(public=true, maxage=86400, mustRevalidate=false, maxStale=86400)
**/
public function index($name, $id)
{
// …
}
view raw @Cache hosted with ❤ by GitHub

Temps de réponse

Une fois les ESIs et le cache HTTP mis en place, on peut analyser les performances avec Webpagetest.

L’outil va nous donner des informations sur le temps de réponse de l’application et surtout des indications sur ce qu’il faudrait améliorer.

Par exemple, sur ce test, il me dit que je peux potentiellement améliorer la gestion des fonts et des images.

// @todo activer gzip sur les fonts

Ce que l’on peut remarquer, c’est que le site commence à envoyer le HTML après 231ms. Dans ce temps il y a en moyenne 40ms de DNS Lookup, 30ms de connexion et 80ms de négociation SSL. Malheureusement sur cette partie je n’ai pas la main, c’est donc l’overhead de base pour toute page du site. Mais comme j’utilise HTTP2 je vais mutualiser toute cette partie pour les images assets servies sur le même domaine.

Les nouvelles problématiques

Tout mettre en cache, c’est bien pour les performances, mais malheureusement certaines informations ont besoin de « temps réel ».

Quand un visiteur ajoute une note sur une recette, il est nécessaire que cette note mise à jour si le visiteur rafraîchit la page.

Il y a plusieurs solutions:

  • Faire des bans de cache lors de l’ajout d’une note
  • Afficher les données du cache et rafraîchir les données en AJAX

Bien évidemment, la première solution est celle qui devrait être implémentée. Maintenant, c’est pas mal de code et de configuration sur varnish. C’est d’autant plus compliqué que, dans sa version gratuite, Varnish ne permet pas de faire un ban sur plusieurs instances.

Faire les bans impliquerait de maintenir une liste des varnishs qui tournent (si jamais je fais scaller cette brique) et de faire n appels curl pour faire un ban partout.

Pour le moment, j’ai fait le choix de la requête AJAX non cachée. Si jamais le site gagne en popularité, il faudra retravailler sur ce point.

Conclusion

Sur un blog ou un site dans lequel le visiteur ne peut quasiment pas interagir avec vos données, c’est très simple de mettre en place du cache varnish et le gain de performance est énorme. Maintenant, si vous avez des besoins plus complexes (site transactionnel, forum,…) vous allez devoir mettre en place une mécanique d’invalidation de cache.

Framework JQuery mobile

Tous ceux qui ont déjà utilisé le dernier né de JQuery, à savoir JQuery mobile vont vraiment apprécier JqmPhp. En effet JqmPhp permet de simplifier une syntaxe assez lourde et surtout de fournir un code optimisé.

Travaillant actuellement sur une application web pour smartphone, je me suis fortement intéressé à ce framework pour rendre mon code plus clair.

Une fois que vous avez téléchargé le framework vous devez le configurer. A savoir que la version actuelle de JQuery mobile est la Alpha 4 mais que Jqmphpest conçu pour la Alpha 3. C’est pourquoi il est possible que certaines des nouvelles fonctionnalités de JQuery Mobile ne soient pas encore prises en charge.

Pour la configuration rendez-vous dans le fichier /lib/jqmphp.php

Les valeurs suivantes sont à changer par les liens vers les nouvelles versions de JQuery Mobile (en local ou sur un serveur distant).

/**
 * Sets the jQuery path.
 * @var JQMPHP_JQ
 */
define('JQMPHP_JQ', 'http://code.jquery.com/jquery-1.5.min.js', true);

/**
 * Sets the jQuery Mobile path.
 * @var JQMPHP_JQM
 */
define('JQMPHP_JQM', 'http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.js', true);

/**
 * Sets the jQuery Mobile CSS path.
 * @var JQMPHP_CSS
 */
define('JQMPHP_CSS', 'http://code.jquery.com/mobile/1.0a3/jquery.mobile-1.0a3.min.css', true);

Maintenant que le framework est configuré on va pouvoir créer notre première page.

<?php   
//  Creation de l'objet jqmphp
$j = new jqmPhp();  

// ajout du titre au header
$j->head()->title('Menu principal');

// Création d'une page ( il peu en avoir plusieurs par jqmphp )
$p = new jqmPage('Menu Principal');

// choix du theme et ajout du titre à la page
$p->theme('c')->title('Menu Principal');

// choix du theme pour le header de la page
$p->header()->theme('c');

// ajout de text HTML dans la page
$p->addContent('Bienvenue sur ma belle page');

// Exemple de création de liste ( courant dans JQuery mobile)
$gm = new jqmListviem();

// création et ajout d'un item à la liste
$ann = $gm->addBasic('Lien 1', 'page1.php','',true);

$cal = $gm->addBasic('Lien 2', 'index.php?action=3');

// ajout de la liste à la page
$p->addContent($gm);

// ajout de la page à l'objet
$j->addPage($p);

// affichage de la page
echo $j;

?>

Comme on peut le remarquer le code est assez simple, mais qu’en est-il du résultat ?

Ceci est un exemple très simple mais vous trouverez sur le site du projet des exemples complexes avec des formulaires, des listes à icônes…

En conclusion : Bien que le framework soit très récent il promet d’être très utile aux développeurs web soucieux des utilisateurs de smartphone.

Nous sommes dans l’attente d’une version qui sera sûrement adaptée à la version beta de JQuery mobile qui sortira prochainement.

Sortie de Symfony 2

La nouvelle mouture tant attendue de Symfony 2 est sortie hier matin en version stable. Après de nombreux mois de tests, de versions releases, beta et RC, la version stable et définitive est enfin accessible.

Au programme, énormément de changements et une simplification apparente du framework. Les bundles font leur apparition, ils vont vous permettre d’insérer dans votre site Internet des modules que vous pouvez télécharger avec composer. Il existe déjà un grand nombre de bundles tout faits et prêts à l’emploi, comme un forum ou une gestion des membres par exemple. Le but est réellement à court terme de fournir tous les codes de base pour un site Internet, afin de se focaliser seulement sur le coeur de votre application.

Bien que ceci soit une différence notable par rapport aux précédentes versions, la sortie de différentes versions du framework en fonction des utilisations et du niveau des développeurs est une chose encore plus étonnante de la part du framework PHP5, qui a la réputation d’être le plus complexe à apprendre. Pour le moment, seule la version standard est téléchargeable, mais la rumeur parle de version Standard Edition, Sandbox, CMF… Chacune présentera des caractéristiques de base différentes, mais en arrière-plan il s’agira toujours du même noyau Symfony 2.

Les habitués de Propel vont être bien déçus d’apprendre qu’il a disparu du framework. En effet, seul Doctrine, en l’occurrence Doctrine 2, est maintenant disponible. C’est la fin de la cohabitation :p. Le grand plus de Doctrine 2 est la mise en cache des résultats de requêtes. Ceci vous permet de gagner du temps sur la génération de vos pages web.

Dans les nouveautés notables, on peut mentionner l’intégration du moteur de templates twig. Twig est aujourd’hui un des meilleurs moteurs de templates PHP disponibles, son intégration n’est pas une grande surprise.

Créer un site Internet pour mobile avec JQuery mobile

Avez-vous déjà pensé à faire une application mobile, ou une version mobile de votre site Internet ?
Si oui, vous avez dû vous confronter à des centaines de problèmes de compatibilité entre les navigateurs mobile.

L’article va vous montrer comment faire des pages HTML compatibles sur tous les téléphones et tablettes.

Afin de bien comprendre l’article, il vous faudra connaître les bases du HTML ainsi que JQuery.

Dans un premier temps, il va falloir télécharger la libraire JQuery mobile. Donc rendez-vous à la page suivante : Télécharger.

Cependant, pour la mise en production, je vous conseille d’utiliser le code suivant pour inclure JQuery mobile :

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
http://code.jquery.com/jquery-1.6.1.min.js
http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js

Pourquoi utiliser ceci plutôt qu’un version locale ?

Imaginons un utilisateur, qui passe de site en site. Fait à fait, il remplit son cache de scripts et pages en tous genres. S’il a déjà été sur un site avec le code précédent, il les a en cache et donc il ne les téléchargera pas. Vous gagnez ainsi de la bande passante et du temps de chargement. :p

Découverte

Nous allons créer notre première page.

<!DOCTYPE html> 
<html> 
	<head> 
		<title>Outweb</title> 
		<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
		http://code.jquery.com/jquery-1.6.1.min.js
		http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js
	</head> 
<body> 

<div  data-role="page"> 
	<div  data-role="header">Outweb</div> 
	<div  data-role="content">Outweb c'est mon site internet.</div> 
	<div  data-role="footer">Mon footer</div> 
</div> 

</body>
</html>

Ceci est l’exemple le plus simple qui soit.

Vous remarquerez l’utilisation de data-role. C’est en effet le point important de JQuery mobile.

Il permet de dire à JQM (JQuery Mobile) comment il doit traiter le div, et quelles classes il doit lui appliquer.

Une page visible n’est pas, contrairement à une page HTML basique, l’ensemble du code HTML, mais seulement le <div data-role= »page »>. Cela permet de charger plusieurs pages en une seule fois et donc de naviguer de l’une à l’autre en toute fluidité.

Page complexe

Comment appliquer un thème à une page ?

Juste une petite modification à faire.

<div  data-role="page" data-theme="b"> 

Et voila un beau thème bleu style IPhone.

Nous allons maintenant faire une liste. La liste est la structure HTML la plus simple à mettre en place dans JQM, car elle permet d’afficher des menus ou des informations même sur des petits écrans.

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet"
	href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
http://code.jquery.com/jquery-1.6.1.min.js
http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js
</head>
<body>

<div data-role="page" data-theme="b">
<div data-role="header">Mon header</div>
<div data-role="content">


<!-- listview permet de mettre en forme toute la liste -->
<ul data-role="listview">
        <!-- Met en place un séparateur dans la liste -->
	<li data-role='list-divider' role='heading'
		class='ui-li ui-li-divider ui-btn ui-bar-b ui-corner-top '><b>Accueil</b></li>
	<li><a href="http://localhost:8080">Ma page d'acceuil'</a></li>
	<li data-role='list-divider' role='heading'
		class='ui-li ui-li-divider ui-btn ui-bar-b  '><b>Articles</b></li>
	<li><a href="http://url.localhost:8080/u">Menu contextuel en
	JavaScript</a></li>
	<li class=' ui-corner-bottom '><a href="http://url.localhost:8080/z">Emuler
	un flux RSS</a></li>
</ul>

</div>
<div data-role="footer" data-position="fixed">Mon footer</div>
</div>

</body>
</html>

Lien entre les pages

Comme je le disais précédemment, vous pouvez mettre plusieurs pages dans un fichier HTML. Voici comment faire le lien entre les pages. Et surtout, si vous le souhaitez, comment mettre des effets de transition entre vos pages.

Pensez à mettre des ids à vos pages, sinon vous ne pourrez pas naviguer entre elles.

<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
<link rel="stylesheet"
	href="http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.css" />
http://code.jquery.com/jquery-1.6.1.min.js
http://code.jquery.com/mobile/1.0b1/jquery.mobile-1.0b1.min.js
</head>
<body>

<div data-role="page" data-theme="b">
<div data-role="header">Mon header</div>
<div data-role="content">



<ul data-role="listview">
	<li data-role='list-divider' role='heading'
		class='ui-li ui-li-divider ui-btn ui-bar-b ui-corner-top '><b>Popup</b></li>
       <!-- data-transition permet de définir l'effet de transition. Les trois disponibles sont présenté ici -->
	<li><a href="#page2" data-transition="pop">Voire la page 2</a></li>
	<li><a href="#page3" data-transition="slidedown">Voire la page 3</a></li>
	<li><a href="#page4" data-transition="flip">Voire la page 4</a></li>
	<li data-role='list-divider' role='heading'
		class='ui-li ui-li-divider ui-btn ui-bar-b  '><b>Accueil</b></li>
	<li><a href="http://localhost:8080">Ma page d'accueil</a></li>
	<li data-role='list-divider' role='heading'
		class='ui-li ui-li-divider ui-btn ui-bar-b  '><b>Articles</b></li>
	<li><a href="http://url.localhost:8080/u">Menu contextuel en
	JavaScript</a></li>
	<li class=' ui-corner-bottom '><a href="http://url.localhost:8080/z">Emuler
	un flux RSS</a></li>
</ul>

</div>
<div data-role="footer" data-position="fixed">Mon footer</div>
</div>


<!-- Les autres pages avec des autres thèmes (oui c'est horrible mais c'est une démo)-->
<div data-role="page" id="page2" data-theme="a">
<div data-role="header">Mon header</div>
<div data-role="content">Ceci est la page 2 (pop) </div>
<div data-role="footer" data-position="fixed">Mon footer</div>
</div>

<div data-role="page" id="page3" data-theme="c">
<div data-role="header">Mon header</div>
<div data-role="content">Ceci est la page 3 (slide)</div>
<div data-role="footer" data-position="fixed">Mon footer</div>
</div>

<div data-role="page" id="page4" data-theme="e">
<div data-role="header">Mon header</div>
<div data-role="content">Ceci est la page 4 (flip)</div>
<div data-role="footer" data-position="fixed">Mon footer</div>
</div>
</body>
</html>

Voilà vous avez maintenant toutes les informations pour mettre en place une version mobile de votre site Internet. Si vous voulez plus de documentation, je vous recommande la documentation officielle qui vient d’être refaite, suite à la sortie de la version beta de JQM.

Comme vous avez pu le voir, JQuery Mobile est un outil performant, novateur et surtout libre. Alors profitez-en !

Système de pagination sous CodeIgniter

Avec Codigniter vous pouvez mettre en place simplement un système de pagination pour afficher vos informations par paquets. Tout cela sans se prendre la tête et se noyer dans des dizaines de lignes de code.

Nous allons créer la base de donnée de l’article : Installation et Configuration de CodeIgniter .

Dans un premier temps il créer le model. Pour cela nous allons utiliser la classe active record de CodeIgniter.

class model extends CI_Model {

    function __construct()
    {
        // Call the Model constructor
        parent::__construct();
    }
    
     public function getAll($debut=0) {
        return $this->db->select('*')->from('message')->order_by("id", "desc")->limit(30, $debut)->get()->result();
    }

	public function count($where = array()) {
        return (int) $this->db->where($where)->count_all_results('message');
    }


}

Puis nous allons créer notre contrôleur.

defined('BASEPATH') OR exit('No direct script access allowed');

class controleur extends MY_Controller {

function liste($nb=0) {
        $messages = array();
	$this->load->model('model');
        $this->load->library('pagination');
        $config['base_url'] = base_url() . "/index.php/controleur/liste/";
        $config['total_rows'] = $this->model->count();
        $config['per_page'] = '30';
        $config['uri_segment'] = 3;
        $config['num_links'] = 5;
        $config['full_tag_open'] = '<div class="pagination">';
        $config['full_tag_close'] = "</div>";
        $config['cur_tag_open'] = '<span class="current">';
        $config['cur_tag_close'] = '</span>';
        $config['next_tag_open'] = $config['prev_tag_open'] = '<span class="disabled">';
        $config['next_tag_close'] = $config['prev_tag_close'] = "</span>";
        $this->pagination->initialize($config);
        $data['pages'] = $this->pagination->create_links();
        $data['messages'] = $this->model->getAll($nb);
	$this->load->view("liste_message",$data);
    }

}

Dans le fichier de vue :

// A vous de mettre en page vos messages
var_dump($messages);

// pour afficher vos lien de paginations 
echo $pages;

Vous aurez un beau système de pagination. Si jamais vous devez toujours passer des paramètres en plus à votre contrôleur vous devez changer quelques lignes :

function liste($param1,$param2,$nb=0) {
...
$config['base_url'] = base_url() . "/index.php/controlleur/liste/$param1/$param2/"; // ajout des params
...
$config['uri_segment'] = 5; // 3+ le nombre de param
...
}

Installation et Configuration de CodeIgniter

Pré requis

  • Un serveur Apache ( lamp, wamp, easy php, mamp, … )
  • Un SGBD mysql

La Base de données

Dans un premier temps voici un exemple de petite base de données. Par exemple la base de données suivante

CREATE DATABASE `tuto` DEFAULT CHARACTER SET latin1 COLLATE latin1_swedish_ci;
USE `tuto`;

CREATE TABLE IF NOT EXISTS `message` (
  `id` int(8) NOT NULL AUTO_INCREMENT,
  `sujet` varchar(255) NOT NULL,
  `message` text NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Maintenant que la base est prête nous alors configurer CodeIgniter pour qu’il puisse s’y connecter.

Rendez-vous dans le fichier : application/config/database.php

$db['default']['hostname'] = 'localhost';
$db['default']['username'] = 'VOTRE LOGIN MYSQL';
$db['default']['password'] = 'VOTRE MOT DE PASSE MYSQL';
$db['default']['database'] = 'tuto';
$db['default']['dbdriver'] = 'mysql';
$db['default']['dbprefix'] = '';
$db['default']['pconnect'] = TRUE;
$db['default']['db_debug'] = TRUE; // à passer à FALSE une fois le dev fini
$db['default']['cache_on'] = FALSE;
$db['default']['cachedir'] = '';
$db['default']['char_set'] = 'utf8';
$db['default']['dbcollat'] = 'utf8_general_ci';
$db['default']['swap_pre'] = '';
$db['default']['autoinit'] = TRUE;
$db['default']['stricton'] = FALSE;

Maintenant le framework peut en théorie se connecter à votre base.

Les configurations systèmes

Pour un fonctionnement optimal de CodeIgniter il y a plusieurs configurations à faire.
Pour les config suivantes rendez-vous dans le fichier /application/config/config.php. Et là beaucoup de choses à changer.

//ligne 17
$config['base_url']	= 'http://localhost/outweb/';
//ligne 60
$config['url_suffix'] = '.html';
//ligne 72
$config['language']	= 'french'; // si vous changez cette valeur il faudra aller chercher les traduction, si vous avez la flemmes ne faites rien ici
//ligne 227
$config['encryption_key'] = 'UN TRUC BIEN COMPLIQUE AVEC DES 1233 ET des !#?$_-éà@';
//ligne 282
$config['global_xss_filtering'] = TRUE;

D’autres paramètres de sécurité sont disponibles mais je les expliquerai dans un autre post.

Les autoloads

Codeigniter permet l’utilisation de librairies, helpers, et de fichiers de config perso.
On peut charger dans le code directement chacun des fichiers de configuration de  l’application mais certains d’entre eux sont indispensables sur chacunes de nos pages. Autant dire à CodeIgniter de les charger automatiquement.
Voici selon moi le minimum indipensable.

$autoload['libraries'] = array("session");
$autoload['helper'] = array("url","database");

Téléchargement

Dans un premier temps il va falloir télécharger le fameux framework. Le mieux étant de prendre la dernière mouture.
Téléchargement du framework.
Dezipper l’archive et copier le dossier qui est dedans à la racine de votre répertoire www.
Pour plus de simplicité nous allons le renommer outweb.
Supprimer le dossier user_guide qui contient une copie de la documentation en ligne de CodeIgniter.

Configuration

Il y a trois grandes parties à configurer dans CodeIgniter :

Une fois les quelques configs précédentes effectuées votre CodeIgniter est prêt.
Si vous vous rendez à l’adresse http://localhost/outweb/ vous aurez le message de bienvenue de CodeIgniter

Nous allons donc pouvoir commencer notre application.

Comme vous le savez surement CodeIgniter est basé sur un model MVC. Par conséquent il est préfèrable de garder cette approche tout au long du développement.