RABOT Sylvain Maitre de stage : M. Laurent Bordes Responsable technique : M. Ludovic Martin [[CREATION D UNE APPLICATION DE MAINTENANCE DE BASE DE DONNEES]] IUT DE BAYONNE DEPARTEMENT INFORMATIQUE Année 2006-2007
Remerciements M. Laurent Bordes pour m avoir accueilli dans son entreprise et m avoir fait confiance. M. Ludovic Martin pour ses idées et l aide technique qu il m a apportée. Mlle Aurélie Rivière pour ses conseils. Remerciement spécial à M. Yves «creatyves» Becau pour m avoir autorisé à utiliser une de ses photos pour la première page de ce rapport. Vous pouvez trouver toutes ses œuvres à l adresse suivante : http://creatyves.deviantart.com/gallery/ NB : Je tiens à souligner qu il n est pas la peine de chercher de significations particulières à la présence de cette photo en couverture, il n y en a pas. Sa présence est seulement due au fait que c est une œuvre que j apprécie beaucoup. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 2
Sommaire Remerciements... 2 Introduction... 5 Présentation de l entreprise... 6 Présentation du service Digistal.com... 7 Architecture du service Digistal.com... 8 Architecture de la base de données du service Digistal... 10 But du stage... 11 Travail réalisé... 12 Spécification de l application... 12 Caractéristiques de l application... 12 Apache HTTPD 2.2.x... 12 PHP5... 13 PostgreSQL... 13 MVC... 14 SMARTY... 14 xhtml... 14 Prototype & Script.aculo.us... 14 AJAX... 14 Architecture de l application... 15 Arborescence et contenu des dossiers... 15 Position de l application dans l architecture Digistal... 17 Fonctionnement de MVC dans l application... 18 Modules de l application... 19 XTYWYSTX... 19 GhostBuster... 30 Dédale... 31 Reuters... 34 DeviantHorse... 36 DBDump... 37 AJAX dans l application... 38 Conclusion... 40 IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 3
Annexes... 41 Codes critiques... 41 Travaux annexes... 44 K2... 44 afiles... 45 sql4array... 59 acurl... 67 Bibliographie... 77 Sites web... 77 IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 4
Introduction Ce stage s inscrit dans le cadre de mon cursus universitaire au sein de l Institut Universitaire de Technologie de Bayonne dans le but de mettre en pratique les connaissances acquises lors des deux années passées dans cette institution et d obtenir le Diplôme Universitaire Technologique option génie Informatique. Aspirant plus tard à travailler dans le milieu de la conception de sites internet j ai choisi de postuler pour ce stage dans des entreprises liées à cette activité. C est ainsi que la société Dreamclic par le biais de fondateur et gérant, M. Laurent Bordes, m a accueilli durant la période allant du 2 avril au 8 juin 2007. La raison de ma présence en tant que stagiaire dans cette entreprise était la nécessité pour celle-ci de créer une application destinée à la maintenance d une base de données liée à un service proposé par cette société, Digistal.com. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 5
Présentation de l entreprise La société Dreamclic est une agence Internet fondée en 2001 par M. Laurent Bordes, domiciliée 19 rue Vauban à Bordeaux, qui propose des services de création de sites internet pour les entreprises et particuliers. Au moment de ma présence dans cette entreprise le personnel était composé de 5 employés et 2 stagiaires : Laurent Bordes : gérance, relations clients, photographie Ludovic Martin : développement Nina Mussaute : gestion commerciale Aurélie Rivière : conception graphique, relations client, photographie Ludivine Lenoir : développement, conception graphique Renaud Cousin : stagiaire conception graphique Ghislain Traulle : stagiaire marketing La société fournit principalement 2 types de prestations : la création de sites internet à la demande et des services plus ou moins automatisés gérés par des applications développées par l entreprise. Exemple de sites web réalisés par la société : geralddahan.com : site officiel de Gérald Dahan. volkswagen-bordeaux.com : Volkswagen Bordeaux. brienne-auto.fr : BMW Bordeaux. equisup.fr : portail communautaire dédié à l équitation. horseonweb.com : site web de petites annonces équines. Services proposés par Dreamclic : digistal.com : service de création de site web destiné aux éleveurs et marchands de chevaux. trustxchange.com : service de transmission de données via internet. screenit.fr : service de création, gestion et diffusion de contenus multimédia. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 6
Présentation du service Digistal.com M. Bordes étant issu du milieu de l équitation et cavalier lui-même, le service Digistal.com s inscrit dans l orientation qu a prise la société en s emparant d un marché à l époque vierge, à savoir, la création de sites web spécialisés dans l équitation. Digistal.com est un service qui permet aux éleveurs et marchands de chevaux d obtenir un site web clef en main spécialisé dans leur activité. Spécifications du service : Création d une charte graphique Hébergement et gestion du nom de domaine du site Gestion d un catalogue de chevaux accessible par tous depuis le site Gestions de divers types de documents (texte, photos, vidéos) à associer aux chevaux du catalogue Gestion de la généalogie des chevaux Grâce à ce service, les clients possèdent des sites web flexibles et facilement administrables sans nécessiter de connaissances informatiques qui leurs servent de vitrines pour leurs activités. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 7
Architecture du service Digistal.com Ce schéma à pour but de vous faire comprendre de quelle manière a été conçu Digistal mais ne reflète absolument pas l architecture physique du système. En effet les différentes entités présentes sur le schéma (sauf l internaute) pourraient être dans la réalité regroupées sur un seul et même serveur. J ai choisi ici de fragmenter ces différentes entités dans un but pédagogique. Voici un exemple de fonctionnement : L internaute se connecte au site www.exemple-haras.com situé sur le serveur Digistal #2. Le site lui renvoie la page d accueil. L internaute décide de consulter le catalogue du site (www.exempleharas.com/catalogue.html). Le serveur Digistal #2 envoie une requête au Kernel Digistal lui indiquant qu il souhaite la liste des chevaux contenus dans le catalogue. Le Kernel interroge la base de données en fonction de la requête du serveur du site. Le serveur SQL renvoie les données au Kernel. Le Kernel formate les données et les envoie au serveur du site. Le site reçoit les données du Kernel, les formate au format HTML et envoie la page à l internaute. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 8
Diagramme de séquence UML du fonctionnement des sites web Digistal Cette architecture permet que chaque site du service Digistal soit flexible et indépendant de manière à ce que toute modification sur un site n ait aucune répercussion sur les autres. Elle permet aussi d avoir des données centralisées ce qui est très intéressant surtout en ce qui concerne la généalogie des chevaux. Par exemple, prenons le cas où un cheval rentré sur le site Digistal #1 soit le père d un cheval sur le site Digistal #2. Dans cette situation, le propriétaire du site Digistal #2 n aura pas besoin de rentrer la généalogie du cheval du côté du père car il se peut qu elle existe déjà si le propriétaire du site Digistal #1 l a déjà rentrée. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 9
Architecture de la base de données du service Digistal IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 10
But du stage La raison de ma présence en tant que stagiaire dans cette entreprise vient du fait de la dégradation progressive de la qualité des informations contenues dans la base de données de Digistal. A force d utilisation, il s est avéré que certains clients ont rentré des informations erronées ou bien déjà présentes dans cette base. M. Bordes et M. Martin m ont alors fait confiance pour réaliser une application de maintenance de la base de données Digistal permettant la détection et la suppression de ces informations non désirées. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 11
Travail réalisé Mon travail consistant à supprimer des informations d une base de données j ai décidé de nommer mon application XTYWYSTX qui est l acronyme de «X To Y With Y Smaller Than X» qui veut dire en français «de x à y avec y plus petit que x». Spécification de l application M. Martin, responsable technique, m a laissé carte blanche en ce qui concerne la réalisation de mon application, il a juste définit les fonctionnalités qu elle devait implémenter, à savoir : Détection et suppression des doublons présents dans la table «cheval» de la base de données. Faire en sorte que l intégrité référentielle de la base de données soit respectée. Détection des cycles dans la généalogie des chevaux. Gestion des auteurs de documents. J ai rajouté à mon initiative la fonctionnalité suivante : Détection des chevaux dont le sexe ne correspond pas à sa généalogie Caractéristiques de l application Voici la liste de tous les composants qui ont été nécessaire lors de la réalisation de mon application. Apache HTTPD 2.2.x HTTPD est un serveur internet gratuit et open source développé par la fondation Apache. C est a l heure actuelle le serveur web le plus utilisé dans le monde (source : Netcraft). C est lui qui va gérer les requêtes HTTP envoyées par les utilisateurs de XTYWYSTX. Statistiques d utilisation des différents serveurs HTTP dans le monde (source : Netcraft) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 12
PHP5 PHP est l acronyme récursif de «Hypertext Preprocessor». Il s agit d un langage de programmation interprété qui a été conçu dans le but de générer des pages web dynamiques. Sa rapidité, sa syntaxe très proche de celle du C, sa facilité d accès à des programmeurs débutant et sa documentation complète en font à l heure actuelle le langage de programmation le plus populaire et le plus utilisé dans la conception d application web. Dans sa version 5 PHP gère la programmation orientée objet ce qui n était pas le cas dans les versions précédentes ou alors de façon très limitée. J ai choisi ce langage car je possédais déjà quelques années d expérience d utilisation de PHP, de plus, le fait que l entreprise utilise aussi ce langage justifiait totalement ce choix. PostgreSQL PostgreSQL est un serveur de base de données open source qui a pour particularité contrairement à sont concurrent direct, MySQL, de gérer les transactions et d être plus à même de gérer des grosses charges de travail. Les transactions dans le domaine des bases de données ont pour but de garantir l intégrité des données. Elles peuvent être assimilées à des transactions économiques. Prenons l exemple d une personne qui irait acheter du pain à la boulangerie. Cette transaction va être composée de plusieurs étapes à savoir : choisir, payer, recevoir le produit et optionnellement recevoir la monnaie. Si une des étapes de la transaction échoue alors celle-ci va être annulée et chacune des parties va se retrouver à son état initial. Il en va de même dans le domaine des bases de données. Si une des étapes d une transaction échoue, on a la possibilité avec une base de données de type transactionnelle de revenir à l état précédent la transaction. Cela permet si une requête échoue, de ne pas appliquer les requêtes précédentes et suivantes pour ne pas corrompre les données. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 13
MVC Plus qu un outil, MVC pour «Model View Controller», est une méthode de conception d applications informatiques particulièrement adaptée à la création de sites web. Cette méthode différentie trois parties dans une application web qui sont les vues (ensembles de fichiers responsables de la génération du HTML), les modèles (fichiers responsables de la génération des données) et les contrôleurs (fichiers qui agissent sur les données). En MVC les codes de chaque partie doivent être séparés pour une meilleure visibilité de l application. SMARTY Smarty est un framework open source de «templates» (de vues) pour PHP qui à pour but d aider à la génération du HTML en employant un langage qui lui est propre. Son utilisation dans XTYWYSTX était d une part tout à fait justifiée dans le cadre du MVC mais avait plus pour vocation l enrichissement de mes connaissances. xhtml xhtml pour «Extensible HyperText Markup Language» et une amélioration du format HTML qui permet la création de pages internet. Prototype & Script.aculo.us Prototype et Script.aculo.us sont deux framework Javascript, c'est-à-dire, des librairies prêtes à l emploi. La première permet la manipulation du DOM (Document Object Model) c est à dire la manipulation des informations contenues dans un fichier HTML. Le second, qui se base sur Prototype, est une bibliothèque d effets visuels pour les pages HTML. AJAX AJAX est l acronyme de «Asynchronous JavaScript and XML». Il s agit de la méthode de conception de sites web qui est à l origine de la partie technique du WEB 2.0. L AJAX permet aux internautes, grâce au langage Javascript et à une méthode particulière appelée «xmlhttprequest», de communiquer avec un site web sans avoir à recharger les pages internet ce qui permet de gagner du temps, de la ressource puisque seules les informations nécessaires sont renvoyées et est beaucoup plus convivial pour l utilisateur. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 14
Architecture de l application La méthode MVC permet une meilleure lecture du code source, encore faut il savoir où chercher quoi. C est pour cela que j ai attaché une attention toute particulière à l architecture de mon application et à la dénomination des dossiers et fichiers pour rendre la vie plus facile aux personnes qui seront peut être un jour amenées à modifier et/ou améliorer mon application. Il faut savoir que chaque fonctionnalité de l application sera appelé par la suite un «module» et que chaque module est composé, conformément à la méthode MVC, d un contrôleur, d un modèle et d une vue. Arborescence et contenu des dossiers Dossier racine Le dossier racine contient l ensemble des dossiers de l application. Ce dossier contient deux fichiers à savoir, index.php, la page d accueille du site et un fichier.htaccess qui gère les règles de réécriture des URLs. Ce dernier peut être amené à être modifié si l application change d emplacement dans l arborescence d un serveur. Dossier conf Le dossier «conf» ne contient aucun autre dossier et ne possède qu un seul fichier, conf.php, qui est le fichier de configuration de l application. Ce dernier est inclus dans le contrôleur et le modèle de chaque module. C est dans ce fichier que sont initialisées les variables qui servent dans tous les modules de l application comme les informations concernant la connexion au serveur SQL, les paramètres de SMARTY etc. Dossier controllers Le dossier «controllers» contient les fichiers contrôleurs de chaque module qui portent la dénomination suivante : /controllers/<module>.controller.php Dossier data Le dossier «data» contient des fichiers de données sérialisées comme des fichiers de cache et des fichiers de stockage d informations. Les fichiers de données sérialisées sont des fichiers qui contiennent le contenu de variables qui ont été sérialisés, c'est-à-dire, dont on a fait passer la valeur de la forme volatile (stockée en mémoire centrale) à la forme persistante (stockée en mémoire secondaire). Dossier graphics Le dossier «graphics» stock tous les fichiers relatifs à l interface de l application à savoir les feuilles de style CSS et les images. Dossier js Le dossier «js» contient tous les fichiers Javascript de l application. C est aussi ici que sont stockés les framework Prototype et Script.aculo.us dans le sous dossier «scriptaculous». /js/scriptaculous/* /js/<module>.ajax.js IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 15
Dossier lib Le dossier «lib» contient tous les fichiers PHP nécessaire à la bonne marche de l application qui ne rentrent pas dans la méthode MVC, à savoir les fichiers de fonctions et de classes. Ils sont rangés dans des sous dossiers en fonction de leur type et portent les dénominations suivantes : /lib/<type>/<module>.<type>.php» fichiers liés à un module particulier /lib/<type>/<librairie>.<type>.php» autres fichiers /lib/<librairie>/*» librairies externes Dossier models Le dossier «models» contient les fichiers modèles de chaque module qui portent la dénomination suivante : /models/<module>.model.php Dossier views Le dossier «views» contient 4 sous dossiers où sont stockés différents types de fichiers nécessaires au fonctionnement de Smarty : /views/cache/*» fichiers caches générés par Smarty (non utilisé) /views/config/*» fichiers de configuration pour les templates Smarty (non utilisé) /views/templates/<module>.view.tpl» templates pour chaque module /views/templates_c/*» Versions compilées des templates pour chaque module IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 16
Position de l application dans l architecture Digistal Comme vous pouvez le constater sur le schéma ci-dessus, XTYWYSTX ne communique et n agit pas sur la base de données en passant par le Kernel Digistal, comme le font les sites Digistal, mais se connecte directement à la base de données. Le choix de court-circuiter le Kernel présente plusieurs avantages. D une part, il m a évité de me heurter à des limitations techniques liées au protocole de communication avec le Kernel. D autre part, cette connexion directe à la base de données réduit les temps d exécution et permet de ne pas augmenter la charge de travail du Kernel. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 17
Fonctionnement de MVC dans l application Ces schémas symbolisent le fonctionnement et le parcours des données dans l application grâce à la méthode MVC. La première partie du schéma est applicable aux modules qui proposent des actions à l utilisateur (tous à l exception d un seul), la seconde, s applique aux modules entièrement automatisés qui ne nécessitent aucune intervention de la part de l utilisateur. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 18
Modules de l application Dans cette partie seront décris les différents modules de l application, leur fonctionnement général, les problèmes qui se sont posés lors de la réalisation et les solutions trouvées. XTYWYSTX Ce module qui porte le même nom que l application en est le principal module puisque c était sa création qui justifiait ma présence chez Dreamclic. Il est chargé de la détection et de la suppression des enregistrements de chevaux erronés ou présents plusieurs fois dans la table cheval de la base de données. Sa réalisation a été la période la plus longue (temporellement parlant) du stage ce qui représente en tout plus de 50% du temps passé à la réalisation de l application entière. En tout il y aura eu trois versions majeures de ce module pour répondre à des besoins qui ont évolués au fil de sa conception. Le seul point commun entre ces différentes versions c est le fait que toute opération est sollicitée par l utilisateur, rien n a été automatisé car le facteur humain était indispensable dans chaque décision à prendre. De ce fait, l interface à été soigneusement étudiée dans le but d être la plus agréable possible pour l utilisateur. Ce module est le plus sensible de l application car c est lui qui manipule le plus de données. Afin de le valider de nombreux tests on été nécessaires pour vérifier que son utilisation ne corrompt pas les données de la base SQL. Version #1 Caractéristiques du module Ce module étant le premier que j ai commencé à réaliser il n a pas été implémenté en MVC dans sa première version. La vue et le modèle ne faisait qu un dans cette version. Cette première version du module, conformément au souhait exprimé par Mr. Martin, ne détectait que les chevaux ayant un ou des homonymes parfaits dans la base de données. Dans cette version du module toute la procédure de détection avait lieu sous forme de requêtes SQL ce qui avait pour avantage d être relativement rapide. La requête SQL de détection consistait dans un premier temps à compter pour chaque nom de chevaux combien de fois ils étaient présents dans la table et ensuite ne sélectionner que ceux présent plus d une seule fois. SELECT * FROM (SELECT nom, COUNT(nom) AS count_nom FROM cheval GROUP BY nom) AS chevaux WHERE chevaux.count_nom > 1 ORDER BY nom ASC LIMIT 20 OFFSET 0; Requête SQL de détection des homonymes Les noms des chevaux étant formaté quand il sont rentrés dans la base de données je n ai pas eu besoin de d appliquer de filtre sur les nom des enregistrements comme les fonctions TRIM qui permet d enlever les espaces en début et fin de chaine et LOWER qui change la casse des chaînes en minuscule. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 19
Le modèle et le contrôleur de cette première version permettaient à l utilisateur de pouvoir choisir l enregistrement qui reflétait le mieux la réalité et de fusionner les autres enregistrements avec celui choisi. La notion de fusion ici respecte le protocole suivant : Choix du cheval gardé et du cheval supprimé Mise à jour des liens des informations externe du cheval supprimé vers le cheval gardé Suppression du cheval supprimé J avais aussi implémenté une fonction qui sélectionnait automatiquement et qui mettait en valeur deux chevaux strictement identique (nom et caractéristiques) comme vous pouvez le voir sur la capture d écran ci-dessous pour le cheval «alcyon du defey» (cheval gardé présélectionné et surexposé en orange). Interface du module XTYWYSTX version #1 Problèmes : Cette version bien que fonctionnelle n était dans un premier temps pas conforme aux attentes de M. Bordes (principal utilisateur concerné) et ensuite, trop restrictive en ce qui concerne les actions proposées. M. Bordes voulait une détection plus vaste des doublons qui ne s arrête pas aux homonymes stricts (orthographe strictement identique). De plus le module ne prenait pas en compte le fait que chaque enregistrement puisse refléter qu une partie de la réalité. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 20
Version #2 Amélioration du modèle Contrairement à la première version du module, la détection des doublons ne se fait plus grâce au langage SQL mais entièrement en PHP. En effet le SQL ne permettait qu une détection stricte des doublons. Il a fallu réécrire entièrement le module pour pouvoir laisser une marge d erreur dans la détection. Pour cela j ai créé une copie de la table cheval sous forme de tableau PHP. Ensuite j ai conçu une fonction PHP qui détecte les doublons à partir de ce tableau et qui autorise une marge d erreur paramétrable facilement. Pour cela j ai utilisé la distance levenshtein entre deux chaines de caractères. La distance levenshtein est le nombre de caractères qui diffèrent entre deux chaines de caractères. Par exemple la distance levenshtein de deux chaînes identiques est 0, celle entre, «avion» et «aviron» est 1 puisqu une lettre a été rajoutée. Je considère que deux enregistrements sont des doublons quand la distance levenshtein entre 2 noms de chevaux est inférieure ou égal à un pourcentage de la moyenne de la longueur des deux chaines testées : $p = Pourcentage d erreur autorisé $a = Longueur de la chaine avion $b = Longueur de la chaine aviron Si la condition suivante et vraie les deux chevaux seront considérés comme des doublons. levenshtein ( avion, aviron ) <= (($a + $b) / 2) * $p) Dans le but d élargir au plus les résultats retournés par cette fonction les caractères accentués des chaines testées sont remplacé par les caractères équivalents non accentués. La taille de la table cheval a été un facteur à prendre en compte lors de la création de cette fonction car chaque enregistrement doit être testé avec tous les autres. Sur la base de données actuelle qui représente environ 8000 enregistrements cela représentait 64 000 000 de distances levenshtein à évaluer, la complexité de ma fonction étant O(n²) où n correspond au nombre d enregistrement du tableau testé. De plus, la distance levenshtein étant un algorithme gourmand en ressources dont la complexité est O(m*n) où m et n représentent la longueur des chaines testées, il a fallu restreindre le nombre de tests pour réduire le temps d exécution de la fonction. Voici les conditions que doivent respecter deux chaines pour être testées : Elles doivent commencer par la même lettre. La valeur absolue de la différence des longueurs de deux chaines doit être strictement inférieure à 20% de la moyenne de la longueur des chaines. Des conditions de sortie de boucles ont été mises en place pour limiter le temps d exécution. Considérant que la première lettre est déterminante et sachant que le tableau est trié par ordre alphabétique, on arrête de tester un enregistrement aux autres pour passer au suivant quand on IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 21
rencontre un enregistrement dont la place de la première lettre dans l ordre alphabétique est supérieure à celle de la première lettre de l enregistrement testé aux autres. Algorithme de la fonction de détection des doublons IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 22
Sur une table qui contient environ 8000 chevaux situés sur le même serveur que le serveur web, le temps d exécution de cette fonction est d une cinquantaine de secondes. Ne pouvant pas me permettre de régénérer les résultats à chaque rafraichissement de page, les résultats retournés par la fonction sous forme de tableau sont sérialisés et mis dans un cache dont la durée de vie est initialisée dans le fichier config.php, qui par défaut a une valeur de 23 heures. Amélioration de la vue et du contrôleur Dans cette seconde version du module la vue a été totalement refaite, d une part pour se conformer aux nouvelles données fournies par le modèle, d autre part pour proposer plus de fonctionnalités à l utilisateur. C est ainsi que la notion de fusion de la première version du module a été repensée. Avant il n était possible que de choisir un cheval à garder et un à supprimer. Dorénavant le choix du cheval gardé lors d une fusion n a plus d importance car il est possible de choisir, grâce à un système de boutons radio, toutes les caractéristiques voulues pour le cheval gardé en choisissant parmi les caractéristiques des chevaux faisant partie du tuple de chevaux considérées comme étant des doublons. De plus, une fusion peut à partir de cette version contenir plus de deux chevaux ce qui est plus commode pour l utilisateur. Trois nouvelles fonctions ont été rajoutées à cette version. La première fonction est la possibilité de supprimer un cheval de la base de données. La seconde permet à l utilisateur de «différentier» les chevaux d un tuple de doublons. La différentiation de chevaux permet à l utilisateur de dire à l application que plusieurs chevaux sont différents ce qui permet, quand tous les chevaux d un tuple ont été différentiés, de ne plus les afficher jusqu à ce qu un nouveau cheval soit rajouté au tuple. La troisième fonctionnalité permet à l utilisateur de voir dans une page toutes les caractéristiques des chevaux sélectionnés ainsi que les noms de leurs enfants. De plus cette fonctionnalité affiche les résultats fournit par le site des Haras nationaux (www.haras-nationaux.fr), annuaire national équin, en se basant sur une recherche des noms des chevaux sélectionnés. Problème : Le fait que les résultats de la fonction de détection des doublons soient mis dans un cache pose un problème. En effet si un client venait à modifier un cheval pendant la durée de vie du cache des résultats, les modifications du client peuvent être écrasées par une action de fusion car le cache ne prend pas en compte les modifications externes. Autre cas, un utilisateur est en train de télécharger une vidéo sur le serveur Digistal pour un cheval, si le cheval est supprimé par une action de fusion, l utilisateur aura perdu du temps à faire une modification qui au final n aboutira pas. Contrairement au premier, le deuxième cas n est pas gérable car chaque document est associé à un cheval grâce à l identifiant de ce dernier. Or, lors d une fusion, l identifiant du cheval qui résulte de la fusion de chevaux est choisi aléatoirement parmi ceux des chevaux fusionnés avec une préférence pour l identifiant du premier cheval appartenant à un groupe. Donc si ce cas arrivait il n y aurait qu une minuscule chance pour que l identifiant du cheval associé au document téléchargé durant la fusion corresponde à l identifiant du nouveau cheval. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 23
Captures d écran Interface de la version #2 du module XTYWYSTX Console qui permet la gestion des actions sur les chevaux sélectionnés IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 24
Fiches des chevaux sélectionnés avec les résultats fournit par les haras nationaux. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 25
Version #3 Le but de cette version était de remédier au problème mis en exergue précédemment. La version #2 ayant été validée comme fonctionnelle par Mrs Bordes et Martin. La première idée qui m est venue pour résoudre le problème cité fut d instaurer un mode de maintenance dans l administration des sites Digistal empêchant les clients de pouvoir modifier leurs chevaux durant l utilisation de ce module. Cette idée qui aurait été facile à implémenter a été supplantée par une autre idée que m a proposée M. Martin. Son idée consistait à reproduire le comportement des utilitaires de partitionnement de disque dur et en particulier celui de Partition Magic. Ce logiciel propose tout une pléthore d actions à effectuer sur un disque dur. Dans un premier temps il nous demande de choisir les actions que nous voulons effectuer et il les empiles dans un log d actions qu il exécutera d un seul coup au redémarrage de la machine. L idée était donc, au lieu d exécuter les actions au fur et à mesure, de les lister dans un log et de les exécuter toutes ensemble à la fin de l opération avec une vérification des données. Pour cela j ai modifié le contrôleur du module pour qu il empile les actions avec toutes leurs caractéristiques dans un log au lieu des les exécuter et j ai modifié le modèle pour qu il crée deux copie des résultats de la fonction de détection des doublons que j ai mis en cache. Ces deux caches seront nommés respectivement cache de vérification et cache d affichage. Le cache de vérification n est jamais modifié par le contrôleur quand l utilisateur valide une action (fusion ou suppression) contrairement au cache d affichage car ce premier servira lors de l exécution du log d action à vérifier que les chevaux de la base de données qui sont présents dans le log d actions n ont pas été modifiés durant la durée de vie du cache (durée durant laquelle l utilisateur et censé constitué un log d actions) par un client Digistal. Le cache d affichage, qui, comme son nom l indique, contient les données qui seront affichées, est modifié à chaque action pour que l affichage des pages corresponde bien aux actions qui seront effectuées en fin d opération quand l utilisateur rafraichit les pages. Il faut savoir que le log d action est écrasé à chaque fois que le cache d affichage et le cache de vérifications sont régénérés, c'est-à-dire, à la fin de la durée de vie du cache définie ou alors lorsque le pourcentage d erreur autorisé dans la détection des doublons est changé via l interface. L avantage de ce système est, qu en plus de résoudre le problème posé précédemment, qu il permet de revenir en arrière si l utilisateur se rend compte qu il s est trompé dans une action. En effet la vue qui permet d exécuter le log d action permet aussi des actions à ne pas exécuter ce qui fournit une sécurité supplémentaire lors de l utilisation de ce module. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 26
Problème : Il peut arriver qu une action modifie un cheval et de fait son identifiant dans la base de données et que ce cheval soit parent d un ou plusieurs chevaux qui font aussi partie d un ou plusieurs tuples de doublons. Si plusieurs de ces chevaux se retrouvent associées à des actions dans le log d actions seule la première action sera exécutée car le contrôleur ne met pas à jour les enfants des chevaux dans le cache d affichage or c est sur ce cache que sont basées les caractéristiques des actions et ces actions échoueront lors de la vérification des données puisque l identifiant du cheval parent aura sans doute été modifié par la première action. Ce problème, bien que mis à jour, n est pas voué à être résolu car il peut être rencontré que dans de très rares cas et de plus, il ne pose aucun problème de corruption de données. Le seul effet négatif sera la non exécution d un petit nombre d actions qui seront listées dans un rapport qui sera présenté à l utilisateur si au moins une action n a pas pu être exécutée. Diagramme de séquence UML du module XTYWYSTX dans son ensemble IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 27
Captures d écran Interface de la version #3 du module XTYWYSTX Interface d exécution du log d action du module XTYWYSTX version #3 IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 28
Interface du rapport d erreur du module XTYXYSTX version #3 IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 29
GhostBuster Ghostbuster est un module qui va servir à éliminer les enregistrements dans différentes tables de la base données qui n ont plus de raison d être présent dans le but de faire respecter l intégrité référentielle de la base de données. Il s agit par exemple d enregistrements de documents sur des chevaux qui n existent plus. On appelle ces enregistrements des enregistrements fantômes d où le titre du module qui est un clin d œil au célèbre blockbuster américain. Ce module est le seul module entièrement automatisé, l utilisateur ne peut y faire aucune action mais peut y consulter les résultats des opérations effectuées listées dans un rapport. Captures d écran Interface du module Ghostbuster avec les résultats des opérations IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 30
Dédale Ce module qui porte le nom du personnage de la mythologie grecque auquel on associe la conception du célèbre labyrinthe renfermant le Minotaure permet la détection d anomalies dans la généalogie des chevaux présents dans la base de données. J ai choisi ce nom pour ce module parce que la première impression que j ai eue quand on m a demandé la conception de ce module était que cela allait être un vrai casse tête. La définition d une anomalie dans la généalogie prise en compte dans ce module est la présence de cycles. Un cycle est une anomalie qui permet de faire une boucle infinie lorsque l on remonte la généalogie d un cheval. Voici un exemple concret de cycle : Sur ce schéma le haut de la flèche représente le parent et le bas l enfant. On voit ici que les chevaux #1 et #2 sont les parents du cheval #3 et que ce dernier avec le cheval #4 sont les parents du cheval #5. Sur ce schéma l anomalie vient du fait que le cheval #5 soit considéré comme le père du cheval #1 ce qui est totalement absurde (même dans le domaine équin) car cela reviendrait à dire que le cheval #5 est le père de son grand père. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 31
La façon dont a été implémentée la base de données permet de remonter la généalogie d un cheval car chaque enregistrement est renseigné avec l identifiant de sa mère et de son père. Dans cet exemple si on remonte la généalogie l on va s apercevoir que l on peut faire un cycle complet qui va se traduire par une boucle infinie car on peut remonter sa généalogie sans jamais s arrêter si aucune vérification n est faite. Le but de ce module est donc de repérer les cycles dans la base de données. Pour cela j ai créé une copie de la table cheval sous forme de tableau PHP comme je l avais fait pour le module XTYWYSTX au détail près que le tableau ne contient que l identifiant du cheval, son nom et les identifiants des ses parents. A partir de ce tableau j ai conçu une fonction PHP récursive qui permet la détection de cycle en testant la généalogie des chevaux un par un. Cette fonction retourne un booléen qui est initialisé à vrai lorsque la fonction a détecté un cycle dans la généalogie du cheval. A noter, la fonction ne retournera vrai que si le cheval fait directement partie d un cycle comme c est le cas dans l exemple précédent (chevaux #1, #3, #5) mais elle retournera faux si il y a bien un cycle dans sa généalogie mais qu il n en fait pas directement partie comme dans l exemple ci-dessous (cheval #6). IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 32
Le fait de ne considérer la généalogie d un cheval comme erronée que s il fait partie d un cycle a pour but de limiter les résultats pour faciliter la détection de l erreur par l utilisateur. Une fois la liste des chevaux faisant partie d un cycle générée, j ai conçu une fonction qui permet de savoir si un cheval fait partie du même cycle qu un autre cheval. Cette fonction est en fait constituée d un simple test ; S il est possible de trouver le deuxième cheval en remontant la généalogie du premier cheval et (et non pas ou, c est très important) vice et versa alors cela veut dire que les deux chevaux font partie du même cycle. Cette fonction est très intéressante pour l utilisateur, car elle permet, si la généalogie de la base de données contient plusieurs cycles, de regrouper les chevaux en fonction du cycle auquel ils appartiennent. Problème : Lors des tests de la fonction de détection de cycle je me suis rendu compte, lorsque je ne gérais pas encore les boucles infinies, que PHP n autorise que 64 appels de fonction récursif. Cela veut dire que si la généalogie d un cheval comporte plus de 64 parents, la fonction va s arrêter sur une erreur. En clair cela veut dire que ma fonction ne sera pas capable de détecter les cycles contenant plus de 64 chevaux. Capture d écran Interface du module Dédale IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 33
Reuters Ce module, qui porte le nom de la célèbre agence de presse, est destiné à gérer les auteurs de documents dans la base de données. En effet il arrive souvent qu un auteur recrée un compte à chaque fois qu il veut poster des documents au lieu d utiliser son véritable compte. Ce module se contente donc de permettre à l utilisateur la fusion et la modification des auteurs. L interface de ce module a été conçue dans le but d être la plus agréable possible à utiliser. Ainsi, l utilisateur n a jamais besoin de rafraichir la page pour voir le résultat des actions qu il a effectuées. Captures d écran Interface du module Reuters Modification d un auteur IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 34
Fusion d auteurs IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 35
DeviantHorse DeviantHorse est un module créé à mon initiative qui permet la détection des chevaux dont «l orientation sexuelle» a été quelque peu malmenée par les clients. Il s agit en fait de détecter la présence de chevaux qui, par exemple, de sexe masculin, sont considérés comme étant la mère d un ou plusieurs autres chevaux et inversement, c'est-à-dire, des chevaux de sexe féminin étant considérées comme étant le père d un ou plusieurs autres chevaux. SELECT * FROM cheval WHERE "idsexe" IN (2, 3) AND id IN (SELECT "idmere" FROM cheval WHERE "idmere"!= 0); Requête SQL de détection des chevaux mâles étant mère SELECT * FROM cheval WHERE "idsexe" = 1 AND id IN (SELECT "idpere" FROM cheval WHERE "idpere"!= 0); Requête SQL de détection des chevaux femelle étant père La vue et le contrôleur de ce module permettent à l utilisateur de changer le sexe des chevaux concernés par ces problèmes. Captures d écran Interface du module DeviantHorse IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 36
DBDump DBDump est un module qui permet de sauvegarder la base de données sous forme de script SQL. Il a pour but de permettre à l utilisateur de réinitialiser les données de la base dans le cas où ce dernier aurait effectué des actions non désirées ou bien si l utilisateur se rend compte que l application contient des erreurs et ne fait pas ce qu il faut. Il s agit en fait de l ultime sécurité pour garantir les données de la base. Les sauvegardes sous formes de script SQL ne touchent pas à la structure de la table, elles se contentent seulement quand on les exécute de vider les données des tables et de réinitialiser les données comme elles étaient lors de la sauvegarde. Pour cela l utilisateur dispose d une administration qui lui permet de créer des sauvegardes simplement et de les exécuter si besoin est. Capture d écran Interface du module de sauvegarde de la base SQL. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 37
AJAX dans l application Un des principaux but que je m étais fixé pour la réalisation de cette application était de concevoir une interface dite «user friendly» qui veut dire en français «conviviale pour l utilisateur». Pour cela j ai eu recours à une technologie très en vogue depuis deux ans, l AJAX. AJAX et une technologie qui fait appel à deux éléments, le Javascript et le XML. Le Javascript est langage interprété qui s exécute côté client (dans le navigateur de l utilisateur) contrairement au PHP qui s exécute côté serveur. Grâce à lui on peut rendre une page HTML, normalement statique quand elle arrive dans le navigateur du client, dynamique. Le XML quand à lui est un langage de balisage qui sert à la transmission de données. Couplés ensemble, ces deux éléments permettent à une page web de dialoguer avec le serveur pour mettre à jour le contenu d une page web sans avoir à recharger cette dernière. Comparaison du fonctionnement d un site web normal avec celui d un site web utilisant l AJAX IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 38
Ce projet étant ma première réalisation faisant appel à cette technologie je n ai pas implémenté le côté XML, à savoir, dans mon application, le serveur renvoie aux pages des données qui ne sont pas formatées au format XML mais sous forme de chaînes de caractères au format CSV (comma separated values). Concrètement, dans mon application, tous les formulaires qui servent habituellement à envoyer des données au serveur ont été remplacés par l AJAX ce qui a permis, d une part, d éviter le rechargement des pages à chaque action voulue par l utilisateur et, d autre part, à permis de représenter à l écran ces actions sous formes d effets graphiques (flou, surexposition, apparition d éléments etc). C est dans cette optique qu interviennent Prototype et Script.aculo.us. Le premier permettant de rechercher les données nécessaire dans le HTML des pages pour les envoyer au serveur grâce au Javascript, le second permettant de représenter visuellement les actions exécutées par l utilisateur. Cette première approche d AJAX se solde par un bilan en demi-teinte. La partie positive est que le tout soit fonctionnel et répond très favorablement au but que je m étais fixé, à savoir, d être agréable à utiliser pour l utilisateur. Cependant deux choses viennent noircir le tableau. Premièrement l AJAX sur lequel repose une grande partie du fonctionnement de l application ne fonctionne qu avec le navigateur web Firefox. Ce n est pas réellement ennuyeux pour les futurs utilisateurs car ils utilisent ce navigateur mais c est plutôt un échec personnel que de ne pas avoir su rendre mon code fonctionnel pour Internet Explorer. Deuxièmement, je suis conscient que mon code Javascript a un caractère quelque peu brouillon du fait que c était réellement la première fois que j exploitais vraiment les possibilités du Javascript et à cause de la difficulté que j ai rencontré à manipuler le DOM. J ai tout de même gardé une cohérence dans ma manière de développer les scripts dans le but de ne pas désorienter les futurs lecteurs de mon code. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 39
Conclusion Ce stage à été une expérience vraiment épanouissante. C était la première fois que je mettais à disposition mes compétences dans le domaine du développement informatique orienté web pour une personne autre que moi et c est quelque chose que j ai vraiment appréciée et qui m a motivée. Les objectifs de ce projet étaient concrets : arriver à réaliser une application fonctionnelle est sûre, capable de répondre à un besoin de l entreprise sans autre contrainte que celle du temps. En l occurrence, cette dernière n aura pas été réellement un problème puisque la réalisation de cette application aura prise entre 6 et 7 semaines sur les 10 semaines du stage. Même si cette période peut paraître courte il ne faut pas croire que le travail que l on m a confié était si simple que ça. Mon expérience dans la programmation web m a grandement aidé dans la réalisation de ce projet. De plus, j ai profité de la période allant du moment où M. Bordes m a assigné ma mission dans son entreprise et la date de mon entrée dans la société pour réfléchir à des solutions dans le but de mener ma tâche à bien. C est grâce cette avance que j avais prise que je me suis permis de m intéresser à des technologies que je ne connaissais pas et de les intégrer dans le projet. Ce stage m aura permis de vivre réellement toutes les étapes d un projet au sein d une entreprise, de l expression des besoins au déploiement du projet, en passant par les évolutions des besoins et les phases de tests le tout dans une ambiance chaleureuse, décontractée et professionnelle. dans le but de IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 40
Annexes Codes critiques Fonction qui à partir d une copie de la table «cheval» sous forme de tableau PHP retourne un tableau avec tous les chevaux considérés comme doublons. function compare_horses($array) $i = 0; foreach ($array as $akey => $a) $i++; if (strlen($a['fnom']) === 0) continue; foreach ($array as $bkey => $b) if ($a['fnom']0 < $b['fnom']0) break; if ($a['fnom']0!= $b['fnom']0) continue; $sa = strlen($a['fnom']); $sb = strlen($b['fnom']); $average = ($sb + $sa) / 2; if (abs($sa - $sb) > $average * 20 / 100) continue; $levenshtein = levenshtein($a['fnom'], $b['fnom']); $min = $average * PERCENT_COMPARISON / 100; if ($levenshtein < $min) $sorted_horses[$i][] = $b; unset($array[$bkey]); return $sorted_horses; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 41
Fonction qui détecte les cycles dans une copie de la table cheval sous forme de tableau PHP. function is_genealogy_invalid($table, $id, $passed) /* Si pas de parents ------------------------------------- if ($table[$id]['idmere'] === 0 && $table[$id]['idpere'] === 0) return false; /* Mere ------------------------------------- if ($table[$id]['idmere']!== 0) /* Mere Mere ------------------------------------- if ($table[$table[$id]['idmere']]['idmere'] === 0) $mere_mere = false; /* On a trouvé un cycle ------------------------------------- if ($table[$table[$id]['idmere']]['idmere'] == current($passed)) $mere_mere = true; /* On a trouvé un cycle mais le cheval n'en fait pas directement partie ------------------------------------- if (in_array($table[$table[$id]['idmere']]['idmere'], $passed)) $mere_mere = false; $passed); /* On passe à la génération suivante ------------------------------------- $passed[] = $id; $mere_mere = is_genealogy_invalid($table, $table[$table[$id]['idmere']]['idmere'], /* Mere Pere ------------------------------------- if ($table[$table[$id]['idmere']]['idpere'] === 0) $mere_pere = false; if ($table[$table[$id]['idmere']]['idpere'] == current($passed)) $mere_pere = true; if (in_array($table[$table[$id]['idmere']]['idpere'], $passed)) $mere_pere = false; $passed); $passed[] = $id; $mere_pere = is_genealogy_invalid($table, $table[$table[$id]['idmere']]['idpere'], IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 42
/* Pere ------------------------------------- if ($table[$id]['idpere']!== 0) /* Pere Pere ------------------------------------- if ($table[$table[$id]['idpere']]['idpere'] === 0) $pere_pere = false; if ($table[$table[$id]['idpere']]['idpere'] == current($passed)) $pere_pere = true; if (in_array($table[$table[$id]['idpere']]['idpere'], $passed)) $pere_pere = false; $passed); $passed[] = $id; $pere_pere = is_genealogy_invalid($table, $table[$table[$id]['idpere']]['idpere'], /* Pere Mere ------------------------------------- if ($table[$table[$id]['idpere']]['idmere'] === 0) $pere_mere = false; if ($table[$table[$id]['idpere']]['idmere'] == current($passed)) $pere_mere = true; if (in_array($table[$table[$id]['idpere']]['idmere'], $passed)) $pere_mere = false; $passed[] = $id; $pere_mere = is_genealogy_invalid($table, $table[$table[$id]['idpere']]['idmere'], $passed); return ($pere_pere $mere_mere $pere_mere $mere_pere) === true? true : false; Fonction qui sert à déterminer si deux chevaux font parti du même cycle. function is_in_same_cycle($table, $id, $id2) return is_genealogy_invalid($table, $id, array($id2)) && is_genealogy_invalid($table, $id2, arr ay($id)); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 43
Travaux annexes Voici la liste des travaux que j ai effectués au sein de l entreprise à partir du moment où ma mission principale a été accomplie. K2 K2 est un thème open source pour weblog créé par Michael Heilemann (www.binarybonsai.com). Ce thème est une évolution du thème «Kubrick» du même auteur qui est le thème proposé par défaut par le blogware Wordpress (www.wordpress.org). J ai effectué un port de ce thème (feuille de style CSS) pour la plateforme de blog du site www.equisup.fr qui à été mise en ligne durant mon stage. Rendu final du port que j ai effectué pour la plateforme de blog Equisup IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 44
afiles afiles est une classe open source sous licence LGPL que j ai développée dans le but d aider à la manipulation des fichiers et des dossiers dans une arborescence web. Cette classe dispose d une documentation située à l adresse suivante : http://absynthe.is.free.fr/afiles/ Code source : /* * Project: Absynthe files * File: afiles.class.php5 * Author: Sylvain 'Absynthe' Rabot <sylvain@abstraction.fr> * Website: http://absynthe.is.free.fr/afiles/ * Version: alpha 2 * Date: 29/05/2007 * License: LGPL class afiles * Default path to an entity * @var string * @access private private $path = false; * Constructor which allows to chose the path of the entity used for following actions * @param string $path public function construct($path = false) $this->path = $path; * Methods which allows to chose the path of the entity used for following actions * @param string $path public function set_path($path = false) $this->path = $path; * Return the path of the entity used by default * @return string public function get_path() return $this->path; * Return the type of an entity * @param string $path * @return string IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 45
public function type($path = null) $this->test_var($path, $this->path); if (is_dir($path)) return 'dir'; if (is_file($path)) return 'file'; return false; * Return the list of all entity included in the path * @param string $path * @param boolean $withroot * @return array public function ls($path = null, $withroot = true) $this->test_var($path, $this->path); if (!is_dir($path)) return false; ) if ($handle = opendir($path)) while (false!== ($file = readdir($handle))) if ($file!= '..' && $file!= '.' && $file!= '' && (substr($file,0,1)!= '.') if ($withroot) $infos[] = $path.'/'.$file; $infos[] = $file; closedir($handle); $infos = array_map(array($this, 'format_path'), $infos); return $infos; return false; * Return the list of directories included in the path * @param string $path * @param boolean $withroot * @return array IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 46
public function lsd($path = null, $withroot = true) $this->test_var($path, $this->path); if (!is_dir($path)) return false; $infos = array(); if ($handle = opendir($path)) while (false!== ($file = readdir($handle))) if (is_dir($path.'/'.$file)) if ($file!= '..' && $file!= '.' && $file!= '') if ($withroot) $infos[] = $path.'/'.$file; $infos[] = $file; $infos = array_map(array($this, 'format_path'), $infos); closedir($handle); return $infos; return false; * Return infos of all entity included in the path * @param string $path * @param boolean $withroot * @return array public function ll($path = null, $withroot = true) $this->test_var($path, $this->path); if (!is_dir($path)) return false; if ($handle = opendir($path)) while (false!== ($file = readdir($handle))) if ($file!= '..' && $file!= '.' && $file!= '') if ($withroot) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 47
$temp = $this->format_path($path.'/'.$file); $infos[$temp] = $this->infos($temp); $temp = $this->format_path($path.'/'.$file); $infos[$temp] = $this->infos($temp); closedir($handle); return $infos; return false; * Return infos of all directories included in the path * @param string $path * @param boolean $withroot * @return array public function lld($path = null, $withroot = true) $this->test_var($path, $this->path); if (!is_dir($path)) return false; if ($handle = opendir($path)) while (false!== ($file = readdir($handle))) if (is_dir($path.'/'.$file)) if ($file!= '..' && $file!= '.' && $file!= '') if ($withroot) $temp = $this->format_path($path.'/'.$file); $infos[$temp] = $this->infos($temp); $temp = $this->format_path($file); $infos[$temp] = $this->infos($path.'/'.$file); closedir($handle); return $infos; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 48
return false; * Create a file with or without content * @param string $content * @param string $path * @return boolean public function mkfile($content = '', $path = null) $this->test_var($path, $this->path); if ($handle = fopen($path, 'w+')) if (strlen($content)!= 0) fwrite($handle, $content); fclose($handle); return true; return false; * Read the content of a file * @param string $path * @param boolean $byline * @param int $length * @return string/array public function read_file($path = null, $byline = false, $length = 1024) $this->test_var($path, $this->path); if (!is_file($path)) return false; if ($byline) if ($handle = fopen($path, 'r')) while (!feof($handle)) $lines[] = fgets($handle, $length); fclose($handle); return $lines; return false; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 49
return file_get_contents($path); * Create a directory with/without chmod * @param string $path * @param int $chmod * @return boolean public function mkdir($path = null, $chmod = null) $this->test_var($path, $this->path); if (@mkdir($path)) if (!is_null($chmod)) chmod($path, $chmod); return true; return false; * Move a file or a directory * @param string $path * @param string $where * @return boolean public function mv($path, $where) if (!is_dir($where)) return false; if (is_dir($path)) $tree = $this->tree($path); $this->cp($path, $where); $this->rm($tree); if (is_file($path)) $this->cp($path, $where); $this->rm($path); return true; * Remove files or/and directories * @param string $path IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 50
* @return boolean public function rm($path = null) $this->test_var($path, $this->path); if (!is_array($path)) $path = array($path); foreach ($path as $file) if (is_dir($file)) $tree = $this->tree($path); rsort($tree); foreach ($tree as $f) if (is_dir($f)) rmdir($f); if (is_file($f)) unlink($f); if (is_file($file)) unlink($file); return false; return true; * Copy files or/and directories * @param string $path * @param string $where * @return boolean public function cp($path, $where) if (!is_dir($where)) return false; if (!is_array($path)) $path = array($path); foreach($path as $file) if (is_file($file)) copy($file, $where.'/'.$file); if (is_dir($file)) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 51
$files = $this->tree($file); $this->mkdir($where.'/'.$file); foreach ($files as $f) if (is_file($f)) copy($f, $where.'/'.$f); if (is_dir($f)) $this->mkdir($where.'/'.$f); return true; * Return the mod of a file/directory * Credits goes to Ambriel_Angel (www dot ambriels dot net) * @param string $path * @return int public function mod($path) $this->test_var($path, $this->path); // Initialisation $val = 0; $perms = fileperms($path); // Owner; User $val += (($perms & 0x0100)? 0x0100 : 0x0000); $val += (($perms & 0x0080)? 0x0080 : 0x0000); $val += (($perms & 0x0040)? 0x0040 : 0x0000); // Group $val += (($perms & 0x0020)? 0x0020 : 0x0000); $val += (($perms & 0x0010)? 0x0010 : 0x0000); $val += (($perms & 0x0008)? 0x0008 : 0x0000); // Global; World $val += (($perms & 0x0004)? 0x0004 : 0x0000); $val += (($perms & 0x0002)? 0x0002 : 0x0000); $val += (($perms & 0x0001)? 0x0001 : 0x0000); // Read // Write // Execute // Read // Write // Execute // Read // Write // Execute // Misc $val += (($perms & 0x40000)? 0x40000 : 0x0000); // temporary file (01000000) $val += (($perms & 0x80000)? 0x80000 : 0x0000); // compressed file (02000000) $val += (($perms & 0x100000)? 0x100000 : 0x0000); // sparse file (04000000) $val += (($perms & 0x0800)? 0x0800 : 0x0000); // Hidden file (setuid bit) (04000) $val += (($perms & 0x0400)? 0x0400 : 0x0000); // System file (setgid bit) (02000) $val += (($perms & 0x0200)? 0x0200 : 0x0000); // Archive bit (sticky bit) (01000) return decoct($val); * Return infos concerning the entity * @param string $path IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 52
* @param boolean $withroot * @param boolean $content * @param boolean $byline * @param int $length * @return array public function infos($path = null, $withroot = true, $content = true, $byline = false, $l ength = 1024) $this->test_var($path, $this->path); if (is_dir($path)) if ($handle = opendir($path)) $infos[type] = 'dir'; $infos[path_infos] = pathinfo($path); $infos[atime] = fileatime($path); $infos[ctime] = filectime($path); $infos[mtime] = filemtime($path); $infos[chmod] = $this->mod($path); $infos[owner_id] = fileowner($path); $infos[owner_infos] = posix_getpwuid($infos[owner_id]); $infos[group_id] = filegroup($path); $infos[group_infos] = posix_getgrgid($infos[group_id]); $infos[dir_count] = 0; $infos[files_count] = 0; $infos[size] = $this->filesize($path.'/'.$file); $infos[files] = array(); $infos[directories] = array();!= '.')) while (false!== ($file = readdir($handle))) if (is_dir($path.'/'.$file)) if ($file!= '..' && $file!= '.' && $file!= '' && (substr($file,0,1) $infos[dir_count]++; if ($withroot) $infos[directories][] = $path.'/'.$file; $infos[directories][] = $file; if (is_file($path.'/'.$file)) $infos[files_count]++; if ($withroot) $infos[files][] = $path.'/'.$file; $infos[files][] = $file; ries]); $infos[files] $infos[directories] = array_map(array($this, 'format_path'), $infos[files]); = array_map(array($this, 'format_path'), $infos[directo IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 53
$this->sort_results($infos[directories]); $this->sort_results($infos[files]); closedir($handle); ath); return $infos; return false; if (is_file($path)) if ($handle = fopen($path, 'r')) $infos[type] = 'file'; $infos[path_infos] = pathinfo($path); $infos[atime] = fileatime($path); $infos[ctime] = filectime($path); $infos[mtime] = filemtime($path); $infos[chmod] = $this->mod($path); $infos[owner_id] = fileowner($path); $infos[owner_infos] = posix_getpwuid($infos[owner_id]); $infos[group_id] = filegroup($path); $infos[group_infos] = posix_getgrgid($infos[group_id]); $infos[lines_count] = 0; $infos[size] = $this->filesize($path); $infos[md5] = md5_file($path); $infos[sha1] = sha1_file($path); if ($content) $infos[content] = $byline === true? array() : file_get_contents($p while (!feof($handle)) if ($byline && $content) $infos[content][] = fgets($handle, $length); fgets($handle, $length); $infos[lines_count]++; fclose($handle); return $infos; return false; return false; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 54
* Return tree of a directory * @param string $path * @param boolean $expand2files * @return array public function tree($path = null, $expand2files = true) $this->test_var($path, $this->path); $directories = $this->lsd($path); for ($x = 0; $x < count($directories); $x++) if (!is_dir($directories[$x])) continue;!= '.')) if ($handle = opendir($directories[$x])) while (false!== ($file = readdir($handle))) if (is_dir($directories[$x]."/".$file)) if ($file!= '..' && $file!= '.' && $file!= '' && (substr($file,0,1) $directories[] = $directories[$x]."/".$file; closedir($handle); $directories[] = false; $directories[] $directories = $path; = array_map(array($this, 'format_path'), $directories); if ($expand2files) foreach ($directories as $dir) $expanded_directories[] = $dir; if ($handle = opendir($dir)) while (false!== ($file = readdir($handle))) if (is_file($dir.'/'.$file)) $expanded_directories[] = $dir.'/'.$file; $expanded_directories[] = false; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 55
es); $expanded_directories = array_map(array($this, 'format_path'), $expanded_directori $this->sort_results($expanded_directories); $this->sort_results($directories); return $expand2files === true? $expanded_directories : $directories; * Return the size of an entity * @param string $path * @return int public function filesize($path = null) $this->test_var($path, $this->path); if (is_file($path)) return filesize($path); $tree = $this->tree($path); $size = 0; foreach ($tree as $file) if (is_file($file)) $size += filesize($file); return $size; * Serialize and creates a file with the serial * @param anything $var * @param string $path * @return boolean public function serialize($var, $path = null) $this->test_var($path, $this->path); if($this->mkfile(serialize($var), $path)) return true; return false; * Unserialize a file * @param string $path * @return array IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 56
public function unserialize($path = null) $this->test_var($path, $this->path); if(is_file($path)) return unserialize($this->read_file($path)); return false; * Parse a ini file * @param string $path * @return array public function parse_ini($path = null, $whithsection = true) $this->test_var($path, $this->path); if(is_file($path)) return parse_ini_file($path, $whithsection); return false; * Make ini file * @param array $content * @param string $path * @return boolean public function mkini($content, $path = null) $this->test_var($path, $this->path); $out = ''; if (!is_array($content)) return false; foreach ($content as $key => $ini) if (is_array($ini)) $out.= "n[".$key."]nn"; foreach ($ini as $var => $value) $out.= $var." tt= ".$this->quote_ini($value)."n"; $out.= $key." tt= ".$this->quote_ini($ini)."n"; return $this->mkfile($out, $path); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 57
/* Private section ------------------------------------------------- * Set a variable to $default parameter if it's null * @param anything $var * @param anything $default private function test_var(&$var, $default) if (is_null($var) strlen(trim($var)) === 0) $var = $default; * Replace '//' by '/' in paths * @param string $path * @return string private function format_path($path) return preg_replace('#/2,#', '/', $path); * Quote ini var if needed * @param anything $var * @return anything private function quote_ini($var) return is_string($var) === true? '"'.str_replace('"', '"', $var).'"' : $var; * Sort results * @param array $array * @return array private function sort_results(&$array) if (is_array($array)) array_multisort(array_map('strtolower', $array), SORT_STRING, SORT_ASC, $array); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 58
sql4array sql4array est une classe PHP5 open source sous licence LGPL que j ai développée dans le but de pouvoir manipuler des tableaux PHP grâce au langage SQL. Cette classe à caractère hautement expérimental ne gère pour l instant que les requêtes de types SELECT sur des tableaux à 2 dimensions. Elle à été nominé par le site PHPClasses.org pour être élue «Classe la plus innovante du mois de mai 2007» par les membres du site. La documentation est située à l adresse suivante : http://absynthe.is.free.fr/sql4array/ Code source : /* * Project: Absynthe sql4array * File: sql4array.class.php5 * Author: Absynthe <sylvain@abstraction.fr> * Website: http://absynthe.is.free.fr/sql4array/ * Version: alpha 3 * Date: 04/05/2007 * License: LGPL /* * Warnings : * This classe is under developpment. * Don't take all results for granted. * If you find out a bug or if you improved this, * please contact me to share your work like I did. /* * Clauses available : * SELECT, DISTINCT, FROM, WHERE, ORDER BY, LIMIT, OFFSET * * Operators available : * =, <, >, <=, >=, <>,!=, IS, IS IN, IS NOT, IS NOT IN, LIKE, ILIKE, NOT LIKE, NOT ILIKE * * Functions available in WHERE clause : * LOWER(var), UPPER(var), TRIM(var) class sql4array /* Init -------------------------------------------- private $query = FALSE; // the last query private $parse_query = FALSE; // array of the last query private $parse_query_lower = FALSE; // lowercase array of the last query private $parse_select = FALSE; // array of the 'select' clause private $parse_select_as = FALSE; // array of the 'select' clause with alias of the column as key and column name as value private $parse_from = FALSE; // value of the 'from' clause private $parse_from_as = FALSE; // array of the 'from' clause with alias of the table as key and table name as value private $array_columns = FALSE; // array with all columns of the table listed private $parse_where = FALSE; // string with the PHP condition private $distinct_query = FALSE; // boolean private $table = FALSE; private $time_start = 0; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 59
private $time_end = 0; private $response = array(); /* Query function -------------------------------------------- public function query($query) /* Initialization -------------------------------------------- $this->destroy(); $this->query = $query; $this->time_start = microtime(true); /* Query parsing -------------------------------------------- $this->parse_query(); $this->parse_select(); $this->parse_select_as(); $this->parse_from(); $this->parse_from_as(); $this->get_array_columns(); $this->parse_where(); /* Query execution -------------------------------------------- $this->exec_query(); /* Response sorting -------------------------------------------- $this->parse_order(); $this->time_end = microtime(true); return $this->response; /* Query duration -------------------------------------------- public function duration() return $this->time_end - $this->time_start; /* Destroy current values -------------------------------------------- private function destroy() $this->query = FALSE; $this->parse_query = FALSE; $this->parse_query_lower = FALSE; $this->parse_select = FALSE; $this->parse_select_as = FALSE; $this->parse_from = FALSE; $this->parse_from_as = FALSE; $this->parse_where = FALSE; $this->array_columns = FALSE; $this->distinct_query = FALSE; $this->time_start = 0; $this->time_end = 0; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 60
$this->table $this->response = FALSE; = array(); /* Parse SQL query -------------------------------------------- private function parse_query() $this- >parse_query = preg_replace('#order(s)2,by(s+)(.*)(s+)(asc DESC)#i', 'ORDER BY \ 3 \5', $this->query); $this- >parse_query = preg_split('#(select DISTINCT FROM JOIN WHERE ORDER(s+)BY LIMIT OFF SET)+#', $this->parse_query, -1, PREG_SPLIT_DELIM_CAPTURE); if (count($this->parse_query) < 2) trigger_error("unable to parse query, make sure all keywords are in uppercase", E_ USER_ERROR); $this->parse_query $this->parse_query_lower = array_map('trim', $this->parse_query); = array_map('strtolower', $this->parse_query); /* Parse SQL 'select' clause -------------------------------------------- private function parse_select() $key = array_search("distinct", $this->parse_query_lower); if ($key === FALSE) $key = array_search("select", $this->parse_query_lower); $this->distinct_query = TRUE; $string $arrays = $this->parse_query[$key+1]; = preg_split('#((s)*,(s)*)#i', $string, -1, PREG_SPLIT_NO_EMPTY); foreach ($arrays as $array) $this->parse_select[] = $array; /* Parse again SQL 'select' clause with 'as' keyword -------------------------------------------- private function parse_select_as() foreach ($this->parse_select as $select) if (eregi('as', $select)) $arrays = preg_split('#((s)+as(s)+)#i', $select, -1, PREG_SPLIT_NO_EMPTY); $this->parse_select_as[$arrays[1]] = $arrays[0]; $this->parse_select_as[$select] = $select; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 61
/* Parse SQL 'from' clause -------------------------------------------- private function parse_from() $key = array_search("from", $this->parse_query_lower); $this->parse_from = $this->parse_query[$key+1]; /* Parse again SQL 'from' clause with 'as' keyword -------------------------------------------- private function parse_from_as() if (eregi('as', $this->parse_from)) $arrays = preg_split('#((s)+as(s)+)#i', $this->parse_from, - 1, PREG_SPLIT_NO_EMPTY); $table = $arrays[0]; global $$table; $this->parse_from_as[$arrays[1]] $this->table $table = $this->parse_from; = $table; = $$table; global $$table; $this->parse_from_as[$this->parse_from] $this->table = $table; = $$table; /* Parse again SQL 'from' clause with 'as' keyword -------------------------------------------- private function get_array_columns() if (count($this->table) > 0) foreach (current($this->table) as $key => $value) $this->array_columns[$key] = $key; foreach ($this->array_columns as $key => $value) if (array_search($key, $this->parse_select_as)!== FALSE) $this->array_columns[array_search($key, $this->parse_select_as)] = $value; trigger_error("array given as table is empty.", E_USER_ERROR); /* Parse SQL 'where' clause -------------------------------------------- private function parse_where() $key = array_search("where", $this->parse_query_lower); if ($key === FALSE) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 62
return $this->parse_where = "return TRUE;"; $string = $this->parse_query[$key+1]; if (trim($string) == '') return $this->parse_where = "return TRUE;"; /* SQL Functions -------------------------------------------- $patterns[] = '#LOWER((.*))#ie'; $patterns[] = '#UPPER((.*))#ie'; $patterns[] = '#TRIM((.*))#ie'; $replacements[] $replacements[] $replacements[] = "'strtolower(\1)'"; = "'strtoupper(\1)'"; = "'trim(\1)'"; /* Basics SQL operators -------------------------------------------- $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(= IS)(s)+([[:digit:]]+)(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(= IS)(s)+(' ")(.*)(' ")(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(> <)(s)+([[:digit:]]+)(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(<= >=)(s)+([[:digit:]]+)(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(<> IS NOT!=)(s)+([[:digit:]]+)(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(<> IS NOT!=)(s)+(' ")(.*)(' ")(s)*#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(is)?(not IN)(s)+((.*))#ie'; $patterns[] = '#(([a-za-z0-9._]+)(())?([a-za-z0-9.]+)())?(s)+(is)?(in)(s)+((.*))#ie'; $replacements[] $replacements[] $replacements[] $replacements[] $replacements[] $replacements[] $replacements[] >parse_in("\10").') '"; $replacements[] >parse_in("\10").') '"; = "'\1'.$this->parse_where_key("\4").'\5 == \9 '"; = "'\1'.$this->parse_where_key("\4").'\5 == "\10" '"; = "'\1'.$this->parse_where_key("\4").'\5 \7 \9 '"; = "'\1'.$this->parse_where_key("\4").'\5 \7 \9 '"; = "'\1'.$this->parse_where_key("\4").'\5!= \9 '"; = "'\1'.$this->parse_where_key("\4").'\5!= "\10" '"; = "'\1'.$this->parse_where_key("\4").'\5!= ('.$this- = "'\1'.$this->parse_where_key("\4").'\5 == ('.$this- /* match SQL operators -------------------------------------------- $ereg = array('%' => '(.*)', '_' => '(.)'); $patterns[] $patterns[] $patterns[] $patterns[] = '#([a-za-z0-9.]+)(s)+like(s)*(' ")(.*)(' ")#ie'; = '#([a-za-z0-9.]+)(s)+ilike(s)*(' ")(.*)(' ")#ie'; = '#([a-za-z0-9.]+)(s)+not LIKE(s)*(' ")(.*)(' ")#ie'; = '#([a-za-z0-9.]+)(s)+not ILIKE(s)*(' ")(.*)(' ")#ie'; $replacements[] = "'ereg("'.strtr("\5", $ereg).'", '.$this- >parse_where_key("\1").')'"; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 63
$replacements[] = "'eregi("'.strtr("\5", $ereg).'", '.$this- >parse_where_key("\1").')'"; $replacements[] = "'!ereg("'.strtr("\5", $ereg).'", '.$this- >parse_where_key("\1").')'"; $replacements[] = "'!eregi("'.strtr("\5", $ereg).'", '.$this- >parse_where_key("\1").')'"; $this- >parse_where = "return ".stripslashes(trim(preg_replace($patterns, $replacements, $string)))." ;"; /* Return variable to test -------------------------------------------- private function parse_where_key($key) if (ereg('.', $key)) list($table, $col) = explode('.', $key); return '$row['.$this->array_columns[$col].']'; return '$row['.$this->array_columns[$key].']'; /* Format IN clause for PHP -------------------------------------------- private function parse_in($string) $array = explode(',', $string); $array = array_map('trim', $array); return implode(' ', $array); /* Execute query -------------------------------------------- private function exec_query() $klimit = array_search("limit", $this->parse_query_lower); $koffset = array_search("offset", $this->parse_query_lower); if ($klimit!== FALSE) $limit = (int) $this->parse_query[$klimit+1]; if ($koffset!== FALSE) $offset = (int) $this->parse_query[$koffset+1]; $irow = 0; $distinct = array(); foreach ($this->table as $row) // Offset if ($koffset!== FALSE && $irow < $offset) $irow++; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 64
continue; if (eval($this->parse_where)) if ($this->parse_select_as[0] == '*') if ($this->distinct_query && in_array($row, $distinct)) continue; if (!$this->distinct_query) $this->response[] = $row; $this->response[] = $row; $distinct[] = $row; foreach ($this->parse_select_as as $key => $value) $temp[$key] = $row[$value]; if ($this->distinct_query && in_array($row, $distinct)) continue; if (!$this->distinct_query) $this->response[] = $row; $this->response[] = $row; $distinct[] = $row; // Limit if ($klimit!== FALSE && count($this->response) == $limit) break; $irow++; /* Parse SQL order by parameters -------------------------------------------- private function parse_order() $key = array_search("order by", $this->parse_query_lower); if ($key === FALSE) return; $string $arrays = $this->parse_query[$key+2]; = explode(',', $string); if (!is_array($arrays)) $arrays[] = $string; $arrays = array_map('trim', $arrays); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 65
$multisort = "array_multisort("; foreach ($arrays as $array) list($col, $sort) = preg_split('#((s)+)#', $array, -1, PREG_SPLIT_NO_EMPTY); $multisort.= "$this->split_array($this- >response, '$col'), SORT_".strtoupper($sort).", SORT_STRING, "; $multisort.= "$this->response);"; eval($multisort); /* Return response -------------------------------------------- private function return_response() return $this->response; /* Return a column of an array -------------------------------------------- private function split_array($input_array, $column) $output_array = array(); foreach ($input_array as $key => $value) $output_array[] = $value[$column]; return $output_array; /* Entire array search -------------------------------------------- private function entire_array_search($needle, $array) foreach($array as $key => $value) if ($value === $needle) $return[] = $key; if (!is_array($return)) $return = FALSE; return $return; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 66
acurl acurl est une classe PHP5 open source sous licence LGPL que j ai développée qui est en réalité une API destinée à faciliter les requêtes HTTP et FTP avec le module curl de PHP. Code source : /* * Project: Absynthe curl * File: acurl.class.php5 * Author: Sylvain 'Absynthe' Rabot <sylvain@abstraction.fr> * Website: http://absynthe.is.free.fr/acurl/ * Version: alpha 1 * Date: 05/06/2007 * License: LGPL * Take a look at the declaration of methods. * All parameters initialized to false in declarations are optional if you set them with set_o ption(). * If you set values with both set_option and argument, arguments not FALSE will be taken. * Puting arguments doesn't overwrite values set by set_option, arguments are temporary. * * Location header is taken into account automatically, * you will receive headers and content of the page pointed by the redirection * * @example : * <code> * * $acurl = new acurl(); * * $acurl->set_option('url', 'http://absynthe.is.free.fr/'); * $acurl->set_option('port', 80); * $acurl->set_option('login', 'Absynthe'); * $acurl->set_option('password', 'mypassword'); * $acurl->set_option('headers', true); * $acurl->set_option('cookie', '/path/to/my/cookie.txt'); * $acurl- >set_option('user_agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.8.1.4) Gecko/2007 0515 Firefox/2.0.0.4'); * * $answer = $acurl->ping(); * $answer = $acurl->http_request(); * $answer = $acurl->http_auth_request(); * $answer = $acurl->http_post_request('var=foo&var1=fooo'); * $answer = $acurl->http_post_request(array('var' => 'foo', 'var1' => 'fooo')); * $answer = $acurl->http_post_auth_request('var=foo&var1=fooo'); * $answer = $acurl->http_post_auth_request(array('var' => 'foo', 'var1' => 'fooo')); * * $answer = $acurl- >ftp_upload('/path/to/my/file.txt', 'ftp.abstraction.fr/path/to/file.txt', 21, 'Absynthe', 'my password'); * $answer = $acurl- >ftp_download('/path/to/my/file.txt', 'ftp.abstraction.fr/path/to/file.txt', 21, 'Absynthe', ' mypassword'); * * </code> IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 67
class acurl /* Init ---------------------------------- private $handler = false; private $url = false; private $port = false; private $login = false; private $password = false; private $headers = false; private $cookie = false; private $user_agent = false; * Constructor of the object * * @param string $url : URL of the page * @param int $port : Port used to get the page * @param string $login : HTTP login * @param string $password : HTTP password * @param boolean $headers : set it to true if you want headers in the answer * @param string $cookie : path to the cookie file if you want to use the object like a we b browser * @param string $user_agent : if you need to look like a web browser public function construct($url = false, $port = false, $login = false, $password = false, $headers = false, $cookie = false, $user_agent = false) if (!function_exists('curl_init')) trigger_error('sorry but PHP is not compiled with curl', E_USER_ERROR); exit; $this->handler $this->url $this->port $this->login $this->password $this->headers $this->cookie $this->user_agent = curl_init(); = $url; = $port; = $login; = $password; = $headers; = $cookie; = $user_agent; * Destructor public function destruct() if (is_resource($this->handler)) curl_close($this->handler); * Wake up method to enable unserialization public function wakeup() if (!is_resource($this->handler)) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 68
$this->handler = curl_init(); * Set object options * * @param string $var * @param string $value public function set_option($var, $value) $var = strtolower($var); $this->$var = $value; * Ping an URL * * @param string $url : URL to ping * @param int $port : Port used to ping * @param int $post : Ping with post request * @return boolean public function ping($url = false, $port = false, $post = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, false); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, false); curl_setopt($this->handler, CURLOPT_NOBODY, true); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $port); /* Preparation of post request ---------------------------------- if ($post!== false) curl_setopt($this->handler, CURLOPT_POST, true); if (is_array($post)) $string = ''; foreach ($post as $key => $value) $string.= "$key=$value&"; curl_setopt($this->handler, CURLOPT_POSTFIELDS, $string); curl_setopt($this->handler, CURLOPT_POSTFIELDS, $post); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 69
/* Execution ---------------------------------- return curl_exec($this->handler); * Retrieve content through HTTP * * @param string $url : URL of the page * @param int $port : Port used to get the page * @param boolean $headers : set it to true if you want headers in the answer * @param string $cookie : path to the cookie file if you want to use the object like a we b browser * @param string $user_agent : if you need to look like a web browser * @return string / array public function http_request($url = false, $port = false, $headers = false, $cookie = fals e, $user_agent = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($headers, $this->headers); $this->test_var($cookie, $this->cookie); $this->test_var($user_agent, $this->user_agent); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, $headers); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->handler, CURLOPT_AUTOREFERER, true); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $port); if (is_file($cookie)) curl_setopt($this->handler, CURLOPT_COOKIE, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEJAR, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEFILE, $cookie); if ($user_agent!== false) curl_setopt($this->handler, CURLOPT_USERAGENT, $this->user_agent); /* Execution ---------------------------------- if ($headers) return array("headers" => curl_exec($this->handler), "content" => curl_exec($this- >handler)); return curl_exec($this->handler); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 70
* Retrieve content through HTTP with authentication * * @param string $url : URL of the page * @param int $port : Port used to get the page * @param string $login : HTTP login * @param string $password : HTTP password * @param boolean $headers : set it to true if you want headers in the answer * @param string $cookie : path to the cookie file if you want to use the object like a we b browser * @param string $user_agent : if you need to look like a web browser * @return string / array public function http_auth_request($url = false, $port = false, $login = false, $password = false, $headers = false, $cookie = false, $user_agent = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($login, $this->login); $this->test_var($password, $this->password); $this->test_var($headers, $this->headers); $this->test_var($cookie, $this->cookie); $this->test_var($user_agent, $this->user_agent); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, $headers); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->handler, CURLOPT_AUTOREFERER, true); curl_setopt($this->handler, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($this->handler, CURLOPT_USERPWD, "$login:$password"); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $this->port); if (is_file($cookie)) curl_setopt($this->handler, CURLOPT_COOKIE, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEJAR, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEFILE, $cookie); /* Execution ---------------------------------- if ($headers) return array("headers" => curl_exec($this->handler), "content" => curl_exec($this- >handler)); return curl_exec($this->handler); * Send a post request and retrieve the content * * @param string/array $post : Post request IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 71
* @param string $url : URL of the page * @param int $port : Port used to get the page * @param boolean $headers : set it to true if you want headers in the answer * @param string $cookie : path to the cookie file if you want to use the object like a we b browser * @param string $user_agent : if you need to look like a web browser * @return string/array public function http_post_request($post, $url = false, $port = false, $headers = false, $c ookie = false, $user_agent = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($headers, $this->headers); $this->test_var($cookie, $this->cookie); $this->test_var($user_agent, $this->user_agent); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, $headers); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->handler, CURLOPT_AUTOREFERER, true); curl_setopt($this->handler, CURLOPT_POST, true); if (is_string($post)) curl_setopt($this->handler, CURLOPT_POSTFIELDS, $post); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $port); if ($user_agent!== false) curl_setopt($this->handler, CURLOPT_USERAGENT, $this->user_agent); if (is_file($cookie)) curl_setopt($this->handler, CURLOPT_COOKIE, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEJAR, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEFILE, $cookie); /* Preparation of post request ---------------------------------- if (is_array($post)) $string = ''; foreach ($post as $key => $value) $string.= "$key=$value&"; curl_setopt($this->handler, CURLOPT_POSTFIELDS, $string); curl_setopt($this->handler, CURLOPT_POSTFIELDS, $post); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 72
/* Execution ---------------------------------- if ($headers) return array("headers" => curl_exec($this->handler), "content" => curl_exec($this- >handler)); return curl_exec($this->handler); * Send a post request and retrieve the content with HTTP authentication * * @param string/array $post : Post request * @param string $url : URL of the page * @param int $port : Port used to get the page * @param string $login : HTTP login * @param string $password : HTTP password * @param boolean $headers : set it to true if you want headers in the answer * @param string $cookie : path to the cookie file if you want to use the object like a we b browser * @param string $user_agent : if you need to look like a web browser * @return string/array public function http_auth_post_request($post, $url = false, $port = false, $login = false, $password = false, $headers = false, $cookie = false, $user_agent = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($login, $this->login); $this->test_var($password, $this->password); $this->test_var($headers, $this->headers); $this->test_var($cookie, $this->cookie); $this->test_var($user_agent, $this->user_agent); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, $headers); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->handler, CURLOPT_AUTOREFERER, true); curl_setopt($this->handler, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($this->handler, CURLOPT_USERPWD, "$login:$password"); curl_setopt($this->handler, CURLOPT_POST, true); if (is_string($post)) curl_setopt($this->handler, CURLOPT_POSTFIELDS, $post); if ($user_agent!== false) curl_setopt($this->handler, CURLOPT_USERAGENT, $this->user_agent); if (is_file($cookie)) curl_setopt($this->handler, CURLOPT_COOKIE, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEJAR, $cookie); curl_setopt($this->handler, CURLOPT_COOKIEFILE, $cookie); IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 73
/* Preparation of post request ---------------------------------- if (is_array($post)) $string = ''; foreach ($post as $key => $value) $string.= "$key=$value&"; curl_setopt($this->handler, CURLOPT_POSTFIELDS, $string); curl_setopt($this->handler, CURLOPT_POSTFIELDS, $post); /* Execution ---------------------------------- if ($headers) return array("headers" => curl_exec($this->handler), "content" => curl_exec($this- >handler)); return curl_exec($this->handler); * Upload a file to a ftp * * @param string $path : path to the file to upload * @param string $url : url of the ftp with the path where to upload * @param int $port : Port used to get the page * @param string $login : FTP login * @param string $password : FTP password * @return boolean public function ftp_upload($path, $url = false, $port = false, $login = false, $password = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($login, $this->login); $this->test_var($password, $this->password); /* Checkpoint ---------------------------------- if (is_file($path)) if (!$fhandler = fopen($path, 'r')); return false; return false; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 74
/* Var updates if needed ---------------------------------- $file = pathinfo($path); if (!preg_match("#^(ftp://)#i", $url)) $url = "ftp://".$url; if ($login!== false && $password!== false) if (!preg_match("#ftp://([[:alnum:]-._])+:([[:alnum:]-._])+@(.*)#i", $url)) $url = preg_replace("#(ftp://)(.*)#i", "\1$login:$password@\2", $url); if (!preg_match("#(/)^#i", $url)) $url.= "/".$file['basename']; $url.= $file['basename']; /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_UPLOAD, true); curl_setopt($this->handler, CURLOPT_INFILE, $fhandler); curl_setopt($this->handler, CURLOPT_INFILESIZE, filesize($path)); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $port); /* Execution ---------------------------------- return curl_exec($this->handler); * Download a file from a FTP * * @param string $path : path where to ulpoad * @param string $url : url of the ftp with the path of the file to download * @param int $port : Port used to get the page * @param string $login : FTP login * @param string $password : FTP password * @return boolean public function ftp_download($path, $url = false, $port = false, $login = false, $password = false) /* Init ---------------------------------- $this->test_var($url, $this->url); $this->test_var($port, $this->port); $this->test_var($login, $this->login); $this->test_var($password, $this->password); /* Var updates if needed ---------------------------------- if (!preg_match("#^(ftp://)#i", $url)) $url = "ftp://".$url; if ($login!== false && $password!== false) if (!preg_match("#ftp://([[:alnum:]-._])+:([[:alnum:]-._])+@(.*)#i", $url)) IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 75
$url = preg_replace("#(ftp://)(.*)#i", "\1$login:$password@\2", $url); /* curl Setup ---------------------------------- curl_setopt($this->handler, CURLOPT_URL, $url); curl_setopt($this->handler, CURLOPT_HEADER, false); curl_setopt($this->handler, CURLOPT_RETURNTRANSFER, true); curl_setopt($this->handler, CURLOPT_FOLLOWLOCATION, true); curl_setopt($this->handler, CURLOPT_AUTOREFERER, true); if ($port!== false) curl_setopt($this->handler, CURLOPT_PORT, $port); /* Execution ---------------------------------- $content = curl_exec($this->handler); /* Writing ---------------------------------- if($fhandler = fopen($path, 'w+')) if (fwrite($fhandler, $content)) return true; return false; return false; * Set a variable to $default parameter if it's false * @param anything $var * @param anything $default private function test_var(&$var, $default) if ($var === false) $var = $default; IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 76
Bibliographie Voici la liste des ouvrages qui m ont aidé lors de mon stage. Auteurs : Eric Daspet, Cyril Pierre de Geyer Edition : Eyrolles Prix public : 42,00 EUR ISBN10 : 2-212-12004-4 ISBN13 : 978-2-212-12004-2 EAN13 : 9782212120042 Auteurs : Christophe Porteneuve Edition : Eyrolles Prix public : 42,00 EUR ISBN10 : 2-212-12028-1 ISBN13 : 978-2-212-12028-8 EAN13 : 9782212120288 Sites web Voici la liste des sites web que j ai été amené à visiter pour mes recherches lors de mon projet : PHP.net : http://fr.php.net Documentation officielle de PHP. Smarty : http://smarty.php.net Documentation officielle du framework SMARTY Prototype : http://www.prototypejs.org Documentation officielle du framework Prototype. Script.aculo.us : http://script.aculo.us Documentation officielle du framework script.acwulo.us. Developpez.net : http://www.developpez.net Forum de développeurs. IUT DE BAYONNE DEPARTEMENT INFORMATIQUE 77