eZ Publish PHP 02 2011

55
5/2009

Transcript of eZ Publish PHP 02 2011

Page 1: eZ Publish PHP 02 2011

5/2009�

Page 2: eZ Publish PHP 02 2011

� �0/�0�0

Page 3: eZ Publish PHP 02 2011

www.phpsolmag.org �

Table des matières

Page 4: eZ Publish PHP 02 2011

Table des maTières

2/2011

� �/�0��

Le périodique phpsolutions est publié parSoftware Press Sp. z o.o. SK

Bokserska 1, 02-682 Varsovie, PologneTél. 0975180358, Fax. +48 22 244 24 59

www.phpsolmag.org

Président de Software Press Sp. z o.o. SK : Paweł Marciniak

Directrice de la publication : Ewa Łozowicka

Dépôt légal : à parution

ISSN : 1731-4593

Rédacteur en chef : Łukasz Bartoszewicz

Couverture : Sławomir Sobczyk

DTP : Sławomir Sobczyk [email protected]

Composition : Sławomir Sobczyk

Correction : Valérie Viel, Thierry Borel, Barbara Bourdelles

Bêta-testeurs : Brice Favre, Valérie Viel, Cyril David,

Christophe Milhau, Alain Ribault, Stéphane Guedon, Eric Boulet, Mickael Puyfages, Christian Hernoux,

Isabelle Lupi, Antoine Beluze, Timotée Neullas, Yann Faure, Adrien Mogenet, Jean-François Montgaillard,

Turmeau Nicolas, Jonathan Marois, Wilfried Ceron, Wajih Letaief, François Van de Weerdt, Eric Vincent,

Franck Michaël Assi, Francis Hulin-Hubard, Nicolas Dumas, David Michaud.

Les personnes intéressées par la coopération sont priées de nous contacter :

[email protected]

Publicité : [email protected]

Pour créer les diagrammes on a utilisé le programme

AVERTISSEMENTLes techniques présentées dans les articles

ne peuvent être utilisées qu’au sein des réseaux internes. La rédaction du magazine n’est pas

responsable de l’utilisation incorrecte des techniques présentées. L’utilisation des techniques présentées peut

provoquer la perte des données !

VARIA6 ActualitésActualités du monde du développement.Christophe Villeneuve

7 Interview de Christophe GeschéInterview de Christophe Gesché, Directeur Technique pour le site Delcampe.

PROJETS8 Accès objet à MySQL avec PHP5Mbella Ekoume K. DemazyLorsqu’on décide de faire du PHP orienté objet sans avoir à faire de la programmation procédurale, très sou-vent on est coincé lorsqu’il s’agit d’accéder à une base de données et plus particulièrement à MySQL. Cet arti-cle explique comment accéder à une base de données MySQL en orienté objet avec PHP 5 en utilisant l’exten-sion Mysqli.

DOSSIER21 Le guide de développement de datatype eZ Publish

Jérôme Vieilledent, Nicolas PastorinoLes datatypes eZ Publish sont probablement l’une des fonctionnalités les plus puissantes d’eZ Publish. Ils per-mettent de traiter des éléments de contenu comme vous l’entendez. Cependant, la complexité de leur dé-veloppement est proportionnelle à leur puissance. Cet-te première partie vous démontrera les fonctionnalités de base que l’on se doit d’implémenter lors du dévelop-pement de son propre datatype eZ Publish.

PRATIQUE40 Écrire un Web Service en PHPYohann PoironDes systèmes peuvent interagir avec le service Web de la manière prescrite par sa description en utilisant des mes-sages SOAP, typiquement transmis via le protocole HTTP et une séria-lisation XML en conjonction avec d’autres normes liées au Web.

Page 5: eZ Publish PHP 02 2011

Table des matièresTable des matières

POUR LES DÉBUTANTS44 SQL/PHP : communication avec une base de données avec PDO (1/2)Cilia Mauro, Magali ContensinLes bases de données sont très utilisées dans les ap-plications Web. La création, l’interrogation et la manipu-lation des données de la base sont réalisées en SQL. Dans cet article, vous apprendrez à vous connecter à une base de données grâce à PHP via l’extension PDO.

SÉCURITÉ51 La sécurité de l’informationTony FachauxAujourd’hui la plupart des données d’une entreprise sont informatisées. Dans la mesure où les menaces à l’encontre de la sécurité de l’information se veulent chaque jour davantage féroces et polymorphes, il est fondamental de sécuriser au maximum ces données. Les grands acteurs technologiques comme Google, Microsoft ou Yahoo sont régulièrement victimes d’at-taques informatiques. Cet article présente les moyens techniques et fonctionnels mis en œuvre pour sécuriser l’information.

Page 6: eZ Publish PHP 02 2011

Actualités

�/�0��

Symfony live 2011La version �.0 de Symfony sera libé-rée au moment du Symfony live �0�� qui se déroulera début mars à Paris. Actuellement, la version béta est dis-ponible pour vous permettre de vous rendre compte des nombreuses évolu-tions et de la nouvelle puissance de ce framework PHP (http://symfony-reloa-ded.org/). Par ailleurs, pour connaître toutes les informations autour de l’évè-nement ‘Symfony live’ et vous inscrire, une seule adresse : http://www.symfony-live.com/.

Drupal 7Après plusieurs années de développe-ments, la version 7 du CMS Drupal vient de sortir. Pour faire fonctionner cette nouvelle version, il vous faut au moins PHP 5.2 ou 5.3 avec MySQL 5.0 ou Pos-tgreSQL 8.3. Par ailleurs, l’interface a été refondu avec plus de modules de bases et de nouveaux thèmes mais aussi une optimisition plus poussée ainsi que la sécurité.http://www.drupal.org

PHP 5.3.5 et 5.2.17Deux nouvelles versions de PHP ont été libéré, qu’il est important de mettre à jour car une correction au niveau de la sécurité a été apporté.Pour rappel, la version 5.�.� sera la der-nière version de cette branche car elle a été stoppé, pour privilégier la version PHP 5.�http://www.php.net

Joomla 1.6La nouvelle version de Joomla vient d’être publiée. Elle apporte de nom-breuses évolutions comme une amé-lioration des nombreux outils aux niveaux de l’accessibilité, avec une nouvelle structure, une amélioration du code sémantique, une nouvelle gestion de multilangue et génération de la mise en page xhtml. http://www.joomla.fr

PhractalPhractal est une application réalisée en PHP 5.� et basée sur la technique Man-delbrot, pour vous permettre de réaliser des simples fractales pour le web. Ce jeune projet devrait évoluer très rapide-ment car les techniques mathématiques sont très vastes. http://sourceforge.net/projects/phra-ctal/

SPIP 2.1.8SPIP vient de sortir une nouvelle ver-sion mineure de son CMS, en signa-lant à l’ensemble des utilisateurs des versions �.x d’effectuer la migration à cause d’un bug important au niveau de la sécurité.http://www.spip.net

Wampserver 2.1.xAprès de nombreux mois d’attentes, l’application Wampserver vient de sor-tir en 3 versions : • 32 bits,• 64 bits,• Portable.

WampServer est un environnement AMP, avec comme éléments de base : Apache / MySQL / PHP pour le système d'exploitation Windows. Par ailleurs, de nouveaux outils ont été ajouté pour répondre aux différentes attentes des utilisateurs et surtout des besoins des développeurs, comme : • PhpMyAdmin : Application de gestion des bases de données MySQL,• SQL Butty : Application de gestion SQL,• Xdebug : Débogueur,• WebGrind : Débogueur en Frontend,• XDC : Client Xdebug.

La version 32 bits et 64 bits propose les dernières versions disponibles de ces outils pour vous permettre en un seul clic de lancer votre environnement.La version portable (disponible actuellement en 32 bits) propose de nouvel-les options utiles. En plus des différentes options proposées ci-dessus, le menu se voit doter maintenant d'un bouton d'éjection de la clef en automati-que. Cette option permet de quitter l'environnement et de déconnecter la clef en un seul clic. Ainsi, vous gagnez du temps et vous êtes certains de ne pas laisser des traces sur l'ordinateur utilisé.Une nouvelle branche de Wampserver vient d'être publiée. Cette version est destinée pour tous les supports amovible comme les clefs USB.

FlogRFLOGR est une nouvelle plateforme de galerie photos, réalisée en PHP et distribuée en Open Source.Le but de ce projet est de vous per-mettre d’afficher vos photos venant directement du site Flick’R d’une manière interactive avec de nom-breuses options vous permettant de proposer un album photos très com-plet comme : • Affichages de différentes vues.• Positionner sur une carte le lieu de la photo.• Gestion des tags.• Possibilité de poster des commentaires.• Différents modes de classements.

Une version de démonstration est disponible pour vous rendre compte de la puis-sance de la plateforme http://thecarruthfamily.com/michael/photos/index.php.

Site officiel : http://code.google.com/p/flogr/.

Rédaction des actualités : Christophe Villeneuve

Page 7: eZ Publish PHP 02 2011

Interview

www.phpsolmag.org

PHP Solutions : Comment voyez-vous le web de de-main ?

Christophe Gesché :

– Internet intégrera de plus en plus les actions de la vie quotidienne. Par exemple, les achats en li-gne viendront compléter la vente directe. Sur le si-te Delcampe, on voit d’ailleurs très nettement une augmentation des ventes au fil du temps ; c’est ce que l’on pourrait appeler faire ses courses en pan-toufles. L’Internet sera disponible partout (cela com-mencera avec des technologies 3G) et l’interaction virtuelle sera possible à tout moment, jusqu’à en ar-river par exemple à la réalité augmentée.

– L’Internet se développera pour proposer une plus grande interaction virtuelle. Les échanges en direct feront de moins en moins partie de notre réalité.

– Le partage des multimédias continuera également de se développer.

– On arrivera également à une convergence toujours plus grande des médias

– L’Internet étant plus vaste chaque jour, les moteurs de recherche devront s’adapter et deviendront plus pertinents, plus sémantiques et plus thématiques. Et pourquoi ne pas en arriver à un moteur de re-cherche intelligent ?

PS : Quels sont les types d’entreprises/clients qui se tournent vers vos services ?

CG :

– Les négociants/commerçants en objets de collection:– Les professionnels pouvant être concernés par une

des catégories du site (libraires, brocanteur, …).– Les Maisons de Vente spécialisées (en philatélie,

numismatique, …).– Les particuliers qui sont acheteurs et/ou vendeurs

sur le site.

Interview de Christophe Gesché, Directeur Technique pour le site Delcampe

PS : Quels sont les outils que vous utilisez pour as-surer à vos clients la qualité du service ?

CG :

– Technique :oUne bonne infrastructure des serveurs.oUne programmation toujours en recherche d’op-

timisation, pour prendre le moins de temps pos-sible.

oSuivi des nouvelles technologies hardware et software.

– Support :oUtilisation d’un outil de gestion de messagerie

électronique.oOutils internes de gestion et de contrôle.oConsultation auprès d’experts et de personnes

reconnues du monde de nos utilisateurs.– Marketing

oAnalyse des besoins des utilisateurs et analyse de leur satisfaction afin de répondre au mieux à leurs demandes.

PS : Quels sont les projets à venir de Delcampe ?

CG :

– Delcampe pour les mobiles : cela permettra aux membres de gérer leurs achats et leurs ventes à partir de leur mobile.

– Création d’applications en Flex d’un point de vue communicationnel.

– Développement de l’application Web majeure, le si-te Delcampe.net.

– Développement du référencement.– Continuer l’optimisation à tous points de vue.– Encore améliorer la capacité d’expansion.– Modernisation des normes et de l’interface.– Industrialisation des procédures et développement

pour faire face à notre croissance.

Page 8: eZ Publish PHP 02 2011

�/�0���

Projets

L’extension Mysql très souvent utilisée en PHP pour se connecter à une base de données Mys-ql est une extension exclusivement procédurale

et n’offre de ce fait aucun mécanisme d’accès aux don-nées en orienté objet. Cependant, la version 5 de PHP, à savoir PHP5, ayant été conçu pour accroître les capa-cités orientées objet de PHP, a prévu des moyens de le faire, et cet article nous montrera comment.

Grâce à l’évolution qu’a connue le langage PHP en-tre la version 4 et la version 5 la programmation orienté objet avec ce langage a connu pas mal de modifications et d’amélioration ; et l’accès aux données n’est pas res-té en marge de cette logique. C’est ainsi que l’accès ob-jet à une base de données MySQL est désormais pos-sible grâce à l’utilisation de l’extension Mysqli (ou Mysql improved pour améliorée) de PHP. Cette extension a les avantages suivants par rapport à ces prédécesseurs :

• Une interface aussi bien procédurale qu’orientée objet ;

• Le support des commandes préparées ;• Le support des commandes multiples ;• Le support des transactions ;• Des capacités de débogage avancées.

Les classes de l’extension MysqliMysqli possède les classes suivantes à l’origine de ces avantages :

• La classe mysqli : elle permet de créer des objets de type mysqli _ object. Cette dernière possède des méthodes et des propriétés nécessaires pour permettre par exemple de se connecter à la ba-se de données, d’envoyer des requêtes préparées à cette dernière, ou alors d’effectuer des transac-tions.

• La classe mysqli _ result : elle permet de gérer les résultats des requêtes SQL effectuées à l’aide de l’objet mysqli précédent. C’est ainsi qu’elle pos-sède des méthodes et des propriétés permettant de manipuler de plusieurs manières les résultats issus d’une requête SQL en rendant par exemple ceux-ci

Accès objet à MySQL avec PHP5Lorsqu’on décide de faire du PHP orienté objet sans avoir à faire de la programmation procédurale, très souvent on est coincé lorsqu’il s’agit d’accéder à une base de données et plus particulièrement à MySQL.

Cet article explique :• Comment accéder à une base de données MySQL en orienté

objet avec PHP 5 en utilisant l’extension Mysqli.

Ce qu’il faut savoir :• Les bases de la programmation avec le langage PHP. • La POO et son implémentation dans PHP 5.• Les itérateurs en PHP5.• L’architecture MVC.• Le langage SQL et son utilisation avec le SGBD Mysql.• L’extension Mysqli de PHP 5.

Figure 1. Enregistrement d’un nouveau produit

Page 9: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org �

Listing 1. Script de création de la base de données cabinet

----Basededonnées:'cabinet'--CREATEDATABASE'cabinet'DEFAULTCHARACTERSETlatin1COLLATElatin1 _ swedish _ ci;USEcabinet;

----------------------------------------------------------

----Structuredelatable'produit'--

CREATETABLE'produit'('prd _ id'bigint(20)NOTNULLauto _ incrementCOMMENT'identification du produit,cléprimairedelatableproduit','prd _ libelle'varchar(200)NOTNULLCOMMENT'libelléduproduit','prd _ reference'varchar(20)NOTNULLCOMMENT'reférenceduproduit:;doitetreunique','prd _ fabricant'varchar(200)NOTNULLCOMMENT'fabricantduproduit','prd _ qteStock'int(11)NOTNULLCOMMENT'quantitedeproduitenstock','prd _ qteAllert'int(11)NOTNULLCOMMENT'lestockd''alertepournecessitéunapprovisionnement','prd _ unite'varchar(100)defaultNULLCOMMENT'unitéduproduit',PRIMARYKEY('prd _ id'),UNIQUEKEY'prd _ reference'('prd _ reference'))TYPE=InnoDBAUTO _ INCREMENT=16;

----Contenudelatable'produit'--

INSERTINTO'produit'('prd _ id','prd _ libelle','prd _ reference','prd _ fabricant','prd _ qteStock','prd _qteAllert','prd _ unite')VALUES(4,'alcool','alc','novartis',10,20,'cm'),(6,'benzene','ben','chococam',10,2,'cm'),(8,'24DNPH','DNPH','areva',124588,1250,'ml'),(9,'acidechlohydrique','hcl','chimie',124588,0,'ml'),(10,'aspirine','asp','chimie',124588,0,'ml'),(11,'uranium235','U235','AIEA',125,12,'bekerel'),(12,'polonium210','pol','AIEA',25,0,'ur'),(13,'bromure','br','chimie',1254,0,'l'),(14,'urée','201010','engrais',100,0,'sac'),(15,'phosphate','ph','arcelor',145,0,'cm');

Listing 2. Le fichier config.class.php

<?php//inclusionetcentralisationdesclassesdel'applicationinclude _ once'produit.class.php';

class Config { private$HOST="localhost"; private$USER="root"; private$PASSWORD=""; private$DATABASE="cabinet"; publicfunctionconnect() { //fonctionpermettantdececonnecteralabdenutilisantl'extensionmysqli //elleretournel'objetdeconnection $con=newmysqli($this->HOST,$this->USER,$this->PASSWORD,$this->DATABASE); $con->set _ charset("utf8");//definition du jeu de caractère par defaut du client if(mysqli _ connect _ error()) $con='echecdelaconnexionàlabasededonnées('.mysqli _ connect _errno().')'.mysqli _ connect _ error(); return$con; } publicfunctiondisconnect(mysqli$con) { //cettefonction,permetdecedéconnecterdelaBD //elleprendenentréeunobjetdeconnectiondetypemysqli $val=$con->close(); if($val!=true) { echo("echecdelafermeturedelaconnectionalaBD"); } }

}

Page 10: eZ Publish PHP 02 2011

�/�0���0

Projets

mysqli->insert _ id : retourne l’identifiant automa-tiquement généré pour un attribut déclaré AUTO _INCREMENT.

• Les méthodes :

mysqli([string$host[,string$username[, string $passwd [, string $base [, int$port[,

string$socket]]]]]]) : permet de créer un objet mysqli, afin de se connecter à la base de données MySQL.

close(void) : permet de fermer une connexion à la base de données MySQL ; elle retourne true en cas de succès et false sinon.

query(string $query [, int $mode]) : permet d’exécuter une requête sur la base de données ;

sous forme de tableau associatif, de tableau indicé, d’obtenir le nombre de tuple d’un résultat etc.

• La classe mysqli _ stmt : elle permet d’effectuer des requêtes préparées sur la base de données.

• La classe mysqli _ driver : elle permet la gestion des drivers mysqli.

La classe mysqliCette classe possède un certain nombre de propriétés et de méthodes ; nous nous limiterons à n’évoquer que quelques-unes.

• Propriétés :

mysqli->affected _ rows : contient le nombre de lignes affectées par la dernière requête INSERT,UPDATE,REPLACE ou DELETE.

Listing 3. La classe Produit

classProduit { private$prd _ id; private$prd _ libelle; private$prd _ reference; private$prd _ fabricant; private$prd _ qteStock; private$prd _ qteAllert; private$prd _ unite; publicfunction_ _ construct() { //leconstructuerdelaclasseproduit $this->prd _ id=""; $this->prd _ libelle=""; $this->prd _ reference=""; $this->prd _ fabricant=""; $this->prd _ qteStock=""; $this->prd _ qteAllert=""; $this->prd _ unite=""; } publicfunctionget _ id() { //retournel'idduproduitcourant. return$this->prd _ id; } publicfunctionset _ id($id) { //definit la valeur de l'id du produit $this->prd _ id=$id; } publicfunctionget _ libelle() { //retournelelibelleduproduitcourant return$this->prd _ libelle; } publicfunctionset _ libelle($libelle) { //defini le libelle du produit $this->prd _ libelle=$libelle; } publicfunctionget _ reference() { //retournelareferenceduproduitcourant

return$this->prd _ reference; } publicfunctionset _reference($reference) { //definila reference du produit courant $this->prd _ reference=$reference; } publicfunctionget _ fabricant() { //retournelefabricantduproduit return$this->prd _ fabricant; } publicfunctionset _fabricant($fabricant) { //defini le fabricant du produit $this->prd _fabricant=$fabricant; } publicfunctionget _ qteStock() { //retournelaquantitéenstock return$this->prd _ qteStock; } publicfunctionset _ qteStock($qteStock) { //defini la quantité en stock $this->prd _ qteStock=$qteStock; }

publicfunctionget _ qteAllert() { //retournelaquantitéd'alerte return$this->prd _ qteAllert; } publicfunctionset _qteAllert($qteAllert) { //defini la quantité d'alerte $this->prd _ qteAllert=$qteAllert; } publicfunctionget _ unite() { //retournel'unite return$this->prd _ unite; } publicfunctionset _ unite($unite) { //retournel'unite $this->prd _ unite=$unite; } }

Page 11: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org ��

Listing 4. Les méthodes de la classe Produit et la classe Moniterateur

publicfunctionGetOneProduit(mysqli$id _ con,$id) { /* *cettemethodepermetd'obteniruntupledelatableproduitàpartir *desonidetdel'iddeconnectionàlabd *elleretourneunobjetproduit */ try { if(ctype _ digit($id)) { $sql="SELECT*FROMproduitWHEREprd _ id='$id'"; $result=$id _ con->query($sql); $ligne=$result->fetch _ assoc(); $unobjet=newProduit(); $unobjet->set _ id($ligne['prd _ id']); $unobjet->set _ libelle($ligne['prd _ libelle']); $unobjet->set _ reference($ligne['prd _ reference']); $unobjet->set _ qteStock($ligne['prd _ qteStock']); $unobjet->set _ qteAllert($ligne['prd _ qteAllert']); $unobjet->set _ unite($ligne['prd _ unite']); $unobjet->set _ fabricant($ligne['prd _ fabricant']); } else { thrownewException("Erreur sur l'identificateur"); } } catch(Exception$e) { echo$e->getMessage(); } return$unobjet; }

publicfunctionGetAllProduit(mysqli$id _ con,$order,$by) { /*cetteméthodepermetd'obtenirlalistedesproduitsdalabasededonnées *elleprendenentrée: *$id _ con:l'objetdeconnexionalabasededonnée, * $by:lacollonesuivantlaquellelesresultatsserontordonnés * $order:l'odredetridesrésultats:ASCouDESC *cetteméthoderetourneunitérateurdetypeMoniterateur **/ $tab=array('prd _ id','prd _ libelle','prd _ reference','prd _ fabricant','prd _qteStock','prd _ qteAllert','prd _ unite'); try { if((strtoupper($order)=="ASC"||strtoupper($order)=="DESC")&&in _array($by,$tab)) { $sql="SELECT*FROMproduitORDERBY$by$order"; $myIt=newMoniterateur($sql,$id _ con); return$myIt; } else { thrownewException("Erreur de paramètres"); } } catch(Exception$e) { return$e->getMessage(); } } publicfunctionUpdateProduit(mysqli$id _ con,$id,$libelle,$reference,$fabricant,$qteStock,$qteAllert,$unite) { /* *cettemethodemetajouruntupledelatableproduità *partirdesonID *enentréeonalesdifferentschampscorrespondantau *propriétédelatableproduitdanslaBDetl'iddela *connexionamysql */

Page 12: eZ Publish PHP 02 2011

�/�0����

Projets

Listing 4. Les méthodes de la classe Produit et la classe Moniterateur – suite $sql="UPDATEproduitSET prd _ libelle='$libelle',prd _ fabricant='$fabricant',prd _ qteStock='$qteStock', prd _ qteAllert='$qteAllert',prd _ unite='$unite' WHEREprd _ id='$id'"; try { $result=$id _ con->query($sql); if(!$result) { thrownewException("Erruerlorsdelamiseàjourdel'enregistrement"); } } catch (Exception$e) { $result=$e->getMessage(); } return$result; } publicfunctionSetProduit(mysqli$id _ con) { /* *cettemethodepermetd'insérerunnouvelenregistrement *danslatableproduit; * en entrée on a l'identifiant de la connexion à la BD */ $id=$this->get _ id(); $libelle=$this->get _ libelle(); $reference=$this->get _ reference(); $fabricant=$this->get _ fabricant(); $qteStock=$this->get _ qteStock(); $qteAllert=$this->get _ qteAllert(); $unite=$this->get _ unite(); $sql="INSERTINTOproduitVALUES('$id','$libelle','$reference','$fabricant','$qteStock','$qteAllert','$unite')"; try { $result=$id _ con->query($sql); if(!$result) { thrownewException("Uneerreurc'estproduitelorsdel'insertiondel'enregistrementdanslaBD"); } } catch(Exception$e) { $result=$e->getMessage(); } return$result; } publicfunctionDeleteOneProduit($id,mysqli$id _ con) { /* *cettemethodesechargedesupprimeruntupledanslatable * produit identifié par son id *enentréeona: *-l'idduproduitàsupprimer * - l'identifiant de connexion à la BD */ try { if(ctype _ digit($id)) { $sql="DELETEFROMproduitWHEREprd _ id='$id'"; $result=$id _ con->query($sql); } else { thrownewException("Erreur sur l'identificateur"); } }

Page 13: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org ��

le paramètre $mode est une constante valant MYS-QLI _ USE _ RESULT ou MYSQLI _ STORE _ RESULT (valeur par défaut) suivant le comportement désiré.

select _ db(string$db) : permet de sélectionner une base de données ;

La classe mysqli_result- Les propriétés :

mysqli _ result->field _ count : récupère le nom-bre de champs dans un jeu de résultats.

mysqli _ result->num _ rows : retourne le nombre de lignes d’un résultat.

- Les méthodes :

fetch _ all([int$resulttype]) : permet de lire tous les résultats d’une requête et de les retour-ner sous forme de tableau associatif, numérique ou les deux à la fois, ceci suivant la valeur du para-mètre $resulttype qui peut être : MYSQLI _ ASSOCpouruntableauassociatif,MYSQLI _ NUMpour un tableau numérique, cette va-leur étant la valeur par défaut, MYS-QLI _ BOTHpourlesdeux.

fetch _ array ([ int $resulttype ] ) : per-met de retourner une ligne de résultats sous forme

Listing 4. Les méthodes de la classe Produit et la classe Moniterateur – suite

catch(Exception$e) { $result=$e->getMessage(); } return$result; }

//laclasseMoniterateurquiimplémentel'interfaceIterator

classMoniterateurimplementsIterator{ private$result=null; private$current=null; private$key=0; protected$sql=null; function_ _ construct($sql,mysqli$con) { //leconstructerurdelaclasse:ilprendenentréeunerequettesql //et un identifiant de connexion à la base de données mysql $this->result=$con->query($sql); } publicfunctioncurrent() { //retournelavaleurdel'élémentcourantdenotrecollection return$this->current; } publicfunctionkey() { //retournelaclédelélémentcorantdenotrecollection return$this->key; } publicfunctionnext() { //cetteméthoderetournel'élémentsuivantdenotrecollection $this->current=$this->result->fetch _ assoc();$this->key++; } publicfunctionrewind() { //on déplace le pointeur vers la première enregistrement de notre collection $this->result->data _ seek(0); $this->key=-1; $this->next(); } publicfunctionvalid() { //ontestesilerésultatcourrantesrcorrecte:end'autretermes'ils'agitd'untableau returnis _ array($this->current); } }

Page 14: eZ Publish PHP 02 2011

�/�0����

Projets

d’un tableau associatif, d’un tableau indexé, ou les deux suivant la valeur du paramètre $resultty-pe qui peut être : MYSQLI _ ASSOC, MYSQLI _ NUM,MYSQLI _ BOTH(valeurpardéfaut).

fetch _ assoc ( void ) : permet de récupérer une ligne de résultats sous forme de tableau asso-ciatif ;

fetch _ object ([ string $class _ name [,array $params ]]) : retourne la ligne courante d’un jeu de résultats sous forme d’objet ; en para-mètre nous avons $class _ name qui représente le nom de la classe à instancier , et $params un ta-bleau contenant la liste des paramètres à passer au constructeur de cette classe.

fetch _ row ( void ) : permet de récupérer une ligne de résultats sous forme de tableau in-dexé.

La classe mysqli_stmt- Les propriétés :

mysqli _ stmt->affected _ rows : retourne le nombre total de lignes modifiées, effacées, ou insé-rées par la dernière requête.

mysqli _ stmt->insert _ id : récupère l’ID généré par la dernière requête INSERT.

mysqli _ stmt ->num _ rows : retourne le nombre de ligne d’un résultat Mysql.

Listing 5. Le contrôleur de la classe produit : ctrl.produit.php

//ce fichier représente le controleur du module de gestion des produits include _ once'config.class.php';

$cobj=new Config();

$operation=trim($ _ GET['action']); if(isset($ _ GET['id'])&&ctype _ digit($ _ GET['id'])) { $id=trim($ _ GET['id']); } $mod=trim($ _ GET['mod']); $id _ con=$cobj->connect();switch($mod){ case"produit": { //gestiondecequiconcernelatableproduit if($operation=='update') { $prd=newProduit(); $prd->set _ id($id); $prd->set _ fabricant(trim($ _ REQUEST['fabricant'])); $prd->set _ libelle(trim($ _ REQUEST['nom _ produit'])); $prd->set _ qteAllert(trim($ _ REQUEST['stock _ allert'])); $prd->set _ qteStock(trim($ _ REQUEST['qte _ stock'])); $prd->set _ reference(trim($ _ REQUEST['reference'])); $prd->set _ unite(trim($ _ REQUEST['unite'])); $result=$prd->UpdateProduit($id _ con,$prd->get _ id(),$prd->get _ libelle(),$prd->get _reference(),$prd->get _ fabricant(), $prd->get _ qteStock(),$prd->get _ qteAllert(),$prd->get _ unite()); $cobj->disconnect($id _ con); } if($operation=='delete') { $prd=newProduit(); $result=$prd->DeleteOneProduit($id,$id _ con); $cobj->disconnect($id _ con); } if($operation=='new') { $prd=newProduit(); $prd->set _ fabricant(trim($ _ REQUEST['fabricant'])); $prd->set _ libelle(trim($ _ REQUEST['nom _ produit'])); $prd->set _ qteAllert(trim($ _ REQUEST['stock _ allert'])); $prd->set _ qteStock(trim($ _ REQUEST['qte _ stock'])); $prd->set _ reference(trim($ _ REQUEST['reference'])); $prd->set _ unite(trim($ _ REQUEST['unite'])); $result=$prd->SetProduit($id _ con); $cobj->disconnect($id _ con); } break; }}

Page 15: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org �5

- Les méthodes :

prepare(string$query) : permet de préparer une requête SQL pour exécution.

reset ( void ) : permet d’annuler une requête préparée.

La classe mysqli_driver- Les propriétés : Cette classe est pour le moment très peu documen-

tée mais possède néanmoins les propriétés suivan-tes :

Listing 6. La vue nouveau produit: new_produit.php

<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><head><metahttp-equiv="Content-Type"content="text/html;charset=UTF-8"/><title>NouveauProduit</title></head><body> <div> <formaction="ctrl.produit.php?action=new&mod=produit"name="form _ produit"id="form _ produit"method="post"> <fieldset> <legend> GestiondesProduits </legend> <table> <tr> <td><label>NomProduit</label></td> <td><inputname="nom _ produit"type="text"id="nom _produit"/></td> </tr> <tr> <td><label>Fabricant</label></td> <td><inputname="fabricant"type="text"id="fabricant"/></td> </tr> <tr> <td><label>QuantitéenStock</label></td> <td><inputname="qte _ stock"type="text"id="qte _ stock"/></td> </tr> <tr> <td><label>Stockd alerte</label></td> <td><inputname="stock _ allert"type="text"id="stock _allert"/></td> </tr> <tr> <td><label>UnitéProduit</label></td> <td><inputname="unite"type="text"id="unite"/></td> </tr> <tr> <td><label>RéferenceProduit</label></td> <td><inputname="reference"type="text"id="reference"/></td> </tr> <tr> <td>&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <tdalign="center"><inputname="Enregistrer"type="submit"value="Enregistrer"/></td> <tdalign="center"><inputname="Annuler"type="reset"value="Annuler"/></td> </tr> </table> </fieldset> </form> </div></body></html>

Page 16: eZ Publish PHP 02 2011

�/�0����

Projets

client _ version : qui indique la version du client. driver _ version : qui indique la version du driver Mysqli.

- Les méthodes

Il n’y en a que deux pour le moment et elles sont également peu documentées, il s’agit de :

embedded _ server _ end(void) : qui permet d’ar-rêter le serveur embarqué.

embedded _ server _ start ( bool $start ,array$arguments,array$groups) : qui per-met d’initialiser et de démarrer le serveur embarqué.

Mise en œuvrePour illustrer l’utilisation de mysqli pour accéder à MyS-QL, nous allons mettre en place une application web ba-sique. L’architecture ici étant l’architecture MVC (Model View Controller) cela nous permettra de séparer l’accès aux données de la présentation et des traitements.

Figure 2. Liste des produits

Figure 3. Détails/modification d’un produit

Page 17: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org ��

La base de donnéesPour pouvoir observer comment tout ceci est mis en œuvre, nous avons besoin d’une base de données MySQL. Cette base de données s’appellera cabinet et possèdera une table, la table produit. Le Listing 1 représente le script SQL correspondant pour cette base de données.

Connexion et déconnexion à la base de donnéesNous allons illustrer cela en écrivant deux fonctions que nous utiliserons tout au long de cet article car toutes les manipulations que nous aurons à faire et qui porte-ront sur les données nécessiteront que l’on se connec-te puis que l’on se déconnecte à la base de données. Étant donné que la connexion à la base de données nécessite des paramètres de connexion que sont l’hô-te, l’utilisateur, le login, le mot de passe ainsi que le nom de la base de données à laquelle on souhaite se connecter, ces paramètres seront également définis. Le Listing 2 représente le fichier config.class.php qui contiendra tout cela.

Les méthodes connect() et disconnect() permet-tent respectivement de se connecter et se déconnecter de la base de données en utilisant l’extension mysqli.

La classe ProduitPrécédemment nous avons fait allusion à la table pro-duit dans notre base de données ; maintenant, nous al-lons écrire la classe correspondante et nous écrirons également les méthodes d’accès aux enregistrements de cette table. Le Listing 3 nous montre le code de la classe Produit : il s’agit dans un premier temps d’une classe basique contenant juste les propriétés, les mé-thodes d’accès aux propriétés, (les accesseurs de pré-fixe get_et mutateurs de préfixe set_) et le construc-

teur de la classe. Ainsi une méthode comme get_id() permet d’obtenir la valeur de la propriété $prd_id de la classe Produit ; tandis que la méthode set_id($id) permet de définir ou de modifier cette valeur.

Le Listing 4 quant à lui contient la liste des méthodes complémentaires de la classe Produit. Celles-ci agi-ront sur les données, chacune d’une manière précise :

- GetOneProduit : elle prend en entrée l’identifiant de la connexion à Mysql et l’identifiant d’un produit dans la table produit, afin de le retourner sous la forme d’un objet de type Produit.

- GetAllProduit : cette méthode prend en entrée l’identifiant de connexion à MySQL, l’ordre dans le-quel les résultats devront être retournés, et la co-lonne suivant laquelle les résultats seront triés ; elle retourne la liste des produits présents dans la table produit sous forme d’un objet de type Moniterateur.

- UpdateProduit : cette méthode permet de met-tre à jour une ligne dans la table produit; elle prend en entrée l’identifiant de connexion à MySQL et d’autres paramètres correspondant aux proprié-tés de la classe Produit. Elle retourne true en cas de succès ou un message d’erreur sinon.

- SetProduit : cette méthode, qui prend en entrée l’identifiant de connexion à MySQL, permet d’insé-rer un enregistrement dans la table produit ; elle re-tourne true en cas de succès, ou un message d’er-reur sinon.

Figure 4. Suppression d’un produit

A propos de la sécurité

Dans l’exemple que nous prenons, l’aspect sécurité est peu ou pas assez pris en compte ; il est donc possible que le co-de qui suit puisse être vulnérable aux attaques comme les SQL Injections ou tout autre attaque courante.

Page 18: eZ Publish PHP 02 2011

�/�0����

Projets

- DeleteOneProduit : elle permet de supprimer un enregistrement dans la table produit; elle prend en entrée l’identifiant du produit à supprimer dans la table et l’identifiant de connexion à la base de don-nées MySQL.

On se rend bien compte en lisant les méthodes de cette classe que la logique est pratiquement la même :

- Dans un premier temps on écrit la requête SQL qui doit être exécutée.

- Ensuite on l’exécute, via la méthode query de l’objet mysqli.

- Enfin les résultats sont récupérés et traités en utili-sant la méthode fetch _ assoc de l’objet mysqli _result en vue d’être retournés sous le format sou-haité : un objet ou un itérateur.

Le contrôleur de la classe ProduitLe Listing 5 représente le fichier ctrl.produit.php qui contient les codes nécessaires aux différents traite-ments qui seront effectués. Avant tout traitement, la mé-thode connect de connexion à la base de données est appelée et à la fin de chaque traitement, la méthode de déconnexion, disconnect est appelée.

Dans chaque partie il est question d’un traitement spécifique appelant une méthode précise de la classe produit :

- $operation == new : ici le traitement à effectuer est l’enregistrement d’une nouvelle ligne dans la ta-ble produit,

- $operation==update : ici le traitement est la mi-se à jour d’un enregistrement dans la table produit,

- $operation == delete : ici le traitement est la suppression d’un enregistrement dans la table pro-duit.

Test de notre applicationNous allons maintenant utiliser les méthodes de no-tre classe en vue d’illustrer de manière pratique ce que nous obtenons comme résultat :

- Insertion d’un nouvel enregistrement dans la table produit.

Le Listing 6 représente le code de la vue nou-

veau produit qui permet d’enregistrer un nouveau produit dans la base de données. Nous allons en-registrer le produit Vitamine C ; on obtient alors dans un navigateur le formulaire représenté dans la Figure 1.

- Liste des enregistrements de la table produit.

Pour effectuer cette opération nous allons utiliser un itérateur : il s’agit d’une interface qui fournit un ensemble de méthodes permettant de passer en revue une collection d’objet en utilisant l’opérateur foreach comme on le fait de manière classique pour un tableau.

Nous utiliserons pour notre cas l’itérateur Iterator : il possède les méthodes suivantes que nous redéfini-rons :

Listing 7. La vue liste des produits: list_produit.php

<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><head><metahttp-equiv="Content-Type"content="text/html;charset=UTF-8"/><title>ListedesProduits</title></head><body>

<?php include _ once'config.class.php'; $cobj=newConfig(); //connexionalaBD $id _ con=$cobj->connect(); $lst _ prd=$prd->GetAllProduit($id _con,'ASC','prd _ libelle'); ?>

<tablewidth="600"border="1"cellpadding="1"cellspacing="0"> <tr> <thscope="col">Libelle</th> <thscope="col">Fabricant</th> <thscope="col">Unité</th> <thscope="col">Quantitéenstock</th> <thscope="col">Stockd`allerte</th> <thscope="col">Opération</th> </tr> <?php foreach($lst _ prdas$prd) { echo( "<tr>". "<td><ahref='update _ produit.php?id=".$prd['prd _id']."'>".$prd['prd _ libelle']."</a></td>". "<td>".$prd['prd _fabricant']."</td>". "<td>".$prd['prd _unite']."</td>". "<td>".$prd['prd _qteStock']."</td>". "<td>".$prd['prd _qteAllert']."</td>". "<td><ahref='ctrl.produit.php?action=delete&mod=produit&id=".$prd['prd _ id']."'>Supprimer</td>". "</tr>"); } //deconnexiondelaBD $cobj->disconnect($id _ con); ?> </table></body></html>

Page 19: eZ Publish PHP 02 2011

MySQL avec PHP5

www.phpsolmag.org ��

Listing 8. La vue update produit: update_produit.php

<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><htmlxmlns="http://www.w3.org/1999/xhtml"><head><metahttp-equiv="Content-Type"content="text/html;charset=UTF-8"/><title>MiseàjourProduit</title></head><body> <div> <?php //récupérationdel'enregistrementapartirdesonid include _ once'config.class.php'; $id=trim($ _ GET['id']); $cobj=new Config(); //connexionalaBD $id _ con=$cobj->connect(); $prd=newProduit(); $ligne=$prd->GetOneProduit($id _ con,$id); ?> <formaction="ctrl.produit.php?action=update&id=<?phpecho$id;?>&mod=produit"name="form _ produit"id="form _ produit"method="post"> <fieldset> <legend> Miseàjourd unProduit </legend> <table> <tr> <td><label>NomProduit</label></td> <td><inputname="nom _ produit"type="text"id="nom _ produit"value="<?phpecho$ligne->get _ libelle();?>"/></td> </tr> <tr> <td><label>Fabricant</label></td> <td><inputname="fabricant"type="text"id="fabricant"value="<?phpecho$ligne->get _ fabricant();?>"/></td> </tr> <tr> <td><label>QuantitéenStock</label></td> <td><inputname="qte _ stock"type="text"id="qte _ stock"value="<?phpecho$ligne->get _ qteStock();?>"/></td> </tr> <tr> <td><label>Stockd alerte</label></td> <td><inputname="stock _ allert"type="text"id="stock _ allert"value="<?phpecho$ligne->get _ qteAllert();?>"/></td> </tr> <tr> <td><label>UnitéProduit</label></td> <td><inputname="unite"type="text"id="unite"value="<?phpecho$ligne->get _ unite();?>"/></td> </tr> <tr> <td><label>RéférenceProduit</label></td> <td><inputname="reference"type="text"id="reference"value="<?phpecho$ligne->get _ reference();?>"/></td> </tr> <tr> <td>&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <tdalign="center"><inputname="Modiffier"type="submit"value="Modiffier"/></td> <tdalign="center"><inputname="Annuler"type="reset"value="Annuler"/></td> </tr> </table>

</fieldset>

</form>

</div>

</body></html>

Page 20: eZ Publish PHP 02 2011

�/�0���0

Projets

• current() : retourne l’élément courant de la collec-tion,

• key() : retourne la valeur de la clé de l’élément cou-rant de la collection,

• next() : permet de se positionner sur l’élément sui-vant de la collection,

• rewind() : permet de se positionner sur le premier élément de la collection,

• valid() : permet de savoir si l’on a atteint la fin de la collection.

Le Listing 7 représente le code source de la vue liste produit elle permet d’afficher sous forme de ta-bleau la liste des produits présents dans la table produit. Ce qui nous donne dans un navigateur la Figure 2.

On remarque bien la présence de notre nouvel en-registrement vitamine c à la dernière ligne de notre ta-bleau.

- Détails / modification d’un produit : Le code de cette vue se trouve dans le Listing 8 :

il permet de voir les détails et de modifier les don-nées relatives à un produit donné. On obtient dans un navigateur la Figure 3 pour le cas de notre pro-duit Vitamine C.

- Suppression d’un produit :

La colonne opération du tableau de la liste des produits contient un lien permettant de suppri-mer un produit de notre base de données ; ce lien pointe directement sur le fichier ctrl.produit.php dont nous avons parlé plus haut. Nous pou-vons donc cliquer sur ce lien pour supprimer no-tre produit vitamine C et en revenant à notre lis-ting on se rend bien compte comme le montre la Figure 4 que ce produit n’existe plus dans notre liste.

MBELLA EKOUME K. DEMAZYTechnicien Supérieur en Réseaux & Télécom, il a poursuivi son cursus en faisant une maîtrise en développement logiciel. Très orienté WEB, celui-ci se passionne pour les solutions Open Source et cela fait ma-intenant 3 ans qu’il travaille en tant que développeur professionnel. Pour le contacter, envoyer un mail à l’adresse [email protected].

Sur Internet :

• http://www.php.net/download-docs.php - Le manuel de PHP5 sous divers formats, pour plus de détails sur la classe Mysqli.

Bibliographie :

Jeans Engels, PHP5 cours et exercices, avec la contribution de Olivier Salvatori, groupe Eyrolles, �00�.

ConclusionNous venons à travers cet article de voir comment ac-céder à MySQL en orienté objet grâce à l’extension mysqli ; nous avons également pu effectuer des trai-tements grâce aux classes mysqli_stmtetmysqli_result et à leurs méthodes. Désormais vous pouvez développer votre application PHP5 en adoptant une approche complètement orienté objet, même au ni-veau de l’accès aux données. Cependant pour ceux qui ne sont pas encore familiarisés avec l’approche orientée objet de PHP5 et veulent pouvoir tirer pro-fit des avantages de l’extension mysqli, ils peuvent dans ce cas opter pour l’approche procédurale de cet-te API ; cela pourrait d’ailleurs faire l’objet d’un article futur.

Étant donné que cet article ne saurait être exhaus-tif sur ce sujet, nous vous invitons à compléter vos connaissances en faisant un tour sur Internet et en lisant quelques livres spécialisés.

Page 21: eZ Publish PHP 02 2011

www.phpsolmag.org ��

Dossier

Contenu Orienté Objet Avant d’entamer le développement d’un datatype per-sonnalisé, il est recommandé de bien comprendre la phi-losophie de Contenu Orienté Objet (OOC) d’eZ Publish.

Un contenu dans eZ Publish consiste en un objet, un Ob-jet de Contenu, qui est une instance d’une Classe de Conte-nu. Une classe de contenu est une collection d’attributs, les Attributs de Classe de Contenu, typés (ligne de texte, nom-bre, data, texte riche, relation d’objet). Ces types sont dési-gnés par le terme Datatypes ci-après. Sur la base de cette définition, un objet de contenu consiste en une collection d’attributs, les Attributs d’Objet de Contenu, instances ou re-présentations concrètes des attributs de classe de contenu, chacun stockant le contenu effectif (voir Figure 1).

Une description complète de ces concepts peut-être trou-vée dans la documentation officielle de gestion de contenu pour eZ Publish : http://doc.ez.no/eZ-Publish/Technical-ma-nual/4.x/Concepts-and-basics/Content-management.

Anatomie d’un datatype eZ PublishUn datatype est la plus petite entité de stockage. Il dé-termine comment un type d’information spécifique doit être validé, stocké, récupéré, formaté, etc.

Structure standard Concrètement, un datatype consiste en une classe PHP étendant la classe eZDataType, et d’un ensemble de templates. Les datatypes personnalisés doivent être contenus dans une extension au système.

Le guide de développement de datatype eZ Publish

Les datatypes eZ Publish sont probablement l’une des fonctionnalités les plus puissantes d’eZ Publish car ils permettent de traiter des éléments de contenu comme vous l’entendez. Cet article vous expliquera les fonctionnalités de base que l’on se doit d’implémenter lors du développement de son propre datatype eZ Publish.

Cet article explique :• Après lecture de ce didacticiel, vous connaîtrez les bases con-

ceptuelles des datatypes eZ Publish. Cela permettra d’adresser la plupart des cas d’utilisation de ce concept, dans le cadre d’un projet demandant la personnalisation du framework. Les exemples détaillés pourront servir de référence de code géné-rique, ré-utilisables dans de nombreux autres cas que celui su-ivi dans ce didacticiel.

Ce qu'il faut savoir :• Ce didacticiel s’adresse aux développeurs eZ Publish expéri-

mentés voire à des développeurs intermédiaires souhaitant élargir leurs connaissances techniques de la plateforme de ge-stion de contenu eZ Publish.

• Des compétences en programmation orientée objet, ain-si qu’une bonne compréhension des concepts de gestion de contenu d’eZ Publish sont également requis. Vous aurez égale-ment besoin d’une installation fonctionnelle d’eZ Publish. Vous pouvez télécharger la dernière version ici : http://share.ez.no/download, et suivre les instructions d’installation ici même : http://doc.ez.no/eZ-Publish/Technical-manual/4.4/Installation

Figure 1. Contenu orienté objet (OOC) dans eZ Publish

Page 22: eZ Publish PHP 02 2011

�/�0����

Dossier

9/20102

Extension ZIP pour PHP

Page 23: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

Premièrement, vous devez choisir un ID (identifiant unique) pour votre datatype, également connu sous le nom de datatype string. Il sera stocké à côté du contenu afin que le système gère les instances de ce datatype cor-rectement lors de la manipulation du contenu. Par conven-tion, le datatype string ne contient pas de caractères spé-ciaux, et n’est composé que de minuscules. Le Listing 1 présente la structure standard d’une extension de dataty-pe, ayant pour ID mycustomdatatypeid (voir Listing 1).

Notez les différentes conventions de nommage uti-lisées :

• Le fichier PHP contenant la classe du dataty-pe DOIT être nommé comme suit : <datatype _string>type.php (mycustomdatatypeidtype.php dans notre cas).

• Tous les templates sont nommés directement d’après le datatype string (mycustomdatatypeid.tpl dans notre cas). C’est une convention système, à laquelle on peut faire exception si l’on retourne dynamiquement les noms des templates utilisés par le datatype. Ceci sort du cadre de ce didacticiel.

Enregistrement du datatypeComme expliqué ci-dessus, un datatype consiste prin-cipalement en une classe PHP étendant eZDataType. Il doit aussi être enregistré dans eZ Publish, ceci passant par de la configuration et une ligne de PHP.

La partie PHPNotez que le datatype string est stocké dans une constante de classe (voir Listing 2). C’est une bonne pratique, permettant d’y faire référence facilement par la suite. Ensuite, le constructeur appelle le constructeur parent, en passant en paramètres le datatype string, et le label du datatype, tel qu’il apparaîtra dans l’inter-face d’administration lors de l’édition d’une classe de contenu. Ce label est traduisible en utilisant la fonction-nalité i18n native d’eZ Publish (http://pubsvn2.ez.no/phpdoc/trunk/html/default/ezpI18n.html#tr).

Finalement, en dehors de la structure de classe, le datatype est enregistré auprès du moteur de conte-nu. Cette expression PHP fait correspondre le dataty-pe string à votre classe PHP. Notez que la traduction est activée par défaut. Si, pour une raison quelconque, vous décidez de la désactiver, vous pouvez appeler le constructeur parent en passant une valeur non nulle au troisième argument, properties (voir Listing 3).

D’autres propriétés sont disponibles (pour ce troisiè-me argument), mais elles ne seront pas abordées pré-sentement.

La partie configurationIci nous indiquons à eZ Publish qu’il doit également cher-cher des datatypes dans le dossier d’extension mycustomdatatypeextension. Nous déclarons également notre

Listing 1. Structure d’un datatype

extension/mydatatypeextension/|--datatypes|'--mycustomdatatypeid|'--mycustomdatatypeidtype.php|--design|'--standard|'--templates||--class||'--datatype|||--edit|||'--mycustomdatatypeid.tpl||'--view||'--mycustomdatatypeid.tpl|'--content|'--datatype||--edit||'--mycustomdatatypeid.tpl|'--view|'--mycustomdatatypeid.tpl|--extension.xml|--ezinfo.php'--settings|--content.ini.append.php|--datatype.ini.append.php'--design.ini.append.php

Listing 2. Classe PHP du datatype

<?php/*** File containing MyCustomDatatypeID datatype definition*@licencehttp://www.gnu.org/licenses/gpl-2.0.txtGNUGPLv2*@version@@@VERSION@@@*/classMyCustomDatatypeIDTypeextendseZDataType{constDATA _ TYPE _ STRING='mycustomdatatypeid';

/***Constructor*/publicfunction_ _ construct(){parent::eZDataType(self::DATA _ TYPE _ STRING,'Mycustomdatatypelabel');}}

eZDataType::register(MyCustomDatatypeIDType::DATA _TYPE _ STRING,'MyCustomDatatypeIDType');

Listing 3. Constructeur

publicfunction_ _ construct(){$datatypeLabel=ezpI18n::tr('datatypes/mycustomdatatype','Mycustomdatatypelabel');//UseezpI18n::tr()witheZP4.3orlater.Before,useezi18n()functionparent::eZDataType(self::DATA _ TYPE _ STRING,$datatypeLabel,array('translation _ allowed'=>false));}

Listing 4. extension/mycustomdatatypeextension/settings/content.ini.append.php

<?php/*#?inicharset="utf-8"?

[DataTypeSettings]ExtensionDirectories[]=mycustomdatatypeextensionAvailableDataTypes[]=mycustomdatatypeid

*/?>

Page 24: eZ Publish PHP 02 2011

�/�0����

Dossier

datatype string mycustomdatatypeid, signifiant la dispo-nibilité de notre datatype (voir Listing 4). Ici, nous décla-rons que notre extension contient des éléments de de-sign (dont des templates, voir Listing 5).

Le projetPour une meilleure compréhension, nous allons placer l’étude des datatypes eZ Publish dans le cadre d’un projet. Nous proposons de développer un datatype gérant les IDs produits Amazon, basé sur l’extension JVamazonadvertising (http://projects.ez.no/jvama-zonadvertising). L’extension entière, contenant le da-tatype finalisé, peut-être récupérée dans le dépôt SVN suivant : http://svn.projects.ez.no/jvamazonadverti-sing/trunk/ (le package proposé en téléchargement n’est pas à jour).

SpécificationsLa fonction de ce datatype est de supporter les ID pro-duits spécifiques à Amazon (ASIN), en procurant :

• Une validation dans l’interface d’édition de contenu, de telle sorte que l’éditeur ne puisse sai-sir qu’un ASIN valide, vérifié via AWS (Amazon Web Services).

• Un remplissage automatique de l’attribut si le champ est vide.– Une option dédiée devra être activée dans l’in-

terface d’édition de classe.– L’attribut devra être renseigné par le premier ré-

sultat de la recherche correspondante dans le catalogue Amazon.

– Le contributeur pourra choisir d’activer ou non cette recherche automatique, depuis l’interface d’édition de contenu.

– La recherche dans le catalogue Amazon se fe-ra au travers d’AWS, et portera sur le contenu des autres attributs de l’objet venant d’être édité (nom, description).

– L’administrateur pourra définir le(s) attribut(s) de contenu sur le contenu duquel (desquels) porte-ra la recherche.

– L’administrateur pourra choisir dans quelle caté-gorie produit la recherche sera faite,

• Il doit être possible d’effectuer une recherche de produits amazon depuis l’interface d’édition afin de sélectionner le plus approprié.

Captures d’écran

Plan de développementLes fonctionnalités exposées ci-dessus vont être im-plémentées dans la classe PHP de datatype nom-mée JVamazonidType, assortie d’un ensemble de templates nécessaires à la création de l’interface uti-lisateur.

L’intégralité des étapes suivantes reposera sur l’hypothèse que l’extension JVAmazonAdvertising est installée et configurée correctement (consulter la do-cumentation de l’extension pour ce faire : http://svn.projects.ez.no/jvamazonadvertising/trunk/doc/INS-TALL). Le datatype string sera jvamazonid, la classe PHP se trouvera donc dans le fichier suivant : exten-sion/jvamazonadvertising/datatypes/jvamazonid/jva-mazonidtype.php.

Le développement se fera en trois parties :

• Classe de contenu,• Objet de contenu,• ActionHTTPspécifique.

A chaque partie correspond un groupe de méthode et un ensemble de templates, détaillés ci-après.

Listing 5. extension/mycustomdatatypeextension/settings/design.ini.append.php

<?php/*#?inicharset="utf-8"?

[ExtensionSettings]DesignExtensions[]=mycustomdatatypeextension

*/?>

Page 25: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org �5

Notre datatype au niveau de la classe de contenuPetit préambule pratiquePour suivre pas à pas le cheminement ci-dessous, ren-dez-vous dans l’interface d’administration d’eZ Publish, cliquez “Setup” dans la barre de menu principale, puis “Classes”, puis “Content” comme illustré dans la Figu-re 2 et 3.

Vous pouvez choisir n’importe quelle classe de contenu dans la liste qui s’affiche. Le contexte peut nous faire éditer la classe “Product” (voir Figure 3).

Côté templateL’objectif est de pouvoir ajouter un attribut du datatype que nous sommes en train de développer à une clas-se de contenu existante, ou bien une nouvelle classe. Comme le montre la Figure 4, représentant notre data-type dans l’interface d’édition d’une classe de contenu, la partie supérieure de l’interface, par attribut, est stan-dard et commune à tous les datatypes. Elle ne deman-de pas de création de template.

La partie inférieure, optionnelle (le template corres-pondant, même vide, doit cependant exister), est néces-saire lorsque de la configuration supplémentaire est re-quise, ce qui est notre cas. Nous allons devoir créer, dans un template, cet élément d’interface (voir Figure 4).

Ce découpage d’interface standard/personnalisé se retrouve lors de la visualisation de l’attribut dans la clas-se de contenu, voir Figure 5. Nous allons également créer cet élément d’interface personnalisé dans un tem-plate propre à notre datatype (voir Figure 5). Commen-çons donc par créer la partie d’interface personnalisée de l’édition de classe de contenu.

Template d’éditionComme nous l’avons vu plus haut (cf Structure Stan-dard), c’est dans le template jvamazonid.tpl, placé dans extension/jvamazonadvertising/design/standard/tem-plates/class/datatype/edit/ que nous allons permettre au webmestre de :• Activer la recherche de produit correspondant dans

le catalogue Amazon si le champ de saisie, au ni-veau de l’édition de l’objet de contenu, a été laissé vide. Ceci doit pouvoir se contrôler par une option dédiée,

• Contrôler quels attributs de contenus serviront de texte de recherche, dans le cas où l’option ci-des-sus est activée,

• Définir sur quelle catégorie du catalogue Amazon portera la recherche, si elle est activée ou manuelle,

• Définir sur quel emplacement du catalogue Amazon (BrowseNode) portera la recherche, si elle est acti-vée ou manuelle.

Ajoutons ces trois contrôles dans jvamazonid.tpl (cf Listing 6).

MarkupNotez le réflexe <fieldset>/<legend>, qui permet de dé-ployer l’interface en deux parties visuellement distinc-tes, en s’appuyant sur les styles par défaut de l’inter-face d’administration d’eZ Publish. Une autre bonne pratique est d’utiliser une balise <label> correspondant à chaque balise <input>, en les liant par l’attribut ‘for’ de la balise <label>.

Variables à dispositionLa principale variable nous intéressant ici est $class_attribute, un objet PHP de la classe eZContent-ClassAttribute (détails : http://doc.ez.no/eZ-Publish/Technical-manual/4.x/Reference/Objects/ezcontent-classattribute), donnant accès à toutes les propriétés et données de notre attribut de classe de contenu. La référence complète des attributs est consultable ici : http://goo.gl/aeumK.

Figure 2. Navigation vers l’interface d’édition de classe de contenu

Figure 3. Edition de la classe de contenu ‘Product’

Figure 4. Interface d’édition de notre datatype, au niveau de la classe de contenu.

Figure 5. Interface de visualisation de notre datatype, au niveau de la classe de contenu.

Page 26: eZ Publish PHP 02 2011

�/�0����

Dossier

Listing 6. extension/jvamazonadvertising/design/standard/templates/class/datatype/edit/jvamazonid.tpl

<divclass="block"><fieldset> <legend> {"Reference field for ASIN search"|i18n("design/standard/class/datatype/jvamazonid")} </legend> <labelfor="ContentClass _ jvamazonid _ asin _ search _ field _ {$class _ attribute.id}"> {"Enter attribute identifiers as for object name (ie. &lt;my _ attribute _ identifier&gt;)"|i18n( "design/standard/class/datatype/jvamazonid")}</label> <inputtype="text"size="50" name="ContentClass _ jvamazonid _ asin _ search _ field _ {$class _ attribute.id}" id="ContentClass _ jvamazonid _ asin _ search _ field _ {$class _ attribute.id}" value="{$class _ attribute.data _ text1}"/> <i>{"Ifleftempty,willbecontentobjectname"|i18n("design/standard/class/datatype/jvamazonid")}</i> <br/><br/> <labelfor="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}"> {"AutomaticallysearchASINonpublishifobjectattributeisleftempty(defaultvalue)"|i18n("design/standard/class/datatype/jvamazonid")}</label> <inputtype="checkbox"name="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}"id="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}"{if$class _ attribute.data _ int1}checked="checked"{/if}/></fieldset><fieldset><legend>{"AmazonSearchIndex(category)tosearchinto"|i18n("design/standard/class/datatype/jvamazonid")}</legend><labelfor="ContentClass _ jvamazonid _ asin _ search _ index _ {$class _ attribute.id}">{"Enteravalidsearchindex(productcategory)."|i18n("design/standard/class/datatype/jvamazonid")}<br/></label><inputtype="text"size="50"name="ContentClass _ jvamazonid _ asin _ search _ index _ {$class _ attribute.id}"id="ContentClass _ jvamazonid _ asin _ search _ index _ {$class _ attribute.id}"value="{$class _ attribute.data _ text2}"/><br/><i>{"Searchindexesarelistedhere"|i18n("design/standard/class/datatype/jvamazonid")}:<ahref={"http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/APPNDX _ SearchIndexValues.html"|ezurl}target=" _ blank">http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/APPNDX _SearchIndexValues.html</a></i></fieldset><fieldset><legend>{"AmazonBrowseNode(sub-category)tosearchinto"|i18n("design/standard/class/datatype/jvamazonid")}</legend><labelfor="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}">{"Enteravalidbrowsenode(productlocationinAmazoncatalog).Leaveemptyifnotapplicable"|i18n("design/standard/class/datatype/jvamazonid")}<br/></label><inputtype="text"size="50"name="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}"id="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}"value="{$class _ attribute.data _ int2}"/><br/><i>{"Findabrowsenodesnon-exhaustivelisthere"|i18n("design/standard/class/datatype/jvamazonid")}:<ahref={"http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?APPNDX _SearchIndexValues.html"|ezurl}target=" _ blank">http://docs.amazonwebservices.com/AWSECommerceService/latest/DG/index.html?APPNDX _ SearchIndexValues.html</a></i></fieldset></div>

Listing 7. Nom de la balise <input> principale

<inputtype="text"size="50"name="ContentClass _ jvamazonid _ asin _ search _ field _ {$class _ attribute.id}"id="ContentClass _ jvamazonid _ asin _ search _ field _ {$class _ attribute.id}" value="{$class _ attribute.data _ text1}"/>

Listing 8. Récupération des données

<inputtype="checkbox"name="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}"id="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}"{if$class _ attribute.data _ int1}checked="checked"{/if}/>

Page 27: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

Construction du nom des <input>L’attribut ‘name’ des balises <input> suit une règle de nommage stricte :

ContentClass_<data_type_string>_<nom_de_l_input>_<ID_d_attribut_de_classe>

qui doit être respectée dans le template, et conséque-mment dans la partie PHP, lors du traitement des en-trées. Le Listing 7 montre un exemple extrait du templa-te d’édition de l’attribut de classe.

On y retrouve bien les éléments constitutifs de la rè-gle de nommage (voir Tableau 1). Ce motif doit être sui-vi pour toutes les balises <input> utilisées, entraînant l’unicité de nom de toutes les balises <input> pouvant être présentes dans le formulaire d’édition de classe de contenu (qui contient généralement plusieurs attributs, on évite donc tout conflit).

Récupération des donnéesLors de l’édition de notre attribut, ou bien lors de sa créa-tion initiale, il convient d’afficher les donnés de configu-ration stockées précédemment (ou initialisées à leurs valeurs par défaut). Le Listing 8 présente un exemple, tiré du précédent template.

On note ici que la valeur du champ de stockage da-ta_int1 (détails sur les champs de stockage disponibles dans la partie PHP, ci-après) est utilisée pour cocher l’entrée «Automatically search ASIN on publish if object attribute is left empty (default value)».

Template de visualisationVoir Listing 9.

MarkupOn encapsule l’ensemble de cette partie d’interface personnalisée dans un <divclass=”block”></div>,

ceci permettant de s’appuyer sur les styles par défaut de l’interface d’administration. Une autre bonne prati-que est d’utiliser des balises <label> autour de chaque élément de configuration.

Variables et données à dispositionComme pour le template d’édition, la variable $class_attribute nous permet d’avoir accès aux propriétés et données de notre attribut de classe. Le contenu de cette variable est le même que pour l’édition de classe, et la récupération des données stockées se fait de la même façon que lors de l’édition :

{$class_attribute.data_text1|wash}

L’utilisation de l’opérateur wash permet de “laver” la sortie (équivalent de htmlentities en PHP, cf http://www.php.net/htmlentities), évitant une injection potentielle.

Côté PHPTravaillons maintenant dans notre classe PHP JVAmazonIDType. Les principales méthodes de publication de la clas-se de contenu sont, dans leur ordre d’appel :

• initializeClassAttribute() - Initialise l’attribut de classe, avec des valeurs par défaut lorsque l’at-tribut est créé (ajout à une classe de contenu),

• validateClassAttributeHTTPInput() - Validation des entrées,

• fixupClassAttributeHTTPInput() - Méthode post-validation,

• fetchClassAttributeHTTPInput() - Récupération effective des données postées et stockage.

InitialisationLa méthode initializeClassAttribute() reçoit en paramètre une référence du Content Class Attribute

Listing 9. extension/jvamazonadvertising/design/standard/templates/class/datatype/view/jvamazonid.tpl

<divclass="block"><label>{"Reference field for ASIN search"|i18n("design/standard/class/datatype/jvamazonid")}:</label><p>{$class _ attribute.data _ text1|wash}</p><label>{"AutomaticallysearchASINonpublishifobjectattributeisleftempty(defaultvalue)"|i18n("design/standard/class/datatype/jvamazonid")}:</label><p>{$class _ attribute.data _ int1|choose('No'|i18n("design/standard/class/datatype/jvamazonid"),'Yes'|i18n("design/standard/class/datatype/jvamazonid"))}</p><label>{"AmazonSearchIndex(category)tosearchinto"|i18n("design/standard/class/datatype/jvamazonid")}:</label><p>{$class _ attribute.data _ text2|wash}</p><label>{"AmazonBrowseNode(sub-category)tosearchinto"|i18n("design/standard/class/datatype/jvamazonid")}:</label><p>{$class _ attribute.data _ int2}</p></div>

Tableau 1. Eléments de nommage d’une balise input

Préfixe 'ContentClass'

Datatype string 'jvamazonid'

Nom de l’input 'asin_search_field'

ID de l’attribut de classe, généré dynamiquement {$class_attribute.id}

Page 28: eZ Publish PHP 02 2011

�/�0����

Dossier

(classe PHP eZContentClassAttribute, détails ici : http://doc.ez.no/eZ-Publish/Technical-manual/4.x/Reference/Objects/ezcontentclassattribute). Elle permet, alors que l’on ajoute un nouvel attribut de notre datatype à une

classe de contenu, de peupler au besoin les champs de stockage utilisés avec des valeurs par défaut. De ma-nière générique, on assigne une valeur à un champ de la manière suivante :

Listing 10. Initialisation de l'attribut de classe

<?/***Setsdefaultvaluesforanewclassattribute.*@parameZContentClassAttribute$classAttribute*@returnvoid*/publicfunctioninitializeClassAttribute($classAttribute){// Default value for search fieldif(!$classAttribute->attribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD)){$classAttribute->setAttribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD,self::CLASSATTRIBUTE _ DEFAULT _ EMPTY);}//Defaultvaluefor"Allowsearchifempty"if($classAttribute->attribute(self::CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD)===null){$classAttribute->setAttribute(self::CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD,self::CLASSATTRIBUTE _ ALLOW _ SEARCH _ DEFAULT);}//Defaultvalueforsearchindexif(!$classAttribute->attribute(self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD)){$classAttribute->setAttribute(self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD,self::CLASSATTRIBUTE _ SEARCHINDEX _ DEFAULT);}//Defaultvalueforbrowsenodeif(!$classAttribute->attribute(self::CLASSATTRIBUTE _ BROWSENODE _ FIELD)){$classAttribute->setAttribute(self::CLASSATTRIBUTE _ BROWSENODE _ FIELD,self::CLASSATTRIBUTE _ BROWSENODE _ DEFAULT);}}

Listing 11. Validation de l'attribut de classe

/** * Validates the input from the class definition formconcerningthisattribute.*@parameZHTTPTool$http*@paramstring$baseSeemstobealways'ContentClass'.*@parameZContentClassAttribute$classAttribute*@returninteZInputValidator::STATE _ ACCEPTED|eZInputValidator::STATE _ INVALID|eZInputValidator::STATE _ INTERMEDIATE*/publicfunctionvalidateClassAttributeHTTPInput($http,$base,$classAttribute){returneZInputValidator::STATE _ ACCEPTED;}

Listing 12. Récupération des données et stockage de l'attribut de classe

/** * Handles the input specific for one attribute from theclasseditinterface.*@parameZHTTPTool$http*@paramstring$baseSeemstobealways'ContentClass'.*@parameZContentClassAttribute$classAttribute*@returnvoid*/publicfunctionfetchClassAttributeHTTPInput($http,$base,$classAttribute){// Search field(s)$searchFieldName=$base.self::SEARCH _ FIELD _VARIABLE.$classAttribute->attribute('id');if($http->hasPostVariable($searchFieldName)){$searchFieldValue=$http->postVariable($searchFieldName);$classAttribute->setAttribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD,$searchFieldValue);}//Allowsearchifobjectattributeempty$searchIfEmptyFieldName=$base.self::ALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE.$classAttribute->attribute('id');if($http->hasPostVariable($searchIfEmptyFieldName))//Checkbox:onlysetifposted{$classAttribute->setAttribute(self::CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD,1);}//Searchindex$searchIndexFieldName=$base.self::SEARCH _INDEX _ FIELD _ VARIABLE.$classAttribute->attribute('id');if($http->hasPostVariable($searchIndexFieldName)){$searchIndexFieldValue=$http->postVariable($searchIndexFieldName);$classAttribute->setAttribute(self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD,$searchIndexFieldValue);}//BrowseNode$browseNodeFieldName=$base.self::BROWSENODE _FIELD _ VARIABLE.$classAttribute->attribute('id');if($http->hasPostVariable($searchIndexFieldName)){$browseNodeFieldValue=$http->postVariable($browseNodeFieldName);$classAttribute->setAttribute(self::CLASSATTRIBUTE _ BROWSENODE _ FIELD,$browseNodeFieldValue);}}

Listing 13. Récupération via eZHTTPTool

if($http->hasPostVariable($searchFieldName)){$searchFieldValue=$http->postVariable($searchFieldName);$classAttribute->setAttribute(self::CLASSATTRIBUTE _DEFAULT _ FIELD,$searchFieldValue);}

Page 29: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

$classAttribute->setAttribute( <nom_du_champ>, <va-leur> );

L’utilisation de constantes de classe est recommandée pour désigner les champs de stockage, ainsi que pour les valeurs par défaut, entraînant une meilleure facto-risation, et une meilleure lisibilité du code, comme le montre le Listing 10.

Ici on initialise les champs de stockage (en base de données) avec les valeurs suivantes, s’ils n’en contien-nent pas déjà (voir Tableau 2).

Champs de stockage disponible13 champs sont disponibles pour stocker des données de configuration dans un attribut de classe de contenu. Nous pouvons donc utiliser les 13 à notre convenance et suivant nos besoins, dans le code de notre datatay-pe, comme décrit dans la partie “Initialisation”. Voici la liste des champs disponibles, tous provenant de la table ezcontentclass_attribute (voir Tableau 3).

Validation, récupération et stockageLes 3 méthodes que nous allons décrire ci-après per-mettent de traiter l’information entrée par le webmestre lorsqu’il publie une classe de contenu, notamment de la valider et de la stocker. Chacune de ces méthodes reçoit

en paramètre une référence du Content Class Attribu-te (classe PHPeZContentClassAttribute) dans lequel sera stockée la configuration - et de l’objet eZHTTPTool (détails : http://pubsvn2.ez.no/phpdoc/trunk/html/default/eZHTTPTool.html) permettant de manipuler les données transférées via HTTP (POST, GET...).

ValidationLa première méthode appelée est validateClassAttributeHTTPInput(...), qui sert à s’assurer que les entrées reçues ont le bon format. On peut imaginer un datatype gé-rant du texte, dont un élément de configuration serait la taille maximale du texte qu’un éditeur peut saisir. Il faudrait, dans cette méthode, s’assurer que l’entrée est bien numérique.

Cette méthode peut retourner 3 statuts différents :

• eZInputValidator::STATE _ ACCEPTED - Les don-nées sont validées,

• eZInputValidator::STATE _ INVALID - Les don-nées sont invalidées et une erreur s’affiche dans le formulaire de saisie,

• eZInputValidator::STATE _ INTERMEDIATE - Ni l’un ni l’autre, une action supplémentaire est nécessaire via la méthode fixupClassAttributeHTTPInput(). Il convient dans ce cas d’altérer directement les varia-bles POST via eZHTTPTool.

Tableau 2. Attribution des valeurs par défaut aux champs de stockage

Constante de nommage du champ Nom réel du champ

Constante contenant la valeur par défaut

Valeur par défaut

self::CLASSATTRIBUTE _ DEFAULT _FIELD

data_text� self::CLASSATTRIBUTE _ DEFAULT _EMPTY

‘’

self::CLASSATTRIBUTE _ ALLOW _SEARCH _ FIELD

data_int� self::CLASSATTRIBUTE _ ALLOW _SEARCH _ DEFAULT

0

self::CLASSATTRIBUTE _SEARCHINDEX _ FIELD

data_text� self::CLASSATTRIBUTE _SEARCHINDEX _ DEFAULT

‘All’

self::CLASSATTRIBUTE _BROWSENODE _ FIELD

data_int� self::CLASSATTRIBUTE _BROWSENODE _ DEFAULT

0

Tableau 3. Champs de stockage disponibles

Nom du champ Type

data_int� Entier - int(��)

data_int� Entier - int(��)

data_int� Entier - int(��)

data_int� Entier - int(��)

data_float� Décimal - float

data_float� Décimal - float

data_float� Décimal - float

data_float� Décimal - float

data_text� Chaîne de caractères courte - varchar(50)

data_text� Chaîne de caractères courte - varchar(50)

data_text� Chaîne de caractères courte - varchar(50)

data_text� Chaîne moyenne - varchar(�55)

data_text5 Chaîne longue - longtext

Page 30: eZ Publish PHP 02 2011

�/�0���0

Dossier

Le corps de cette méthode est trivial dans notre cas, comme le montre le Listing 11.

La méthode fixupClassAttributeHTTPInput() sera donc vide. Vous aviez suivi ? Parfait. On continue.

Récupération et stockageC’est la méthode fetchClassAttributeHTTPInput() qui assure ces deux fonctions. Nous devons stocker trois informations :

Listing 14. Utilisation de constantes de classe

constALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE=' _jvamazonid _ asin _ search _ if _ empty _ ',CONTENT _ FIELD _ VARIABLE=' _ jvamazonid _data _ text _ ';

/***Fetchesallvariablesfromtheobjectandhandlesthem*Datastorecanbedonehere*@parameZHTTPTool$http * @param string $base POST variable name prefix (Always"ContentObjectAttribute")*@parameZContentObjectAttribute$contentObjectAttribute*@returntrueiffetchingofclassattributesaresuccessfull,falseifnot*/publicfunctionfetchObjectAttributeHTTPInput($http,$base,$contentObjectAttribute){//Reconstitutiondunomdeschampsàtraiter$fieldName=$base.self::CONTENT _ FIELD _VARIABLE.$contentObjectAttribute->attribute('id');$allowSearchFieldName=$base.self::ALLOW _SEARCH _ IF _ EMPTY _ VARIABLE.$contentObjectAttribute->attribute('id');}

Listing 15. Initialisation de l'attribut d'objet

/** * Initialise l'attribut avant d'afficher le template d'édition * Utile pour définir une valeur par défaut. Les valeurs par défaut doivent être définies dans l'attribut declasse.*@parameZContentObjectAttribute$contentObjectAttributeAttributd'objetpourlanouvelleversion*@paramint$currentVersionNumérodeversion.NULL s'il s'agit de la première version.*@parameZContentObjectAttribute$originalContentObjectAttributeAttributd'objetdelaversionprécédente*@seeeZDataType::initializeObjectAttribute()*/publicfunctioninitializeObjectAttribute($contentObjectAttribute,$currentVersion,$originalContentObjectAttribute){//Onnepositionnelavaleurpardéfautquepour la première version.if($currentVersion===null){// On récupère la valeur par défaut telle que définie dans l'attribut de classe et on la positionne dansl'attributd'objet$contentClassAttribute=$contentObjectAttribute->contentClassAttribute();$allowSearchDefault=$contentClassAttribute->attribute('data _ int1');$contentObjectAttribute->setAttribute('data _ int',$allowSearchDefault);}}

Listing 16. Validation de l'attribut d'objet

constALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE=' _jvamazonid _ asin _ search _ if _ empty _ ',CONTENT _ FIELD _ VARIABLE=' _ jvamazonid _data _ text _ ';

/***Validel'inputauniveaudel'objetdecontenu * Vérifie si l'ID Amason est un ASIN valide*@parameZHTTPTool$http * @param string $base Préfixe du nom de la variablePOST(Toujours"ContentObjectAttribute")*@parameZContentObjectAttribute$contentObjectAttribute*@returneZInputValidator::STATE _ ACCEPTED|eZInputValidator::STATE _ INVALID|eZInputValidator::STATE _INTERMEDIATE*/publicfunctionvalidateObjectAttributeHTTPInput($http,$base,$contentObjectAttribute){//Reconstitutiondunomduchamp$fieldName=$base.self::CONTENT _ FIELD _VARIABLE.$contentObjectAttribute->attribute('id');$returnState=eZInputValidator::STATE _ACCEPTED;if($http->hasPostVariable($fieldName)){$fieldValue=$http->postVariable($fieldName);if(trim($fieldValue)!='')//Onabienune valeur renseignée, on en vérifie la validité{//Utilisationdelafonctionfetchamazonadvertising/item _ lookupviaPHP$amazonResult=eZFunctionHandler::execute('amazonadvertising','item _ lookup',array('id' =>$fieldValue));//Entréeinvalidesileretourduwebservicen'estpasunobjethéritantdeJVAmazonAdvertisingAbstractItemif(!$amazonResultinstanceofJVAmazonAdvertisingAbstractItem){//Nepasoublierdepréciserl'erreur$validationError=ezpI18n::tr('extension/jvamazonadvertising/error','InvalidAmazonproductID');$contentObjectAttribute->setValidationError($validationError);$returnState=eZInputValidator::STATE _ INVALID;}}}return$returnState;}

/***"Répare"lesdonnéespostées*Cetteméthoden'estappeléequesilaméthodedevalidationaretournéeZInputValidator::STATE _INTERMEDIATE*@parameZHTTPTool$http*@paramstring$base*@parameZContentObjectAttribute$objectAttribute * @see eZDataType::fixupObjectAttributeHTTPInput()*/publicfunction fixupObjectAttributeHTTPInput($http,$base,$objectAttribute){//Lecaséchéant,altérericilesvariablesPOSTvia$http(eZHTTPTool)}

Page 31: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

• [Booléen] Doit-on chercher automatiquement un ASIN dans le catalogue Amazon si le champ de contenu n’est pas renseigné ?

• [Chaîne de caractères] De quels attributs de conte-nus les termes de recherche seront extraits ?

• [Chaîne de caractères] Dans quel index de recher-che Amazon doit-on effectuer la recherche ?

• [Entier] Dans quel noeud du catalogue de recher-che Amazon doit-on effectuer la recherche ?

Nous stockerons ces quatre informations dans les champs respectifs :

• data_int1,• data_int2,• data_text1,• data_text2.

Voici le corps de la méthode (cf Listing 12).Pour chacune des entrées, on doit reconstituer le

nom des variables POST, en suivant le même schéma qu’expliqué dans “Construction du nom des <input>”. On s’appuiera pour cela sur la variable $base passée en argument à la méthode, contenant la chaîne de ca-ractères ‘ContentClass’, sur l’ID de l’attribut de classe récupéré dynamiquement, ainsi que sur le nom même du champ, auquel nous accéderons par une constante, évitant le codage en dur. Voici un exemple :

$searchFieldName=$base.self::SEARCH_FIELD_VARIABLE . $classAttribute->attribute( 'id');équivalent de ce que nos avons observé dans le tem-plate précédemment :

<inputtype=»text»size=»50»name=»ContentClass_jvamazonid_asin_search_field_{$class_attribute.id}»...

L’étape suivante pour chaque variable POST est d’en tes-ter l’existence, et si elle est bien présente dans les don-nées reçues, la stocker. La variable $http reçue en pa-ramètre, instance (singleton) de la classe eZHTTPTool (détails ici : http://pubsvn2.ez.no/phpdoc/trunk/html/de-fault/eZHTTPTool.html), expose une interface simple et efficace d’accès aux données HTTP. On ne se prive pas de l’utiliser pour la récupération de chacune des 3 infor-mations (Listing 13).

Nous avons à ce stade terminé l’implémentation de notre datatype au niveau classe de contenu. Il est temps d’aborder le niveau objet de contenu.

Notre datatype au niveau de l’objet de contenuCôté PHPL’essentiel du code au niveau contenu se situe là-enco-re dans la classe PHP du datatype, dans l’implémenta-tion de différentes méthodes. Nous distinguerons deux groupes de méthodes :

• Méthodes de publication (validation, stockage du contenu),

• Méthodes de fonctionnement courant (tri, indexa-tion, fourniture du contenu brut...).

La plupart de ces méthodes ne sont pas obligatoires mais leur implémentation est vivement conseillée afin d’avoir un datatype fonctionnellement complet.

Méthodes de publicationCes méthodes permettent de traiter l’information entrée par le contributeur lorsqu’il publie un contenu, notam-ment de la valider et de la stocker.

Chacune de ces méthodes reçoit en paramètre une référence du Content Object Attribute (objet de la clas-se eZContentObjectAttribute, plus d’information ici : http://goo.gl/Wv0ux ) - dans lequel sera stocké le contenu

Tableau 4. Séquence d’appel des méthodes de publicationinitializeObjectAttribute() Initialise l’attribut, avec une valeur par défaut choisi par exemple dans la clas-

se de contenuvalidateObjectAttributeHTTPInput() ValidationfixupObjectAttributeHTTPInput() Méthode post-validationfetchObjectAttributeHTTPInput() Récupération effective des données postées et stockageonPublish() Méthode appelée une fois le processus de publication terminé

Tableau 5. Types de champs de stockage disponibles au niveau objet de contenu

Type de champ Descriptiondata _ text Tout type de données textuelles, y compris des données formatées (XML,

objets sérialisés...)data _ int Entiers.

Peut s’agit de clés primaires pointant vers des données secondaires stockées dans des tables spécifiques de la base de données (c’est par ex. le cas pour le datatype ezuser dont les données sont stockées dans la table ezuser)

data _ float Nombres à virgule flottante (prix...)

Page 32: eZ Publish PHP 02 2011

�/�0����

Dossier

Listing 17. Récupération des données et stockage de l'attribut d'objet

constALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE=' _jvamazonid _ asin _ search _ if _ empty _ ',CONTENT _ FIELD _ VARIABLE=' _ jvamazonid _data _ text _ ';

/** * Récupère toutes les données postées pour l'attributd'objetetlestraite*Lestockagedesdonnéess'effectueici*@parameZHTTPTool$http * @param string $base Préfixe du nom de la variable POST(Toujours"ContentObjectAttribute")*@parameZContentObjectAttribute$contentObjectAttribute*@returntruesitoutestOK*/publicfunctionfetchObjectAttributeHTTPInput($http,$base,$contentObjectAttribute){//Reconstitutiondunomdeschampspostés$fieldName=$base.self::CONTENT _ FIELD _VARIABLE.$contentObjectAttribute->attribute('id');$allowSearchFieldName=$base.self::ALLOW _SEARCH _ IF _ EMPTY _ VARIABLE.$contentObjectAttribute->attribute('id');//Enregistrel'ASINdansdata _ textif($http->hasPostVariable($fieldName)){$contentObjectAttribute->setAttribute('data _ text',$http->postVariable($fieldName));}//Enregistrelavaleurpourlarechercheautomatiqueif($http->hasPostVariable($allowSearchFieldName)){$contentObjectAttribute->setAttribute('data _ int',1);}else{$contentObjectAttribute->setAttribute('data _ int',0);}returntrue;}

Listing 18. Traitement post-publication

/***Effectuelesactionsnécessairessurlesdonnéesde l'attribut après la publication de l'objet*Silecontenudel'attributestvide,onrechercheunASINautomatiquementavecunerechercheAWS*Retournetruesilavaleurestcorrectementstockée*@parameZContentObjectAttribute$contentObjectAttribute*@parameZContentObject$contentObjectL'objetpublié*@paramarray$publishedNodes*@returnbool*@seeeZDataType::onPublish()*/publicfunctiononPublish($contentObjectAttribute,$contentObject,$publishedNodes){$content=$contentObjectAttribute->attribute('data _ text');$allowSearchIfEmpty=(bool)$contentObjectAttribute->attribute('data _ int');if(!$content&&$allowSearchIfEmpty)//Pasdecontenuenregistré,onessayederécupérerl'ASINautomatiquement

{$classAttribute=$contentObjectAttribute->contentClassAttribute();$searchPattern=$classAttribute->attribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD);$searchQuery=$this->getSearchQuery($searchPattern,$contentObject->attribute('id'));$aAmazonResult=$this->doSearch($searchQuery,$contentObjectAttribute);if(count($aAmazonResult)>0){$contentObjectAttribute->setAttribute('data _ text',$aAmazonResult[0]->id);$contentObjectAttribute->store();returntrue;}}}

/***Retournelarequêtederecherchepourl'attributd'objet à partir du motif de recherche défini dans la classedecontenu*@paramstring$searchPattern*@paramint$contentObjectID*@returnstring*/privatefunctiongetSearchQuery($searchPattern,$contentObjectID){$contentObject=eZContentObject::fetch($contentObjectID);$searchValue='';preg _ match _ all('#<([ >]+)>#U',$searchPattern,$matches,PREG _ SET _ ORDER);//Getallattributeidentifiers in searh pattern (intheformof<attribute _ identifier>)if(count($matches)>0){$searchValue=$searchPattern;$dataMap=$contentObject->dataMap();// Boucle sur les résultats et vérifie s'il s'agit d'identifiers pour cet objet de contenu//Sicen'estpaslecas,onlessupprimeforeach($matchesas$match){list($matchPattern,$identifier)=$match;if(isset($dataMap[$identifier])){$searchValue=str _ replace($matchPattern,$dataMap[$identifier]->title(),$searchValue);}else{$searchValue=str _ replace($matchPattern,'',$searchValue);}}$searchValue=trim($searchValue);}return$searchValue;}

/***Effectuelarecherched'ASINdanslecatalogueAmazon*@paramstring$searchQuery*@parameZContentObjectAttribute$contentObjectAttribute*@returnarrayArrayofobjectsimplementingIJVAmazonAdvertisingItem(seeamazonadvertising.iniforexactclass)*/privatefunctiondoSearch($searchQuery,eZContentObjectAttribute$contentObjectAttribute)

Page 33: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

Listing 18. Traitement post-publication – suite

{$classAttribute=$contentObjectAttribute->contentClassAttribute();//NowperformanItemSearch$aItemSearchParams=array('keywords'=>$searchQuery,'search _ index'=>$classAttribute->attribute(self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD));//IsthereaBrowseNodetolookinto?if($classAttribute->attribute(self::CLASSATTRIBUTE _ BROWSENODE _ FIELD)>0){$aRawParams=array('BrowseNode'=>$classAttribute->attribute(self::CLASSATTRIBUTE _ BROWSENODE _ FIELD));$aItemSearchParams['raw _ params']=$aRawParams;}$aAmazonResult=eZFunctionHandler::execute('amazonadvertising','item _ search',$aItemSearchParams);return$aAmazonResult;}

Listing 19. Implémentation des méthodes de fonctionnement courant

/***Retournetruesil'attributd'objetcontientducontenu*@parameZContentObjectAttribute$contentObjectAttribute*@returnbool*@seeeZDataType::hasObjectAttributeContent()*/publicfunctionhasObjectAttributeContent($contentObjectAttribute){returntrim($contentObjectAttribute->attribute('data _ text'))!='';}

/***Retournelecontenu.*Lerésultatestuntableauassociatif:* -search _ query:Larequêtederechercheconstruiteàpartirdumotifderecherchedel'attributdeclasse* -search _ pattern:Lemotifderecherche* -search _ index:L'indexderecherchetelqu'ilaétérenseignédansl'attributdeclasse(ex.MP3Downloads)* -product _ id:L'IDdeproduitAmazon(ASIN,ISBN,...)*@parameZContentObjectAttribute*@returnarray*/publicfunctionobjectAttributeContent($contentObjectAttribute){$classAttribute=$contentObjectAttribute->contentClassAttribute();$searchPattern=$classAttribute->attribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD);$aContent=array('search _ query' =>$this->getSearchQuery($searchPattern,$contentObjectAttribute->attribute('contentobject _ id')),'search _ pattern'=>$searchPattern,'search _ index' =>$classAttribute->attribute(self::CLASSATTRIBUTE _SEARCHINDEX _ FIELD),'product _ id' =>$contentObjectAttribute->attribute('data _ text'));

return$aContent;}

/***Méta-donnéespourlemoteurderecherche*@parameZContentObjectAttribute*@returnstring*/publicfunctionmetaData($contentObjectAttribute){return$contentObjectAttribute->attribute('data _text');}

/***Titre*@parameZContentObjectAttribute*@namestring*@returnstring*/publicfunctiontitle($contentObjectAttribute,$name=null){return$contentObjectAttribute->attribute('data _text');}

/***@returntrue*/publicfunctionisIndexable(){returntrue;}/***Initialisel'attributd'objetàpartird'unereprésentationtextuelle*@parameZContentObjectAttribute*@paramstring*@seeeZDataType::fromString()*/publicfunctionfromString($objectAttribute,$string){$objectAttribute->setAttribute('data _ text',$string);}/***Retournelareprésentationtextuelledel'attributd'objet*@parameZContentObjectAttribute*@seeeZDataType::toString()*@returnstring*/publicfunctiontoString($objectAttribute){return$objectAttribute->attribute('data _ text');}/***Retourneletypedetri*@seeeZDataType::sortKeyType()*/publicfunctionsortKeyType(){return'string';}/***Retournelaclédetri*@parameZContentObjectAttribute$contentObjectAttribute*@returnstring*@seeeZDataType::sortKey()*/publicfunctionsortKey($contentObjectAttribute){returnstrtolower($contentObjectAttribute->attribute('data _ text'));}

Page 34: eZ Publish PHP 02 2011

�/�0����

Dossier

- et de l’objet eZHTTPTool permettant de manipuler les données transférées via HTTP (POST, GET...). La seule exception à cela est la méthode onPublish(), un peu particulière, qui sera vue en détail.

Au moment de la publication d’un objet de contenu, eZ Publish itère sur ses différents attributs et appelle toutes les méthodes de publication de leurs datatypes respectifs. Cependant, ne connaissant pas à l’avance le nombre et le type de champ(s) pour tel ou tel datatype, eZ Publish ne filtre pas lui-même les données postées. C’est au datatype, et donc à vous, de savoir reconnaître quel(s) est/sont le(s) champ(s) concerné(s).

Par convention, le nom du/des champ(s) d’un attri-but posté est constitué suivant le motif suivant :

ContentObjectAttribute_<nom_du_champ>_<identi-fiant_attribut_numérique_unique>

A noter que la valeur ContentObjectAttribute dans le nom du champ est passée dans le paramètre $base des méthodes de publication. Note : Cette règle de nom-mage est identique à celle utilisée au niveau classe de contenu. Ainsi, il vous appartient de reconstruire le nom

du/des champ(s) posté(s), de le(s) valider et d’en récu-pérer la/les valeur(s). Pour ce faire, comme pour la clas-se de contenu, il est préférable d’utiliser des constantes de classe pour la partie fixe du nom de la variable POST (<nom_du_champ>), afin d’éviter de se retrouver avec des valeurs en dur dans le code (Listing 14).

Les principales méthodes de publication sont, dans leur ordre d’appel (voir Tableau 4).

Initialisation de l’attributLa méthode initializeObjectAttribute() permet d’insérer une valeur par défaut le cas échéant (premiè-re version d’un objet). Dans notre cas, elle permettra de pré-cocher ou non la case à cocher autorisant la recher-che automatique d’ASIN (Listing 15).

Validation des données postéesIl est toujours utile de valider les données postées par un utilisateur/contributeur, que ce soit par sécurité ou pour s’assurer que ce qui a été saisi correspond bien à ce qui est attendu côté serveur. De cette manière on évite souvent bien des erreurs et/ou des failles poten-tielles.

Tableau 6. Méthodes de fonctionnement courant d’un datatype eZ Publish

Méthode DescriptionboolhasObjectAttributeContent($contentObjectAttribute)

Retourne true si l’attribut d’objet contient réellement du contenu.Appelée notamment dans un template via $node.data _ map.myattribute.has _ content

mixedobjectAttributeContent($contentObjectAttribute)

Retourne le contenu brut. Il peut s’agir d’une simple chaîne de caractères comme d’un tableau ou même un objet PHP

boolisIndexable() Retourne true si le datatype supporte l’indexation par le moteur de re-cherche

string|arraymetadata($contentObjectAttribute)

Doit retourner le texte brut indexé par le moteur de recherche.Il peut s’agir d’une chaîne de caractères ou d’un tableau associatif (on par-le alors de sous-attributs).Dans le cas d’un tableau associatif, les clés sont les suivantes :• id => identifiant du sous-attribut,• text => valeur textuelle du sous-attribut.

stringtitle($contentObjectAttribute,$name=null)

Retourne la valeur textuelle telle qu’elle doit apparaître si l’attribut est uti-lisé dans le motif du nom de l’objet de contenu

voidfromString($contentObjectAttribute,$string)

Initialise l’attribut d’objet à partir d’une chaîne de caractère. Peut être très utile dans le cadre de scripts en ligne de commande

stringtoString($contentObjectAttribute) Retourne la représentation en chaîne de caractères de l’attribut d’objetstringsortKeyType() Retourne le type de tri :

• string,• int,• false si le tri n’est pas supporté.Le type de tri float n’est pas supporté

stringsortKey($contentObjectAttribute) Retourne la clé pour le tri. Il s’agit du texte qui sera utilisé pour trier au ni-veau de l’attribut d’objet

voidtrashStoredObjectAttribute($contentObjectAttribute,$version=null)

(facultatif) Effectue toutes les actions nécessaires sur l’attribut lorsque l’objet est déplacé vers la corbeille.Utile notamment si le contenu de l’attribut est stocké dans une table externe.

voiddeleteStoredObjectAttribute($contentObjectAttribute,$version=null)

(facultatif) Effectue toutes les actions nécessaires sur l’attribut lorsque l’objet est définitivement supprimé.Utile notamment si le contenu de l’attribut est stocké dans une table externe.

Page 35: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org �5

C’est la méthode validateObjectAttributeHTTPInput() qui joue ce rôle ici. Elle reçoit en paramètre une instance de eZHTTPTool permettant de retrouver les données saisies, ainsi que l’attribut d’objet. Les va-leurs de retour possibles sont les même que pour la va-lidation au niveau classe de contenu, à l’exception que lorsque eZInputValidator::STATE_INTERMEDIATE sera retourné, c’est la méthode fixupObjectAttributeHTTPInput() qui sera invoquée (voir Listing 15).

Récupération et stockageUne fois les données validées, il est nécessaire de les traiter et de les stocker dans la base de données. Cette étape est assurée par la méthode fetchObjectAttributeHTTPInput(). Un attribut d’objet peut stocker ses données primaires dans 3 types de champs de la table ezcontentobject_attribute (voir Table 5).

Dans notre cas, nous stockerons l’ASIN directement dans data_text et la valeur booléenne pour la recherche automatique dans data_int (Listing 17).

Traitement post-publicationDans certains cas, il peut s’avérer utile d’effectuer des actions une fois que l’objet ait été publié, notamment si la valeur d’un attribut est dépendante d’autres attributs. C’est notre cas lorsque le contributeur coche la case de recherche automatique. En effet, nous devons décoder le motif de recherche enregistré dans la classe de conte-nu et remplacer les identifiants par les valeurs publiées.

Dans ce genre de cas nous utilisons la méthode onPublish(). Elle permet en effet d’accéder non seu-lement à l’attribut sur lequel on travaille, mais égale-ment à l’objet de contenu entier, dans l’état publié, ain-si qu’aux différents noeuds assignés. Nous allons ainsi pouvoir effectuer notre recherche de produit et récupé-rer le premier de la liste retournée (Listing 18).

Méthodes de fonctionnement courantPour leur fonctionnement courant et en dehors de la pu-blication tous les datatypes doivent implémenter un cer-tain nombre de méthodes permettant de fournir au systè-me bon nombre d’informations utiles comme les données indexées par le moteur de recherche, la façon de trier les données ou encore la restitution du contenu brut.

Bien que ce ne soit pas strictement le cas, il convient de considérer les méthodes listées ci-dessous comme obliga-toires pour chaque datatype (voir Tableau 6 et Listing 19).

Côté templateComme pour la partie classe de contenu, 2 templates sont nécessaires pour la partie contenu :

• Le template de vue ou view template, pour la vue de l’objet par défaut (prévisualisation dans l’admin par exemple),

• Le template d’édition ou edit template.

Listing 20. extension/jvamazonadvertising/design/standard/templates/content/datatype/view/jvamazonid.tpl

{if$attribute.has _ content} {*AppelduwebserviceAmazonpourrécupérerlesinformationsduproduitàpartirdesonASINrenseignédansl'attribut*} {def$amazonItem=fetch('amazonadvertising','item _ lookup',hash('id',$attribute.data _ text))} <ahref={$amazonItem.url|ezurl}target=" _blank">{$attribute.data _ text}</a>{if$amazonItem.binding}({$amazonItem.binding}){/if}{*BindingcorrespondàlacatégorieAmazonduproduit*}{/if}

Listing 21. extension/jvamazonadvertising/settings/datatype.ini.append.php

[ViewSettings]GroupedInput[]=jvamazonid

Listing 22. Template d'édition de l'attribut d'objet

{* Champ ASIN - Identifiant Amazon *}<inputid="ezcoa-{$attribute.contentclassattribute _id} _ {$attribute.contentclass _ attribute _ identifier}"class="boxezcc-{$attribute.object.content _class.identifier} ezcca-{$attribute.object.content _class.identifier} _ {$attribute.contentclass _attribute _ identifier}"type="text"size="70"name="{$attribute _ base} _ jvamazonid _ data _text _ {$attribute.id}"value="{$attribute.data _ text|wash(xhtml)}"/><br/><br/>

{*Caseàcocherpourautoriserunerecherched'ASINautomatiquesilechampASINestvideàlapublication*}<inputid="ezcoa-{$attribute _ base}-{$attribute.contentclassattribute _ id} _ {$attribute.contentclass _ attribute _ identifier} _ allow _ search _empty"class="ezcc-{$attribute.object.content _ class.identifier}ezcca-{$attribute.object.content _ class.identifier} _ {$attribute.contentclass _ attribute _identifier}"type="checkbox"name="{$attribute _ base} _ jvamazonid _ asin _search _ if _ empty _ {$attribute.id}"{if$attribute.data _ int}checked="checked"{/if}/><labelstyle="display:inline;"for="ezcoa-{$attribute _ base}-{$attribute.contentclassattribute _ id} _ {$attribute.contentclass _ attribute _ identifier} _ allow _ search _empty">{"AutomaticallysearchAmazonIDonpublishifempty"|i18n("design/standard/class/datatype/jvamazonid")}</label>

Listing 23. extension/jvamazonadvertising/settings/datatype.ini.append.php

[EditSettings]GroupedInput[]=jvamazonid

Listing 24. Bouton de CustomHTTPAction

<inputclass="buttonjvamazonid _ search _ button"type="submit"id="jvamazonid _ search _ button-{$attribute.id}"name="CustomActionButton[{$attribute.id} _search _ asin]"value="{"SearchAmazonID"|i18n("design/standard/content/datatype/jvamazonid")}"/>

Page 36: eZ Publish PHP 02 2011

�/�0����

Dossier

Listing 25. Méthode du datatype pour la customHTTPAction

/** * Gère les actions n'étant pas directement reliées à la publication de l'objet de contenu,*commelasuppressiond'uneimageavecledatatypeeZImage,*oucommel'ajoutd'unenouvellelignedansunematrice*@seekernel/classes/eZDataType::customObjectAttributeHTTPAction()*@parameZHTTPTool$http*@paramstring$actionLenomdel'action*@parameZContentObjectAttribute$contentObjectAttribute*@paramarray$parametersTableauassociatif*-module(Référenceaumoduledecontenuactuel)*-current-redirection-uri(URIquiserautilisépourredirigerl'utilisateuraprès l'action)*-base _ name(généralementContentObjectAttribute)*/publicfunctioncustomObjectAttributeHTTPAction($http,$action,$contentObjectAttribute,$parameters){if($action=='search _ asin'){$contentObjectID=$contentObjectAttribute->object()->attribute('id');eZContentObject::clearCache(array($contentObjectID));// Suppression du cache mémoire afin de récupérertouteladatamap$classAttribute=$contentObjectAttribute->contentClassAttribute();$searchPattern=$classAttribute->attribute(self::CLASSATTRIBUTE _ DEFAULT _ FIELD);$searchQuery=$this->getSearchQuery($searchPattern,$contentObjectID);$aAmazonResult=$this->doSearch($searchQuery,$contentObjectAttribute);if(count($aAmazonResult)>0){//Onstockelesrésultatsdanslapropriété"HTTPvalue"quiesttemporaireetaccessiblevial'attribut"value"dansletemplate//$attribute.value$contentObjectAttribute->setHTTPValue(array('search _ results'=>$aAmazonResult));}else//Pasderésultat,onindique-1{$contentObjectAttribute->setHTTPValue(array('search _ results'=>-1));}}}

Listing 26. Affichage des résultats de recherche

{*OnajoutejQueryetlecodeJS*}{ezscript _ require(array('ezjsc::jquery','jvamazonid.js'))}

<aname="jvamazonid _ {$attribute.id}"></a>{* Champ ASIN - Identifiant Amazon *}<inputid="ezcoa-{$attribute.contentclassattribute _ id} _ {$attribute.contentclass _ attribute _ identifier}"class="box ezcc-{$attribute.object.content _ class.identifier} ezcca-{$attribute.object.content _ class.identifier} _ {$attribute.contentclass _ attribute _ identifier}"type="text"size="70"name="{$attribute _ base} _ jvamazonid _ data _ text _ {$attribute.id}"value="{$attribute.data _ text|wash(xhtml)}"/><br/><br/>

{*Boutonderecherche*}<inputclass="buttonjvamazonid _ search _ button"type="submit"id="jvamazonid _ search _ button-{$attribute.id}"name="CustomActionButton[{$attribute.id} _ search _ asin]"value="{"SearchAmazonID"|i18n("design/standard/content/datatype/jvamazonid")}"/>

{*Caseàcocherpourautoriserunerecherched'ASINautomatiquesilechampASINestvideàlapublication*}<inputid="ezcoa-{$attribute _ base}-{$attribute.contentclassattribute _ id} _ {$attribute.contentclass _ attribute _identifier} _ allow _ search _ empty"class="ezcc-{$attribute.object.content _ class.identifier} ezcca-{$attribute.object.content _ class.identifier} _{$attribute.contentclass _ attribute _ identifier}" type="checkbox"name="{$attribute _ base} _ jvamazonid _ asin _ search _ if _ empty _ {$attribute.id}"{if$attribute.data _ int}checked="checked"{/if}/><labelstyle="display:inline;"for="ezcoa-{$attribute _ base}-{$attribute.contentclassattribute _ id} _ {$attribute.contentclass _ attribute _ identifier} _ allow _ search _ empty">{"AutomaticallysearchAmazonIDonpublishifempty"|i18n("design/standard/content/datatype/jvamazonid")}</label>

{*Leboutonderechercheaétécliqué*}{ifis _ set($attribute.value.search _ results)}{if$attribute.value.search _ results|ne(-1)}{*Onredirigesurl'ancredel'attribut*}<scripttype="text/javascript">document.location=document.location+'#jvamazonid _ {$attribute.id}';</script>

Page 37: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

Template de vueCe template est le plus simple à mettre en place. Il s’agit en effet ici de se contenter d’afficher le contenu de l’at-tribut avec un minimum de formatage. Dans l’admin, ce contenu sera systématiquement positionné dans un conteneur de type bloc.

Dans le cas qui nous intéresse, nous souhaitons simplement afficher un lien sponsorisé renvoyant vers le produit Amazon correspondant avec en libellé son ASIN, ainsi que sa catégorie le cas échéant.

Ainsi, nous allons vérifier que l’attribut a bien un contenu d’enregistré. Si tel est le cas, nous interrogeons le webservice Amazon par l’intermédiaire de la fonction fetch item_lookup mise à disposition par l’extension jvAmazonAdvertising, ce afin de récupérer toutes les in-formations concernant le produit (Listing 20). Pour plus d’informations sur les fonctions fetch, RDV aux adres-ses http://goo.gl/oHMfk et http://goo.gl/OM1ZX.

Bien que le webservice soit interrogé en temps réel, la performance d’affichage ne s’en trouvera que légè-

rement altérée du fait que le cache de vue s’applique dans le cas présent; le webservice ne sera donc appelé qu’une seule fois.

La fonction item_lookup renvoie un objet de type JVAmazonAdvertisingResultItem. Cet objet contient notamment les attributs url et binding (catégorie Ama-zon) du produit que nous souhaitons afficher. A noter que l’attribut url est automatiquement sponsorisé si vous avez activé la fonctionnalité dans votre surchar-ge de amazonadvertising.ini ([AssociateSettings].AssociateEnabled & [AssociateSettings].AssociateTag).

Pour plus de lisibilité, il est possible de grouper l’af-fichage des éléments de l’attribut dans un <fieldset>. Pour ce faire, il suffit d’éditer une surcharge de data-type.ini afin de signifier que nous souhaitons une vue groupée (Listing 21).

Ainsi, le résultat du template sera contenu dans un tag <fieldset> ayant pour légende le nom de l’attribut tel qu’il a été défini dans la classe de contenu.

Tableau 7. Paramètres de la méthode customObjectAttributeHTTPAction()

Paramètre DescriptioneZHTTPTool$http Instance de eZHTTPTool permettant de manipuler les variables passées par HTTPstring$action Le nom de l’action (search_asin dans notre cas)eZContentObjectAttribute$contentObjectAttribute

Référence à notre attribut d’objet

array$parameters Tableau associatif avec pour clés :• module (référence au module courant),• current-redirection-uri (URL de redirection),• base_name (généralement “ContentObjectAttribute”).

Listing 26. Affichage des résultats de recherche – suite

<p><strong>{'Productsearchresults'}:</strong><br/><em>{'Click on the right product name to fill the field'}.</em></p><tableclass="list"cellspacing="0"><tr><th>{'Productname'|i18n('design/standard/content/datatype/jvamazonid')}</th><th>{'Productimage'|i18n('design/standard/content/datatype/jvamazonid')}</th><th>{'Maincategory'|i18n('design/standard/content/datatype/jvamazonid')}</th><th>{'Productgroup'|i18n('design/standard/content/datatype/jvamazonid')}</th><th>{'Productlink'|i18n('design/standard/content/datatype/jvamazonid')}</th></tr>{foreach$attribute.value.search _ resultsas$product}<tr><td><ahref="javascript:;"class="jvamazonid _ result"id="amazonproduct _ {$attribute.id} _ {$product.id}">{$product.title|wash}</a></td><td>{if$product.image}<imgsrc={$product.image|ezurl}/>{/if}</td><td>{$product.binding}</td><td>{$product.productgroup}</td><td><ahref={$product.url|ezurl}target=" _ blank">{$product.url|shorten(50)}</a></td></tr>{/foreach}</table>{else}<p><em>{'NoproducthasbeenfoundinAmazoncatalogforthesearchquery"%searchquery"'|i18n('design/standard/content/datatype/jvamazonid',,hash('%searchquery',$attribute.content.search _ query))}</em></p>{/if}{/if}

Page 38: eZ Publish PHP 02 2011

�/�0����

Dossier

Template d’éditionLe template d’édition est celui qui s’affiche lorsqu’un contributeur édite un contenu. Il n’est pas forcément beaucoup plus complexe que le template de vue mais nécessite une plus grande rigueur. En effet, il est nécessaire de s’assurer que chaque identifiant fourni dans ce template soit à la fois unique et iden-tifiable par le datatype au niveau du code PHP. Pour ce faire, il est d’usage d’utiliser un nom de champ classique suivi de l’identifiant unique de l’attribut. De cette manière, chaque champ ou groupe de champs reste unique et relié à l’attribut. Il est également courant de préfixer le nom du/des champ(s) par le contenu de la variable $attribute_base, contenant le plus souvent la valeur ContentObjectAttribute (Listing 22).

A noter que ces éléments sont par convention sépa-rés par des underscores pour donner le motif suivant :

ContentObjectAttribute_<nom_du_champ>_<identi-fiant_unique_attribut>

Dans notre cas, nous aurons donc un champ texte per-mettant de saisir l’ASIN, ainsi qu’une case à cocher in-diquant si nous souhaitons chercher un produit corres-pondant à notre contenu dans le catalogue Amazon au moment de la publication. Cette recherche sera effec-tuée sur la base du motif renseigné dans la classe de contenu.

De la même manière que pour le template de vue, il est possible de grouper les champs dans un <fieldset> en activant le Grouped Input (Listing 23).

Figure 6. Résultat de l’action HTTP personnalisée

Actions HTTP spécifiquesParfois, se contenter de stocker l’information n’est pas suffisant. Il peut en effet être utile d’effectuer des actions intermédiaires qui ne dépendent pas directe-ment de la publication. C’est par exemple le cas pour le datatype ezimage lorsque l’on supprime une image stockée.

Dans notre cas, nous souhaitons pouvoir effectuer une recherche de produit Amazon directement depuis l’interface d’édition, sans avoir à publier, ce qui nous permet de nous assurer que le produit retourné corres-pond bien au résultat attendu.

Pour ce faire, eZ Publish met à notre disposition les Custom HTTP Actions. Cela consiste en une méthode dédiée déclenchée par la soumission du formulaire d’édi-tion de l’objet via un bouton submit spécifique. Ce bou-ton a de spécial simplement le fait d’être nommé sur le motif suivant : CustomActionButton[<id_attribut>_<nom_action>]. Ainsi, pour notre recherche, nous al-lons définir une action nommée search_asin, ce qui donnera le code du Listing 24.

Dans notre classe PHP, nous allons rajouter la mé-thode customObjectAttributeHTTPAction(). Elle se-ra appelée si un contributeur clique sur le CustomAc-tionButton. Cette méthode reçoit 4 paramètres (voir Tableau 7).

Dans cette méthode, nous allons simplement faire la même opération que dans la méthode onPublish(), mais en gardant tous les résultats afin de les afficher au contributeur afin qu’il choisisse le produit qui lui convient le mieux. Les résultats seront stockés dans une variable spéciale de $contentObjectAttribute,

Page 39: eZ Publish PHP 02 2011

eZ Publish

www.phpsolmag.org ��

JéRôME VIEILLEDENTNICOLAS PASTORINOJérôme Vieilledent – Software Engineer at eZ Systems – Twitter : @jvieilledentJérôme est un développeur web autodidacte. Il a appris le PHP par lui-même, et a fait ses débuts avec eZ Publish en 2007. Il est déve-loppeur certifié eZ Publish et travaille actuellement dans l’équipe d’ingénierie logicielle d’eZ Systems, depuis Lyon.Nicolas Pastorino – Director Community at eZ Systems – Twitter : @jeanvoye – [email protected] et formateur sur eZ Publish de 2005 à 2008, d’abord en Norvège puis pour la région Europe de l’Ouest, il a ensuite rejoint l’équipe de gestion de produit, ainsi que l’équipe d’ingénierie en 2009, travaillant sur leur articulation. Il prend maintenant soin de la Communauté eZ Publish (38 000+ embres), l’intensifiant, la fai-sant croître, et la structurant.

HTTPValue. Cette variable est directement accessible depuis le template du datatype ($attribute.value) et nous pouvons y stocker n’importe quel type de don-nées. Le Listing 25 illustre l’action.

Dans le template, comme le montre le Listing 26, nous affichons les résultats de la recherche. Le contri-buteur aura juste à cliquer sur le nom du bon produit pour remplir le champ texte avec son ASIN via Javas-cript (Listing 27). Voici le résultat final (voir Figure 6).

ConclusionAprès une courte introduction aux concepts de gestion de contenu d’eZ Publish (que vous pourrez approfondir au besoin), nous avons parcouru tour à tour, dans le ca-dre d’un interfaçage avec le catalogue Amazon et ses web-services (AWS), la structure standard d’un data-type eZ Publish (PHP, enregistrement du datatype), ce qui doit être fait pour qu’il s’intègre correctement aux classes de contenu, puis enfin aux objets de contenu (aux plans graphique et logique métier).

L’exemple utilisé est suffisamment générique pour servir d’inspiration pour n’importe quel autre datatype : il implémente quasiment toutes les méthodes à disposi-tion, illustrant le spectre fonctionnel complet et la puis-sance d’un datatype eZ Publish. Nous espérons vous avoir montré comment vous pourrez l’exploiter dans vos cas métiers concrets.

Listing 27. Code JS

$(document).ready(function(){$('a.jvamazonid _ result').click(function(){varaID=$(this).attr('id').split(' _ ');varattributeID=aID[1];varasin=aID[2];varnameAttribute='ContentObjectAttribute _jvamazonid _ data _ text _ +attributeID;$('input[name="'+nameAttribute+'"]').val(asin);});

});

Nous vous invitons à venir retrouver la communau-té eZ Publish sur son portail : http://share.ez.no, point d’entrée principal. On y retrouve une vaste base de connaissances (didacticiels), des blogs d’experts, des forums hyperactifs, un canal IRC, du twitter, des flux RSS, ainsi que la possibilité de développement colla-boratif d’extensions, ainsi que du noyau d’eZ Publish même, conjointement avec l’équipe d’eZ (eZ Publish Community Project).

A bientôt !

Sur Internet

• http://share.ez.no/download – Télécharger eZ Publish,• http://share.ez.no – Portail de la Communauté eZ Publish,• http://twitter.com/ezcommunity – La communauté eZ Publish sur Twitter,• http://doc.ez.no – La documentation officielle,• [email protected] – Contact,• http://pubsvn2.ez.no/phpdoc/trunk/html/index.html – Documentation de l’API,• http://share.ez.no/learn/ez-publish/a-quick-and-friendly-introduction-to-ezpersistentobject – “Introduction rapide aux ob-

jets persistents eZ Publish“• http://share.ez.no/learn/ez-publish/creating-a-simple-custom-workflow-event – “Comment étendre le système de Work-

flows d’eZ Publish“• http://share.ez.no/learn/ez-publish/extending-ez-publish-s-rest-api-developer-preview-2 – “Comment étendre l’API REST

d’eZ Publish“• http://goo.gl/oHMfk et http://goo.gl/OM1ZX – Les fonctions fetch eZ Publish, puissant outil du language de template

Page 40: eZ Publish PHP 02 2011

�/�0���0

Pratique

Cet article vous présentera dans un premier temps les services de l’interface par l’intermé-diaire du WSDL, abordera la mise en oeuvre du

service en PHP et montrera comment déployer le ser-vice sur votre serveur Web.

Définir les services de l’interfaceNous allons essayer d’écrire notre propre Web Servi-ce SOAP, qui réalisera l’addition / soustraction de deux entiers. La première tâche est de créer un document WSDL décrivant notre service dans un format compré-hensible par les clients.

ContenuDans la mesure où il s’agit d’un standard basé sur XML, la structure du WSDL vous semblera familière si vous avez une connaissance un peu approfondie du XML. Le fichier WSDL se divise en plusieurs parties : un ensem-ble d’éléments décrivant les types de données utilisés par le service, les messages que le service peut rece-voir, ainsi que les liaisons SOAP associées à chaque message.

Le document WSDL commence par l’entête XML standard contenant un identificateur de version <?xmlversion=»1.0»?>, et que l’élément racine du docu-ment se nomme <definitions>. Ce dernier, donnant le nom du service, peut prendre plusieurs attributs fa-cultatifs, qui le décrivent et déclarent les espaces de noms utilisés (namespaces) à utiliser dans le reste du document. Ensuite, les types complexes nécessaires

pour la définition de l’interface du service sont définis au sein de l’élément types.

La section message définit deux messages. Le pre-mier est getResultRequest, qui est une requête pour relayer le message getResult et prend deux paramè-tres de type int nommés : integer1, integer2 et un paramètre de type string nommé : operation. L’autre message est getResultResponse, qui est la réponse au message getResult, contenant une valeur de type int, nommé result.

La section portType combine plusieurs éléments message pour composer une opération : getResult. Chaque opération se réfère à un message en entrée (input) et à des messages en sortie (output). L’élé-ment <binding> décrit les spécifications concrètes de la manière dont le Web Service sera implémenté et dé-finit la façon dont les messages doivent être transmis. Il spécifie également les espaces de noms et la valeur de l’entête soapAction pour la méthode getResult. Enfin, le service du document permet d’invoquer le service donné, ce qui sert à regrouper un ensemble de ports reliés (port). La plupart du temps, c’est une URL invo-quant un service SOAP.

Conseil de créationVoici quelques conseils pour créer votre document WSDL :

• Documentez toujours votre fichier WSDL, à la fois par le biais de la balise documentation, et en fournissant

écrire un Web Service en PHPDes systèmes peuvent interagir avec le service Web de la manière prescrite par sa description en utilisant des messages SOAP, typiquement transmis via le protocole HTTP et une sérialisation XML en conjonction avec d’autres normes liées au Web.

Cet article explique :• La création d'un WSDL.• L'utilisation des classes SoapClient et SoapServer.• Comment créer un Web Service via PHP.

Ce qu’il faut savoir :• Le lecteur devra avoir des notions sur la technologie SOAP

et PHP.

Page 41: eZ Publish PHP 02 2011

Web Service

www.phpsolmag.org ��

de la documentation dans un fichier externe au docu-ment WSDL.

• Utilisez la recommandation 2001 de XML Schema.• Utilisez l’élément type, dans tous les cas, ne faites

pas référence aux types dans l’élément message.

ExemplesJe vais vous décrire pas à pas la construction du WSDL :

• Le Listing 1 commence par définir l’entête XML, puis l’élément definitions.

• Le Listing 2 présente l’élément message.

Le Listing 3 présente l’élément PortType. Un port est simplement une suite d’opérations. Dans de nombreux langages de programmation, on appelle ceci une biblio-thèque, un module ou une classe, mais dans le mon-de des Web Services, les points de connexion sont des ports, et la définition abstraite d’un port est appe-lée Type de Port. Ici, nous créons la définition abstraite d’une procédure getResult, en WSDL, dont l’input est le message d’invocation de la méthode, et l’output le message de retour de la méthode.

Le Listing 4 va décrire l’élément binding du WSDL. Cette partie va nous permettre de rattacher le port à SOAP. Les types de ports sont indépendants du protocole. Ici, nous utiliserons des requêtes SOAP (soap:binding) de type RPC (style='rpc') via HTTP (transport='http://schemas.xmlsoap.org/soap/http') avec un encodage SOAP (encodingStyle='http://schemas.xmlsoap.org/soap/encoding/').

Le Listing 5 présente l’élément service. Le service, déclaré ci-dessous, est défini avec une seule occurren-ce du port getResultPort, ainsi que la définition de ce service. Le port est lié à la liaison getResultBinding. Enfin, on donne l’information sur l’URL du point de sor-tie de l’application (ici l’URL du serveur SOAP).

Remarque : Notez bien que si on ne trouve qu’un seul élément documentation dans cet exemple, il faudra nor-malement en déclarer un pour chaque élément… L’exem-ple montre bien le caractère verbeux de WSDL. Cepen-dant, il est dans la pratique assez rare d’écrire le corps du fichier à la main : la plupart des environnements de déve-loppement savent en effet générer le fichier WSDL sur la base des caractéristiques du service développé.

Séparation et capitalisationLa facilité de maintenance est toujours au cœur des préoc-cupations des éditeurs. Il se révèle de ce point de vue très bénéfique de distinguer dans un contrat WSDL l’aspect Abstrait (ce qui est indépendant de la technologie et des protocoles) et l’aspect Concret (ce qui est dépendant). Cette distinction permet de joindre ou réutiliser des sous-parties de modèles WSDL et de les redéployer à loisir : le WSDL est classiquement séparable en trois parties :

Listing 1. Partie 1 : Définitions

<?xmlversion="1.0"?>

<!-- partie 1 : Definitions -->

<definitionsname='getResult'targetNamespace='http://example.org/getResult'xmlns:tns='http://example.org/getResult'xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'xmlns:xsd='http://www.w3.org/2001/XMLSchema'xmlns:soapenc='http://schemas.xmlsoap.org/soap/encoding/'xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'xmlns='http://schemas.xmlsoap.org/wsdl/'>...</definitions>

Listing 2. Partie 2 : Message

...<messagename='getResultRequest'><partname='operation'type='xsd:string'/><partname='integer1'type='xsd:int'/><partname='integer2'type='xsd:int'/></message><messagename='getResultResponse'><partname='result'type='xsd:string'/></message>...

Listing 3. Partie 3 : PortType

...<portTypename='getResultPortType'><operationname='getResult'><inputmessage='tns:getResultRequest'/><outputmessage='tns:getResultResponse'/></operation></portType>...

Listing 4. Partie 4 : Binding

...<bindingname='getResultBinding'type='tns:getResultPortType'><soap:bindingstyle='rpc'transport='http://schemas.xmlsoap.org/soap/http'/><operationname='getResult'><soap:operationsoapAction='urn:xmethods-delayed-quotes#getResult'/><input><soap:bodyuse='encoded'namespace='urn:xmethods-delayed-calcul'encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/></input><output><soap:bodyuse='encoded'namespace='urn:xmethods-delayed-calcul'encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'/></output></operation></binding>...

Listing 5. Partie 5 : Service

...<servicename='getResultService'><documentation>Retournelerésultatdel'opération</documentation><portname='getResultPort'binding='getResultBinding'><soap:addresslocation='http://votre-adresse.com/samples/soap-server.php'/></port></service>...

Page 42: eZ Publish PHP 02 2011

�/�0����

Pratique

Dans ce but, WSDL permet d’importer des WSDL et des schémas. La partie concrète d’un contrat WSDL peut donc importer les définitions abstraites des services qui eux même peuvent importer des schémas. Cette démarche apporte une plus grande facilité de maintenance et de li-sibilité. La Figure 1 illustre la définition des services de l'in-terface. Maintenant il est temps de créer notre serveur.

Mettre en oeuvre et déployer le service, appel par un clientNous allons continuer notre apprentissage des Web Services dans cette deuxième partie. Elle est consa-crée à la mise en œuvre et au déploiement du Web Ser-vice, et l’appel par un client.

Tout d’abord, nous allons mettre en œuvre la métho-de getResult(), qui sera accessible en tant que fonc-tion de service par la demande de messages entrants à partir du Web. Ensuite, nous allons créer un objet SoapServer et communiquer avec la fonction de service à l’aide de la méthode SoapServer::addFunction(). Comme vous le verrez, le constructeur SoapServer() a un seul paramètre: le chemin du document WSDL qui décrit le service.

Mettre en oeuvre et déployer le serviceAprès avoir vu la définition du service, l’étape suivante consiste à le mettre en œuvre en utilisant un langage de programmation. Dans mon cas je vais utiliser le PHP. Pourquoi ce langage ? Tout simplement parce que je développe tous les jours avec, et que les implémenta-tions d’un serveur et d’un client sont très simples. Vous pouvez aussi bien utiliser JAVA, .NET, etc. Tout dépend de votre connaissance avec tel ou tel langage. L’écritu-re du code pour le service est très mécanique.

On va s’attaquer à notre serveur, préparez vos doigts ça va coder ! Nous allons d’abord créer un fichier nom-mé soap-server.php. Jusque là tout le monde suit ? Le SoapServer peut fonctionner sans un document WSDL de la même manière que le SoapClient le peut. Mais il n’y a pas d’avantages évidents à le mettre en place de cette façon. D’ailleurs nous verrons dans le prochain chapitre l’appel d’un client SOAP sans WSDL ! Voici le code sour-ce (Listing 6), les commentaires parlent d’eux-même.

Dans un premier temps on définit la méthode getResult($operation, $integer1, $integer2) et le traitement de celle-ci. Le retour sera le résultat de l’opération. Ensuite on instancie l’objet SoapServer en mode WSDL. Pour cela, on lui passe en premier argu-ment le WSDL operation.wsdl (On suppose qu’il est dans le même répertoire que notre fichier soap-server.php). Si vous voulez travailler en mode non WSDL il suffit de définir le paramètre à NULL et déclarer l’URI.

Remarque : Ici, pour une raison de rapidité, j’ai seu-lement renseigné ce paramètre mais d’autres sont pos-sibles. Je vous propose de lire la documentation : http://www.php.net/manual/fr/soapserver.soapserver.php.

• Les schémas d’une part, qui définissent les infor-mations contenues dans les messages reçus/émis par le service : ces schémas peuvent préexister avant la notion de service car ils représentent les entités métier.

• Une définition abstraite du service, contenant sa description sans les modalités d’implémentation (protocole et déploiement).

• Une partie concrète qui propose les liaisons d’une interface selon un certain format avec un protocole donné à une adresse donné.

Figure 1. Définir les services de l’interface

Figure 2. Listing des méthodes disponibles par le serveur SOAP

Figure 3. Résultat de l’appel à la méthode de l’addition entre 3 et 4

Page 43: eZ Publish PHP 02 2011

Web Service

www.phpsolmag.org ��

Ensuite on ajoute la méthode getResult au serveur qui va gérer les requêtes SOAP. Pour passer plusieurs fonc-tions au serveur il suffit d’utiliser un tableau de noms de fonctions : $server->addFunction(array(«getResult»,«getInt»));

Enfin on lance le serveur SOAP lorsque la requête est supposée être dans les données brutes POST de la requête HTTP. Dans le cas où le serveur ne reçoit pas une requête POST on affiche la liste des fonctions dis-ponibles par le Web Service. Vous enregistrez le fichier, et si tout se passe bien on se donne rendez-vous sur cette adresse : http://localhost/server-soap.php. La Fi-gure 2 montre le résultat que vous devez obtenir. Tout marche ?

Appel du Web Service par le clientVoici un client pour accéder à notre propre serveur SOAP. Il suffit de créer un nouveau fichier PHP et de coller le code donné dans le listing 7. Remarque : On suppose que le fichier a créé est dans le même réper-toire que notre fichier soap-server.php et operation.ws-dl. C’est un peu plus facile, n’est-ce pas ? La Figure 3 nous donne l’affichage du résultat.

ConclusionDans cet article, j’ai décrit seulement les fonctionnalités de base de l’extension SOAP pour PHP. En réalité, ce dernier est beaucoup plus complexe et permet de faire bien d’autres choses. Mais il n’est pas possible de vous montrer toutes ces caractéristiques dans un article aussi court. L’extension SOAP est totalement documen-tée ici : http://www.php.net/manual/en/book.soap.php

Pour commencer le déploiement simple d’un Web Service si vous respectez les quelques principes de ba-ses présentés ici vous n’aurez aucun problème. Pour mettre en place des Web Services plus complexes, je vous propose de bien lire la documentation.

YOHANN POIRONAutodidacte en matière de développement de sites en PHP, j’ai tou-jours poussé ma curiosité sur les sujets et les actualités du Web. C’est ainsi que j’ai récemment crée un blog me permettant de parler de ma veille personnelle et de mon quotidien professionnel autour des ré-flexions et commentaires sur les thèmes des nouvelles technologies, des services innovants, du développement, de l’interopérabilité dans le domaine médical, des réseaux sociaux et du buzz marketing. http://www.blog-nouvelles-technologies.frJe suis actuellement engagé en tant que responsable interopérabilité et développeur Web pour la société Openxtrem pour laquelle je réali-se des développements permettant de commercialiser le système en tant que pur EAI (Enterprise Application Integration) pour les structu-res de santé, en faisant l’extraction des fonctionnalités ‘métier’ (bloc, plan de soins, etc...). http://www.openxtrem.com

Sur Internet

• http://www.blog-nouvelles-technologies.fr/archives/845/ – Première partie de l’article sur l’écriture d’un Web Ser-vice en PHP (� parties disponibles) – Aspects complé-mentaires,

• http://www.w3.org/TR/wsdl – Description du langage WSDL,

• http://pear.php.net/ – Une autre implémentation SOAP pour PHP : PEAR::SOAP,

• http://dietrich.ganx4.com/nusoap – Une autre implémen-tation SOAP pour PHP : NuSOAP,

• http://www.poedit.net/ – Site web du logiciel POEdit po-ur l’édition de fichiers de traduction,

• http://www.cakephp-fr.org – Site de la communauté francophone de CakePHP (forums …),

[email protected] – Adresse de l’auteur de l’article pour toutes questions ou remar-ques.

Listing 6. Implémentation du SoapServer

/* * Fonction getResult sert ŕ addition ou soustraire 2 entiersetretournelerésultat*@param$operationTypedel'opération(add/substract)*@param$integer1Entier1*@param$integer2Entier2*@return$resultRésultatdel'opération*/functiongetResult($operation,$integer1,$integer2){$result=0;if($operation=="add"){$result=$integer1+$integer2;}if($operation=="substract"){$result=$integer1-$integer2;}return$result;}//DésactivationducacheWSDLini _ set("soap.wsdl _ cache _ enabled","0");//Catchl'erreursil'instanciationlaclasseSoapServer//échoue,onretournel'erreurtry{$server=newSoapServer('operation.wsdl');//Onajoutelaméthode"getResult"queleserveurvagérer$server->addFunction("getResult");}catch(Exception$e){echo'erreur'.$e;}// Si l'appel provient d'une requęte POST (WebService)if($ _ SERVER['REQUEST _ METHOD']=='POST'){//OnlanceleserveurSOAP$server->handle();}else{echo'<strong>ThisSOAPservercanhandlefollowingfunctions:</strong>';echo'<ul>';foreach($server->getFunctions()as$func){echo'<li>',$func,'</li>';}echo'</ul>';}HYPERLINK"http://exemple.com/"HYPERLINK"http://exemple.com/fre

Listing 7. Implémentation du SoapClient

$client=newSoapClient("operation.wsdl");echo$client->getResult("add",3,4);

Page 44: eZ Publish PHP 02 2011

�/�0����

Pour les débutants

De nombreuses applications web utilisent des bases de données afin de stocker des infor-mations nécessaires à l’application : blogs, fo-

rums, paniers de commande, galeries d’images, etc. Une application web utilisant une base de données re-pose sur une architecture client/serveur mettant en jeu un serveur web permettant l’exécution de programmes (Apache avec le module PHP), un serveur de base de données (MySQL, Oracle, PostgreSQL, …) et un client web (navigateur). Le client émet une requête HTTP. Le serveur web la reçoit et transmet une réponse HTTP au client. Lorsque la réponse nécessite l’interrogation d’une base de données, une interface de programma-tion (API) est utilisée. Le serveur web et le serveur de base de données peuvent être installés sur un même ordinateur (en local) ou sur deux ordinateurs distants (Figure 1).

Dans les articles précédents de cette série SQL, vous avez appris à créer une base de données, à stoc-ker des informations et à extraire des données de la base. Vous allez à présent apprendre à vous connec-ter à une base de données et à l’interroger depuis un script PHP avec PDO. Cette extension permet de se connecter à un SGBD, d’envoyer des requêtes et de traiter les résultats. C’est une interface dédiée à l’in-terrogation et à la manipulation de bases de données. Elle présente l’avantage de ne pas être spécifique à un SGBD, comme le sont les extensions fournies par les éditeurs de bases de données (extensions mys-qli et mysql pour MySQL, extension OCI8 pour Ora-cle, extension pgsql pour PostgreSQL, …). Ceci fa-

cilite le développement, en effet il suffit de connaître les fonctions d’une seule API pour communiquer avec les principaux SGBD utilisés dans le développement d’applications web. L’utilisation de PDO facilite éga-lement la migration de base de données dans le code d’une application. Par exemple, si l’application repose sur MySQL et qu’une migration vers PostgreSQL est réalisée, les fonctions utilisées dans le code PHP pour envoyer des requêtes et les traiter restent les mêmes. La migration nécessite uniquement la modification de la ligne de code indiquant le pilote utilisé, à condition bien sûr que les requêtes suivent strictement la norme SQL.

Cet article sera illustré par l’exemple d’une biblio-thèque dont le schéma est le même que celui don-né dans les précédents articles de la série SQL, pour laquelle deux utilisateurs ont été créés (lecteur et bibliothecaire) avec différents droits d’accès (voir la série d’articles sur SQL parus chaque mois depuis oc-tobre 2010). Pour pouvoir reproduire cet exemple, vous devez utiliser un serveur de base de données MySQL, de préférence la version 5 et un serveur web avec PHP version 5. Les distributions XAMPP (Windows, Linux, Mac OS), WAMP (Windows), EasyPHP (Windows) ou MAMP (Mac OS) vous fourniront l’environnement de travail nécessaire.

La première partie de cet article présente les termes et la notation objet nécessaires à la compréhension et à l’utilisation de l’extension PDO. Vous apprendrez ensuite à vous connecter à une base de données, à la manipuler et à l’interroger via PDO.

SQL/PHP :communication avec une base de données avec PDO (1/2)Les bases de données sont très utilisées dans les applications Web. La création, l’interrogation et la manipulation des données de la base sont réalisées en SQL. Dans cet article, vous apprendrez à vous connecter à une base de données grâce à PHP via l’extension PDO.

Cet article explique :• Comment se connecter et envoyer des requêtes à un SGBD

depuis PHP.

Ce qu’il faut savoir :• Notions de HTML.• Bases de PHP.• Bases de SQL : manipuler et interroger les données.

Page 45: eZ Publish PHP 02 2011

SQL et PDO

www.phpsolmag.org �5

Notation objetUne classe est un modèle pour créer plusieurs objets présentant des caractéristiques communes. Elle définit des propriétés (variables) et des méthodes (fonctions). Chaque objet peut être manipulé en utilisant ses mé-thodes.

Avant de présenter les classes utilisées dans cet ar-ticle, voici un exemple concret pour illustrer les notions de classe, méthodes et propriétés. Une classe Contact pourrait être utilisée pour stocker les contacts d’un car-net d’adresse. Les informations stockées pour cha-que contact sont les propriétés de cette classe (nom, prénom, date de naissance, adresse, numéro de télé-phone, …). Chaque personne du carnet est un objet Contact. Les informations contenues dans chaque objet sont spécifiques à une personne du carnet, mais cha-que objet suit le même modèle. Des opérations peuvent être réalisées sur un objet, telles que le calcul de l’âge à partir de la date de naissance. Le résultat de ce calcul est spécifique à la valeur de la propriété date de nais-sance de chaque contact, mais les instructions pour ef-fectuer le calcul sont identiques pour chaque objet. El-les sont donc placées dans une méthode de la classe Contact.

Dans cet article, trois classes prédéfinies seront uti-lisées :

• PDO : classe de connexion à la base de données,• PDOStatement : classe qui permet notamment de

récupérer les résultats des requêtes SELECT,• PDOException : classe qui gère les exceptions (er-

reurs).

Créer un objetPour créer un objet de la classe, il faut utiliser l’opéra-teur new suivi du nom de la classe. L’instruction suivan-te crée un objet de la classe PDO et le stocke dans la variable $cnx :

$cnx=newPDO($dsn,$login,$passwd);

Parfois, certaines méthodes retournent des objets, il n’est donc pas nécessaire de les créer pour les utiliser. C’est le cas de la méthode query de la classe PDO qui crée et retourne un objet de la classe PDOStatement (cf Interroger la base).

Invoquer une méthodePour utiliser une méthode sur un objet, il faut la préfixer par le nom de l’objet suivi d’une flèche. Par exemple, la classe PDO fournit une méthode getAvailableDri-vers qui retourne la liste de tous les pilotes disponibles (mysql, oci, pgsql, ...). La variable $cnx stockant un ob-jet de la classe PDO, il est possible d’invoquer la mé-thode sur cet objet en utilisant l’instruction suivante :$cnx->getAvailableDrivers();

Accéder à une constanteLorsque des constantes de classe sont utilisées, el-les doivent être préfixées du nom de la classe suivi de l’opérateur de résolution de portée (::). La classe PDO a une constante ATTR_SERVER_VERSION qui peut être passée en paramètre à la méthode getAttribute afin d’obtenir la version du serveur de base de données :

$cnx->getAttribute(PDO::ATTR _ SERVER _VERSION);

Se connecter à la base de donnéesActiver le pilotePlusieurs pilotes sont disponibles pour PDO : mysql pour MySQL, oci pour Oracle, pgsql pour PostgreSQL, etc. Pour se connecter à une base de données, il est nécessaire que le pilote PDO correspondant soit activé. Grâce à l’instruction phpinfo(), il est possible de vé-rifier que le pilote est présent sur la ligne PDO drivers du tableau PDO (Figure 2). Dans cet article, vous aurez besoin du pilote mysql.

Si le pilote mysql n’est pas activé, la ligne suivante devra être ajoutée ou décommentée dans le fichier de configuration php.ini. Pour Windows :

extension=php _ pdo _ mysql.dll

Sous Linux et Mac OS X l’extension nécessaire se nomme pdo_mysql.so. Après cette étape, il vous faut relancer le serveur Web pour que l’extension soit prise en compte.

Etablir la connexionPour établir une connexion vers une base de données, il faut créer un objet de la classe PDO. Lors de la créa-tion, il faut définir des paramètres :

− le DSN (Data Source Name), c’est une chaîne de caractères contenant les informations de connexion à la base (pilote, adresse IP ou nom du serveur,

Figure 1. Serveur Web et serveur de bases de données

Page 46: eZ Publish PHP 02 2011

�/�0����

Pour les débutants

port, …). Le site php.net indique pour chaque pilote le format attendu. Pour MySQL :

m y s q l : h o s t = a d r e s s e ; p o r t = n u m _port;dbname=nom _ base

− le login (optionnel),− le mot de passe (optionnel),− un tableau associatif d’options pour le pilote (option-

nel).

Dans cet article, le serveur MySQL est situé sur le mê-me ordinateur que le serveur Web (localhost), le port est celui par défaut : 3306 (Figure 1a) et la base de don-nées s'appelle : biblio. Le DSN est donc :

mysql:host=localhost;port=3306;dbname=biblio

Dans le cas où le serveur de base de données serait hébergé par un autre ordinateur (192.168.0.25) qui écoute sur le port 3312 (Figure 1b), le DSN serait :

mysql:host=192.168.0.25;port=3312;dbname=biblio

Deux utilisateurs ont des privilèges sur la base biblio : un lecteur qui a des droits en lecture sur la table livre et un bibliothécaire qui a tous les droits sur toute la ba-se. Afin de pouvoir totalement manipuler la base dans cet article, la connexion doit être établie en tant que bi-bliothecaire dont le mot de passe est motdepasse2.

Ces informations de connexion ont été placées dans les constantes DSN, LOGIN et PASSWORD dans le script base.php (Listing 1). La variable $options est un ta-bleau associatif contenant des options présentées dans la suite de l’article. Les scripts qui communiquent avec la base incluront tous ce script :

require'base.php';

Séparer les informations de connexion facilite la mainte-nance des applications (modification de login, de mot de passe, d’adresse du serveur ou changement de SGBD) et augmente la sécurité (les informations de connexion sont centralisées dans un unique fichier).

Le Listing 2, qui inclut le Listing 1, établit une connexion vers le SGBD. La connexion est réalisée par l’instruction suivante :

$cnx=newPDO(DSN,LOGIN,PASSWORD,$options);

En cas de succès, un objet PDO est créé et stocké dans la variable $cnx.

La dernière section de cette partie est consacrée à la gestion des erreurs. Y sont résumées les principa-les causes d’erreur de connexion ainsi que la manière dont le script doit les gérer.

La connexion est non persistante et reste active du-rant la durée de vie de l’objet PDO. La déconnexion sera présentée à la fin de cet article.

Connexion persistanteLes connexions persistantes ne sont pas fermées auto-matiquement à la fin de l’exécution d’un script. Elles sont mises en cache et réutilisées quand d’autres scripts émettent une requête de connexion utilisant les mêmes identifiants (login/password). Cette mise en cache évite l’établissement d’une nouvelle connexion à chaque fois qu’un script veut communiquer avec la base de don-nées, ce qui accélère la communication entre le serveur web et le serveur de bases de données.

Pour que la connexion soit persistante il suffit de l’indiquer au pilote en donnant la valeur true à l’attribut ATTR_PERSISTENT . Ceci est réalisé en ajoutant une

Listing 1. base.php

<?php/*definition des constantes de connexion*/define('DSN','mysql:host=localhost;port=3306;dbname=biblio');/*identifiants de l'utilisateur*/define('LOGIN','bibliothecaire');define('PASSWORD','motdepasse2');/*tableauassociatifd'optionspourlepilote,mode d'affichage des erreurs = exceptions,modederecuperationdesresultats=tableauassociatif*/$options=array(PDO::ATTR _ ERRMODE=>PDO::ERRMODE _ EXCEPTION,PDO::ATTR _ DEFAULT _ FETCH _ MODE=>PDO::FETCH _ ASSOC);?>

Listing 2. insertion.php

<?phprequire'base.php';try{/*connexionPDOversleSGBD*/$cnx=newPDO(DSN,LOGIN,PASSWORD,$options);/*requeted'insertion*/$req _ insert="INSERTINTOauteur(nom,prenom,date _ naissance)VALUES('Pagnol','Marcel','1895-02-28')";$cnx->exec($req _ insert);/*recupererl'iddel'auteurnouvellementinséré*/$id _ auteur=$cnx->lastInsertId();echo'ajoutauteur:',$id _ auteur,'<br/>';/* requete d'affichage des auteurs */$req _ affich='SELECTnom,prenomFROMauteurORDERBYnom';/*envoidelarequeteauSGBD*/$res=$cnx->query($req _ affich);/* parcourir le resultat et l'afficher dans un tableauHTML*/echo'<table><tr><th>Prenom</th><th>Nom</th></tr>';while($ligne=$res->fetch()){ echo'<tr><td>',$ligne['prenom'],'</td><td>',$ligne['nom'],'</td></tr>';}echo'</table>';/*fermercurseur*/$res->closeCursor();/*deconnexion*/$cnx=null;}catch(PDOException$e){/* en cas d'erreur afficher un message*/die('echec:'.$e->getMessage());}?>

Page 47: eZ Publish PHP 02 2011

SQL et PDO

www.phpsolmag.org ��

Tableau 1. Méthodes fournies par l’extension PDO

Classe Méthode DescriptionPDO exec Execute une requête SQL et retourne le nombre de lignes affectées

(INSERT, UPDATE, DELETE)

getAttribute Retourne la valeur d’un attribut d’une connexion à une base de don-nées

getAvailableDrivers Retourne la liste des pilotes PDO disponibles

lastInsertId Retourne l’identifiant de la dernière ligne insérée

query Execute une requête SQL, crée et retourne un objet de la classe PDOStatement (SELECT)

PDOException getCode Retourne le code d’erreur

getFile Retourne le fichier dans lequel l’exception a été levée

getLine Retourne la ligne dans laquelle l’exception a été levée

getMessage Retourne un texte explicatif en anglais

PDOStatement closeCursor Ferme le curseur

fetch Récupère la ligne suivante d’un résultat

fetchAll Retourne un tableau contenant toutes les lignes du résultat

setFetchMode Définit le type de résultat souhaité (voir tableau �)

Tableau 2. Constantes de PDO définissant des types de sorties

Constante DescriptionFETCH_ASSOC tableau associatif dont les clés sont les noms des colonnes, attention si la requête retourne des colon-

nes de même nom, c’est la valeur correspondant à la dernière colonne qui est retenue (les autres va-leurs sont écrasées)

FETCH_NAMED tableau associatif dont les clés sont les noms de colonnes, si plusieurs colonnes ont le même nom, un tableau est créé pour ce nom, il stocke toutes les valeurs des colonnes de même nom

FETCH_NUM tableau avec accès par indice

FETCH_OBJ objet dont les propriétés sont les noms des colonnes

valeur dans le tableau associatif d’options $options du Listing 1 :

PDO::ATTR_PERSISTENT=>true

Gérer les erreursLes erreurs peuvent intervenir à différents niveaux lors de la connexion, et génèrent ainsi des messages spé-cifiques :

− nom d'utilisateur ou mot de passe incorrect : Access denied for user ‘bibliothecair’@’localhost’

(using password: YES)− adresse du serveur de bases de données incorrec-

te : Unknown MySQL server host ‘loclhost’− port incorrect : Can’t connect to MySQL server on ‘localhost’− nom de base erroné : Access denied for user ‘bibliothecaire’@’%’ to data-

base ‘bibio’− login et mot de passe de l'utilisateur correct, mais

aucun privilège sur la base demandée : Access denied for user ‘pierre’@’%’ to database ‘bi-

blio’

Lorsque le serveur Web et le SGBD ne sont pas situés sur le même ordinateur, il faut s’assurer également que l’utilisateur est autorisé à se connecter à la base depuis le serveur Web.

Lorsqu’une connexion échoue, une exception PDO est levée. L’exception est un objet PDOException qui fournit des informations sur l’erreur survenue : code d’erreur, fichier, numéro de ligne et texte explicatif en anglais quant à l’exception survenue. Ces informations sont retournées respectivement par les méthodes get-Code, getFile, getLine et getMessage de la classe PDOException (Tableau 1). Le message renvoyé par getMessage comporte trois parties, un code d’erreur de cinq caractères défini dans le standard ANSI SQL (SQLSTATE), un code et un message d’erreur spécifi-ques au pilote (ici mysql) :

SQLSTATE[28000] [1045] Access denied for user ‘bi-bliothecair’@’localhost’ (using password: YES)

L’utilisation d’exceptions permet de centraliser le traite-ment des erreurs. Les exceptions doivent être intercep-tées et traitées. Pour ce faire, il faut utiliser deux blocs : try et catch. L’instruction qui est susceptible de pro-voquer l’exception est placée dans un bloc try. Si un

Page 48: eZ Publish PHP 02 2011

�/�0����

Pour les débutants

problème survient lors de la création de l’objet, PHP igno-re la suite des instructions dans le bloc try et exécute les instructions du bloc catch, où sont centralisés les trai-tements des erreurs. Il faut préciser entre parenthèses le type d’exceptions à intercepter (PDOException) ainsi que la variable qui va stocker les informations ($e) :

try{/*codedeconnexionàlabaseetmanipulationdesdonnées*/}catch(PDOException$e){/*traitementdeserreurs*/}

Si la connexion est réalisée sans erreur, alors toutes les instructions du bloc try sont exécutées et le contenu du bloc catch est ignoré.

Le bloc catch du Listing 2 intercepte et traite l’excep-tion. En cas d’erreur, un message est affiché. Il contient le

mot echec suivi du texte explicatif de l’exception fourni par la méthode getMessage. Lors de la phase de développe-ment des applications, il est utile d’obtenir des informa-tions sur les erreurs MySQL. Une fois les scripts testés, vous devez supprimer les informations liées aux erreurs de connexion ou d’envoi de requêtes, pour des raisons de sécurité. Dans le Listing 2, il suffit de supprimer l’ap-pel à la méthode getMessage pour que les informations destinées à la phase de test ne soient plus fournies. En phase de production, le bloc catch devient donc :

catch(PDOException$e){die("echec");}

Il faut noter également que, toujours pour des raisons de sécurité, vous devez impérativement intercepter les exceptions. En effet, si vous n’utilisez pas de blocs try/catch dans le Listing 2, la non interception de l’excep-tion provoque une erreur fatale pour PHP. Le message d’erreur expose le contenu de la pile d’exception, dans lequel figurent les informations de connexion (login, mot de passe, nom de la base, ...) :

Fatal error: Uncaught exception ‘PDOException’ ... PDO->__construct(‘mysql:host=localhost’, ‘bibliothe-caire’, ‘motdepasse2’, Array) #1 {main} thrown in /www/PDO/insertion.php on line 3.

Cette erreur est retournée au navigateur et/ou stockée dans le fichier de log des erreurs, en fonction des régla-ges du fichier php.ini.

Modifier la baseMaintenant que vous êtes connectés à la base de données, vous pouvez apporter des modifications à celle-ci. Dans l’ar-ticle SQL : langage de manipulation des données du mois de décembre 2010, vous avez appris à insérer des données (INSERT), les modifier (UPDATE) ou les supprimer (DELETE). Vous allez maintenant voir comment soumettre ces requê-tes au SGBD avec PDO. La méthode est la même pour tou-tes ces requêtes qui n’extraient pas d’information de la base. Cette partie sera illustrée par une commande d’insertion.

Soumettre la requêteLe Listing 2 insère un nouvel auteur dans la table auteur de la base biblio. La requête SQL stockée dans la va-riable $req_insert est la suivante :

Figure 3. Résultat du Listing 2

Figure 2. Extrait du phpinfo()

Page 49: eZ Publish PHP 02 2011

SQL et PDO

www.phpsolmag.org ��

INSERTINTOauteur(nom,prenom,date _naissance)VALUES('Pagnol','Marcel','1895-02-28')

L’identifiant de l’auteur ne nécessite pas d’être rensei-gné car il est incrémenté automatiquement par MySQL lors d’une insertion.

La méthode exec de la classe PDO envoie au ser-veur la requête SQL passée en paramètre :

$cnx->exec($req _ insert);

Elle retourne un entier correspondant au nombre de li-gnes affectées par la requête. Dans le cas d’une de-mande de mise à jour ou de suppression, il peut y avoir zéro, une ou plusieurs lignes concernées en fonction de la restriction.

Gérer les erreursIl existe différents types d’erreurs pouvant empêcher une insertion, une modification ou une suppression de données :

− nom de table incorrect : Table ‘biblio.autur’ doesn’t exist− nom de colonne incorrect : Unknown column ‘prnom’ in ‘field list’ − erreur de syntaxe (oubli d'opérateur, de parenthè-

ses, …) : You have an error in your SQL syntax; [...]− duplication de la clé primaire (insertion et modifica-

tion) : Duplicate entry ‘7’ for key ‘PRIMARY’− enregistrement d'une clé étrangère non référencée

(insertion et modification) : Cannot add or update a child row: a foreign key

constraint fails ('biblio'.'livre', CONSTRAINT 'fk_zo-ne' FOREIGN KEY ('code_zone') REFERENCES 'zone' ('code_zone') ON DELETE SET NULL ON UPDATE CASCADE)

− non renseignement d'une donnée de type NOTNULL (insertion et modification) :

Field ‘titre’ doesn’t have a default value− erreur de type de données (insertion et modifica-

tion) : Incorrect integer value: ‘tre’ for column ‘nb_pages’

at row 1− nombre de valeurs différentes du nombre de colon-

nes indiquées (insertion) : Column count doesn’t match value count at row 1− utilisateur avec privilèges insuffisants (droit de lec-

ture uniquement) : INSERT command denied to user ‘lec-

teur’@’localhost’ for table ‘livre’

En cas d’erreur le comportement de PDO est fixé par la valeur de l’attribut ATTR_ERRMODE. Par défaut, la métho-

de exec retourne la valeur false. Dans ce cas, le déve-loppeur doit faire un test pour vérifier si la modification de la base a réussi. Attention, une requête qui ne pro-duit pas d’erreur mais qui n’a affecté aucune ligne dans la base de données retournera la valeur 0 (instructions UPDATE ou DELETE). Cette valeur est interprétée comme false dans un test, pensez donc à utiliser l’opérateur de comparaison tenant compte du type === afin de vous assurer dans le test que la valeur retournée est le boo-léen false et non pas l’entier 0. Lorsque l’attribut AT-TR_ERRMODE prend la valeur ERRMODE_EXCEPTION, une exception est générée lorsqu’il y a une erreur. C’est la solution choisie dans cet article (Listing 1).

Il est préférable de tester vos requêtes en mode console ou grâce à phpMyAdmin avant de les insérer dans votre script afin de bien vérifier qu’elles ne com-portent aucune erreur de syntaxe ou autre.

Interroger la baseLa création d’une page Web présentant des données extraites d’une base de données suit toujours les mê-mes étapes : le script PHP établit une connexion vers le serveur de base de données. La requête SQL est envoyée au serveur de base de données. Celui-ci l’exé-cute et retourne un résultat au script PHP. Le script ex-trait les données de chaque ligne de résultat, les met en forme et les envoie au navigateur.

Dans cette partie, vous allez apprendre à soumettre une requête SQL de sélection au SGBD, extraire et pré-senter les données du résultat.

Soumettre la requêteLe Listing 2 affiche tous les auteurs de la table auteur de la base biblio dans l’ordre alphabétique (Figure 3). Il soumet la requête suivante stockée dans la variable $req_affich :

SELECTnom,prenomFROMauteurORDERBYnom;

La méthode query de la classe PDO envoie la requête au SGBD :

$res = $cnx->query($req _ affich);

Elle retourne un objet de la classe PDOStatement stoc-ké dans la variable $res dans cet exemple. Cet objet permet de manipuler les lignes du résultat retourné par le SELECT.

Extraire le résultatLe résultat de la requête SELECT peut comporter zéro, une ou plusieurs lignes. Le parcours du résultat est réa-lisé ligne par ligne, en utilisant une boucle while. La mé-thode fetch de la classe PDOStatement extrait la ligne suivante du résultat (ici stocké dans $res) et la retourne. Lorsque toutes les lignes du résultat ont été extraites, la

Page 50: eZ Publish PHP 02 2011

�/�0��50

Pour les débutants

méthode fetch retourne false et PHP sort de la boucle while (Listing 2).

Le type de la donnée retournée par la fonction fetch dépend des options PDO : tableau avec accès par in-dice, tableau associatif ou objet. Le Tableau 2 indique les différents types disponibles. Dans les exemples de cet article, les données sont récupérées dans un ta-bleau associatif. Pour ce faire, la valeur de l’attribut AT-TR_DEFAULT_FETCH_MODE est fixée à PDO::FETCH_AS-SOC (Listing 1). Préciser ce type lors de la création de la connexion dans $options permet de l’appliquer à tou-tes les requêtes. Le nom et le prénom de chaque auteur sont donc obtenus respectivement avec $ligne['nom'] et $ligne['prenom'].

Toutes les lignes du tableau peuvent être récupérées en une seule fois en utilisant la méthode fetchAll :

$lignes=$res->fetchAll();

Elle retourne un tableau dont la case 0 comporte un tableau associatif stockant le résultat de la première li-gne, la case 1 le résultat de la deuxième ligne, etc. Cet-te méthode n’est pas conseillée si vous devez extrai-re un nombre très important de données car elle peut consommer beaucoup d’espace mémoire.

Gérer les erreursLes échecs dans les requêtes d’extraction sont produits principalement par :

− un nom de table incorrect : Base table or view not found− un nom de colonne incorrect : Column not found− l'oubli de quotes autour de chaînes de caractères

dans le WHERE : Unknown column ... in ‘where clause’− une erreur de syntaxe : Syntax error or access violation− un utilisateur avec des droits de lecture restreints

à certaines tables : SELECT command denied to user ‘lecteur’@’localhost’

for table ‘auteur’

Le comportement du script lors de l’échec d’une re-quête dépend de la valeur de l’attribut ATTR_ERRMO-DE. Par défaut, aucune exception n’est générée et la méthode query retourne la valeur false. Un test doit alors être effectué pour gérer l’erreur car sinon l’invo-cation de la méthode fetch provoquera une erreur fa-tale car elle n’est pas appliquée à un objet PDOState-

ment mais au booléen false. Dans les exemples de cet article, la valeur de l’attribut a été fixée à ERRMODE_EXCEPTION dans le tableau d’options $options passé au constructeur (Listing 1). Le contenu du try est exé-cuté entièrement en cas de succès ou partiellement en cas d’exception.

Se déconnecterLa valeur null affectée à $cnx, ferme la connexion. Si cette étape n’est pas réalisée, PHP ferme automati-quement la connexion à la fin du script (si la connexion n’est pas persistante).

Il faut noter que lorsque le script ne récupère pas toutes les lignes d’un résultat, certains pilotes de SGBD échouent lors d’une requête ultérieure dans le même script. L’instruction $res->closeCursor() permet de fermer le curseur et d’éviter de tels problèmes.

ConclusionDans cet article, vous avez appris à établir une connexion à une base de données avec PHP via l’ex-tension PDO. Vous savez maintenant manipuler et ex-traire des données depuis un script PHP. Quelle que soit la complexité de la requête, la méthode reste la même. Les connaissances en SQL que vous avez ac-quises dans cette série d’articles vous permettront d’af-finer les requêtes que vous pourrez insérer dans vos applications Web. Dans le prochain numéro, vous ver-rez comment construire des requêtes dynamiquement, insérer des données dans la base à partir d’un formu-laire et sécuriser vos applications grâce à des requêtes préparées.

CILIA MAURO, MAGALI CONTENSINCilia Mauro est gestionnaire de bases de données et développeur d’applications web au CNRS. Elle enseigne les bases de données et PHP à l’université.Contact : [email protected] Contensin est chef de projet en développement d’applica-tions au CNRS. Elle enseigne depuis plus de dix ans le développe-ment d’applications web à l’université et est l’auteur de nombreux articles sur le développement web en PHP.Contact : http://magali.contensin.online.fr

Sur Internet

• http://php.net/manual/fr/book.pdo.php

Page 51: eZ Publish PHP 02 2011

www.phpsolmag.org 5�

Sécurité

Aujourd’hui la plupart des données d’une entre-prise sont informatisées. Dans la mesure où les menaces à l’encontre de la sécurité de l’infor-

mation se veulent chaque jour davantage féroces et po-lymorphes, il est fondamental de sécuriser au maximum ces données.

Les grands acteurs technologiques comme Goo-gle, Microsoft ou Yahoo sont régulièrement victimes d’attaques informatiques. Ces dernières ont, depuis leur existence au début des années 2000 des visées intellectuelles, mais pas seulement. Le hacker des années 2000, fier d’avoir pénétré un système d’in-formation sans l’avoir dégradé pour autant, a bien évolué.

Actuellement, toute attaque informatique, ou pres-que, comporte des objectifs lucratifs ou destructeurs. L’espionnage industriel par exemple peut motiver un hacker. Une tierce personne lui achète les informations de recherche dans le but de développer la concurrence. Une tâche grassement/hautement rémunérée qui attire plus d’un pirate informatique.

Dans ce contexte ô combien hostile, nous définirons d’abord la sécurité de l’information, avant d’aborder les moyens techniques et fonctionnels utilisés à l’heure ac-tuelle pour protéger le système d’information d’une en-treprise.

DéfinitionLa sécurité de l’information est un processus qui vise à protéger les données d’une société. La sécurité de

l’information regroupe tous les moyens techniques et fonctionnels à mettre en œuvre pour protéger les données d’une société.

• Disponibilité : l’information doit être continuelle-ment disponible.

• Intégrité : l’information ne doit pas être altérée.• Confidentialité: l’information doit être uniquement

accessible aux personnes autorisées.• Preuve/Traçabilité : Tous les mouvements de don-

nées doivent être tracés.

La mise en place d’un processusPour sécuriser l’information, il convient de mettre en place un processus de protection des données. Ce pro-cessus doit suivre les étapes suivantes :

• Inspection : Evaluer les besoins en sécurité de l’entreprise…

• Protection : …avant d’appliquer des moyens de protection de l’information.

• Détection : Mettre en œuvre des moyens pour pré-venir l’attaque…

• Réaction : …afin d’être réactif en cas d’atta-que et pouvoir élaborer un plan de reprise d’ac-tivité…

• Réflexion: …et procéder à une analyse post-in-cident.

Les moyens techniques de protection

La sécurité de l'informationCet article présente d’une manière générale la sécurité de l’information dans un contexte professionnel.

Cet article explique :• Ce qu’est la sécurité de l’information.• Les moyens techniques et fonctionnels mis en œuvre pour sé-

curiser l’information.

Ce qu'il faut savoir :• Aucun pré-requis n’est nécessaire.

Page 52: eZ Publish PHP 02 2011

�/�0��5�

Sécurité de l’information

Afin de protéger l’information dans une entreprise, il convient de mettre en œuvre des mesures techniques de protection. Pour ce faire, l’ensemble de l’infrastruc-ture informatique doit disposer d’éléments de sécurité. Les pré-requis minimaux d’un point de vue architecture technique sont les suivants :

• Pare-feu réseau : équipement qui contrôle tous les flux réseaux entrants et sortants.

• Cloisonnement du réseau : Il est important de segmenter à l’aide de DMZ (zone démilitarisée). La segmentation du réseau permet le filtrage des flux et donc leur sécurisation.

• IPS (Intrusion Prevention System) : équipement qui détecte les attaques réseaux par rapport à une base de signature.

• WAF (Web Application Firewall) : équipement in-dispensable lorsque la société héberge des appli-cations web critiques. Cet équipement détecte les attaques applicatives. Vous trouverez sur la figure 1 un exemple d’architecture type de protection appli-cative.

• Proxyavec filtrage de contenu: équipement qui analyse les flux web sortants afin de déceler d’éventuelles attaques.

• Relais de messagerie : équipement qui analyse les flux mails afin de détecter le SPAM et les virus. Vous trouverez sur la figure 2 un exemple d’archi-tecture type de protection des mails.

• Passerelle VPN SSL : équipement qui permet de protéger les accès distants au système d’informa-tion. Vous trouverez sur la figure 3 un exemple d’architecture type de protection des accès dis-tants.

• Anti-virus/Anti-malware : les postes de travail doi-vent disposer d’un antivirus à jour.

• Les patchs : tous les éléments de l’infrastructure doivent être patchés afin de palier aux vulnérabili-tés.

• Le chiffrement : les données critiques doivent être chiffrées afin d’empêcher à un attaquant de lire les informations sensibles même s’il les récupère.

• La sauvegarde : la société doit disposer d’une bonne politique de sauvegarde de ses équipements afin de pouvoir restaurer les données en cas d’atta-que.

• La redondance : les équipements de l’infrastructu-re doivent être redondés afin d’assurer la haute dis-ponibilité de l’information.

• Les accès WiFi : Les accès Wi-Fi doivent dispo-ser d’une clé de chiffrement ainsi que d’un algorith-me robuste. Pour ce faire, le mécanisme WPA2 est le plus robuste à moins d’utiliser une authentifica-tion par certificat avec une architecture 802.1x ce qui représente la sécurité la plus robuste en terme d’accès WiFi.

• Le coffre fort numérique : Le coffre fort numéri-que permet aux utilisateurs d’archiver leurs docu-ments de façon sécurisée.

• La désactivation des périphériques non indis-pensables : Afin d’éviter l’intrusion de virus au sein de la société, la désactivation de périphériques non

Figure 1. Exemple d’architecture de protection applicative

Figure 2. Exemple d’architecture de protection mail

Page 53: eZ Publish PHP 02 2011

www.phpsolmag.org 5�

Sécurité

RéflexionChaque incident de sécurité doit être analysé après l’in-cident. Il convient de s’interroger sur la cause de l’inci-dent. Il faut ensuite mettre en œuvre des mesures pour que ce type d’incident ne se reproduise plus. Dans cer-tains cas, des poursuites judiciaires peuvent être en-gagées.

NormesUne norme est un document de référence décrivant des mesures à mettre en œuvre. Les normes ISO 27001 et ISO 27002, par exemple, sont de plus en plus utili-sées pour sécuriser l’information.

La norme ISO 27001 décrit la mise en œuvre d’un système de management de la sécurité de l’informa-tion. Et la norme ISO 27002 prévoit les bonnes prati-ques de sécurité à mettre en œuvre au sein du système d’information.

Il existe aussi une norme de gestion des risques : l’ISO 27005.

MéthodesEn sécurité de l’information, il existe plusieurs métho-des pour réaliser des analyses de risques. Parmi les plus connues, on peut citer les méthodes suivantes :

• MEHARI,• EBIOS.

ConclusionLa sécurité de l’information est aujourd’hui un maillon indispensable à la bonne gestion d’une entreprise. Il convient donc de ne pas la prendre à la légère et d’en respecter, a minima, les bonnes pratiques. Malheureu-sement, force est de constater que la sécurité de l’infor-mation est rarement l’une des préoccupations majeures des entreprises.

TONY FACHAUxL’auteur travaille en tant que consultant sécurité pour la société LEXSI (Laboratoire d’EXpertise en Sécurité Informatique). Il se pas-sionne pour toutes les problématiques liées à la sécurité dans un contexte professionnel.

Figure 3. Exemple d’architecture de protection des accès ditants.

indispensables comme les ports USB, les lecteurs de cartes ou encore les lecteurs de CD/DVD est in-dispensable.

Les moyens fonctionnels de protectionDes mesures fonctionnelles doivent aussi exister afin de compléter les mesures techniques précédemment décrites. Ces moyens fonctionnels doivent être a mi-nima les suivants :

• Politique de sécurité : une politique de sécurité du système d’information doit être écrite et respec-tée au sein de l’organisation.

• Sécurité physique : des mesures de sécurité phy-sique doivent être mises en œuvre (accès aux lo-caux par badge, accès au datacenter, etc.)

• La sensibilisation des utilisateurs : les utilisa-teurs étant bien souvent les maillons faibles de la sécurité, il convient de réaliser des campagnes de sensibilisation régulières.

• La désignation d’un responsable de la sécurité du système d’information : un RSSI doit être dé-signé afin de gérer toutes les activités sensibles de l’entreprise.

Détection et réactionDes équipes opérationnelles doivent être présentes afin de maintenir à jour l’architecture technique ainsi que les moyens fonctionnels décrits précédemment. Ces équipes doivent aussi mettre en place des moyens de détection. Cela peut par exemple être des remontées d’alertes depuis les systèmes. Par exemple, s’il y a cin-quante tentatives de connexion sur un système avec un mot de passe erroné, il y a alors de grandes probabilités que le système soit victime d’une attaque par brute for-ce. L’équipe opérationnelle doit alors agir. Cette même équipe doit aussi analyser régulièrement les journaux des équipements.

Toute détection d’incident doit être résolue et remon-tée au responsable afin de prendre des mesures cor-rectrices.

Page 54: eZ Publish PHP 02 2011

�/�0��5�

Sécurité de l’information

5/201050

Pour les débutants

49

Club .PRO

Pour plus de renseignement : [email protected] Rejoignez le Club .PRO

Ston eld InworldSton eld Inworld propose aux entreprises des solutions globale d’intègration d’Internet et des Univers Virtuels dans leur stratégie de développement. Au-delà de ses services, la société consacre 30% de ses ressources à des travaux de R&D sur le e-Commerce et le e-Learning dans les Mondes Virtuels.

COGNIX SystemsConseil, conception et développement d’applications évoluées pour les systèmes d’informations Internet/intranet/extranet. Alliant les compétences d’une SSII et d’une Web Agency, Cognix Systems conçoit des applicatifs et portails web à l’ergonomie travaillée et des sites Internet à forte valeur ajoutée.http://www.cognix-systems.com

Intelligence PowerConseil, Expertises, Formations et Projets E-business centrés au tour du cśur de métier : la Business Intelligence. Intelligence Power vous propose des solutions innovantes pour aligner la technologie sur la stratégie de votre entreprise.http://www.intelligencepower.com

Web AllianceVous souhaitez être en première page des moteurs de recherche ? Rejoignez-nous, 100% des clients Web Alliance sont en 1ère page de Google. Web Alliance, société de conseil spécialisée dans le référencement internet, vous propose son expertise (référencement, liens sponsorisés, web-marketing).www.web-alliance.fr

Anaska FormationAnaska est le spécialiste des formations sur les technologies OpenSource. En partenariat avec MySQL AB, Mandriva, Zend et d'autres acteurs de la communauté, Anaska vous propose un catalogue de plus de 50 formations dédiés aux technologies du Libre.http://www.anaska.com

WEB82Création et hébergements de sites web pour particuliers, associations, entreprises, e-commerce. Développement entierement aux normes W3C (www.w3.org) de sites web de qualité, au graphisme soigné et employant les dernieres technologies du web (PHP5, MySQL5, Ajax, XHTML, CSS2).http://www.web82.net

Core-TechsExpert des solutions de gestion et de communication d’entreprise en Open Source, Core-Techs conçoit, integre, déploie et maintient des systemes de Gestion de Contenu Web, de Gestion Documentaire, de Gestion de la Relation Client (CRM), d’ecommerce et de travail ollaboratif.http://www.core-techs.fr

POP FACTORYPoP Factory,SSII spécialisée Web. Développement de solutions applicatives spéci ques ; offre de solutions packagées : catalogue numérique, e-commerce, livre/magazine numérique, envoi SMS. Nous accompagnons nos clients tout au long de leur projet : audit, conseil, développement, suivi et gestion.http://www.popfactory.com / [email protected]

Blue Note SystemsSpécialistes en CRM Open Source, nous proposons une offre complète de prestations sur la solution SugarCRM. Notre valeur ajoutée réside dans une expertise réactive et une expérience des problématiques de la GRC. Nous vous aidons à tirer le meilleur parti de votre solution CRM.http://www.bluenote-systems.com

5/201050

Pour les débutants

49

Club .PRO

Pour plus de renseignement : [email protected] Rejoignez le Club .PRO

Ston eld InworldSton eld Inworld propose aux entreprises des solutions globale d’intègration d’Internet et des Univers Virtuels dans leur stratégie de développement. Au-delà de ses services, la société consacre 30% de ses ressources à des travaux de R&D sur le e-Commerce et le e-Learning dans les Mondes Virtuels.

COGNIX SystemsConseil, conception et développement d’applications évoluées pour les systèmes d’informations Internet/intranet/extranet. Alliant les compétences d’une SSII et d’une Web Agency, Cognix Systems conçoit des applicatifs et portails web à l’ergonomie travaillée et des sites Internet à forte valeur ajoutée.http://www.cognix-systems.com

Intelligence PowerConseil, Expertises, Formations et Projets E-business centrés au tour du cśur de métier : la Business Intelligence. Intelligence Power vous propose des solutions innovantes pour aligner la technologie sur la stratégie de votre entreprise.http://www.intelligencepower.com

Web AllianceVous souhaitez être en première page des moteurs de recherche ? Rejoignez-nous, 100% des clients Web Alliance sont en 1ère page de Google. Web Alliance, société de conseil spécialisée dans le référencement internet, vous propose son expertise (référencement, liens sponsorisés, web-marketing).www.web-alliance.fr

Anaska FormationAnaska est le spécialiste des formations sur les technologies OpenSource. En partenariat avec MySQL AB, Mandriva, Zend et d'autres acteurs de la communauté, Anaska vous propose un catalogue de plus de 50 formations dédiés aux technologies du Libre.http://www.anaska.com

WEB82Création et hébergements de sites web pour particuliers, associations, entreprises, e-commerce. Développement entierement aux normes W3C (www.w3.org) de sites web de qualité, au graphisme soigné et employant les dernieres technologies du web (PHP5, MySQL5, Ajax, XHTML, CSS2).http://www.web82.net

Core-TechsExpert des solutions de gestion et de communication d’entreprise en Open Source, Core-Techs conçoit, integre, déploie et maintient des systemes de Gestion de Contenu Web, de Gestion Documentaire, de Gestion de la Relation Client (CRM), d’ecommerce et de travail ollaboratif.http://www.core-techs.fr

POP FACTORYPoP Factory,SSII spécialisée Web. Développement de solutions applicatives spéci ques ; offre de solutions packagées : catalogue numérique, e-commerce, livre/magazine numérique, envoi SMS. Nous accompagnons nos clients tout au long de leur projet : audit, conseil, développement, suivi et gestion.http://www.popfactory.com / [email protected]

Blue Note SystemsSpécialistes en CRM Open Source, nous proposons une offre complète de prestations sur la solution SugarCRM. Notre valeur ajoutée réside dans une expertise réactive et une expérience des problématiques de la GRC. Nous vous aidons à tirer le meilleur parti de votre solution CRM.http://www.bluenote-systems.com

Page 55: eZ Publish PHP 02 2011

www.phpsolmag.org 55

Sécurité