mercredi 21 janvier 2009

Preview image plugin avec l'action hover

/**
* Ce plugin permet d'afficher l'image passer en rel
*/


this.screenshotPreview = function(){
/* CONFIG */

xOffset = 10;
yOffset = 30;

// these 2 variable determine popup's distance from the cursor
// you might want to adjust to get the right result

/* END CONFIG */
$("a.screenshot").hover(function(e){
this.t = this.title;
this.title = "";
var c = (this.t != "") ? "
" + this.t : "";
$("body").append("

"+ c +"

");
$("#screenshot")
.css("top",(e.pageY - xOffset) + "px")
.css("left",(e.pageX + yOffset) + "px")
.fadeIn("fast");
},
function(){
this.title = this.t;
$("#screenshot").remove();
});
$("a.screenshot").mousemove(function(e){
$("#screenshot")
.css("top",(e.pageY - xOffset) + "px")
.css("left",(e.pageX + yOffset) + "px");
});
};


// starting the script on page load
$(document).ready(function(){
screenshotPreview();
});

vous trouverez d'autre fonction javascript util sur le forum shoops.fr

mercredi 14 janvier 2009

PHP strtolower - Plugin smarty

Ce plugin smarty transformer la chaine de caractère encodé UTF-8 en minuscule


function smarty_modifier_lower_utf8($inputString)
{
$outputString = utf8_decode($inputString);
$outputString = strtolower($outputString);
$outputString = utf8_encode($outputString);
return $outputString;
}

Si vous avez des difficulté ou d'autre question au sujet de smarty, vous pouvez poster vos question sur le Forum Shoops.fr dans la rubrique informatique / smarty

mardi 23 décembre 2008

Hologe + la date du jour en javascript

Insérez ce code entre les balises <head> et </head> :

<script language="javascript">

function HeureCheckEJS()
{
krucial = new Date;
heure = krucial.getHours();
min = krucial.getMinutes();
sec = krucial.getSeconds();
jour = krucial.getDate();
mois = krucial.getMonth()+1;
annee = krucial.getFullYear();
if (sec < 10)
sec0 = "0";
else
sec0 = "";
if (min < 10)
min0 = "0";
else
min0 = "";
if (heure < 10)
heure0 = "0";
else
heure0 = "";
if (mois == 1)
mois = "Janvier";
if (mois == 2)
mois = "Février";
if (mois ==3)
mois = "Mars";
if (mois == 4)
mois = "Avril";
if (mois == 5)
mois = "Mai";
if (mois == 6)
mois = "Juin";
if (mois == 7)
mois = "Juillet";
if (mois == 8)
mois = "Août";
if (mois == 9)
mois = "Septembre";
if (mois == 10)
mois = "Octobre";
if (mois == 11)
mois = "Novembre";
if (mois == 12)
mois = "Décembre";
if (jour == 1)
jour1 = "er";
else
jour1 = "";
DinaHeure = "Il est " + heure0 + heure + "h" + min0 + min + " et nous sommes le " + jour + jour1 + " " + mois + " " + annee + ".";
which = DinaHeure
if (document.getElementById){
document.getElementById("ejs_heure").innerHTML=which;
}
setTimeout("HeureCheckEJS()", 1000)
}
window.onload = HeureCheckEJS;


</script>


puis créez un div comme suivant entre les balise <body> et </body> :
<div id="ejs_heure"></div>

vendredi 7 novembre 2008

Url Rewriting - Apache mod_rewrite

Découvrons la réécriture d'URL "à la volée".
Il était impossible de créer ce site sur les regex, sans vous parler du MOD_Rewrite du serveur Apache qui permet de réécrire les URL à la volée (aussi appelé URL Rewriting) Ce module, en effet, utilise toute la puissance des expressions régulières.
1) Installation
Pour utiliser le module de réecriture des URL à la volée, votre hébergeur doit avoir activé ce MOD, ou vous devez avoir accès au paramêtre de configuration d' Apache (fichier httpd.conf).

a) Vérifier - Actif ou pas ?
Le meilleur moyen de savoir si le module est actif c'est de le vérifier avec le phpinfo();
Dans la section Apache Loaded Modules, vous devez trouver la mention mod_rewrite. Si vous ne trouvez pas, rien n'est perdu, passez au point b)

b) Activer le module
Si vous avez accès au fichier de config ou si vous possédez un dédié, il suffit d'ouvri le httpd.conf dans le dossier Apache et de décommenter les 2 lignes suivantes en enlevant le # en début de ligne :

LoadModule rewrite_module modules/mod_rewrite.so
AddModule mod_rewrite.c

/!\ Dès que cela est fait, n'oubliez-pas de redémarrer Apache.

Si le point a) et le point b) se sont révélés tous deux négatifs, rien n'est encore perdu, vous pouvez quand même passer au point c) et faire un ultime test.

c) Tester
A partir de cet instant, on entre réellement dans le vif du sujet :
- Commencez par créer une page test.php à la racine de votre serveur. - Vous y insererez un simple texte : "Bonjour, on teste le mod_rewrite d'Apache"
- Créez un fichier .htaccess et vous y insererez le code suivant :

RewriteEngine on
RewriteRule ^test\.html$ /test.php [L]

- Déposez ce "htaccess" et votre page "test.php" via votre FTP à la racine de votre site (*)
* ou dans un même répertoire

Il ne vous reste plus qu'à entrer l'URL suivante dans votre naviguateur favori : http://votresite.com/test.html, et vous devriez voir apparaitre le texte de la page "test.php".

Et là, deux hypothèses :
- soit la page apparaît et l' URL test.html est bien réecrite en test.php
- soit vous avez une erreur 404, ou plus probable une erreur 500 et là... rien à faire, le mod_rewrite n'est pas actif chez votre hébergeur.

Dans ce deuxième cas, enlevez immédiatement le htaccess via votre client FTP

d) Explications du code insérez dans votre htaccess

RewriteEngine on
Cette ligne est une instruction qui active la réécriture d'URL. Peu importe les régles de réécriture que vous mettrez en place, cette instruction devra toujours être mise dans votre fichier htaccess.

RewriteRule ^test\.html$ /test.php
Cette ligne est la règle d'écriture proprement dite et nous allons la détailler :

RewriteRule C'est un mot-clé indispensable qui introduit chaque règle d'écriture

^test\.html$ Cette ligne détermine la partie qui devra être recherchée afin de la réécrire.
Vous y trouver les deux caractères spéciaux de début et fin de chaine que vous connaissez avec les autres motifs de regex.

/test.php est la chaine qui remplacera celle trouvée par la regex. C'est en général le nom de page qui existe vraiment sur votre espace.
[L] Il s'agit d'un flag (drapeau) qui signifie que la règle est la dernière à être appliquée pour ce cas et que le module ne doit plus tenter de réécrire cette chaîne. En gros, on applique la règle une seule fois et basta.


La réécriture d'url étant une manipulation créée par l'utilisateur, il convient de faire très attention à ce qu'on met dans son htaccess afin de ne pas tomber dans des excès qui mettrait le serveur sur les rotules. Bien qu'ils soient généralement correctement configurés pour ne pas tomber dans ce genre de piège, je le répète... faites très attention !
Rappelez-vous que prudence est mère de sûreté !
2) Réécrire des URL dynamiques
Le cas le plus fréquent de l'emploi de l'URL Rewrite est la réécriture des URL de votre site afin de présenter à l'internaute une url plus mnémotechnique, facilitant dans la foulée son indexation par les moteurs de recherche qui ne laisseront plus sur le côté des pages dynamiques avec de multiples paramètres.
Votre site dynamique comporte surement des URL du genre :

http://votresite.com/frames.php?page=index&view=source
http://votresite.com/photo.php?album=12&id=1

.... ce qui n' est pas très joli, difficilement mémorisable et finalement qui pose de réels problèmes d' indexation aux moteurs de recherche. En effet la plupart ne prennent en compte qu'un nombre limité de paramètres dans l' URL, voir qui les ignorent complètement !

Nous allons donc réécrire ces URL d'une manière plus jolie.

http://votresite.com/frames.php?page=index&view=source
deviendra : http://votresite.com/index.source.html
http://votresite.com/photo.php?album=12&id=1
deviendra : http://votresite.com/photo/12/1

La première URL :

RewriteEngine on
RewriteRule ([a-z]+)\.([a-z]+)\.html$ /frames.php?page=$1&view=$2

La deuxième URL :

RewriteEngine on
RewriteRule ([0-9]+)/([0-9]+)$ /photo.php?album=$1&id=$2
2) Réécrire conditionnelle
Il est possible de conditionner la réécriture des URL, afin notamment d'effectuer des tests sur certains paramètres avant de réécrire les URL.
Les utilisations les plus courantes sont : bannir un domaine (ou une adresse IP) de votre site, protéger vos dossiers images et encore bien d'autres.

La syntaxe est la suivante :

RewriteEngine on
RewriteCond TEST VALEUR
RewriteRule ACTION


Exemples :
Bannir les utilisateurs venant d' un domaine spécifique :

RewriteEngine on
RewriteCond %(HTTP_REFERER) ^http://(www.)?domaine_a_bannir.com
RewriteRule .* http://www.google.fr [L]

[L] est un "drapeau" qui signifie que cette réécriture sera la dernière. Tout ce qui se trouve après ne sera pas exécuté.

Rediriger les utilisateurs d' Internet Explorer (par exemple)
On va rediriger tous les utilisateurs de ce pas beau Internet Explorer vers le site de Mozilla

RewriteEngine on
RewriteCond %{HTTP_USER_AGENT} MSIE
RewriteRule .* http://www.mozilla.org [L]

Quelques masques à connaitre

* %(HTTP_USER_AGENT) ==> Contient notemment le naviguateur et le système d' exploitation
* %(HTTP_REFERER) ==> La page (si elle est renseignée) d' ou vient votre visiteur
* %(HTTP_HOST) ==> Le domaine
* %(REQUEST_FILENAME) ==> La page qui a été demandée
* %(REMOTE_ADDR) ==> L' adresse IP

samedi 1 novembre 2008

Optimisation mysql

MySQL est très souvent utilisé pour concevoir des sites dynamiques. Le problème se pose lorsque le site en question devient de plus en plus visité (ou alors lorsqu'il comprend des scripts habituellement gros consommateurs en ressources MySQL, tels que certains forums par exemple). Il arrive alors souvent que le serveur ne supporte plus la charge imposée par les clients. Au mieux vous aurez une erreur de "max_user_connections" résolvable assez facilement si vos requêtes sont mal conçues, au pire vous pouvez avoir un plantage du serveur MySQL qui n'arrive plus à répondre face aux requêtes qui lui sont adressées.


Face à ces problèmes il existe plusieurs solutions. De nombreux webmasters préfèrent directement changer le serveur (ou augmenter ses capacités) sans se poser de questions. Il est évident qu'avant de changer le serveur pour un plus puissant, il faut regarder si les requêtes MySQL sont bien conçues au lieu de foncer sur un changement de matériel sans réflexion préalable, d'une part car optimiser un script est "gratuit" comparé à l'achat ou à l'upgrade d'un ou de plusieurs serveurs, d'autre part car face à un script très mal optimisé, l'augmentation des capacités matérielles a une limite.


Nous allons donc tout d'abord dans cet article présenter les quelques erreurs pouvant être simplement réparées en ajoutant un index ou en modifiant une ou deux lignes dans un script PHP, puis nous passerons au "lourd", à savoir des propositions d'amélioration de structures de table MySQL en fonction de cas concrets. Nous supposerons que les champs de vos tables MySQL utilisent des types appropriés à leur contenu. Pour choisir le bon type de champ en fonction des données qu'il sera amené à contenir des données - MySQL.


Modification des requêtes de sélection :



Une règle d'or lorsque l'on conçoit une requête MySQL : ne ramener que ce dont on a besoin. Plus le nombre de données à ramener sera faible, plus ce sera rapide (à requête équivalente bien entendu). Prenons l'exemple d'une structure de table destinée à stocker des informations sur les membres d'un site, cette table s'appellera "membres" :


Structure de la table qui nous servira d'exemple


Les champs :



Si nous souhaitons récupérer simplement la liste des pseudos de tous les membres, une solution sale consiste à faire une requête de ce type :


SELECT * FROM membres


Ensuite on récupère via PHP tous les pseudos :


<?php

mysql_connect
('hote','user','passe') OR die('Erreur de connexion à la base');



mysql_select_db('base') OR die('Selection de la base impossible');



$requete=mysql_query('SELECT * FROM membres');





mysql_close();



while($r=mysql_fetch_array($requete))

{

         echo $r['pseudo'];


}

?>


Cette solution n'est pas optimisée car le serveur MySQL renvoie tous les champs de la table à PHP (le * dans la requête signifie "tous les champs"), alors que nous n'avons besoin que du champ "pseudo". D'une manière générale, et même si vous avez besoin de tous les champs de votre table, n'utilisez jamais de SELECT * mais indiquez toujours la liste des champs dont vous avez besoin.


Voici une solution optimisée au niveau de la requête :



<?php

 mysql_connect
('hote','user','passe') OR die('Erreur de connexion à la base');



 mysql_select_db('base') OR die('Selection de la base impossible');



 $requete=mysql_query('SELECT pseudo FROM membres');





 mysql_close();



 while($r=mysql_fetch_array($requete))

 {

         echo $r['pseudo'];


 }

?>



La fermeture de la connexion :



Lorsque vous n'avez plus besoin de la connexion MySQL, il est impératif de la fermer, sous peine d'avoir des erreurs de "max_user_connections". Cette erreur signifie que vous avez dépassé le nombre de connexions maximales à MySQL simultanément. Avant de toucher aux fichiers de configuration, il serait judicieux d'optimiser ses scripts. Pour fermer la connexion on utilise la fonction mysql_close(). Voici un exemple de script mal conçu au niveau de la connexion :



<?php

 mysql_connect
('hote','user','passe') OR die('Erreur de connexion à la base');




 mysql_select_db('base') OR die('Selection de la base impossible');



 $requete=mysql_query('SELECT pseudo FROM membres');




 while($r=mysql_fetch_array($requete))

 {

        
echo $r['pseudo'];


 }

?>


On constate sur ce code qu'il n'y a aucune fermeture de la connexion ouverte. Il faut donc fermer la connexion proprement après avoir effectué la dernière requête. Les fonctions de "fetch" ne nécessitent pas d'avoir la connexion ouverte. Il est important de signifier que la connexion sera automatiquement fermée dès la fin du script, inutile donc de mettre un mysql_close() à la dernière ligne.


Voici donc le code optimisé :


<?php

 mysql_connect
('hote','user','passe') OR die('Erreur de connexion à la base');




 mysql_select_db('base') OR die('Selection de la base impossible');



 $requete=mysql_query('SELECT pseudo FROM membres');






 mysql_close();



 while($r=mysql_fetch_array($requete))

 {

        
echo $r['pseudo'];


 }

?>



La clause LIMIT :



Cette clause très particulière est très souvent utilisée en cas de besoin de paginer les résultats (forum par exemple). C'est entre autres elle qui est responsable de la lenteur des forums dont je vous parlerai plus bas dans cet article. La clause LIMIT permet de limiter le nombre d'enregistrements retournés par MySQL. Reprenons notre requête d'affichage des pseudos des membres. Plus le nombre de membres va augmenter, plus il va devenir important de fractionner par pages l'affichage de la liste des membres sous peine d'avoir des milliers de membres à afficher d'un coup (ce qui n'est pas vraiment recommandé pour le serveur web, ni pour le visiteur qui devra télécharger la page générée...). Si nous souhaitons afficher les 20 premiers membres, nous pourrons utiliser une clause LIMIT, la requête sera celle-ci : SELECT pseudo FROM membres LIMIT 20



Si nous souhaitons récupérer les 20 membres à partir du 40ème, nous procèderons comme ceci :


SELECT pseudo FROM membres LIMIT 40,20


Le premier nombre correspond au nombre d'enregistrements qu'il faut sauter avant d'afficher le nombre d'enregistrements correspondant au second nombre (20 dans notre cas). Or, le gros problème de cette requête est qu'elle va sélectionner toutes les valeurs de la table avant de "faire le tri" à savoir avant d'envoyer les 20 enregistrements à PHP. Or sélectionner des milliers d'enregistrements est très, très long. C'est pour ça que les forums peu optimisés fonctionnent rapidement à leur ouverture, les performances se dégradant nettement au fur et à mesure que les membres postent des messages.


Solution optimisée :



La table membres que nous avons crée contient un champ nommé id_membre en AUTOINCREMENT. Ce type de champ nécessite un index pour pouvoir être appliqué à une table. Nous allons voir ci dessous ce qu'est un index, sachez qu'il accélère, quand il est bien conçu, les requêtes de sélection. Chaque membre aura donc un numéro différent, le numéro du prochain membre à s'inscrire sera incrémenté par rapport au précédent. Nous pouvons donc utiliser ce champ pour notre requête de sélection. Nous allons supprimer la clause LIMIT et utiliser un BETWEEN à la place. Le BETWEEN permettra de recueillir uniquement les valeurs que nous souhaitons sans parcourir toute la table inutilement. La requête devient ceci (il est très important d'avoir un index sur le champ id_membre sous peine de n'obtenir que de faibles gains) :


SELECT pseudo FROM membres WHERE id_membre BETWEEN 40 AND 60



Cette requête permettra de récupérer 20 membres (60-40 = 20) tous situés après le 39ème membre (car le premier id du membre qui sera récupéré portera la valeur 40). Le seul inconvénient de cette méthode est que si vous supprimez par exemple un membre dont l'id est situé entre ces deux valeurs, vous ne récupèrerez plus que 19 enregistrements au lieu de 20. Le jeu en vaut la chandelle à mon avis, et votre serveur vous remerciera. Pour les serveurs non compatibles avec le BETWEEN, sachez que la requête ci-dessus est équivalente à ceci :


SELECT pseudo FROM membres WHERE id_membre > 40 AND id_membre < 60




Les index :



Imaginez un livre. Plus le livre contient de pages, plus vous allez mettre du temps à rechercher l'information désirée. C'est pour cela qu'il existe en général une table des matières au début ou à la fin du livre. Cette table vous permet d'accéder très rapidement sans feuilleter le livre à l'information que vous recherchez. Un index en MySQL fonctionne sur ce principe, à savoir qu'il va vous permettre d'accélérer nettement (une requête 10 fois plus rapide n'est pas un cas exceptionnel dans le cas d'un index bien utilisé) vos requêtes de sélection. Il faut cependant être conscient qu'un index ralentit les requêtes d'insertion ou de mise à jour (dans une moindre mesure) de donnés, il faut donc les utiliser avec parcimonie. Indexer tous les champs d'une table MySQL est stupide. D'une manière générale, il faut indexer les champs apparaissant dans une clause WHERE de la requête. Nous allons rajouter un champ date_inscription à notre table de membres, puis nous utiliserons une requête type pour récupérer les membres étant inscrits depuis moins d'une semaine. Voici la nouvelle structure de la table :


Structure de la table avec un champ date_inscription


Une requête type consistant à récupérer les membres inscrits depuis moins de 7 jours consiste à faire ceci :



<?php

 mysql_connect
('hote','user','passe') OR die('Erreur de connexion à la base');



 mysql_select_db('base') OR die('Selection de la base impossible');



 $requete=mysql_query('SELECT pseudo FROM membres WHERE date_inscription >'.time()-7*3600*24);





 mysql_close();



 while($r=mysql_fetch_array($requete))

 {

echo $r['pseudo'];



 }

?>



Nous voyons donc que le champ "date_inscription" sera utilisé par MySQL pour savoir si le pseudo du membre doit être retourné ou non. On constate donc qu'il serait judicieux de placer un index sur le champ "date_inscription" pour pouvoir accélérer la recherche des pseudos concernés. Pour ajouter un index dans PHPMyAdmin, on clique sur la petite icône représentant un éclair, en face du champ "date_inscription". L'index est maintenant ajouté et la requête est optimisée.


Pensez également aux index multi colonnes : Si vous avez une requête de la forme 


SELECT pseudo FROM membres WHERE date_inscription > X AND id_membre BETWEEN  40 AND 60



Il est judicieux de créer dans ce cas un index sur deux colonnes (date_inscription et id_membre). Pour ce faire, dans PHPMyadmin, regardez la zone "créer une clé sur ... colonnes". Mettez "2" dans le nombre de colonnes et cliquez sur Exécuter. Vous avez accès à ceci :


Créer un index sur plusieurs colonnes


Dans la zone "Type de clé" choisissez "Index". Choisissez ensuite comme premier champ "id_membre" et ensuite "date_inscription", puis cliquez sur "sauvegarder". Ne vous occupez pas de la colonne "taille" de de PHPMyadmin, cette colonne ne sert (en gros) que pour les index sur les champs de caractères (VARCHAR entre autres). L'avantage est double pour les champs de ce type : Si vous mettez "2" dans la zone "taille", l'index indexera les deux premiers caractères de chaque mot. Il serait peu recommandé de mettre la longueur maximale du champ dans cette zone car vous perdrez en performances et en espace disque.


Il est important de souligner que les index sur les champs de type numériques sont plus rapides à traiter par MySQL. Ainsi, il est peu recommandé d'utiliser dans les clauses WHERE les champs de type chaînes de caractères. Pour sélectionner des informations sur un membre, on n'utilisera donc pas son pseudo. La requête suivante n'est pas optimisée (même si un index est placé sur le champ pseudo) :


SELECT nom,prenom FROM membres WHERE pseudo = 'webmaster'



On préfèrera la requête suivante :


SELECT nom,prenom FROM membres WHERE id_membre = X


D'une part, vous gagnerez en performances, mais d'autre part vous gagnerez en espace disque car on a été obligé d'indexer le champ id_membre (vu que c'est un champ AUTOINCREMENTE, il doit être obligatoirement accompagné d'un index). Donc au lieu d'avoir deux index (l'un sur le champ id_membre et l'autre sur le champ pseudo) nous n'en avons qu'un, ce qui cumule les avantages que ce soit en terme de performances (en écriture ou en lecture) ainsi qu'en espace disque (indexer un champ de type "chaîne" prend plus de place).




Les tables de type HEAP (ou MEMORY) :



Pour des données peu sensibles (compteur de connectés, système personnel de sessions utilisant MySQL, etc...) vous pouvez utiliser les tables de type HEAP (ou MEMORY selon les versions de MySQL). Ce type de table est très particulier puisque les données sont stockées dans la mémoire vive. Il n'y a ainsi plus d'accès au disque dur pour récupérer les données ce qui augmente nettement les performances.


Attention cependant, ce type de tables n'est pas à utiliser pour stocker des données sensibles, car en cas de redémarrage de MySQL ou/et de plantage du serveur, les données seront perdues.



La clause EXPLAIN :



Cette clause rajoutée aux requêtes de type SELECT va vous permettre d'auditer les performances de vos requêtes. Lorsque vous rajoutez le mot EXPLAIN devant votre requête de type SELECT (dans PHPMyadmin) vous allez pouvoir avoir quelque chose qui ressemble à ceci (avec des valeurs pouvant varier bien entendu) :


EXPLAIN - MySQL


On constate qu'il y a plusieurs colonnes résultant de cette action. Voici une explication des colonnes retournées :




  • id : le numéro de la requête SELECT dans la requête globale.

  • Select_type (quelques valeurs que peut prendre cette colonne) :

    • SIMPLE : SELECT simple (sans UNION ni sous-requêtes).

    • SUBQUERY : Premier SELECT d'une sous-requête.




  • Table : Table utilisée.

  • Type : type de jointure utilisée. Voici quelques types de jointures, du plus rapide au plus lent :

    • system : Cas rare, lorsque la table comporte une seule ligne (jointure de type CONST).


    • const : la table possède au plus une seule ligne répondant à la valeur demandée qui sera lue dès le début de la requête. Ce type de jointure est très rapide.

    • eq_ref : C'est le meilleur type de jointure possible. Ce type est utilisé lorsque toutes les parties d'un index sont utilisées par la jointure. L'index doit être de type UNIQUE ou PRIMARY KEY.

    • ref : Si MySQL ne peut pas retourner une seule ligne en fonction de la jointure, toutes les lignes ayant des valeurs correspondantes seront lues par MySQL.

    • ref_or_null : Comme le type ref, mais un coût supplémentaire pour les recherches ayant des valeurs NULL comme cas possible. Il est d'ailleurs recommandé de mettre vos champs en NOT NULL pour accélérer les traitements.


    • range : seules les lignes dans un intervalle donné seront lues dans l'index.

    • index : même chose que ALL, seul l'index est utilisé ce qui peut accélérer légèrement les performances étant donné que la taille du fichier d'index est généralement plus faible que celle des données.

    • ALL : à bannir, une analyse complète de la table est faite.




  • Possible_keys : Cette colonne indique quels index MySQL va pouvoir choisir pour trouver les lignes correspondant à la requête.

  • Key : Cette colonne indique l'index que MySQL aura finalement utilisé pour trouver les lignes correspondant à la requête.

  • Key_len : Cette colonne indique la taille de la clé utilisée par MySQL.


  • Ref : Cette colonne indique quelle colonne ou quelles constantes sont utilisées pour sélectionner les lignes de la table

  • Rows : Estimation du nombre de lignes à parcourir avant d'obtenir le résultat. Plus cette valeur est faible, meilleures seront les performances.

  • Extra : Cette colonne contient quelques informations supplémentaires très utiles sur la procédure de résolution de la requête. Voici quelques informations utiles :

    • Using filesort : MySQL va avoir besoin d'effectuer un second passage pour trier les lignes. Sur un petit nombre de lignes retournées ça peut convenir, mais essayez d'éviter d'obtenir des Usign filesort avec 1000 enregistrements retournés par exemple...


    • Using Index : MySQL n'ouvre pas la table et lit directement les informations dans l'index. C'est très rapide et devrait être présent le plus possible dans vos requêtes.

    • Using Temporary : MySQL va créer une table temporaire pour contenir le résultat. Cela se passe généralement en cas d'utilisation d'une clause ORDER BY sur une colonne différente de celle utilisée pour la clause GROUP BY. Cela nuit aux performances.

    • Using Where : Une clause Where sera utilisée pour limiter le nombre de lignes retournées.






Cas particulier : les forums (gratuits ou payants) disponibles en téléchargement :



De nombreux forums (payants ou gratuits) tel que PHPBB ou encore IPB, Simple Machines Forum, etc... sont très peu optimisés ce qui fait que dès que les forums en question deviennent fréquentés (avec un nombre de messages conséquent) le temps de génération de chaque page augmente très rapidement en parallèle. Ces forums ne supportent donc pas une forte charge qui pourrait leur être imposée. Face à ce problème, il n'y a généralement (après avoir désactivé toutes les fonctionnalités inutiles gourmandes proposées) pas d'autre solution que de changer le serveur, car reprogrammer entièrement de tels forums est une pure perte de temps (par où commencer ? il y a tellement de travail...). Le problème de ces forums est qu'ils utilisent pour sélectionner une tranche de topics (ou de messages) la clause LIMIT dans leurs requêtes MySQL (mal conçues). La solution ultime consiste à concevoir soi-même son propre forum. Bien sûr cela demande du temps et de bonnes connaissances en optimisation PHP et MySQL (sous peine de refaire les mêmes erreurs). Il existe cependant de bons forums gratuits open source. Je peux vous en citer un : Punbb. Ce forum est léger, personnalisable et disponible en français.


Une bonne solution pour réaliser son forum personnel est d'utiliser les requêtes de type BETWEEN. Il faudra généralement pour cela rajouter un ou deux champs supplémentaires (deux dans la table des topics et un dans la table des messages) si vous avez des sous catégories. Sinon un seul champ par table suffira. Ce champ servira de "marqueur de position" et c'est sur ce champ que sera fait le BETWEEN, ce qui fait que seuls les topics de la page désirée seront scannés, le forum tiendra ainsi la charge même avec plusieurs milliers de topics/messages.


Optimisation PHP

PHP est un langage de programmation interprété, c'est à dire que le fichier texte contenant le code PHP est analysé puis traité directement (pas de code compilé). Nous allons voir comment améliorer les performances de vos scripts PHP pour tirer le maximum de performances.

Les différentes techniques d'optimisation que nous allons voir ici vous permettront :



  • De générer vos pages plus rapidement pour le visiteur

  • D'économiser des ressources serveur

  • D'accueillir plus de visiteurs en même temps sur votre site

  • De coder plus proprement, car optimisation rime souvent (pas toujours) avec clarté et propreté.


Suite aux différentes remarques plus ou moins fondées que j'ai pu lire sur le net, je tiens à préciser que le début de cet article ne vous fera pas gagner un temps de génération énorme si votre page est mal codée et qu'il s'agit de micro-optimisations,
ça ne remplace donc en AUCUN cas un algorithme optimisé et ce pour n'importe quelle page de votre site. Privilégiez donc une amélioration de vos algorithmes avant de finir par optimiser les quelques tournures qui pourraient vous faire perdre quelques
millièmes de secondes supplémentaires.


Nous allons donc commencer par optimiser les quelques millisecondes que vous pourriez
perdre par l'emploi de fonctions moins efficaces que d'autres. Tous les benchs qui
vont suivre ont été effectués avec la version 5.2.4 de PHP, la dernière au moment
où j'écris ces lignes. Ils consistent en l'exécution d'une boucle comptant un nombre
n d'itérations (ce nombre n est précisé en face
de chaque bench car les différences sont parfois tellement minimes qu'elles exigent
un nombre d'itérations plus important afin que l'on puisse les constater). A l'intérieur
de cette boucle se trouve le code pour lequel on teste les performances. A la fin
du script, on décompte le nombre de secondes qu'il a nécessité pour s'exécuter et
on compare donc les différents codes entre-eux. Les benchs ont été exécutés 5 fois
de manière à prendre la valeur moyenne du temps de génération et limiter les erreurs.
A l'attaque !



Les simples et doubles quotes :



Commençons avec un classique en PHP à savoir les simples et doubles quotes (guillemets). Elles tiennent leur différence principale
dans le fait que tout ce qui se trouve à l'intérieur des guillemets simple n'est
pas interprété, contrairement à ce qui se trouve à l'intérieur des guillemets doubles. En
pratique le code suivant fait exactement la même chose :


<?php


     
echo

'Voici une chaîne de test'
;


?>





<?php


     
echo

"Voici une chaîne de test"
;


?>



Par contre le code suivant donnera deux résultats totalement différents :


Version 1:



<?php



     $mavariable
=
'bonjour tout le monde'
;


     echo
'Valeur de la variable
: $mavariable'




?> 





Version 2:


<?php


     $mavariable
= 'bonjour tout le monde'
;



     echo
"Valeur de la variable
: $mavariable"



?>


Résultats avec 200 itérations :


simples et doubles quotes



Lorsque vous utilisez la deuxième version de cet exemple, vous constatez que la
chaîne $mavariable est remplacée par sa valeur, contrairement à
ce qui se passe dans la première version de cet exemple. Or, remplacer $mavariable
par sa valeur nécessite du temps supplémentaire pour PHP car il doit non seulement
rechercher le nom exact de la variable mais ensuite la remplacer par sa valeur.
C'est pourquoi il est plus rapide d'utiliser la version 1. Bien me direz-vous, mais
comment faire si on souhaite afficher le contenu d'une variable ? et bien on va
pouvoir utiliser la concaténation. Reprenons notre code lent et optimisons-le :




Version 1:


<?php


     $mavariable
= 'bonjour tout le monde'
;


     echo
"Valeur de la variable
: $mavariable"




?> 



Version 2:


<?php 


     $mavariable
=

'bonjour tout le monde'



     echo
'Valeur de la variable
: '
.$mavariable
;  



?>


Version 3:



<?php 


     $mavariable
=

'bonjour tout le monde'



     print
'Valeur de la variable
: '
.$mavariable
;  



?>


Nous pouvons également (cf version 3) utiliser
la fonction print. Elle a une différence avec echo cependant : elle renvoie toujours
la valeur 1, ce qui fait que vous pouvez l'utiliser dans des conditions ou autre.
En pratique, elle est supposée être plus lente. Voici une comparaison des temps de génération, toujours avec 200 itérations :

optimisation des echo



On constate qu'il n'y a ici aucune différence moyenne entre print et echo (cependant
attention, j'ai constaté que print pouvait être plus rapide que echo, mais c'est
généralement l'inverse. La moyenne a lissé les résultats).
Vous pouvez (et uniquement pour echo) remplacer le point de concaténation
par une virgule. Ceci peut se révéler plus rapide (sur 200 itérations je n'ai pas
obtenu de différences suffisantes toutefois). Cela vient du fait qu'il est souvent
plus rapide de concaténer un petit bout de chaîne et de tout envoyer dans le même
buffer que de fermer et réouvrir le buffer nécessaire à chaque fois que vous utilisez
echo. Voici en quoi
cela consiste :


Concaténation avec le point :


<?php 


     $mavariable
= 'bonjour tout le monde'



     echo
'Valeur de la variable
: '
.$mavariable
;  



?>


"Concaténation" avec la virgule :




<?php 



     $mavariable
=
'bonjour tout le monde'



     echo
'Valeur de la variable
: '
,$mavariable
;  



?>



Après ce bref passage autour de la fonction print et de la structure de langage
echo, attaquons-nous aux différentes manières de codage. Commençons par la déclaration
(et l'initialisation) des variables. Il existe plusieurs moyens de le faire :


Version 1:


<?php 

     $mavariable
;


     
$mavariable2
;



     
$mavariable 'valeur'
;

     
$mavariable2'valeur'
;

?>


Version 2:




<?php

     $mavariable
;$mavariable2
;



     
$mavariable $mavariable2 'valeur'
;

?>




Voyons laquelle des deux est la plus rapide (toujours sur 200 itérations) :


déclaration de variables



Contrairement à PHP4 où j'avais obtenu des résultats plus rapides pour la version
1, c'est ici la version 2 (plus logique) qui l'emporte d'une courte tête. Passons
maintenant aux conditions. Il existe trois manières de faire des conditions en PHP
:



  • La condition if/else

  • L'instruction Switch

  • L'opérateur ternaire




Les trois permettent de faire la même chose avec plus ou moins de syntaxe. Voyons
trois exemples qui font absolument la même chose mais codés de manière différente
:


Version 1:



<?php 


     $variable
= 1
;



     
$i
=
0
;




     if(
$variable === 1
)



     {


         
$i
++;


     }



     elseif(
$variable
=== 2
)


     {


         
$i

= 2
;


     }


     else


     {



         
$i
= 3
;


     }



?>


Version 2:


<?php


     $variable
= 1
;



     
$i
=
0
;


     switch(
$variable
)



     {


        case
1
:


         
$i
++;



        break;


        case
2
:


         
$i

= 2
;


        break;


        default:



         
$i
= 3
;


        break;



     }



?>



Version 3:


<?php



     $variable
= 1
;


     
$i
=
0
;





     (
$variable
===
1) ?
$i++ : (($variable ===

2) ? $i =
2
: $i=3
);


?>






La première version utilise l'opérateur classique en PHP, à savoir la condition
if/else. La seconde version utilise un switch tandis que la dernière utilise ce
qu'on appelle l'opérateur ternaire. Les trois solutions ont des avantages et inconvénients
différents du point de vue maintenance notamment, mais nous ne sommes ici que pour
juger de leurs performances ! Voici donc les résultats du test, effectué encore
une fois sur 200 itérations :


Conditions



Test encore une fois étonnant, car il est le contraire de ce que j'obtenais en PHP4.
La solution la plus rapide est donc l'opérateur ternaire bien que sa lisibilité
soit absolument affreuse !



Passons maintenant aux boucles, et plus précisément à l'utilisation de la fonction
count() dans une boucle. Vous êtes nombreux à mettre un count()

dans une boucle for de manière à ce que cette boucle s'arrête une fois que la variable
à incrémenter dépasse la valeur retournée par la fonction count().
Or, en général,
vous travaillez sur un tableau de dimension fixe, c'est à dire que
count() va retourner
à chaque fois la même valeur. Lorsque vous faites un tour de boucle, la valeur du count() est donc recalculée pour rien ! Voici les deux codes dont je veux parler
:


Version 1:



<?php



    $w
=
0
;


   
$tableau
= array(
'test',
'hop'
);



   
$n
=
count($tableau
);




    for(
$i

= 0; $i
<
$n;
$i
++)


    {



           
$w
++;


    }


?>




Version 2:




<?php


    $w
=

0
;


   
$tableau
= array(
'test',
'hop'
);





    for(
$i
= 0; $i
<
count($tableau);

$i
++)


    {


           
$w
++;



    }


?>



Voici maintenant les résultats pratiques de 200 itérations :


boucles et count




Placer un count() dans une boucle est ici deux fois plus lent !
On privilégiera donc la première solution. Dans le même genre de chose, voyons voir
l'impact que peut avoir d'utiliser une boucle for ou une boucle while :



Version 1:



<?php


    $i
=0
;



    while(
$i
<
50
)


    {


       
$i
++;



    }


?>








Version 2:




<?php


   for($i =
0
; $i

< 50; $i
++)


   {




   }



?>





Comparons maintenant les résultats de ces deux codes réalisant la même fonction
(attention, en cas de code plus complexe, le for peut s'avérer bien utile car il
accepte des conditions supplémentaires), toujours sur 200 itérations :


For et while 


Comme on peut le constater, le while est légèrement plus rapide.


Ouverture d'un fichier
:



Il y a beaucoup de fonctions permettant en PHP d'ouvrir un fichier pour lire son contenu. Nous allons tester trois fonctions qui ouvriront un fichier contenant une
quinzaine de lignes. Voici les trois fonctions utilisées :



  • file_get_contents()

  • file()

  • fopen() suivi de fread() pour récupérer les données et fclose pour fermer le handle.




Voici les codes utilisés :



Version 1:



<?php

   $fichier
=file_get_contents('bench.txt'
);



?>



Version 2:



<?php


   $fichier
=file('bench.txt'
);



?>


Version 3:




<?php


   $fp
=

fopen
('bench.txt', 'r'
);


   
$fichier
= fread($fp,
filesize('bench.txt'
));



   
fclose($fp
);


?>





Nous utiliserons toujours une boucle de 200 itérations. Voici les résultats (sans
appel) : la fonction fopen() est de loin la plus efficace. La fonction
file() nécessite la création d'un tableau et est de ce fait plus
lente, de peu devant la fonction file_get_contents() qui malgré
sa simplification n'est pas plus rapide.



Ouverture de fichier



Voyons un peu maintenant deux méthodes pour concaténer une valeur à une variable
chaîne. Imaginions que vous ayez une variable contenant la valeur bonjour
et que vous souhaitiez qu'elle contienne la valeur bonjour tout le monde.
On peut utiliser trois méthodes, l'une d'entre-elles est cependant inintéressante
puisqu'elle consiste à réassigner entièrement la nouvelle valeur à la variable :



<?php


   $variable
=

'bonjour'
;


   
$variable
=
'bonjour tout le monde'
;


?>




Nous n'allons donc pas l'utiliser. Voyons maintenant deux autres méthodes utilisant
la concaténation ou la recopie de valeur :



Version 1:




<?php


   $variable
=

'bonjour'
;


   
$variable
=
$variable.' tout le monde'
;


?>




Version 2:




<?php


   $variable  
=

'bonjour'
;


   
$variable
.=
' tout le monde'
;


?>



Voyons maintenant le résultat des tests effectués encore et toujours avec une boucle
de 200 itérations :


concaténation 



La concaténation l'emporte de peu, mais elle marque un net avantage en fonction
de la longueur de la chaîne initiale. Plus la chaîne initiale est longue, plus la
concaténation l'emporte aisément sur la recopie.


Remplacement d'une expression dans une chaîne :


Il existe de multiples façons en PHP de remplacer une portion de chaîne par une
autre valeur. Voici les deux fonctions principalement utilisées pour cela :



  • preg_replace()


  • str_replace()



Sachez qu'il est plus rapide d'utiliser str_replace() que preg_replace(),
car str_replace()
ne prend qu'une chaîne fixe en paramètre, contrairement à preg_replace() qui utilise
les expressions régulières.
Bien sûr dans le cas où vous souhaitez remplacer une chaîne selon une condition
bien précise, la fonction str_replace() n'est généralement pas
assez puissante et il vous faudra recourir à la fonction preg_replace().




Parcours d'un tableau :



Nous allons remplir
un tableau de 100 000 valeurs aléatoires, puis nous allons parcourir ce tableau 200
fois des deux façons suivantes :



Version 1:



<?php


   $w
=

0
;


   foreach(
$tableau
AS $cle
=> $valeur)



   {


       
$w++;


   }


?>




Version 2:





<?php

$w 
0

while(list(
$cle$valeur) = each($tableau)) 




    
$w++; 

}

resest($tableau);

?>


Pour les versions 3 et 4, il s'agit des mêmes codes que les versions 1 et 2 respectivement, sauf que l'on parcourt le tableau une seule fois, on ommet donc l'utilisation du reset() dans le while(list() = each())




Voici les résultats obtenus :



parcours d'un tableau 



Le while() combiné au list() est plus lent que la fonction foreach() et ce dans tous les cas de figures. Préférez donc l'utilisation du foreach()




Après toutes ces micro optimisations, nous allons maintenant parler d'autres méthodes
pour optimiser vos scripts PHP de manière à améliorer cette fois-ci de manière conséquente
les performances. Ces modifications sont plutôt orientées algorithmes ou bases de
données. Pour consulter quelques règles d'optimisation de MySQL, vous pouvez consulter
ce lien : Optimiser MySQL.



En PHP, il existe de nombreuses solutions destinées à simplifier la tâche des designers
et développeurs. Elles peuvent non seulement leur faciliter la tâche, mais également
accélérer le temps de génération de vos scripts si elles sont bien utilisées. On
peut parler notamment des moteurs de templates, dont vous trouverez une version
conçue par mes soins ici (en PHP5) :
Moteur de templates PHP
. Grâce à l'utilisation de ce genre de sources, vous
disposerez la plupart du temps d'un mode "cache". Cacher les informations consiste
à les enregistrer dans un fichier ou une base de données de manière à réduire les
traitements nécessaires pour afficher les données la prochaine fois. Par exemple,
lorsque vous affichez la liste de vos actualités, vous savez qu'elle ne sera pas
mise à jour toutes les minutes, il est donc inutile de redemander à la base tout
le contenu des actualités et d'effectuer des traitements dessus à chaque fois qu'un
visiteur clique sur la page. Vous pouvez récupérer
une bonne fois pour toutes cette liste, la mettre en cache (dans un fichier texte
par exemple) et la réutiliser ensuite au lieu de faire appel à la base de données.
Ce genre d'astuce peut très facilement diminuer de 50 % le temps de génération de
vos pages et consommera moins de ressources CPU (au mépris parfois
de la quantité de mémoire vive consommée). Ces scripts et méthodes d'optimisation
prennent toute leur importance dès lors que vous avez un site web un minimum visité.
Si vous avez dix visiteurs journaliers et que vos traitements ne sont pas trop lourds,
il est inutile de mettre en place un système de cache, mais qui peut le plus peut
le moins comme on dit !


Vous trouverez un forum de discution autour de ce sujet sur Shoops.fr



Partager sur face book