Archives par mot-clef : SQL

Tout ce qu’il faut savoir pour scroller tranquilement

Catégorie : PHP | Laisser un commentaire

Ah, avant toute chose qu’est-ce que j’entends par le mot « scroller » ? On pourrait appeler ça « scrapper », « looter », voir même « pirater ». En fait l’idée est de créer un petit programme dont le but va être de récupérer des informations sur un site Web qui se trouve en ligne (ou pas). Exactement comme fait le GoogleBot ou les autres robots de moteurs de recherche. Et comme font aussi les robots qui cherchent des adresses e-mails pour faire du spam… Bref, on voit qu’il peut y avoir un petit problème éthique derrière le scrolling, mais c’est une discipline qui peut être tout à fait noble et utile autant pour le scrolleur que pour le scrollé. Mais je ne vais pas faire de débat philosophique, plutôt parler technique.%%% Durant le stage que j’effectue actuellement, j’ai pas mal l’occasion de scroller des sites Web, voici donc un résumé de fonctions ou de techniques qui peuvent être utiles. !!! Le scrolling en résumé L’idée principale est de faire automatiquement rapidement ce que l’on aurait dû faire à la main en mettant beaucoup de temps. Pour cela, on fait une petite étude des urls du site que l’on souhaite scroller. Par exemple, si c’est une liste d’article, les pages de chaque article auront peut-être un url avec quelque chose du genre : @@article.php?id=1452112@@. A partir de la : /// $url = ‘article.php?id={id}’; for($i=1; $i<=$nbArticle; $i++) { $urlAScroller = str_replace('{id}', $i, $url); $content = file_get_contents($urlAScroller); // Regex et explode sur $content pour récupérer les informations souhaitées } /// @@file_get_contents()@@ récupère sous forme d'une chaine de caractères le contenu de la page passée en paramètre. Donc en effectuant des expressions régulières sur la variable @@$content@@ on peut récupérer ce que l'on souhaite sur la page. Imaginons le code (généré, donc HTML) d'une page article.php : ///

Annuaire de la Bnbox company

Tout un tas de site pour une compagnie naissante (sic !)

La Bnbox

Indescriptiblement souriant
http://la-bnbox.fr

Bible Ipsum

Générateur de texte de remplissage
http://bibleipsum.free.fr
/// Voici un script pour scroller cette magnifique page (on va dire qu’elle a l’id n°1) : /// function parserS($str) { return trim(mysql_real_escape_string($str)); } $urlAScroller = ‘article.php?id=1′; $content = file_get_contents($urlAScroller); $content = utf8_decode($content); // j’imagine ici que l’encodage est en UTF-8 $items = explode(‘

‘, $content); $values = array(); foreach($items AS $k => $val) { $nom = parserS(preg_replace(‘!^(.+)

.*$!isU’, ‘$1′, $val)); $categorie = parserS(preg_replace(‘!^.*(.+).*$!isU’, ‘$1′, $val)); $url = parserS(preg_replace(‘!^.*(.+).*$!isU’, ‘$1′, $val)); $values[] = « (‘$nom’, ‘$categorie’, ‘$url’) »; } $qry = INSERT INTO annuaire (nom, categorie, url) VALUES ‘.implode(‘,’, $values); $insert = mysql_query($qry); if (!$insert) echo ‘Erreur insertion : ‘.mysql_error().’
‘; /// Bon bien sûr, dans la réalité, on a rarement une page aussi bien construite. Mais le principe reste le même. !!!Quelques fonctions utiles * Je viens d’en parler : [file_get_contents()|http://fr2.php.net/manual/fr/function.file-get-contents.php]. * Les regex en général, notamment : [preg_replace|http://fr2.php.net/manual/fr/function.preg-replace.php], [preg_match|http://fr2.php.net/manual/fr/function.preg-match.php], [preg_match_all|http://fr2.php.net/manual/fr/function.preg-match-all.php], [preg_split|http://fr2.php.net/manual/fr/function.preg-split.php]. (et [str_replace|http://fr2.php.net/manual/fr/function.str-replace.php] bien entendu) * Les fonctions [explode|http://fr2.php.net/manual/fr/function.explode.php] et [implode|http://fr2.php.net/manual/fr/function.implode.php] * Les fonctions d’encodage, décodage. Par exemple [utf8_decode()|http://fr2.php.net/manual/fr/function.utf8-decode.php]. En effet, selon l’encodage de la page à scroller, vous allez peut-être récupèrer une chaine de caractères en UTF-8 alors que vous travaillez en ISO. * Les fonctions de parsage et de mise en forme : [htmlentities|http://fr2.php.net/manual/fr/function.htmlentities.php], [html_entity_decode|http://fr2.php.net/manual/fr/function.html-entity-decode.php], [strip_tags|http://fr2.php.net/manual/fr/function.strip-tags.php], [trim|http://fr2.php.net/manual/fr/function.trim.php], [ucwords|http://fr2.php.net/manual/fr/function.ucwords.php], [ucfirst|http://fr2.php.net/manual/fr/function.ucfirst.php], [strotolower|http://fr2.php.net/manual/fr/function.strtolower.php], [strtoupper|http://fr2.php.net/manual/fr/function.strtoupper.php], [mysql_real_escape_string|http://fr2.php.net/manual/fr/function.mysql_real_escape_string.php] !!!Quelques techniques utiles * Pour avoir un programme un peu rapide, il peut être bon d’optimiser ses requêtes SQL (si vous enregistrez vos résultats dans une BDD bien entendu) Billet [Optimisation des requêtes SQL|/post/2009/Optimiser-ses-requetes-MySQL] à lire à ce sujet. * Parfois preg_match_all peut être très pratique. Billet [Une fonction bien utile : preg_match_all|http://30minparjour.la-bnbox.fr/post/2009/Une-fonction-regex-bien-utile-preg_match_all] à lire à ce sujet. * Il peut être utile d’avoir une variable que l’on incrémente à chaque scroll pour savoir où est-ce qu’on en est. On peut alors facilement mettre en place un @@continue@@ (pour sauter un scroll), un @@break@@ (pour arrêter une boucle) ou un @@exit@@ (pour arrêter le programme afin de voir ce qui se passe), ce qui peut être utile pour débugger ou gérer les effets de bord. (premier scroll, dernier scroll, par exemple. Ce sont en général des cas particuliers. Et puis « effet de bord, c’est la classe, non ?)

Cahier des charges : Doodle like en PHP objet

Catégorie : Projets | 2 commentaires

Bon, il me semble que j’ai fait un bon tour de la théorie au sujet du PHP objet (j’ai encore au moins 2 billets théoriques à écrire), et je l’applique déjà depuis plusieurs semaines durant mon stage. Mais maintenant j’aimerai bien me faire un petit projet tout en PHP objet. Je ne sais pas si vous connaissez [Doodle|http://www.doodle.com/] ? Je trouve le concept vraiment génial. Ce site permet à n’importe qui de créer des sondages classiques, ou alors des sortes de sondages permettant de fixer facilement une date d’un rendez-vous pour autant de personnes que l’on souhaite. Je vais donc réaliser dans les jours à venir (et j’espère arriver aux termes de ce projet) un Doodle like ! (j’ai pas encore d’idées de nom… mais ça viendra) Voici donc un cahier des charges d’un Doodle like. !!!Feuille de route * Version tout en PHP (orienté objet) sans aucun Javascript ou autre. Fonctionnalité : « Trouver une date ». * Ajout du Javascript pour la fonctionnalité « Trouver une date » et le reste du site. * Ajout de fonctionnalités « Faire un choix », « Sondage », « Qui prend quoi, pour un repas », ou autre, et amélioration des fonctionnalités présentes. En PHP (orienté objet) d’abord puis en améliorant avec le Javascript. !!!Introduction Doodle like est un site Internet visant à simplifier drastiquement les prises de décisions au sein d’une équipe ou d’un groupe. Grâce à un système de sondage simple, instinctif et friendly, n’importe quel internaute peut proposer une date de rendez-vous à un groupe (restreint ou non) de personnes, s’évitant ainsi de longues heures perdues au téléphone ou en envoi/réception d’e-mails. !!!But * Apprendre à (mieux) utiliser le PHP orienté objet * Apprendre à (–mieux–) utiliser Jquery * (Apprendre à utiliser le XML comme fichier de configuration. Et donc SimpleXML ou autre. -> pas nécessaire finalement, donc à voir) * S’amuser * Créer un système sécurisé où aucune information privée non désirée ne peut filtrer vers l’extérieur. Aucune information ne sera utilisée à des fins commerciales ou autre (hors publicité type Google Adsense, et encore, modérement), et aucune information privée ne sera conservée ou traitée. !!!Détails des fonctionnalités !!Trouver une date Tables SQL : * trouverUneDate(id, nom_createur, email_createur, webcreateur, date_creation, date_last_modif, titre, titre-rewrite-et-unique, description, nb_reponse, reponse_unique_multiple_ou_limite(0,1, nb), commentaire_autorise(0,1), nb_commentaire, autorisation(0->que admin, 1->que inscris, 2-> tout le monde), actif(0,1)) -> p-e chaine unique pour participation et administration * dateACheck(id, id_sondage, date(timestamp)) * horaireACheck(id, id_date, horaire(format à définir mais si possible le plus libre possible. Ex : 8:00-9:00@paris)) * commentaire(id, idSondage, pseudo, e-mail, web, date_creation, date_last_modif, msg) Étapes de création d’un sondage : * Choix du titre, description, pseudo, e-mail, choix des options (pré-remplies -> autorisation, commentaire, restriction à quelques utilisateurs (inscris) ou avec un mot de passe) * Choix des dates et des horaires Fin d’un sondage : * Blocage du sondage * Envoi d’un e-mail (personnalisé) donnant la réponse à tous les utilisateurs si désirés Administration d’un sondage : (pour les créateurs de sondages qui se sont inscris) * Modification du titre, de la description, des options possibles * Modification des dates et des horaires possibles * Supprimer, modifier une ou des réponses à un sondage * Supprimer le sondage Participer à un sondage : * Répondre au sondage * Si autorisation = 2 : Modification, suppression de toutes les réponses possibles * Si autorisation = 1 : Modification, suppression des ses réponses (pour les inscris) * Si autorisation = 0 : Rien ne peut être modifié ou supprimé par les inscris ou les visiteurs !!Système de compte Table SQL : * user(id, pseudo, mdp, email, web, date_inscription, nb_sondagecréé, nb_sondageparticipe, etat(1->ok, 0->banni)) Inscription : * Pseudo, mdp et e-mail sont demandés * Un e-mail avec rappel pseudo, mdp est envoyé Gestion du compte : * Permet de modifier : pseudo, mdp, e-mail, web. * Un e-mail avec rappel pseudo, mdp est renvoyé si l’e-mail à changé Pages spéciales : * Mes sondages * Les sondages auxquels j’ai participé * (Sondages pouvant m’intéresser) !!Pages Table SQL * article(id, auteur, email_auteur, web_auteur, date_creation, date_last_maj, titre, article, commentaire_autorise(0,1), nb_commentaire, tag) * tag(id, tag, nb_tag) !Nous contacter Table SQL : * email(id, emailemeteur, pseudoemetteur, sujetemeteur, msgemeteur, envoye(1-> oui, 0-> non, 2->en attente), (emaildest)) Formulaire * Email, pseudo, sujet, msg, (choix destinataire) !Qui sommes-nous & qu’est-ce que Doodle like Page de la table article !Politique d’accessibilité Page de la table article !!!Parlons technique * Rewriting des pages : titre-du-sondage-rewrite-unique.html * Un peu de SEO : liste des derniers sondages, liste du top des sondages, liste de sondages aux hasards… A améliorer, à méditer. Etude des mots clefs nécessaire !!!Idées futures * Flux RSS des sondages, de ses sondages, des commentaires * Commentaires des sondages * Doodle pour appareil portable

Optimiser ses requêtes MySQL

Catégorie : SQL | Laisser un commentaire

Je travaille actuellement sur une table MySQL qui va contenir près de 7 millions de tuples (avec une quinzaine de colonnes chacun bien sûr, ce ne serait pas drôle sinon !), il se trouve qu’elle en contient déjà près de 3 millions, et que je suis obligé de faire une vingtaine de requêtes différentes dans cette table pour chaque page et que, par conséquent, le site qui l’utilise plante à fond. La mémoire utilisable par MySQL est dépassée. Alors certes, on peut modifier la taille de la mémoire utilisable par MySQL (et il faudrait aussi sûrement modifier la durée maximum d’exécution d’un script PHP), mais le but c’est quand même de charger des pages en moins de 30 secondes. J’ai donc cherché des réponses sur le Web et auprès des développeurs de ma boîte. Voici donc quelques points que j’ai pu retenir : !!!Structure de la table Il vaut mieux éviter, sauf si c’est nécessaire, l’utilisation de champ  »NULL » par défaut. En effet, comme on peut le voir sur [cet article d'Apprendre PHP|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html]  »NULL » est une valeur spéciale qui nécessite un traitement spécial, MySQL ne peut pas le tester avec un égal et ne peut le comparer à la chaine vide ou à 0, il est obligé d’utiliser @@IS@@ ou le symbole équivalent @@<=>@@.  »NULL » par défaut est d’autant plus à bannir pour les index. Utiliser des colonnes avec le meilleur type et la meilleure taille possible. Un prénom n’a par exemple par besoin d’être en VARCHAR(255), un VARCHAR(20) suffit plus que largement. Une bonne technique pour optimiser ses tables est d’utiliser @@PROCEDURE ANALYSE()@@. On créé ses tables au feeling (je parle juste de la structure, vous ne couperez pas à la normalisation tout ça, héhé), on les remplie avec un contenu à peu près définitif est on utilise une requête du style : ///[mysql] SELECT id, titre, message FROM blog PROCEDURE ANALYSE() /// MySQL va nous répondre un joli petit array avec la taille max et min de chaque colonne id, titre et message, quelques autres informations intéressantes et surtout une proposition de type(taille) optimisée à votre table. Encore une fois, c’est [l'article d'Apprendre PHP|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html] qui en montre un exemple vraiment parlant. !!!SELECT * Si l’on effectue une recherche sur une colonne et que celle-ci change assez rarement (ou n’est modifié que par un administrateur), il peut être très avantageux de créer un index ou un index unique (si le contenu de cette colonne est différente pour chaque tuple) dessus. Il suffit de le faire en ligne de commande, ou directement dans PhpMyAdmin. Cela peut mettre un petit peu de temps si la table est grande, ce qui explique qu’il vaut mieux éviter de changer trop souvent la table par la suite puisqu’il faut mettre à jour l’index à chaque insert, update, delete. Si j’ai bien compris MySQL se charge alors de créer une sorte de table virtuelle avec cette colonne ce qui accélère grandement la recherche sur cette dernière. On peut créer autant d’index que l’on souhaite, mais s’il y en a trop l’avantage peut être restreint. (o alors il faut utiliser des USE INDEX (nom_index) dans vos requêtes, mais pour moi ça a plutôt ralenti mes requêtes) * Bien entendu, l’utilisation du joker (*) est à bannir. (en l’utilisant on effectue 2 requêtes au lieu d’une : chercher tous les champs qui correspondent au joker (ce que l’on aurait du faire à la main), puis sélectionner les bonne colonnes selon les bonne lignes) De même, si c’est possible comme pour un forum ou un système de commentaires, au lieu d’utilisation un count(champ1), il vaut mieux mettre le nombre de tuples en dur dans la base de données. * Lors de jointure, il est important que les critères de jointure soient de même type pour que MySQL n’est pas à faire de conversion. L’utilisation de jointures avec JOIN est a priori plus rapide que celles (à l’ancienne) dans le WHERE. Et de même, il est plus intéressant de faire un LEFT JOIN qu’un JOIN (quand le résultat convient avec un LEFT JOIN bien sûr) * On peut faire précéder sa requête SQL par EXPLAIN pour savoir comment MySQL traite notre SELECT. MySQL nous renvoie alors un tableau avec tout un tas d’information. Il suffit de lire la [doc MySQL de EXPLAIN|http://dev.mysql.com/doc/refman/5.0/fr/explain.html] pour utiliser cette commande. !!!INSERT Si l’on a a effectuer plusieurs insertions à la suite, il est plus avantageux de faire un seul INSERT que plusieurs à la suite. Bref, au lieu de faire : /// for($i=0;$i<100;$i++) { $qry ='INSERT INTO ma_table (numero) VALUES ("'.$i.'")'; $insert = mysql_query($qry); if (!$insert) echo 'Erreur lors de l\'insertion : '.mysql_error().'
‘; } /// Faites plutôt : /// $values = array(); for($i=0;$i<100;$i++) { $values[] = $i; } $qry ='INSERT INTO ma_table (numero) VALUES ("'.implode('"), ("', $values).'")'; $insert = mysql_query($qry); if (!$insert) echo 'Erreur lors de l\'insertion : '.mysql_error().'
‘; /// !!!Partitionnement L’autre solution consiste à partitionner sa table. Je vous laisse lire ce [très bon tutoriel sur Developpez.com|http://krierjon.developpez.com/mysql/partitionnement/]. C’est au final ce que j’ai choisi et le résultat est plutôt positif : j’arrive à accéder à toutes mes pages en moins de 30 secondes, et les pages qui n’avaient vraiment pas besoin de toutes les données sont vraiment rapide à charger. !!!Conclusion Vous pouvez lire ce long article très intéressant et très complet de PHP Facile à ce sujet : [Optimisation de MySQL|http://www.lephpfacile.com/manuel-mysql/mysql-optimization.php] Et bien sur l’article dont j’ai déjà parlé sur Apprendre PHP : [Introduction à l'optimisation|http://www.apprendre-php.com/tutoriels/tutoriel-26-mysql-introduction-a-l-optimisation.html]. Aucun de ces 2 articles ne parlent de partitionnement ce qui est assez étonnant. Bon, tout cela étant dit, la meilleure technique consiste assurément à créer un système de cache. Le premier utilisateur allant sur la page se tape les requêtes SQL et la génération du cache, mais toutes les prochaines visites (pour tout le monde) seront très rapide puisqu’elles iront juste lire un fichier HTML. Bien sûr, vous pouvez aussi générer automatiquement tout votre cache en visitant chaque page avec @@file_get_content()@@. D’autre part, si vos données changent assez souvent, vous pouvez créer un petit robot qui se charge de supprimer vos fichiers de cache à intervalle régulier, mais dans ce cas là, il vaut mieux regénérer le cache à chaque modification de la BDD.

Une seule et même fonction pour différents systèmes de gestion de base de données

Catégorie : PHP objet | Un commentaire

Je suis depuis longtemps déjà un féru utilisateur de [FluxBB|http://www.punbb.fr/] (anciennement PunBB), un système de forum bien fichu, valide W3C, très léger et donc configurable à souhait. En ce moment ce projet open source bat un peu de l’aile, mais de nouvelles mises à jours sortent encore assez régulièrement et la nouvelle version (attendue depuis des années) semble de nouveau repartir sur de bonnes bases. Mais bref, je parle de cela car FluxBB gère les bases de données MySQL, PostgreSQL (ou PgSQL) et même SQLite je crois. Pour se faire, au lieu d’avoir un switch ou des if else à chaque requête à effectuer, ils ont créé une class qui regroupe toutes les fonctions utiles (connect, query, fetch_array, …) et dans le code ils y font appelle après avoir instanciée cette class : $db->query, $db->fetch_array. Reste à sélectionner le type de sa base de données dans la partie administration et hop, on change de SGBD sans rien changer au code ! Pratique ! Je n’ai pas encore fini d’apprendre le PHP objet, mais j’avais envi de pratiquer un peu, alors sans regarder ailleurs, j’ai commencé à coder. Et voici une première version. Ma class oublie surement pas mal de fonctions, et je n’ai pas pu la tester partout, et puis il manque la gestion des erreurs, j’aurai pu ajouter un compteur de requêtes, mais c’est ma première véritable class, alors soyez indulgent ^^ /// nom = $nom; } public function connect($hote, $port, $nomBdd, $utilisateur, $mdp) { switch ($this->nom) { case ‘pg’: $requete = ‘host=’.$hote.’ port=’.($port == NULL ? ’5432′ : $port).’ dbname=’.$nomBdd.’ user=’.$utilisateur.’ password=’.$mdp; pg_connect($requete); break; default: mysql_connect($hote.($port == NULL ? ‘:3307′ : ‘:’.$port), $utilisateur, $mdp); mysql_select_db($nomBdd); } } public function close() { switch ($this->nom) { case ‘pg’: pg_close(); break; default: mysql_close(); } } public function query($requete) { switch ($this->nom) { case ‘pg’: $result = pg_query($requete); break; default: $result = mysql_query($requete); } return $result; } public function fetch_array($result) { switch ($this->nom) { case ‘pg’: $arr = pg_fetch_array($result); break; default: $arr = mysql_fetch_array($result, MYSQL_BOTH); } return $arr; } public function fetch_row($result) { switch ($this->nom) { case ‘pg’: $row = pg_fetch_row($result); break; default: $row = mysql_fetch_row($result); } return $row; } public function num_rows($result) { switch ($this->nom) { case ‘pg’: $num_rows = pg_num_rows($result); break; default: $num_rows = mysql_num_rows($result); } return $num_rows; } } $sgbd = new Sgbd(‘mysql’); ?> ///