Você está na página 1de 55

 5/2009

 10/2010
Table des matières

www.phpsolmag.org 
2/2011

Le périodique phpsolutions est publié par


Table des matières
Software Press Sp. z o.o. SK
Bokserska 1, 02-682 Varsovie, Pologne
Tél. 0975180358, Fax. +48 22 244 24 59

VARIA
www.phpsolmag.org

Président de Software Press Sp. z o.o. SK : 6 Actualités


Paweł Marciniak Actualités du monde du développement.
Christophe Villeneuve
Directrice de la publication :
Ewa Łozowicka
7 Interview de Christophe Gesché
Dépôt légal :
Interview de Christophe Gesché, Directeur Technique
à parution pour le site Delcampe.
ISSN : 1731-4593

Rédacteur en chef :
Łukasz Bartoszewicz
PROJETS
8 Accès objet à MySQL avec PHP5
Mbella Ekoume K. Demazy
Couverture : Lorsqu’on décide de faire du PHP orienté objet sans
Sławomir Sobczyk
avoir à faire de la programmation procédurale, très sou-
DTP : vent on est coincé lorsqu’il s’agit d’accéder à une base
Sławomir Sobczyk Studio2W@gmail.com
de données et plus particulièrement à MySQL. Cet arti-
Composition : cle explique comment accéder à une base de données
Sławomir Sobczyk MySQL en orienté objet avec PHP 5 en utilisant l’exten-
sion Mysqli.
Correction :
Valérie Viel, Thierry Borel, Barbara Bourdelles

Bêta-testeurs :
Brice Favre, Valérie Viel, Cyril David,
DOSSIER
Christophe Milhau, Alain Ribault, Stéphane Guedon, 21 Le guide de développement de datatype
Eric Boulet, Mickael Puyfages, Christian Hernoux, eZ Publish
Isabelle Lupi, Antoine Beluze, Timotée Neullas,
Yann Faure, Adrien Mogenet, Jean-François Montgaillard,
Turmeau Nicolas, Jonathan Marois, Wilfried Ceron, Jérôme Vieilledent, Nicolas Pastorino
Wajih Letaief, François Van de Weerdt, Eric Vincent, Les datatypes eZ Publish sont probablement l’une des
Franck Michaël Assi, Francis Hulin-Hubard,
Nicolas Dumas, David Michaud. fonctionnalités les plus puissantes d’eZ Publish. Ils per-
mettent de traiter des éléments de contenu comme
Les personnes intéressées par la coopération vous l’entendez. Cependant, la complexité de leur dé-
sont priées de nous contacter : veloppement est proportionnelle à leur puissance. Cet-
editor@phpsolmag.org
te première partie vous démontrera les fonctionnalités
de base que l’on se doit d’implémenter lors du dévelop-
Publicité :
publicite@software.com.pl pement de son propre datatype eZ Publish.

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


PRATIQUE
40 Écrire un Web Service en PHP
Yohann Poiron
Des systèmes peuvent interagir avec
AVERTISSEMENT le service Web de la manière prescrite
Les techniques présentées dans les articles
ne peuvent être utilisées qu’au sein des réseaux par sa description en utilisant des mes-
internes. La rédaction du magazine n’est pas sages SOAP, typiquement transmis
responsable de l’utilisation incorrecte des techniques
présentées. L’utilisation des techniques présentées peut via le protocole HTTP et une séria-
provoquer la perte des données ! lisation XML en conjonction avec
d’autres normes liées au Web.

 2/2011
Table des matières

POUR LES DÉBUTANTS


44 SQL/PHP : communication avec une base
de données avec PDO (1/2)
Cilia Mauro, Magali Contensin
Les 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’information
Tony Fachaux
Aujourd’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.
Actualités

Symfony live 2011


La version 2.0 de Symfony sera libé-
Wampserver 2.1.x
Après de nombreux mois d’attentes, l’application Wampserver vient de sor-
rée au moment du Symfony live 2011
qui se déroulera début mars à Paris. tir en 3 versions :
Actuellement, la version béta est dis- • 32 bits,
ponible pour vous permettre de vous • 64 bits,
rendre compte des nombreuses évolu- • Portable.
tions et de la nouvelle puissance de ce
framework PHP (http://symfony-reloa-
ded.org/). Par ailleurs, pour connaître WampServer est un environnement AMP, avec comme éléments de base :
toutes les informations autour de l’évè- Apache / MySQL / PHP pour le système d'exploitation Windows. Par ailleurs,
nement ‘Symfony live’ et vous inscrire,
de nouveaux outils ont été ajouté pour répondre aux différentes attentes des
une seule adresse :
http://www.symfony-live.com/. utilisateurs et surtout des besoins des développeurs, comme :
• PhpMyAdmin : Application de gestion des bases de données MySQL,
Drupal 7 • SQL Butty : Application de gestion SQL,
Après plusieurs années de développe-
ments, la version 7 du CMS Drupal vient • Xdebug : Débogueur,
de sortir. Pour faire fonctionner cette • WebGrind : Débogueur en Frontend,
nouvelle version, il vous faut au moins • XDC : Client Xdebug.
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 La version 32 bits et 64 bits propose les dernières versions disponibles de ces
et de nouveaux thèmes mais aussi une outils pour vous permettre en un seul clic de lancer votre environnement.
optimisition plus poussée ainsi que la La version portable (disponible actuellement en 32 bits) propose de nouvel-
sécurité.
http://www.drupal.org 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-
PHP 5.3.5 et 5.2.17 que. Cette option permet de quitter l'environnement et de déconnecter la clef
Deux nouvelles versions de PHP ont
été libéré, qu’il est important de mettre en un seul clic. Ainsi, vous gagnez du temps et vous êtes certains de ne pas
à jour car une correction au niveau de laisser des traces sur l'ordinateur utilisé.
la sécurité a été apporté. Une nouvelle branche de Wampserver vient d'être publiée. Cette version est
Pour rappel, la version 5.2.1 sera la der-
nière version de cette branche car elle destinée pour tous les supports amovible comme les clefs USB.
a été stoppé, pour privilégier la version

FlogR
PHP 5.3
http://www.php.net
Joomla 1.6 FLOGR est une nouvelle plateforme
La nouvelle version de Joomla vient de galerie photos, réalisée en PHP
d’être publiée. Elle apporte de nom- et distribuée en Open Source.
breuses évolutions comme une amé-
lioration des nombreux outils aux Le but de ce projet est de vous per-
niveaux de l’accessibilité, avec une mettre d’afficher vos photos venant
nouvelle structure, une amélioration directement du site Flick’R d’une
du code sémantique, une nouvelle manière interactive avec de nom-
gestion de multilangue et  génération
de la mise en page xhtml. breuses options vous permettant de
http://www.joomla.fr proposer un album photos très com-
plet comme :
Phractal
Phractal est une application réalisée en • Affichages de différentes vues.
PHP 5.3 et basée sur la technique Man- • Positionner sur une carte le lieu de la photo.
delbrot, pour vous permettre de réaliser • Gestion des tags.
des simples fractales pour le web. Ce
jeune projet devrait évoluer très rapide- • Possibilité de poster des commentaires.
ment car les techniques mathématiques • Différents modes de classements.
sont très vastes.
http://sourceforge.net/projects/phra- Une version de démonstration est disponible pour vous rendre compte de la puis-
ctal/
sance de la plateforme http://thecarruthfamily.com/michael/photos/index.php.
SPIP 2.1.8
SPIP vient de sortir une nouvelle ver- Site officiel : http://code.google.com/p/flogr/.
sion mineure de son CMS, en signa-
lant à l’ensemble des utilisateurs des
versions 2.x d’effectuer la migration
à cause d’un bug important au niveau Rédaction des actualités :
de la sécurité.
http://www.spip.net
Christophe Villeneuve

 2/2011
Interview

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

PHP Solutions : Comment voyez-vous le web de de- PS : Quels sont les outils que vous utilisez pour as-
main ? surer à vos clients la qualité du service ?

Christophe Gesché : CG :

– Internet intégrera de plus en plus les actions de – Technique :


la vie quotidienne. Par exemple, les achats en li- o Une bonne infrastructure des serveurs.
gne viendront compléter la vente directe. Sur le si- o Une programmation toujours en recherche d’op-
te Delcampe, on voit d’ailleurs très nettement une timisation, pour prendre le moins de temps pos-
augmentation des ventes au fil du temps ; c’est ce sible.
que l’on pourrait appeler faire ses courses en pan- o Suivi des nouvelles technologies hardware
toufles. L’Internet sera disponible partout (cela com- et software.
mencera avec des technologies 3G) et l’interaction – Support :
virtuelle sera possible à tout moment, jusqu’à en ar- o Utilisation d’un outil de gestion de messagerie
river par exemple à la réalité augmentée. électronique.
– L’Internet se développera pour proposer une plus o Outils internes de gestion et de contrôle.
grande interaction virtuelle. Les échanges en direct o Consultation auprès d’experts et de personnes
feront de moins en moins partie de notre réalité. reconnues du monde de nos utilisateurs.
– Le partage des multimédias continuera également – Marketing
de se développer. o Analyse des besoins des utilisateurs et analyse
– On arrivera également à une convergence toujours de leur satisfaction afin de répondre au mieux
plus grande des médias à leurs demandes.
– L’Internet étant plus vaste chaque jour, les moteurs
de recherche devront s’adapter et deviendront plus PS : Quels sont les projets à venir de Delcampe ?
pertinents, plus sémantiques et plus thématiques.
Et pourquoi ne pas en arriver à un moteur de re- CG :
cherche intelligent ?
– Delcampe pour les mobiles : cela permettra aux
PS : Quels sont les types d’entreprises/clients qui membres de gérer leurs achats et leurs ventes
se tournent vers vos services ? à partir de leur mobile.
– Création d’applications en Flex d’un point de vue
CG : communicationnel.
– Développement de l’application Web majeure, le si-
– Les négociants/commerçants en objets de collection: te Delcampe.net.
– Les professionnels pouvant être concernés par une – Développement du référencement.
des catégories du site (libraires, brocanteur, …). – Continuer l’optimisation à tous points de vue.
– Les Maisons de Vente spécialisées (en philatélie, – Encore améliorer la capacité d’expansion.
numismatique, …). – Modernisation des normes et de l’interface.
– Les particuliers qui sont acheteurs et/ou vendeurs – Industrialisation des procédures et développement
sur le site. pour faire face à notre croissance.

www.phpsolmag.org 
Projets

Accès objet
à MySQL avec PHP5
Lorsqu’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 : Ce qu’il faut savoir :


• Comment accéder à une base de données MySQL en orienté • Les bases de la programmation avec le langage PHP.
objet avec PHP 5 en utilisant l’extension Mysqli. • 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.

L
’extension Mysql très souvent utilisée en PHP • La classe mysqli : elle permet de créer des objets
pour se connecter à une base de données Mys- de type mysqli _ object. Cette dernière possède
ql est une extension exclusivement procédurale des méthodes et des propriétés nécessaires pour
et n’offre de ce fait aucun mécanisme d’accès aux don- permettre par exemple de se connecter à la ba-
nées en orienté objet. Cependant, la version 5 de PHP, se de données, d’envoyer des requêtes préparées
à savoir PHP5, ayant été conçu pour accroître les capa- à cette dernière, ou alors d’effectuer des transac-
cités orientées objet de PHP, a prévu des moyens de le tions.
faire, et cet article nous montrera comment. • La classe mysqli _ result : elle permet de gérer
Grâce à l’évolution qu’a connue le langage PHP en- les résultats des requêtes SQL effectuées à l’aide
tre la version 4 et la version 5 la programmation orienté de l’objet mysqli précédent. C’est ainsi qu’elle pos-
objet avec ce langage a connu pas mal de modifications sède des méthodes et des propriétés permettant de
et d’amélioration ; et l’accès aux données n’est pas res- manipuler de plusieurs manières les résultats issus
té en marge de cette logique. C’est ainsi que l’accès ob- d’une requête SQL en rendant par exemple ceux-ci
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 Mysqli


Mysqli possède les classes suivantes à l’origine de ces
avantages : Figure 1. Enregistrement d’un nouveau produit

 2/2011
MySQL avec PHP5

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


--
-- Base de données: 'cabinet'
--
CREATE DATABASE 'cabinet' DEFAULT CHARACTER SET latin1 COLLATE latin1 _ swedish _ ci;
USE cabinet;

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

--
-- Structure de la table 'produit'
--

CREATE TABLE 'produit' (


'prd _ id' bigint(20) NOT NULL auto _ increment COMMENT 'identification du produit, clé primaire de la table
produit',
'prd _ libelle' varchar(200) NOT NULL COMMENT 'libellé du produit',
'prd _ reference' varchar(20) NOT NULL COMMENT 'reférence du produit:; doit etre unique',
'prd _ fabricant' varchar(200) NOT NULL COMMENT 'fabricant du produit',
'prd _ qteStock' int(11) NOT NULL COMMENT 'quantite de produit en stock',
'prd _ qteAllert' int(11) NOT NULL COMMENT 'le stock d''alerte pour necessité un approvisionnement',
'prd _ unite' varchar(100) default NULL COMMENT 'unité du produit',
PRIMARY KEY ('prd _ id'),
UNIQUE KEY 'prd _ reference' ('prd _ reference')
) TYPE=InnoDB AUTO _ INCREMENT=16 ;

--
-- Contenu de la table 'produit'
--

INSERT INTO '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, 'acide chlohydrique', 'hcl', 'chimie', 124588, 0, 'ml'),
(10, 'aspirine', 'asp', 'chimie', 124588, 0, 'ml'),
(11, 'uranium 235', 'U235', 'AIEA', 125, 12, 'bekerel'),
(12, 'polonium 210', '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
//inclusion et centralisation des classes de l'application
include _ once 'produit.class.php';

class Config
{
private $HOST = "localhost";
private $USER = "root";
private $PASSWORD = "";
private $DATABASE = "cabinet";

public function connect()
{
//fonction permettant de ce connecter a la bd en utilisant l'extension mysqli
//elle retourne l'objet de connection
$con = new mysqli($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 = 'echec de la connexion à la base de données ('. mysqli _ connect _
errno().')'. mysqli _ connect _ error();
return $con;
}

public function disconnect(mysqli $con)
{
//cette fonction, permet de ce déconnecter de la BD
//elle prend en entrée un objet de connection de type mysqli
$val = $con->close();
if($val!= true)
{
echo("echec de la fermeture de la connection a la BD");
}
}

www.phpsolmag.org 
Projets

Listing 3. La classe Produit return $this->prd _ reference;


}
class Produit public function set _
{ reference($reference)
private $prd _ id; {
private $prd _ libelle; //definila reference du produit
private $prd _ reference; courant
private $prd _ fabricant; $this->prd _ reference=
private $prd _ qteStock; $reference;
private $prd _ qteAllert; }
private $prd _ unite;
public function get _ fabricant()
{
public function _ _ construct() //retourne le fabricant du
{ produit
//le constructuer de la classe return $this->prd _ fabricant;
produit }
$this->prd _ id = ""; public function set _
$this->prd _ libelle = ""; fabricant($fabricant)
$this->prd _ reference = ""; {
$this->prd _ fabricant = ""; //defini le fabricant du produit
$this->prd _ qteStock = ""; $this->prd _
$this->prd _ qteAllert = ""; fabricant=$fabricant;
$this->prd _ unite = ""; }
}
public function get _ qteStock()
{
public function get _ id() //retourne la quantité en stock
{ return $this->prd _ qteStock;
//retourne l'id du produit }
courant. public function set _ qteStock($qteStock)
return $this->prd _ id; {
} //defini la quantité en stock
public function set _ id($id) $this->prd _ qteStock=$qteStock;
{ //definit la valeur de l'id du }
produit
public function get _ qteAllert()
$this->prd _ id = $id;
{ //retourne la quantité d'alerte
}
return $this->prd _ qteAllert;

}
public function get _ libelle()
public function set _
{
qteAllert($qteAllert)
//retourne le libelle du produit
{ //defini la quantité d'alerte
courant
$this->prd _ qteAllert =
return $this->prd _ libelle;
$qteAllert;
}
}
public function set _ libelle($libelle)

{
public function get _ unite()
//defini le libelle du produit
{ //retourne l'unite
$this->prd _ libelle = $libelle;
return $this->prd _ unite;
}
}

public function set _ unite($unite)
public function get _ reference()
{ //retourne l'unite
{
$this->prd _ unite = $unite;
//retourne la reference du
}
produit courant
}

sous forme de tableau associatif, de tableau indicé, mysqli->insert _ id : retourne l’identifiant automa-
d’obtenir le nombre de tuple d’un résultat etc. tiquement généré pour un attribut déclaré AUTO _
• La classe mysqli _ stmt : elle permet d’effectuer INCREMENT.
des requêtes préparées sur la base de données.
• La classe mysqli _ driver : elle permet la gestion • Les méthodes :
des drivers mysqli.
mysqli ( [string $host [, string $username
La classe mysqli [, string $passwd [, string $base [, int
Cette classe possède un certain nombre de propriétés $port [,
et de méthodes ; nous nous limiterons à n’évoquer que string $socket]]]]]] ) : permet de créer un objet
quelques-unes. mysqli, afin de se connecter à la base de données
MySQL.
• Propriétés : close(void) : permet de fermer une connexion à la
base de données MySQL ; elle retourne true en cas
mysqli->affected _ rows : contient le nombre de de succès et false sinon.
lignes affectées par la dernière requête INSERT, query(string $query [, int $mode]) : permet
UPDATE ,REPLACE ou DELETE. d’exécuter une requête sur la base de données ;

10 2/2011
MySQL avec PHP5

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


public function GetOneProduit(mysqli $id _ con,$id)
{
/*
* cette methode permet d'obtenir un tuple de la table produit à partir
* de son id et de l'id de connection à la bd
* elle retourne un objet produit
*/

try
{
if(ctype _ digit($id))
{
$sql = "SELECT * FROM produit WHERE prd _ id ='$id'";
$result = $id _ con->query($sql);
$ligne = $result->fetch _ assoc();
$unobjet = new Produit();
$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
{
throw new Exception("Erreur sur l'identificateur");
}

}
catch (Exception $e)
{
echo $e->getMessage();
}
return $unobjet;
}

public function GetAllProduit(mysqli $id _ con, $order, $by)


{
/*cette méthode permet d'obtenir la liste des produits da la base de données
* elle prend en entrée:
* $id _ con : l'objet de connexion a la base de donnée,
* $by : la collone suivant laquelle les resultats seront ordonnés
* $order : l'odre de tri des résultats : ASC ou DESC
* cette méthode retourne un itérateur de type Moniterateur
* */

$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 * FROM produit ORDER BY $by $order";
$myIt = new Moniterateur($sql,$id _ con);
return $myIt;
}
else
{
throw new Exception("Erreur de paramètres");
}
}
catch (Exception $e)
{
return $e->getMessage();
}
}

public function UpdateProduit(mysqli $id _ con,$id,$libelle,$reference,$fabricant,$qteStock,$qteAll
ert,$unite)
{
/*
* cette methode met a jour un tuple de la table produit à
* partir de son ID
* en entrée on a les differents champs correspondant au
* propriété de la table produit dans la BD et l'id de la
* connexion a mysql
*/

www.phpsolmag.org 11
Projets

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



$sql= "UPDATE produit SET
prd _ libelle ='$libelle',prd _ fabricant = '$fabricant',prd _ qteStock='$qteStock',
prd _ qteAllert= '$qteAllert', prd _ unite = '$unite'
WHERE prd _ id = '$id'";

try
{
$result = $id _ con->query($sql);
if(!$result)
{
throw new Exception("Erruer lors de la mise à jour de
l'enregistrement");
}
}
catch
(Exception $e)
{
$result = $e->getMessage();
}

return $result;
}

public function SetProduit(mysqli $id _ con)
{
/*
* cette methode permet d'insérer un nouvel enregistrement
* dans la table produit;
* 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 = "INSERT INTO produit VALUES ('$id','$libelle','$reference','$fabricant','$qteStock','$q
teAllert','$unite')";


try
{
$result = $id _ con->query($sql);
if(!$result)
{
throw new Exception("Une erreur c'est produite lors de l'insertion de
l'enregistrement dans la BD");
}
}
catch (Exception $e)
{
$result = $e->getMessage();
}
return $result;
}

public function DeleteOneProduit($id,mysqli $id _ con)
{
/*
* cette methode se charge de supprimer un tuple dans la table
* produit identifié par son id
* en entrée on a :
* - l'id du produit à supprimer
* - l'identifiant de connexion à la BD
*/

try
{
if(ctype _ digit($id))
{
$sql = "DELETE FROM produit WHERE prd _ id = '$id'";
$result = $id _ con->query($sql);
}
else
{
throw new Exception("Erreur sur l'identificateur");
}
}

12 2/2011
MySQL avec PHP5

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


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

//la classe Moniterateur qui implémente l'interface Iterator

class Moniterateur implements Iterator


{

private $result = null;
private $current = null;
private $key = 0;
protected $sql = null;

function _ _ construct($sql,mysqli $con)
{
//le constructerur de la classe: il prend en entrée une requette sql
//et un identifiant de connexion à la base de données mysql
$this->result = $con->query($sql);
}

public function current()
{
//retourne la valeur de l'élément courant de notre collection
return $this->current;
}

public function key()
{
//retourne la clé de lélément corant de notre collection
return $this->key;
}

public function next()
{
//cette méthode retourne l'élément suivant de notre collection
$this->current = $this->result->fetch _ assoc();
$this->key++;
}

public function rewind()
{
//on déplace le pointeur vers la première enregistrement de notre collection
$this->result->data _ seek(0);
$this->key = -1;
$this->next();
}

public function valid()
{
//on teste si le résultat courrant esr correcte: en d'autre terme s'il s'agit d'un
tableau
return is _ array($this->current);
}

}

le paramètre $mode est une constante valant MYS- - Les méthodes :


QLI _ USE _ RESULT ou MYSQLI _ STORE _ RESULT
(valeur par défaut) suivant le comportement désiré. fetch _ all ([ int $resulttype ]) : permet de
select _ db(string $db) : permet de sélectionner lire tous les résultats d’une requête et de les retour-
une base de données ; ner sous forme de tableau associatif, numérique ou
les deux à la fois, ceci suivant la valeur du para-
La classe mysqli_result mètre $resulttype qui peut être : MYSQLI _ ASSOC
- Les propriétés : pour un tableau associatif, MYSQLI _ NUM
pour un tableau numérique, cette va-
mysqli _ result->field _ count : récupère le nom- leur étant la valeur par défaut, MYS-
bre de champs dans un jeu de résultats. QLI _ BOTH pour les deux.
mysqli _ result->num _ rows : retourne le nombre fetch _ array ([ int $resulttype ] ) : per-
de lignes d’un résultat. met de retourner une ligne de résultats sous forme

www.phpsolmag.org 13
Projets

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":
{
//gestion de ce qui concerne la table produit
if($operation == 'update')
{
$prd = new Produit();

$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 = new Produit();
$result = $prd->DeleteOneProduit($id,$id _ con);
$cobj->disconnect($id _ con);
}
if($operation =='new')
{
$prd = new Produit();

$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;
}
}

d’un tableau associatif, d’un tableau indexé, ou les fetch _ row ( void ) : permet de récupérer
deux suivant la valeur du paramètre $resultty- une ligne de résultats sous forme de tableau in-
pe qui peut être : MYSQLI _ ASSOC, MYSQLI _ NUM, dexé.
MYSQLI _ BOTH (valeur par défaut).
fetch _ assoc ( void ) : permet de récupérer La classe mysqli_stmt
une ligne de résultats sous forme de tableau asso- - Les propriétés :
ciatif ;
fetch _ object ([ string $class _ name [, mysqli _ stmt->affected _ rows : retourne le
array $params ]]) : retourne la ligne courante nombre total de lignes modifiées, effacées, ou insé-
d’un jeu de résultats sous forme d’objet ; en para- rées par la dernière requête.
mètre nous avons $class _ name qui représente le mysqli _ stmt->insert _ id : récupère l’ID généré
nom de la classe à instancier , et $params un ta- par la dernière requête INSERT.
bleau contenant la liste des paramètres à passer au mysqli _ stmt ->num _ rows : retourne le nombre
constructeur de cette classe. de ligne d’un résultat Mysql.

14 2/2011
MySQL avec PHP5

Listing 6. La vue nouveau produit: new_produit.php


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.
dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Nouveau Produit</title>
</head>
<body>

<div>
<form action="ctrl.produit.php?action=new&mod=produit" name="form _ produit" id="form _ produit"
method="post" >
<fieldset>
<legend>
Gestion des Produits
</legend>
<table>
<tr>
<td><label>Nom Produit</label></td>
<td><input name="nom _ produit" type="text" id="nom _
produit"/></td>
</tr>
<tr>
<td><label>Fabricant</label></td>
<td><input name="fabricant" type="text" id="fabricant" /></td>
</tr>

<tr>
<td><label>Quantité en Stock</label></td>
<td><input name="qte _ stock" type="text" id="qte _ stock"/></td>
</tr>

<tr>
<td><label>Stock d`alerte</label></td>
<td><input name="stock _ allert" type="text" id="stock _
allert"/></td>
</tr>

<tr>
<td><label>Unité Produit</label></td>
<td><input name="unite" type="text" id="unite" /></td>
</tr>
<tr>
<td><label>Réference Produit</label></td>
<td><input name="reference" type="text" id="reference"/></td>
</tr>

<tr>
<td>&nbsp;</td>

<td>&nbsp;</td>
</tr>

<tr>
<td align="center"><input name="Enregistrer" type="submit"
value="Enregistrer" /></td>

<td align="center"><input name="Annuler" type="reset" value="Annuler"
/></td>
</tr>

</table>
</fieldset>

</form>

</div>

</body>
</html>

- Les méthodes : La classe mysqli_driver


- Les propriétés :
prepare ( string $query ) : permet de préparer
une requête SQL pour exécution. Cette classe est pour le moment très peu documen-
reset ( void ) : permet d’annuler une requête tée mais possède néanmoins les propriétés suivan-
préparée. tes :

www.phpsolmag.org 15
Projets

Figure 2. Liste des produits

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

client _ version : qui indique la version du client. embedded _ server _ start ( bool $start ,
driver _ version : qui indique la version du driver Mysqli. array $arguments , array $groups ) : qui per-
met d’initialiser et de démarrer le serveur embarqué.
- Les méthodes
Mise en œuvre
Il n’y en a que deux pour le moment et elles sont Pour illustrer l’utilisation de mysqli pour accéder à MyS-
également peu documentées, il s’agit de : QL, nous allons mettre en place une application web ba-
sique. L’architecture ici étant l’architecture MVC (Model
embedded _ server _ end(void) : qui permet d’ar- View Controller) cela nous permettra de séparer l’accès
rêter le serveur embarqué. aux données de la présentation et des traitements.

16 2/2011
MySQL avec PHP5

Figure 4. Suppression d’un produit

La base de données A propos de la sécurité


Pour pouvoir observer comment tout ceci est mis en
œuvre, nous avons besoin d’une base de données Dans l’exemple que nous prenons, l’aspect sécurité est peu
MySQL. Cette base de données s’appellera cabinet ou pas assez pris en compte ; il est donc possible que le co-
de qui suit puisse être vulnérable aux attaques comme les
et possèdera une table, la table produit. Le Listing 1 SQL Injections ou tout autre attaque courante.
représente le script SQL correspondant pour cette base
de données.
teur de la classe. Ainsi une méthode comme get_id()
Connexion et déconnexion à la base permet d’obtenir la valeur de la propriété $prd_id de la
de données classe Produit ; tandis que la méthode set_id($id)
Nous allons illustrer cela en écrivant deux fonctions que permet de définir ou de modifier cette valeur.
nous utiliserons tout au long de cet article car toutes Le Listing 4 quant à lui contient la liste des méthodes
les manipulations que nous aurons à faire et qui porte- complémentaires de la classe Produit. Celles-ci agi-
ront sur les données nécessiteront que l’on se connec- ront sur les données, chacune d’une manière précise :
te puis que l’on se déconnecte à la base de données.
Étant donné que la connexion à la base de données - GetOneProduit : elle prend en entrée l’identifiant
nécessite des paramètres de connexion que sont l’hô- de la connexion à Mysql et l’identifiant d’un produit
te, l’utilisateur, le login, le mot de passe ainsi que le dans la table produit, afin de le retourner sous la
nom de la base de données à laquelle on souhaite se forme d’un objet de type Produit.
connecter, ces paramètres seront également définis. - GetAllProduit : cette méthode prend en entrée
Le Listing 2 représente le fichier config.class.php qui l’identifiant de connexion à MySQL, l’ordre dans le-
contiendra tout cela. quel les résultats devront être retournés, et la co-
Les méthodes connect() et disconnect() permet- lonne suivant laquelle les résultats seront triés ; elle
tent respectivement de se connecter et se déconnecter retourne la liste des produits présents dans la table
de la base de données en utilisant l’extension mysqli. produit sous forme d’un objet de type Moniterateur.
- UpdateProduit : cette méthode permet de met-
La classe Produit tre à jour une ligne dans la table produit; elle prend
Précédemment nous avons fait allusion à la table pro- en entrée l’identifiant de connexion à MySQL
duit dans notre base de données ; maintenant, nous al- et d’autres paramètres correspondant aux proprié-
lons écrire la classe correspondante et nous écrirons tés de la classe Produit. Elle retourne true en cas
également les méthodes d’accès aux enregistrements de succès ou un message d’erreur sinon.
de cette table. Le Listing 3 nous montre le code de la - SetProduit : cette méthode, qui prend en entrée
classe Produit : il s’agit dans un premier temps d’une l’identifiant de connexion à MySQL, permet d’insé-
classe basique contenant juste les propriétés, les mé- rer un enregistrement dans la table produit ; elle re-
thodes d’accès aux propriétés, (les accesseurs de pré- tourne true en cas de succès, ou un message d’er-
fixe get_ et mutateurs de préfixe set_) et le construc- reur sinon.

www.phpsolmag.org 17
Projets

- Dans un premier temps on écrit la requête SQL qui


Listing 7. La vue liste des produits: list_produit.php
doit être exécutée.
<!DOCTYPE ht ml PUBLIC "-//W3C//DTD XHTML 1.0 - Ensuite on l’exécute, via la méthode query de l’objet
Transitional//EN" "http://w w w.w3.org/TR/xht ml1/DTD/
xht ml1-transitional.dtd"> mysqli.
<ht ml x mlns="http://w w w.w3.org/1999/xht ml"> - Enfin les résultats sont récupérés et traités en utili-
<head>
<meta http-eq uiv="Content-Ty pe" content="text/ sant la méthode fetch _ assoc de l’objet mysqli _
ht ml; charset=UTF-8" /> result en vue d’être retournés sous le format sou-
<title>Liste des Produits</title>
</head> haité : un objet ou un itérateur.
<body>

<?php Le contrôleur de la classe Produit


Le Listing 5 représente le fichier ctrl.produit.php qui
include _ once 'config.class.php';
contient les codes nécessaires aux différents traite-
$cobj = new Config(); ments qui seront effectués. Avant tout traitement, la mé-
//connexion a la BD
$id _ con=$cobj->connect(); thode connect de connexion à la base de données est
$lst _ prd = $prd->GetAllProduit($id _ appelée et à la fin de chaque traitement, la méthode de
con,'ASC','prd _ libelle');
déconnexion, disconnect est appelée.
?> Dans chaque partie il est question d’un traitement
<ta ble width="600" border="1" spécifique appelant une méthode précise de la classe
cellpadding="1" cellspacing="0"> produit :
<tr>
<th scope="col">Libelle</th>
<th scope="col">Fa bricant</th> - $operation == new : ici le traitement à effectuer
<th scope="col">Unité</th>
<th scope="col">Quantité en stock</ est l’enregistrement d’une nouvelle ligne dans la ta-
th> ble produit,
<th scope="col">Stock d`allerte</th>
<th scope="col">Opération</th> - $operation == update : ici le traitement est la mi-
</tr> se à jour d’un enregistrement dans la table produit,

<?php - $operation == delete : ici le traitement est la
suppression d’un enregistrement dans la table pro-
foreach ($lst _ prd as $prd)
{ duit.
echo(
"<tr>".
"<td><a Test de notre application
href='update _ produit.php?id=".$prd['prd _ Nous allons maintenant utiliser les méthodes de no-
id']."'>".$prd['prd _ libelle']."</a></td>".
"<td>".$prd['prd _ tre classe en vue d’illustrer de manière pratique ce que
fa bricant']."</td>". nous obtenons comme résultat :
"<td>".$prd['prd _
unite']."</td>".
"<td>".$prd['prd _ - Insertion d’un nouvel enregistrement dans la table
qteStock']."</td>".
"<td>".$prd['prd _ produit.
qteAllert']."</td>".
"<td><a
href='ctrl.produit.php?action=delete&mod=produit& Le Listing 6 représente le code de la vue nou-
id=".$prd['prd _ id']."'>Supprimer</td>". veau produit qui permet d’enregistrer un nouveau
"</tr>");
} produit dans la base de données. Nous allons en-
registrer le produit Vitamine C ; on obtient alors
//deconnexion de la BD
$cobj->disconnect($id _ con); dans un navigateur le formulaire représenté dans
?> la Figure 1.

</ta ble>
</body> - Liste des enregistrements de la table produit.
</ht ml>

Pour effectuer cette opération nous allons utiliser


un itérateur : il s’agit d’une interface qui fournit un
- DeleteOneProduit : elle permet de supprimer un ensemble de méthodes permettant de passer en
enregistrement dans la table produit; elle prend en revue une collection d’objet en utilisant l’opérateur
entrée l’identifiant du produit à supprimer dans la foreach comme on le fait de manière classique pour
table et l’identifiant de connexion à la base de don- un tableau.
nées MySQL.
Nous utiliserons pour notre cas l’itérateur Iterator : il
On se rend bien compte en lisant les méthodes de cette possède les méthodes suivantes que nous redéfini-
classe que la logique est pratiquement la même : rons :

18 2/2011
MySQL avec PHP5

Listing 8. La vue update produit: update_produit.php


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Mise à jour Produit</title>
</head>
<body>

<div>

<?php

//récupération de l'enregistrement a partir de son id

include _ once 'config.class.php';

$id = trim($ _ GET['id']);
$cobj = new Config();
//connexion a la BD
$id _ con=$cobj->connect();
$prd = new Produit();
$ligne = $prd->GetOneProduit($id _ con,$id);

?>
<form action="ctrl.produit.php?action=update&id=<?php echo $id;?>&mod=produit" name="form _ produit"
id="form _ produit" method="post" >
<fieldset>
<legend>
Mise à jour d`un Produit
</legend>
<table>
<tr>
<td><label>Nom Produit</label></td>
<td><input name="nom _ produit" type="text" id="nom _ produit"
value="<?php echo $ligne->get _ libelle();?>"/></td>
</tr>
<tr>
<td><label>Fabricant</label></td>
<td><input name="fabricant" type="text" id="fabricant" value="<?php
echo $ligne->get _ fabricant();?>"/></td>
</tr>
<tr>
<td><label>Quantité en Stock</label></td>
<td><input name="qte _ stock" type="text" id="qte _ stock"
value="<?php echo $ligne->get _ qteStock();?>"/></td>
</tr>
<tr>
<td><label>Stock d`alerte</label></td>
<td><input name="stock _ allert" type="text" id="stock _ allert" value="<?php
echo $ligne->get _ qteAllert();?>"/></td>
</tr>
<tr>
<td><label>Unité Produit</label></td>
<td><input name="unite" type="text" id="unite" value="<?php echo
$ligne->get _ unite();?>"/></td>
</tr>
<tr>
<td><label>Référence Produit</label></td>
<td><input name="reference" type="text" id="reference" value="<?php
echo $ligne->get _ reference();?>"/></td>
</tr>
<tr>
<td>&nbsp;</td>

<td>&nbsp;</td>
</tr>
<tr>
<td align="center"><input name="Modiffier" type="submit" value="Modiffier"/></td>
<td align="center"><input name="Annuler" type="reset" value="Annuler" /></td>
</tr>

</table>

</fieldset>

</form>

</div>

</body>
</html>

www.phpsolmag.org 19
Projets

Conclusion
Sur Internet : Nous venons à travers cet article de voir comment ac-
céder à MySQL en orienté objet grâce à l’extension
• http://www.php.net/download-docs.php - Le manuel de mysqli ; nous avons également pu effectuer des trai-
PHP5 sous divers formats, pour plus de détails sur la tements grâce aux classes mysqli_stmt et mysqli_
classe Mysqli. 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-
• current() : retourne l’élément courant de la collec- veau de l’accès aux données. Cependant pour ceux
tion, qui ne sont pas encore familiarisés avec l’approche
• key() : retourne la valeur de la clé de l’élément cou- orientée objet de PHP5 et veulent pouvoir tirer pro-
rant de la collection, fit des avantages de l’extension mysqli, ils peuvent
• next() : permet de se positionner sur l’élément sui- dans ce cas opter pour l’approche procédurale de cet-
vant de la collection, te API ; cela pourrait d’ailleurs faire l’objet d’un article
• rewind() : permet de se positionner sur le premier futur.
élément de la collection, Étant donné que cet article ne saurait être exhaus-
• valid() : permet de savoir si l’on a atteint la fin de tif sur ce sujet, nous vous invitons à compléter vos
la collection. connaissances en faisant un tour sur Internet et en
lisant quelques livres spécialisés.
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. DEMAZY


Bibliographie : Technicien 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,
Jeans Engels, PHP5 cours et exercices, avec la contribution celui-ci se passionne pour les solutions Open Source et cela fait ma-
de Olivier Salvatori, groupe Eyrolles, 2009.
intenant 3 ans qu’il travaille en tant que développeur professionnel.
Pour le contacter, envoyer un mail à l’adresse dembello85@yahoo.fr.

20 2/2011
Dossier

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 : Ce qu'il faut savoir :


• Après lecture de ce didacticiel, vous connaîtrez les bases con- • Ce didacticiel s’adresse aux développeurs eZ Publish expéri-
ceptuelles des datatypes eZ Publish. Cela permettra d’adresser mentés voire à des développeurs intermédiaires souhaitant
la plupart des cas d’utilisation de ce concept, dans le cadre élargir leurs connaissances techniques de la plateforme de ge-
d’un projet demandant la personnalisation du framework. Les stion de contenu eZ Publish.
exemples détaillés pourront servir de référence de code géné- • Des compétences en programmation orientée objet, ain-
rique, ré-utilisables dans de nombreux autres cas que celui su- si qu’une bonne compréhension des concepts de gestion de
ivi dans ce didacticiel. 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

Contenu Orienté Objet Structure standard


Avant d’entamer le développement d’un datatype per- Concrètement, un datatype consiste en une classe
sonnalisé, il est recommandé de bien comprendre la phi- PHP étendant la classe eZDataType, et d’un ensemble
losophie de Contenu Orienté Objet (OOC) d’eZ Publish. de templates. Les datatypes personnalisés doivent être
Un contenu dans eZ Publish consiste en un objet, un Ob- contenus dans une extension au système.
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 Publish


Un 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. Figure 1. Contenu orienté objet (OOC) dans eZ Publish

www.phpsolmag.org 21
Dossier
Extension ZIP pour PHP

2
22 9/2010
2/2011
eZ Publish

Premièrement, vous devez choisir un ID (identifiant


Listing 1. Structure d’un datatype
unique) pour votre datatype, également connu sous le
nom de datatype string. Il sera stocké à côté du contenu extension/mydatatypeextension/
|-- datatypes
afin que le système gère les instances de ce datatype cor- | '-- mycustomdatatypeid
rectement lors de la manipulation du contenu. Par conven- | '-- mycustomdatatypeidtype.php
|-- design
tion, le datatype string ne contient pas de caractères spé- | '-- standard
ciaux, et n’est composé que de minuscules. Le Listing 1 | '-- templates
| |-- class
présente la structure standard d’une extension de dataty- | | '-- datatype
pe, ayant pour ID mycustomdatatypeid (voir Listing 1). | | |-- edit
| | | '-- mycustomdatatypeid.tpl
Notez les différentes conventions de nommage uti- | | '-- view
| | '-- mycustomdatatypeid.tpl
lisées : | '-- content
| '-- datatype
| |-- edit
• Le fichier PHP contenant la classe du dataty- | | '-- mycustomdatatypeid.tpl
pe DOIT être nommé comme suit : <datatype _ | '-- view
| '-- mycustomdatatypeid.tpl
string>type.php (mycustomdatatypeidtype.php |-- extension.xml
dans notre cas). |-- ezinfo.php
'-- settings
• Tous les templates sont nommés directement |-- content.ini.append.php
d’après le datatype string (mycustomdatatypeid. |-- datatype.ini.append.php
'-- design.ini.append.php
tpl dans notre cas). C’est une convention système,
à laquelle on peut faire exception si l’on retourne Listing 2. Classe PHP du datatype
dynamiquement les noms des templates utilisés par <?php
le datatype. Ceci sort du cadre de ce didacticiel. /**
* File containing MyCustomDatatypeID datatype definition
* @licence http://www.gnu.org/licenses/gpl-2.0.txt GNU
Enregistrement du datatype GPLv2
* @version @@@VERSION@@@
Comme expliqué ci-dessus, un datatype consiste prin- */
cipalement en une classe PHP étendant eZDataType. Il class MyCustomDatatypeIDType extends eZDataType
{
doit aussi être enregistré dans eZ Publish, ceci passant const DATA _ TYPE _ STRING = 'mycustomdatatypeid';
par de la configuration et une ligne de PHP.
/**
* Constructor
La partie PHP */
public function _ _ construct()
Notez que le datatype string est stocké dans une {
constante de classe (voir Listing 2). C’est une bonne parent::eZDataType( self::DATA _ TYPE _ STRING,
'My custom datatype label' );
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, eZDataType::register( MyCustomDatatypeIDType::DATA _


et le label du datatype, tel qu’il apparaîtra dans l’inter- TYPE _ STRING, 'MyCustomDatatypeIDType' );

face d’administration lors de l’édition d’une classe de Listing 3. Constructeur


contenu. Ce label est traduisible en utilisant la fonction-
public function _ _ construct()
nalité i18n native d’eZ Publish (http://pubsvn2.ez.no/ {
phpdoc/trunk/html/default/ezpI18n.html#tr). $datatypeLabel = ezpI18n::tr( 'datatypes/
mycustomdatatype',
Finalement, en dehors de la structure de classe, 'My custom datatype
le datatype est enregistré auprès du moteur de conte- label' );
// Use ezpI18n::tr() with eZP 4.3 or later. Before,
nu. Cette expression PHP fait correspondre le dataty- use ezi18n() function
pe string à votre classe PHP. Notez que la traduction
parent::eZDataType( self::DATA _ TYPE _ STRING,
est activée par défaut. Si, pour une raison quelconque, $datatypeLabel,
vous décidez de la désactiver, vous pouvez appeler le array( 'translation _ allowed' =>
false )
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è- Listing 4. extension/mycustomdatatypeextension/settings/


me argument), mais elles ne seront pas abordées pré- content.ini.append.php
sentement. <?php /* #?ini charset="utf-8"?

[DataTypeSettings]
La partie configuration ExtensionDirectories[]=mycustomdatatypeextension
Ici nous indiquons à eZ Publish qu’il doit également cher- AvailableDataTypes[]=mycustomdatatypeid
cher des datatypes dans le dossier d’extension mycustom */ ?>
datatypeextension. Nous déclarons également notre

www.phpsolmag.org 23
Dossier

Captures d’écran
Listing 5. extension/mycustomdatatypeextension/settings/
design.ini.append.php
<?php /* #?ini charset="utf-8"?

[ExtensionSettings]
DesignExtensions[]=mycustomdatatypeextension

*/ ?>

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 projet
Pour 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écifications
La 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). Plan de développement
• Un remplissage automatique de l’attribut si le Les fonctionnalités exposées ci-dessus vont être im-
champ est vide. plémentées dans la classe PHP de datatype nom-
– Une option dédiée devra être activée dans l’in- mée JVAmazonIDType, assortie d’un ensemble de
terface d’édition de classe. templates nécessaires à la création de l’interface uti-
– L’attribut devra être renseigné par le premier ré- lisateur.
sultat de la recherche correspondante dans le L’intégralité des étapes suivantes reposera sur
catalogue Amazon. l’hypothèse que l’extension JVAmazonAdvertising est
– Le contributeur pourra choisir d’activer ou non installée et configurée correctement (consulter la do-
cette recherche automatique, depuis l’interface cumentation de l’extension pour ce faire : http://svn.
d’édition de contenu. projects.ez.no/jvamazonadvertising/trunk/doc/INS-
– La recherche dans le catalogue Amazon se fe- TALL). Le datatype string sera jvamazonid, la classe
ra au travers d’AWS, et portera sur le contenu PHP se trouvera donc dans le fichier suivant : exten-
des autres attributs de l’objet venant d’être édité sion/jvamazonadvertising/datatypes/jvamazonid/jva-
(nom, description). mazonidtype.php.
– L’administrateur pourra définir le(s) attribut(s) de Le développement se fera en trois parties :
contenu sur le contenu duquel (desquels) porte-
ra la recherche. • Classe de contenu,
– L’administrateur pourra choisir dans quelle caté- • Objet de contenu,
gorie produit la recherche sera faite, • Action HTTP spécifique.
• Il doit être possible d’effectuer une recherche de
produits Amazon depuis l’interface d’édition A chaque partie correspond un groupe de méthode
afin de sélectionner le plus approprié. et un ensemble de templates, détaillés ci-après.

24 2/2011
eZ Publish

Notre datatype au niveau de la classe


de contenu
Petit préambule pratique
Pour 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 Figure 2. Navigation vers l’interface d’édition de classe de contenu
contenu dans la liste qui s’affiche. Le contexte peut
nous faire éditer la classe “Product” (voir Figure 3).

Côté template
L’objectif est de pouvoir ajouter un attribut du datatype Figure 3. Edition de la classe de contenu ‘Product’ 
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). Figure 4. Interface d’édition de notre datatype, au niveau de la
Ce découpage d’interface standard/personnalisé se classe de contenu.
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’édition
Comme nous l’avons vu plus haut (cf Structure Stan- Figure 5. Interface de visualisation de notre datatype, au niveau de
dard), c’est dans le template jvamazonid.tpl, placé dans la classe de contenu.
extension/jvamazonadvertising/design/standard/tem-
plates/class/datatype/edit/ que nous allons permettre Markup
au webmestre de : Notez le réflexe <fieldset>/<legend>, qui permet de dé-
• Activer la recherche de produit correspondant dans ployer l’interface en deux parties visuellement distinc-
le catalogue Amazon si le champ de saisie, au ni- tes, en s’appuyant sur les styles par défaut de l’inter-
veau de l’édition de l’objet de contenu, a été laissé face d’administration d’eZ Publish. Une autre bonne
vide. Ceci doit pouvoir se contrôler par une option pratique est d’utiliser une balise <label> correspondant
dédiée, à chaque balise <input>, en les liant par l’attribut ‘for’ de
• Contrôler quels attributs de contenus serviront de la balise <label>.
texte de recherche, dans le cas où l’option ci-des-
sus est activée, Variables à disposition
• Définir sur quelle catégorie du catalogue Amazon La principale variable nous intéressant ici est $class_
portera la recherche, si elle est activée ou manuelle, attribute, un objet PHP de la classe eZContent-
• Définir sur quel emplacement du catalogue Amazon ClassAttribute (détails : http://doc.ez.no/eZ-Publish/
(BrowseNode) portera la recherche, si elle est acti- Technical-manual/4.x/Reference/Objects/ezcontent-
vée ou manuelle. classattribute), donnant accès à toutes les propriétés
et données de notre attribut de classe de contenu. La
Ajoutons ces trois contrôles dans jvamazonid.tpl référence complète des attributs est consultable ici :
(cf Listing 6). http://goo.gl/aeumK.

www.phpsolmag.org 25
Dossier

Listing 6. extension/jvamazonadvertising/design/standard/templates/class/datatype/edit/jvamazonid.tpl
<div class="block">
<fieldset>
<legend>
{"Reference field for ASIN search"|i18n( "design/standard/class/datatype/jvamazonid" )}
</legend>

<label for="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>
<input type="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>{"If left empty, will be content object name"|i18n( "design/standard/class/datatype/jvamazonid" )}</i>

<br /><br />
<label for="ContentClass _ jvamazonid _ asin _ search _ if _ empty _ {$class _ attribute.id}">
{"Automatically search ASIN on publish if object attribute is left empty (default value)"|i18n(
"design/standard/class/datatype/jvamazonid" )}
</label>
<input type="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>
{"Amazon SearchIndex (category) to search into"|i18n( "design/standard/class/datatype/jvamazonid" )}
</legend>

<label for="ContentClass _ jvamazonid _ asin _ search _ index _ {$class _ attribute.id}">
{"Enter a valid search index (product category)."|i18n( "design/standard/class/datatype/jvamazonid" )}<br />
</label>
<input type="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>{"Search indexes are listed here"|i18n( "design/standard/class/datatype/jvamazonid" )} :
<a href={"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>
{"Amazon BrowseNode (sub-category) to search into"|i18n( "design/standard/class/datatype/jvamazonid" )}
</legend>

<label for="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}">
{"Enter a valid browse node (product location in Amazon catalog). Leave empty if not applicable"|i18n(
"design/standard/class/datatype/jvamazonid" )}<br />
</label>
<input type="text" size="50"
name="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}"
id="ContentClass _ jvamazonid _ browsenode _ {$class _ attribute.id}"
value="{$class _ attribute.data _ int2}" /><br />
<i>{"Find a browse nodes non-exhaustive list here"|i18n( "design/standard/class/datatype/jvamazonid" )} :
<a href={"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


<input type="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


<input type="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} />

26 2/2011
eZ Publish

Listing 9. extension/jvamazonadvertising/design/standard/templates/class/datatype/view/jvamazonid.tpl
<div class="block">
<label>{"Reference field for ASIN search"|i18n( "design/standard/class/datatype/jvamazonid" )} :</label>
<p>{$class _ attribute.data _ text1|wash}</p>
<label>{"Automatically search ASIN on publish if object attribute is left empty (default value)"|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>{"Amazon SearchIndex (category) to search into"|i18n( "design/standard/class/datatype/jvamazonid" )} :</label>
<p>{$class _ attribute.data _ text2|wash}</p>
<label>{"Amazon BrowseNode (sub-category) to search into"|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}

Construction du nom des <input> ceci permettant de s’appuyer sur les styles par défaut
L’attribut ‘name’ des balises <input> suit une règle de de l’interface d’administration. Une autre bonne prati-
nommage stricte : que est d’utiliser des balises <label> autour de chaque
élément de configuration.
ContentClass_<data_type_string>_<nom_de_l_
input>_<ID_d_attribut_de_classe> Variables et données à disposition
Comme pour le template d’édition, la variable $class_
qui doit être respectée dans le template, et conséque- attribute nous permet d’avoir accès aux propriétés
mment dans la partie PHP, lors du traitement des en- et données de notre attribut de classe. Le contenu de
trées. Le Listing 7 montre un exemple extrait du templa- cette variable est le même que pour l’édition de classe,
te d’édition de l’attribut de classe. et la récupération des données stockées se fait de la
On y retrouve bien les éléments constitutifs de la rè- même façon que lors de l’édition :
gle de nommage (voir Tableau 1). Ce motif doit être sui-
vi pour toutes les balises <input> utilisées, entraînant {$class_attribute.data_text1|wash}
l’unicité de nom de toutes les balises <input> pouvant
être présentes dans le formulaire d’édition de classe de L’utilisation de l’opérateur wash permet de “laver” la
contenu (qui contient généralement plusieurs attributs, sortie (équivalent de htmlentities en PHP, cf http://www.
on évite donc tout conflit). php.net/htmlentities), évitant une injection potentielle.

Récupération des données Côté PHP


Lors de l’édition de notre attribut, ou bien lors de sa créa- Travaillons maintenant dans notre classe PHP JVAmazon
tion initiale, il convient d’afficher les donnés de configu- IDType. Les principales méthodes de publication de la clas-
ration stockées précédemment (ou initialisées à leurs se de contenu sont, dans leur ordre d’appel :
valeurs par défaut). Le Listing 8 présente un exemple,
tiré du précédent template. • initializeClassAttribute() - Initialise l’attribut
On note ici que la valeur du champ de stockage da- de classe, avec des valeurs par défaut lorsque l’at-
ta_int1 (détails sur les champs de stockage disponibles tribut est créé (ajout à une classe de contenu),
dans la partie PHP, ci-après) est utilisée pour cocher • validateClassAttributeHTTPInput() - Validation
l’entrée «Automatically search ASIN on publish if object des entrées,
attribute is left empty (default value)». • fixupClassAttributeHTTPInput() - Méthode post-
validation,
Template de visualisation • fetchClassAttributeHTTPInput() - Récupération
Voir Listing 9. effective des données postées et stockage.

Markup Initialisation
On encapsule l’ensemble de cette partie d’interface La méthode initializeClassAttribute() reçoit en
personnalisée dans un <div class=”block”></div>, paramètre une référence du Content Class Attribute

www.phpsolmag.org 27
Dossier

Listing 10. Initialisation de l'attribut de classe Listing 12. Récupération des données et stockage de l'attribut de
classe
<?
/** /**
* Sets default values for a new class attribute. * Handles the input specific for one attribute from
* @param eZContentClassAttribute the class edit interface.
$classAttribute * @param eZHTTPTool $http
* @return void * @param string $base Seems to be always
*/ 'ContentClass'.
public function initializeClassAttribute( * @param eZContentClassAttribute $classAttribute
$classAttribute ) * @return void
{ */
// Default value for search field public function fetchClassAttributeHTTPInput( $http,
if ( !$classAttribute->attribute( self:: $base, $classAttribute )
CLASSATTRIBUTE _ DEFAULT _ FIELD ) ) {
{ // Search field(s)
$classAttribute->setAttribute( self:: $searchFieldName = $base . self::SEARCH _ FIELD _
CLASSATTRIBUTE _ DEFAULT _ FIELD, VARIABLE . $classAttribute->attribute( 'id' );
self:: if( $http->hasPostVariable( $searchFieldName ) )
CLASSATTRIBUTE _ DEFAULT _ EMPTY ); {
$searchFieldValue = $http->postVariable(
} $searchFieldName );
$classAttribute->setAttribute( self::
// Default value for "Allow search if empty" CLASSATTRIBUTE _ DEFAULT _ FIELD, $searchFieldValue );
if ( $classAttribute->attribute( self:: }
CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD ) === null )
{ // Allow search if object attribute empty
$classAttribute->setAttribute( self:: $searchIfEmptyFieldName = $base . self::
CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD, ALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE . $classAttribute-
self:: >attribute( 'id' );
CLASSATTRIBUTE _ ALLOW _ SEARCH _ DEFAULT ); if( $http->hasPostVariable(
$searchIfEmptyFieldName ) ) // Checkbox : only set if posted
} {
$classAttribute->setAttribute( self::
// Default value for search index CLASSATTRIBUTE _ ALLOW _ SEARCH _ FIELD, 1 );
if ( !$classAttribute->attribute( self:: }
CLASSATTRIBUTE _ SEARCHINDEX _ FIELD ) )
{ // Search index
$classAttribute->setAttribute( self:: $searchIndexFieldName = $base . self::SEARCH _
CLASSATTRIBUTE _ SEARCHINDEX _ FIELD, INDEX _ FIELD _ VARIABLE . $classAttribute->attribute( 'id'
self:: );
CLASSATTRIBUTE _ SEARCHINDEX _ DEFAULT ); if( $http->hasPostVariable( $searchIndexFieldName
} ) )
{
// Default value for browse node $searchIndexFieldValue = $http->postVariable(
if ( !$classAttribute->attribute( self:: $searchIndexFieldName );
CLASSATTRIBUTE _ BROWSENODE _ FIELD ) ) $classAttribute->setAttribute(
{ self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD,
$classAttribute->setAttribute( self:: $searchIndexFieldValue );
CLASSATTRIBUTE _ BROWSENODE _ FIELD, }
self::
CLASSATTRIBUTE _ BROWSENODE _ DEFAULT ); // BrowseNode
} $browseNodeFieldName = $base . self::BROWSENODE _
} FIELD _ VARIABLE . $classAttribute->attribute( 'id' );
if( $http->hasPostVariable( $searchIndexFieldName
Listing 11. Validation de l'attribut de classe ) )
{
/** $browseNodeFieldValue = $http->postVariable(
* Validates the input from the class definition $browseNodeFieldName );
form concerning this attribute. $classAttribute->setAttribute( self::
* @param eZHTTPTool $http CLASSATTRIBUTE _ BROWSENODE _ FIELD, $browseNodeFieldValue );
* @param string $base Seems to be always }
'ContentClass'. }
* @param eZContentClassAttribute
$classAttribute Listing 13. Récupération via eZHTTPTool
* @return int eZInputValidator::STATE _ ACCEPTED
|eZInputValidator::STATE _ INVALID|eZInputValidator:: if( $http->hasPostVariable( $searchFieldName ) )
STATE _ INTERMEDIATE {
*/ $searchFieldValue = $http->postVariable(
public function validateClassAttributeHTTPInput( $searchFieldName );
$http, $base, $classAttribute ) $classAttribute->setAttribute( self::CLASSATTRIBUTE _
{ DEFAULT _ FIELD,
return eZInputValidator::STATE _ ACCEPTED; $searchFieldValue );
} }

(classe PHP eZContentClassAttribute, détails ici : http:// classe de contenu, de peupler au besoin les champs de
doc.ez.no/eZ-Publish/Technical-manual/4.x/Reference/ stockage utilisés avec des valeurs par défaut. De ma-
Objects/ezcontentclassattribute). Elle permet, alors que nière générique, on assigne une valeur à un champ de
l’on ajoute un nouvel attribut de notre datatype à une la manière suivante :

28 2/2011
eZ Publish

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


Constante de nommage du champ Nom réel Constante contenant la valeur Valeur par défaut
du champ par défaut
self::CLASSATTRIBUTE _ DEFAULT _ data_text1 self::CLASSATTRIBUTE _ DEFAULT _ ‘’
FIELD EMPTY
self::CLASSATTRIBUTE _ ALLOW _ data_int1 self::CLASSATTRIBUTE _ ALLOW _ 0
SEARCH _ FIELD SEARCH _ DEFAULT
self::CLASSATTRIBUTE _ data_text2 self::CLASSATTRIBUTE _ ‘All’
SEARCHINDEX _ FIELD SEARCHINDEX _ DEFAULT
self::CLASSATTRIBUTE _ data_int2 self::CLASSATTRIBUTE _ 0
BROWSENODE _ FIELD BROWSENODE _ DEFAULT

Tableau 3. Champs de stockage disponibles

Nom du champ Type


data_int1 Entier - int(11)
data_int2 Entier - int(11)
data_int3 Entier - int(11)
data_int4 Entier - int(11)
data_float1 Décimal - float
data_float2 Décimal - float
data_float3 Décimal - float
data_float4 Décimal - float
data_text1 Chaîne de caractères courte - varchar(50)
data_text2 Chaîne de caractères courte - varchar(50)
data_text3 Chaîne de caractères courte - varchar(50)
data_text4 Chaîne moyenne - varchar(255)
data_text5 Chaîne longue - longtext

$classAttribute->setAttribute( <nom_du_champ>, <va- en paramètre une référence du Content Class Attribu-


leur> ); te (classe PHP eZContentClassAttribute) dans lequel
sera stockée la configuration - et de l’objet eZHTTPTool
L’utilisation de constantes de classe est recommandée (détails : http://pubsvn2.ez.no/phpdoc/trunk/html/default/
pour désigner les champs de stockage, ainsi que pour eZHTTPTool.html) permettant de manipuler les données
les valeurs par défaut, entraînant une meilleure facto- transférées via HTTP (POST, GET...).
risation, et une meilleure lisibilité du code, comme le
montre le Listing 10. Validation
Ici on initialise les champs de stockage (en base de La première méthode appelée est validateClassAttrib
données) avec les valeurs suivantes, s’ils n’en contien- uteHTTPInput(...), qui sert à s’assurer que les entrées
nent pas déjà (voir Tableau 2). reçues ont le bon format. On peut imaginer un datatype gé-
rant du texte, dont un élément de configuration serait la taille
Champs de stockage disponible maximale du texte qu’un éditeur peut saisir. Il faudrait, dans
13 champs sont disponibles pour stocker des données cette méthode, s’assurer que l’entrée est bien numérique.
de configuration dans un attribut de classe de contenu. Cette méthode peut retourner 3 statuts différents :
Nous pouvons donc utiliser les 13 à notre convenance
et suivant nos besoins, dans le code de notre datatay- • eZInputValidator::STATE _ ACCEPTED - Les don-
pe, comme décrit dans la partie “Initialisation”. Voici la nées sont validées,
liste des champs disponibles, tous provenant de la table • eZInputValidator::STATE _ INVALID - Les don-
ezcontentclass_attribute (voir Tableau 3). nées sont invalidées et une erreur s’affiche dans le
formulaire de saisie,
Validation, récupération et stockage • eZInputValidator::STATE _ INTERMEDIATE - Ni l’un
Les 3 méthodes que nous allons décrire ci-après per- ni l’autre, une action supplémentaire est nécessaire
mettent de traiter l’information entrée par le webmestre via la méthode fixupClassAttributeHTTPInput(). Il
lorsqu’il publie une classe de contenu, notamment de la convient dans ce cas d’altérer directement les varia-
valider et de la stocker. Chacune de ces méthodes reçoit bles POST via eZHTTPTool.

www.phpsolmag.org 29
Dossier

Listing 14. Utilisation de constantes de classe /**


* Valide l'input au niveau de l'objet de contenu
const ALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE = ' _ * Vérifie si l'ID Amason est un ASIN valide
jvamazonid _ asin _ search _ if _ empty _ ', * @param eZHTTPTool $http
CONTENT _ FIELD _ VARIABLE = ' _ jvamazonid _ * @param string $base Préfixe du nom de la
data _ text _ '; variable POST (Toujours "ContentObjectAttribute")
* @param eZContentObjectAttribute
/** $contentObjectAttribute
* Fetches all variables from the object and * @return eZInputValidator::STATE _ ACCEPTED|eZIn
handles them putValidator::STATE _ INVALID|eZInputValidator::STATE _
* Data store can be done here INTERMEDIATE
* @param eZHTTPTool $http */
* @param string $base POST variable name prefix public function validateObjectAttributeHTTPInput(
(Always "ContentObjectAttribute") $http, $base, $contentObjectAttribute )
* @param eZContentObjectAttribute {
$contentObjectAttribute // Reconstitution du nom du champ
* @return true if fetching of class attributes are $fieldName = $base . self::CONTENT _ FIELD _
successfull, false if not VARIABLE . $contentObjectAttribute->attribute( 'id' );
*/ $returnState = eZInputValidator::STATE _
public function fetchObjectAttributeHTTPInput( ACCEPTED;
$http, $base, $contentObjectAttribute )
{ if( $http->hasPostVariable( $fieldName ) )
// Reconstitution du nom des champs à traiter {
$fieldName = $base . self::CONTENT _ FIELD _ $fieldValue = $http->postVariable( $fieldName
VARIABLE . $contentObjectAttribute->attribute( 'id' ); );
$allowSearchFieldName = $base . self::ALLOW _ if( trim( $fieldValue ) != '' ) // On a bien
SEARCH _ IF _ EMPTY _ VARIABLE . $contentObjectAttribute- une valeur renseignée, on en vérifie la validité
>attribute( 'id' ); {
} // Utilisation de la fonction fetch
amazonadvertising/item _ lookup via PHP
Listing 15. Initialisation de l'attribut d'objet $amazonResult = eZFunctionHandler::
execute( 'amazonadvertising', 'item _ lookup', array(
/** 'id' => $fieldValue
* Initialise l'attribut avant d'afficher le template ) );
d'édition
* Utile pour définir une valeur par défaut. Les // Entrée invalide si le retour
valeurs par défaut doivent être définies dans l'attribut du webservice n'est pas un objet héritant de
de classe. JVAmazonAdvertisingAbstractItem
* @param eZContentObjectAttribute if ( !$amazonResult instanceof
$contentObjectAttribute Attribut d'objet pour la JVAmazonAdvertisingAbstractItem )
nouvelle version {
* @param int $currentVersion Numéro de version. // Ne pas oublier de préciser
NULL s'il s'agit de la première version. l'erreur
* @param eZContentObjectAttribute $validationError = ezpI18n::tr(
$originalContentObjectAttribute Attribut d'objet de la 'extension/jvamazonadvertising/error', 'Invalid Amazon
version précédente product ID' );
* @see eZDataType::initializeObjectAttribute() $contentObjectAttribute-
*/ >setValidationError( $validationError );
public function initializeObjectAttribute( $returnState = eZInputValidator::
$contentObjectAttribute, $currentVersion, STATE _ INVALID;
$originalContentObjectAttribute ) }
{ }
// On ne positionne la valeur par défaut que }
pour la première version.
if( $currentVersion === null ) return $returnState;
{ }
// On récupère la valeur par défaut telle
que définie dans l'attribut de classe et on la positionne /**
dans l'attribut d'objet * "Répare" les données postées
$contentClassAttribute = * Cette méthode n'est appelée que si la méthode
$contentObjectAttribute->contentClassAttribute(); de validation a retourné eZInputValidator::STATE _
$allowSearchDefault = INTERMEDIATE
$contentClassAttribute->attribute( 'data _ int1' ); * @param eZHTTPTool $http
$contentObjectAttribute->setAttribute( * @param string $base
'data _ int', $allowSearchDefault ); * @param eZContentObjectAttribute
} $objectAttribute
} * @see eZDataType::fixupObjectAttributeHTTPInput()
*/
Listing 16. Validation de l'attribut d'objet public function fixupObjectAttributeHTTPInput(
$http, $base, $objectAttribute )
const ALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE = ' _ {
jvamazonid _ asin _ search _ if _ empty _ ', // Le cas échéant, altérer ici les variables
CONTENT _ FIELD _ VARIABLE = ' _ jvamazonid _ POST via $http (eZHTTPTool)
data _ text _ '; }

Le corps de cette méthode est trivial dans notre cas, Récupération et stockage
comme le montre le Listing 11. C’est la méthode fetchClassAttributeHTTPInput()
La méthode fixupClassAttributeHTTPInput() qui assure ces deux fonctions. Nous devons stocker
sera donc vide. Vous aviez suivi ? Parfait. On continue. trois informations :

30 2/2011
eZ Publish

Tableau 4. Séquence d’appel des méthodes de publication


initializeObjectAttribute() Initialise l’attribut, avec une valeur par défaut choisi par exemple dans la clas-
se de contenu
validateObjectAttributeHTTPInput() Validation
fixupObjectAttributeHTTPInput() Méthode post-validation
fetchObjectAttributeHTTPInput() Récupération effective des données postées et stockage
onPublish() 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 Description
data _ 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...)

• [Booléen] Doit-on chercher automatiquement un L’étape suivante pour chaque variable POST est d’en tes-
ASIN dans le catalogue Amazon si le champ de ter l’existence, et si elle est bien présente dans les don-
contenu n’est pas renseigné ? nées reçues, la stocker. La variable $http reçue en pa-
• [Chaîne de caractères] De quels attributs de conte- ramètre, instance (singleton) de la classe eZHTTPTool
nus les termes de recherche seront extraits ? (détails ici : http://pubsvn2.ez.no/phpdoc/trunk/html/de-
• [Chaîne de caractères] Dans quel index de recher- fault/eZHTTPTool.html), expose une interface simple et
che Amazon doit-on effectuer la recherche ? efficace d’accès aux données HTTP. On ne se prive pas
• [Entier] Dans quel noeud du catalogue de recher- de l’utiliser pour la récupération de chacune des 3 infor-
che Amazon doit-on effectuer la recherche ? mations (Listing 13).
Nous avons à ce stade terminé l’implémentation
Nous stockerons ces quatre informations dans les de notre datatype au niveau classe de contenu. Il est
champs respectifs : temps d’aborder le niveau objet de contenu.

• data_int1, Notre datatype au niveau de l’objet


• data_int2, de contenu
• data_text1, Côté PHP
• data_text2. L’essentiel du code au niveau contenu se situe là-enco-
re dans la classe PHP du datatype, dans l’implémenta-
Voici le corps de la méthode (cf Listing 12). tion de différentes méthodes. Nous distinguerons deux
Pour chacune des entrées, on doit reconstituer le groupes de méthodes :
nom des variables POST, en suivant le même schéma
qu’expliqué dans “Construction du nom des <input>”. • Méthodes de publication (validation, stockage du
On s’appuiera pour cela sur la variable $base passée contenu),
en argument à la méthode, contenant la chaîne de ca- • Méthodes de fonctionnement courant (tri, indexa-
ractères ‘ContentClass’, sur l’ID de l’attribut de classe tion, fourniture du contenu brut...).
récupéré dynamiquement, ainsi que sur le nom même
du champ, auquel nous accéderons par une constante, La plupart de ces méthodes ne sont pas obligatoires
évitant le codage en dur. Voici un exemple : mais leur implémentation est vivement conseillée afin
d’avoir un datatype fonctionnellement complet.
$searchFieldName = $base . self::SEARCH_FIELD_
VARIABLE . $classAttribute->attribute( 'id' Méthodes de publication
); Ces méthodes permettent de traiter l’information entrée
équivalent de ce que nos avons observé dans le tem- par le contributeur lorsqu’il publie un contenu, notam-
plate précédemment : ment de la valider et de la stocker.
Chacune de ces méthodes reçoit en paramètre une
<input type=»text» size=»50» name=»ContentClass_ référence du Content Object Attribute (objet de la clas-
jvamazonid_asin_search_field_{$class_ se eZContentObjectAttribute, plus d’information ici :
attribute.id}» ... http://goo.gl/Wv0ux ) - dans lequel sera stocké le contenu

www.phpsolmag.org 31
Dossier

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


$classAttribute = $contentObjectAttribute-
d'objet >contentClassAttribute();
$searchPattern = $classAttribute-
const ALLOW _ SEARCH _ IF _ EMPTY _ VARIABLE = ' _
>attribute( self::CLASSATTRIBUTE _ DEFAULT _ FIELD );
jvamazonid _ asin _ search _ if _ empty _ ',
$searchQuery = $this->getSearchQuery(
CONTENT _ FIELD _ VARIABLE = ' _ jvamazonid _
$searchPattern, $contentObject->attribute( 'id' ) );
data _ text _ ';
$aAmazonResult = $this->doSearch(
$searchQuery, $contentObjectAttribute );
/**

* Récupère toutes les données postées pour
l'attribut d'objet et les traite if( count( $aAmazonResult ) > 0 )
* Le stockage des données s'effectue ici {
* @param eZHTTPTool $http $contentObjectAttribute->setAttribute(
* @param string $base Préfixe du nom de la variable 'data _ text', $aAmazonResult[0]->id );
POST (Toujours "ContentObjectAttribute") $contentObjectAttribute->store();
* @param eZContentObjectAttribute return true;
$contentObjectAttribute }
* @return true si tout est OK }
*/ }
public function fetchObjectAttributeHTTPInput(
$http, $base, $contentObjectAttribute ) /**
{ * Retourne la requête de recherche pour l'attribut
// Reconstitution du nom des champs postés d'objet à partir du motif de recherche défini dans la
$fieldName = $base . self::CONTENT _ FIELD _ classe de contenu
VARIABLE . $contentObjectAttribute->attribute( 'id' ); * @param string $searchPattern
$allowSearchFieldName = $base . self::ALLOW _ * @param int $contentObjectID
SEARCH _ IF _ EMPTY _ VARIABLE . $contentObjectAttribute- * @return string
>attribute( 'id' ); */
private function getSearchQuery( $searchPattern,
// Enregistre l'ASIN dans data _ text $contentObjectID )
if( $http->hasPostVariable( $fieldName ) ) {
{ $contentObject = eZContentObject::fetch(
$contentObjectAttribute->setAttribute( $contentObjectID );
'data _ text', $http->postVariable( $fieldName ) ); $searchValue = '';
} preg _ match _ all( '#<([^>]+)>#U', $searchPattern,
$matches, PREG _ SET _ ORDER ); // Get all attribute
// Enregistre la valeur pour la recherche identifiers in searh pattern (in the form of
automatique <attribute _ identifier>)
if( $http->hasPostVariable(
$allowSearchFieldName ) ) if( count( $matches ) > 0 )
{ {
$contentObjectAttribute->setAttribute( $searchValue = $searchPattern;
'data _ int', 1 ); $dataMap = $contentObject->dataMap();
} // Boucle sur les résultats et vérifie s'il
else s'agit d'identifiers pour cet objet de contenu
{ // Si ce n'est pas le cas, on les supprime
$contentObjectAttribute->setAttribute( foreach( $matches as $match )
'data _ int', 0 ); {
} list( $matchPattern, $identifier ) =
$match;
return true; if( isset( $dataMap[$identifier] ) )
} {
$searchValue = str _ replace(
Listing 18. Traitement post-publication $matchPattern, $dataMap[$identifier]->title(),
$searchValue );
/** }
* Effectue les actions nécessaires sur les données else
de l'attribut après la publication de l'objet {
* Si le contenu de l'attribut est vide, on $searchValue = str _ replace(
recherche un ASIN automatiquement avec une recherche $matchPattern, '', $searchValue );
AWS }
* Retourne true si la valeur est correctement }
stockée
* @param eZContentObjectAttribute $searchValue = trim( $searchValue );
$contentObjectAttribute }
* @param eZContentObject $contentObject L'objet
publié return $searchValue;
* @param array $publishedNodes }
* @return bool
* @see eZDataType::onPublish() /**
*/ * Effectue la recherche d'ASIN dans le catalogue
public function onPublish( $contentObjectAttribute, Amazon
$contentObject, $publishedNodes )
* @param string $searchQuery
{
* @param eZContentObjectAttribute
$content = $contentObjectAttribute->attribute(
$contentObjectAttribute
'data _ text' );
* @return array Array of objects implementing
$allowSearchIfEmpty = (bool)$contentObjectAttrib
IJVAmazonAdvertisingItem (see amazonadvertising.ini for
ute->attribute( 'data _ int' );
exact class)

*/
if( !$content && $allowSearchIfEmpty ) // Pas
private function doSearch( $searchQuery,
de contenu enregistré, on essaye de récupérer l'ASIN
eZContentObjectAttribute $contentObjectAttribute )
automatiquement

32 2/2011
eZ Publish

Listing 18. Traitement post-publication – suite return $aContent;


}
{
$classAttribute = $contentObjectAttribute- /**
>contentClassAttribute(); * Méta-données pour le moteur de recherche
* @param eZContentObjectAttribute
// Now perform an ItemSearch * @return string
$aItemSearchParams = array( */
'keywords' => $searchQuery, public function metaData( $contentObjectAttribute )
'search _ index' => $classAttribute- {
>attribute( self::CLASSATTRIBUTE _ SEARCHINDEX _ FIELD ) return $contentObjectAttribute->attribute( 'data _
); text' );
}
// Is there a BrowseNode to look into ?
if( $classAttribute->attribute( self:: /**
CLASSATTRIBUTE _ BROWSENODE _ FIELD ) > 0 ) * Titre
{ * @param eZContentObjectAttribute
$aRawParams = array( * @name string
'BrowseNode' => $classAttribute- * @return string
>attribute( self::CLASSATTRIBUTE _ BROWSENODE _ FIELD ) */
); public function title( $contentObjectAttribute, $name
$aItemSearchParams['raw _ params'] = = null )
$aRawParams; {
} return $contentObjectAttribute->attribute( 'data _
text' );
$aAmazonResult = eZFunctionHandler::execute( }
'amazonadvertising', 'item _ search', $aItemSearchParams );
return $aAmazonResult; /**
} * @return true
*/
public function isIndexable()
Listing 19. Implémentation des méthodes de fonctionnement {
courant return true;
}
/**
* Retourne true si l'attribut d'objet contient du /**
contenu * Initialise l'attribut d'objet à partir d'une
* @param eZContentObjectAttribute représentation textuelle
$contentObjectAttribute * @param eZContentObjectAttribute
* @return bool * @param string
* @see eZDataType::hasObjectAttributeContent() * @see eZDataType::fromString()
*/ */
public function hasObjectAttributeContent( public function fromString( $objectAttribute, $string
$contentObjectAttribute ) )
{ {
return trim( $contentObjectAttribute->attribute( $objectAttribute->setAttribute( 'data _ text',
'data _ text' ) ) != ''; $string );
} }

/** /**
* Retourne le contenu. * Retourne la représentation textuelle de l'attribut
* Le résultat est un tableau associatif : d'objet
* - search _ query : La requête de * @param eZContentObjectAttribute
recherche construite à partir du motif de recherche de * @see eZDataType::toString()
l'attribut de classe * @return string
* - search _ pattern : Le motif de */
recherche public function toString( $objectAttribute )
* - search _ index : L'index de recherche {
tel qu'il a été renseigné dans l'attribut de classe (ex. return $objectAttribute->attribute( 'data _ text' );
MP3Downloads) }
* - product _ id : L'ID de produit Amazon
(ASIN, ISBN, ...) /**
* @param eZContentObjectAttribute * Retourne le type de tri
* @return array * @see eZDataType::sortKeyType()
*/ */
public function objectAttributeContent( public function sortKeyType()
$contentObjectAttribute ) {
{ return 'string';
$classAttribute = $contentObjectAttribute- }
>contentClassAttribute();
$searchPattern = $classAttribute->attribute( self:: /**
CLASSATTRIBUTE _ DEFAULT _ FIELD ); * Retourne la clé de tri
$aContent = array( * @param eZContentObjectAttribute
'search _ query' => $this- $contentObjectAttribute
>getSearchQuery( $searchPattern, $contentObjectAttribute- * @return string
>attribute( 'contentobject _ id' ) ), * @see eZDataType::sortKey()
'search _ pattern' => $searchPattern, */
'search _ index' => public function sortKey( $contentObjectAttribute )
$classAttribute->attribute( self::CLASSATTRIBUTE _ {
SEARCHINDEX _ FIELD ), return strtolower( $contentObjectAttribute-
'product _ id' => >attribute( 'data _ text' ) );
$contentObjectAttribute->attribute( 'data _ text' ) }
);

www.phpsolmag.org 33
Dossier

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


Méthode Description
bool hasObjectAttributeContent( Retourne true si l’attribut d’objet contient réellement du contenu.
$contentObjectAttribute ) Appelée notamment dans un template via $node.data _ map.
myattribute.has _ content
mixed objectAttributeContent( Retourne le contenu brut. Il peut s’agir d’une simple chaîne de caractères
$contentObjectAttribute ) comme d’un tableau ou même un objet PHP
bool isIndexable() Retourne true si le datatype supporte l’indexation par le moteur de re-
cherche
string|array metadata( Doit retourner le texte brut indexé par le moteur de recherche.
$contentObjectAttribute ) 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.
string title( $contentObjectAttribute, Retourne la valeur textuelle telle qu’elle doit apparaître si l’attribut est uti-
$name = null ) lisé dans le motif du nom de l’objet de contenu
void fromString( $contentObjectAttribute, Initialise l’attribut d’objet à partir d’une chaîne de caractère. Peut être très
$string ) utile dans le cadre de scripts en ligne de commande
string toString( $contentObjectAttribute ) Retourne la représentation en chaîne de caractères de l’attribut d’objet
string sortKeyType() Retourne le type de tri :
• string,
• int,
• false si le tri n’est pas supporté.
Le type de tri float n’est pas supporté
string sortKey( $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
void trashStoredObjectAttribute( (facultatif) Effectue toutes les actions nécessaires sur l’attribut lorsque
$contentObjectAttribute, $version = null ) l’objet est déplacé vers la corbeille.
Utile notamment si le contenu de l’attribut est stocké dans une table externe.
void deleteStoredObjectAttribute( (facultatif) Effectue toutes les actions nécessaires sur l’attribut lorsque
$contentObjectAttribute, $version = null ) l’objet est définitivement supprimé.
Utile notamment si le contenu de l’attribut est stocké dans une table externe.

- et de l’objet eZHTTPTool permettant de manipuler les du/des champ(s) posté(s), de le(s) valider et d’en récu-
données transférées via HTTP (POST, GET...). La seule pérer la/les valeur(s). Pour ce faire, comme pour la clas-
exception à cela est la méthode onPublish(), un peu se de contenu, il est préférable d’utiliser des constantes
particulière, qui sera vue en détail. de classe pour la partie fixe du nom de la variable POST
Au moment de la publication d’un objet de contenu, (<nom_du_champ>), afin d’éviter de se retrouver avec
eZ Publish itère sur ses différents attributs et appelle des valeurs en dur dans le code (Listing 14).
toutes les méthodes de publication de leurs datatypes Les principales méthodes de publication sont, dans
respectifs. Cependant, ne connaissant pas à l’avance le leur ordre d’appel (voir Tableau 4).
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. Initialisation de l’attribut
C’est au datatype, et donc à vous, de savoir reconnaître La méthode initializeObjectAttribute() permet
quel(s) est/sont le(s) champ(s) concerné(s). d’insérer une valeur par défaut le cas échéant (premiè-
Par convention, le nom du/des champ(s) d’un attri- re version d’un objet). Dans notre cas, elle permettra de
but posté est constitué suivant le motif suivant : pré-cocher ou non la case à cocher autorisant la recher-
che automatique d’ASIN (Listing 15).
ContentObjectAttribute_<nom_du_champ>_<identi-
fiant_attribut_numérique_unique> Validation des données postées
Il est toujours utile de valider les données postées par
A noter que la valeur ContentObjectAttribute dans un utilisateur/contributeur, que ce soit par sécurité ou
le nom du champ est passée dans le paramètre $base pour s’assurer que ce qui a été saisi correspond bien
des méthodes de publication. Note : Cette règle de nom- à ce qui est attendu côté serveur. De cette manière on
mage est identique à celle utilisée au niveau classe de évite souvent bien des erreurs et/ou des failles poten-
contenu. Ainsi, il vous appartient de reconstruire le nom tielles.

34 2/2011
eZ Publish

C’est la méthode validateObjectAttributeHTTP


Listing 20. extension/jvamazonadvertising/design/standard/
Input() qui joue ce rôle ici. Elle reçoit en paramètre templates/content/datatype/view/jvamazonid.tpl
une instance de eZHTTPTool permettant de retrouver
{if $attribute.has _ content}
les données saisies, ainsi que l’attribut d’objet. Les va- {* Appel du webservice Amazon pour récupérer
leurs de retour possibles sont les même que pour la va- les informations du produit à partir de son ASIN
renseigné dans l'attribut *}
lidation au niveau classe de contenu, à l’exception que {def $amazonItem = fetch(
lorsque eZInputValidator::STATE_INTERMEDIATE 'amazonadvertising', 'item _ lookup', hash( 'id',
$attribute.data _ text ) )}
sera retourné, c’est la méthode fixupObjectAttribute <a href={$amazonItem.url|ezurl} target=" _
HTTPInput() qui sera invoquée (voir Listing 15). blank">{$attribute.data _ text}</a>{if $amazonItem.
binding} ({$amazonItem.binding}){/if} {* Binding
correspond à la catégorie Amazon du produit *}
Récupération et stockage {/if}

Une fois les données validées, il est nécessaire de les


Listing 21. extension/jvamazonadvertising/settings/
traiter et de les stocker dans la base de données. Cette datatype.ini.append.php
étape est assurée par la méthode fetchObjectAttri
[ViewSettings]
buteHTTPInput(). Un attribut d’objet peut stocker ses GroupedInput[]=jvamazonid
données primaires dans 3 types de champs de la table
ezcontentobject_attribute (voir Table 5). Listing 22. Template d'édition de l'attribut d'objet
Dans notre cas, nous stockerons l’ASIN directement {* Champ ASIN - Identifiant Amazon *}
dans data_text et la valeur booléenne pour la recherche <input id="ezcoa-{$attribute.contentclassattribute _
id} _ {$attribute.contentclass _ attribute _ identifier}"
automatique dans data_int (Listing 17). class="box ezcc-{$attribute.object.content _
class.identifier} ezcca-{$attribute.object.content _
class.identifier} _ {$attribute.contentclass _
Traitement post-publication attribute _ identifier}"
Dans certains cas, il peut s’avérer utile d’effectuer des type="text" size="70"
name="{$attribute _ base} _ jvamazonid _ data _
actions une fois que l’objet ait été publié, notamment si text _ {$attribute.id}"
la valeur d’un attribut est dépendante d’autres attributs. value="{$attribute.data _ text|wash( xhtml )}"
/>
C’est notre cas lorsque le contributeur coche la case de <br /><br />
recherche automatique. En effet, nous devons décoder
{* Case à cocher pour autoriser une recherche
le motif de recherche enregistré dans la classe de conte- d'ASIN automatique si le champ ASIN est vide à la
nu et remplacer les identifiants par les valeurs publiées. publication *}
<input id="ezcoa-{$attribute _ base}-{$attribute.
Dans ce genre de cas nous utilisons la méthode contentclassattribute _ id} _ {$attribute.
onPublish(). Elle permet en effet d’accéder non seu- contentclass _ attribute _ identifier} _ allow _ search _
empty"
lement à l’attribut sur lequel on travaille, mais égale- class="ezcc-{$attribute.object.content _ class.
ment à l’objet de contenu entier, dans l’état publié, ain- identifier} ezcca-{$attribute.object.content _ class.
identifier} _ {$attribute.contentclass _ attribute _
si qu’aux différents noeuds assignés. Nous allons ainsi identifier}"
pouvoir effectuer notre recherche de produit et récupé- type="checkbox"
name="{$attribute _ base} _ jvamazonid _ asin _
rer le premier de la liste retournée (Listing 18). search _ if _ empty _ {$attribute.id}"{if $attribute.
data _ int} checked="checked"{/if} />
<label style="display:inline;" for="ezcoa-
Méthodes de fonctionnement courant {$attribute _ base}-{$attribute.
Pour leur fonctionnement courant et en dehors de la pu- contentclassattribute _ id} _ {$attribute.
contentclass _ attribute _ identifier} _ allow _ search _
blication tous les datatypes doivent implémenter un cer- empty">
tain nombre de méthodes permettant de fournir au systè- {"Automatically search Amazon ID on publish
if empty"|i18n( "design/standard/class/datatype/
me bon nombre d’informations utiles comme les données jvamazonid" )}
indexées par le moteur de recherche, la façon de trier les </label>

données ou encore la restitution du contenu brut.


Listing 23. extension/jvamazonadvertising/settings/
Bien que ce ne soit pas strictement le cas, il convient de datatype.ini.append.php
considérer les méthodes listées ci-dessous comme obliga-
[EditSettings]
toires pour chaque datatype (voir Tableau 6 et Listing 19). GroupedInput[]=jvamazonid

Côté template Listing 24. Bouton de CustomHTTPAction


Comme pour la partie classe de contenu, 2 templates <input class="button jvamazonid _ search _ button"
sont nécessaires pour la partie contenu : type="submit"
id="jvamazonid _ search _ button-{$attribute.
id}"
• Le template de vue ou view template, pour la vue name="CustomActionButton[{$attribute.id} _
search _ asin]"
de l’objet par défaut (prévisualisation dans l’admin value="{"Search Amazon ID"|i18n( "design/
par exemple), standard/content/datatype/jvamazonid" )}" />

• Le template d’édition ou edit template.

www.phpsolmag.org 35
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,
* comme la suppression d'une image avec le datatype eZImage,
* ou comme l'ajout d'une nouvelle ligne dans une matrice
* @see kernel/classes/eZDataType::customObjectAttributeHTTPAction()
* @param eZHTTPTool $http
* @param string $action Le nom de l'action
* @param eZContentObjectAttribute $contentObjectAttribute
* @param array $parameters Tableau associatif
* - module (Référence au module de contenu actuel)
* - current-redirection-uri (URI qui sera utilisé pour rediriger l'utilisateur
après l'action)
* - base _ name (généralement ContentObjectAttribute)
*/
public function customObjectAttributeHTTPAction( $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érer toute la datamap

$classAttribute = $contentObjectAttribute->contentClassAttribute();
$searchPattern = $classAttribute->attribute( self::CLASSATTRIBUTE _ DEFAULT _ FIELD );
$searchQuery = $this->getSearchQuery( $searchPattern, $contentObjectID );
$aAmazonResult = $this->doSearch( $searchQuery, $contentObjectAttribute );
if( count( $aAmazonResult ) > 0 )
{
// On stocke les résultats dans la propriété "HTTP value" qui est temporaire et accessible via
l'attribut "value" dans le template
// $attribute.value
$contentObjectAttribute->setHTTPValue( array( 'search _ results' => $aAmazonResult ) );
}
else // Pas de résultat, on indique -1
{
$contentObjectAttribute->setHTTPValue( array( 'search _ results' => -1 ) );
}
}
}

Listing 26. Affichage des résultats de recherche


{* On ajoute jQuery et le code JS *}
{ezscript _ require( array( 'ezjsc::jquery', 'jvamazonid.js' ) )}

<a name="jvamazonid _ {$attribute.id}"></a>


{* Champ ASIN - Identifiant Amazon *}
<input id="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 />

{* Bouton de recherche *}
<input class="button jvamazonid _ search _ button" type="submit"
id="jvamazonid _ search _ button-{$attribute.id}"
name="CustomActionButton[{$attribute.id} _ search _ asin]"
value="{"Search Amazon ID"|i18n( "design/standard/content/datatype/jvamazonid" )}" />

{* Case à cocher pour autoriser une recherche d'ASIN automatique si le champ ASIN est vide à la publication *}
<input id="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} />
<label style="display:inline;" for="ezcoa-{$attribute _ base}-{$attribute.contentclassattribute _ id} _ {$attribute.
contentclass _ attribute _ identifier} _ allow _ search _ empty">
{"Automatically search Amazon ID on publish if empty"|i18n( "design/standard/content/datatype/jvamazonid" )}
</label>

{* Le bouton de recherche a été cliqué *}


{if is _ set( $attribute.value.search _ results )}
{if $attribute.value.search _ results|ne( -1 )}

{* On redirige sur l'ancre de l'attribut *}
<script type="text/javascript">document.location = document.location+'#jvamazonid _ {$attribute.id}';</script>

36 2/2011
eZ Publish

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


<p>
<strong>{'Product search results'} :</strong><br />
<em>{'Click on the right product name to fill the field'}.</em>
</p>
<table class="list" cellspacing="0">
<tr>
<th>{'Product name'|i18n( 'design/standard/content/datatype/jvamazonid' )}</th>
<th>{'Product image'|i18n( 'design/standard/content/datatype/jvamazonid' )}</th>
<th>{'Main category'|i18n( 'design/standard/content/datatype/jvamazonid' )}</th>
<th>{'Product group'|i18n( 'design/standard/content/datatype/jvamazonid' )}</th>
<th>{'Product link'|i18n( 'design/standard/content/datatype/jvamazonid' )}</th>
</tr>
{foreach $attribute.value.search _ results as $product}

<tr>
<td><a href="javascript:;" class="jvamazonid _ result" id="amazonproduct _ {$attribute.id} _ {$product.
id}">{$product.title|wash}</a></td>
<td>{if $product.image}<img src={$product.image|ezurl} />{/if}</td>
<td>{$product.binding}</td>
<td>{$product.productgroup}</td>
<td><a href={$product.url|ezurl} target=" _ blank">{$product.url|shorten( 50 )}</a></td>
</tr>
{/foreach}
</table>
{else}

<p><em>
{'No product has been found in Amazon catalog for the search query "%searchquery"'|i18n( 'design/standard/
content/datatype/jvamazonid',, hash( '%searchquery', $attribute.content.search _ query ) )}
</em></p>
{/if}
{/if}

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


Paramètre Description
eZHTTPTool $http Instance de eZHTTPTool permettant de manipuler les variables passées par HTTP
string $action Le nom de l’action (search_asin dans notre cas)
eZContentObjectAttribute Référence à notre attribut d’objet
$contentObjectAttribute
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”).

Template de vue rement altérée du fait que le cache de vue s’applique


Ce template est le plus simple à mettre en place. Il s’agit dans le cas présent; le webservice ne sera donc appelé
en effet ici de se contenter d’afficher le contenu de l’at- qu’une seule fois.
tribut avec un minimum de formatage. Dans l’admin, La fonction item_lookup renvoie un objet de type
ce contenu sera systématiquement positionné dans un JVAmazonAdvertisingResultItem. Cet objet contient
conteneur de type bloc. notamment les attributs url et binding (catégorie Ama-
Dans le cas qui nous intéresse, nous souhaitons zon) du produit que nous souhaitons afficher. A noter
simplement afficher un lien sponsorisé renvoyant vers que l’attribut url est automatiquement sponsorisé si
le produit Amazon correspondant avec en libellé son vous avez activé la fonctionnalité dans votre surchar-
ASIN, ainsi que sa catégorie le cas échéant. ge de amazonadvertising.ini ([AssociateSettings].
Ainsi, nous allons vérifier que l’attribut a bien un AssociateEnabled & [AssociateSettings].
contenu d’enregistré. Si tel est le cas, nous interrogeons AssociateTag).
le webservice Amazon par l’intermédiaire de la fonction Pour plus de lisibilité, il est possible de grouper l’af-
fetch item_lookup mise à disposition par l’extension fichage des éléments de l’attribut dans un <fieldset>.
jvAmazonAdvertising, ce afin de récupérer toutes les in- Pour ce faire, il suffit d’éditer une surcharge de data-
formations concernant le produit (Listing 20). Pour plus type.ini afin de signifier que nous souhaitons une vue
d’informations sur les fonctions fetch, RDV aux adres- groupée (Listing 21).
ses http://goo.gl/oHMfk et http://goo.gl/OM1ZX. Ainsi, le résultat du template sera contenu dans un
Bien que le webservice soit interrogé en temps réel, tag <fieldset> ayant pour légende le nom de l’attribut
la performance d’affichage ne s’en trouvera que légè- tel qu’il a été défini dans la classe de contenu.

www.phpsolmag.org 37
Dossier

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

Template d’édition Actions HTTP spécifiques


Le template d’édition est celui qui s’affiche lorsqu’un Parfois, se contenter de stocker l’information n’est
contributeur édite un contenu. Il n’est pas forcément pas suffisant. Il peut en effet être utile d’effectuer des
beaucoup plus complexe que le template de vue actions intermédiaires qui ne dépendent pas directe-
mais nécessite une plus grande rigueur. En effet, il ment de la publication. C’est par exemple le cas pour
est nécessaire de s’assurer que chaque identifiant le datatype ezimage lorsque l’on supprime une image
fourni dans ce template soit à la fois unique et iden- stockée.
tifiable par le datatype au niveau du code PHP. Pour Dans notre cas, nous souhaitons pouvoir effectuer
ce faire, il est d’usage d’utiliser un nom de champ une recherche de produit Amazon directement depuis
classique suivi de l’identifiant unique de l’attribut. De l’interface d’édition, sans avoir à publier, ce qui nous
cette manière, chaque champ ou groupe de champs permet de nous assurer que le produit retourné corres-
reste unique et relié à l’attribut. Il est également pond bien au résultat attendu.
courant de préfixer le nom du/des champ(s) par le Pour ce faire, eZ Publish met à notre disposition les
contenu de la variable $attribute_base, contenant Custom HTTP Actions. Cela consiste en une méthode
le plus souvent la valeur ContentObjectAttribute dédiée déclenchée par la soumission du formulaire d’édi-
(Listing 22). tion de l’objet via un bouton submit spécifique. Ce bou-
A noter que ces éléments sont par convention sépa- ton a de spécial simplement le fait d’être nommé sur le
rés par des underscores pour donner le motif suivant : motif suivant : CustomActionButton[<id_attribut>_
<nom_action>]. Ainsi, pour notre recherche, nous al-
ContentObjectAttribute_<nom_du_champ>_<identi- lons définir une action nommée search_asin, ce qui
fiant_unique_attribut> donnera le code du Listing 24.
Dans notre classe PHP, nous allons rajouter la mé-
Dans notre cas, nous aurons donc un champ texte per- thode customObjectAttributeHTTPAction(). Elle se-
mettant de saisir l’ASIN, ainsi qu’une case à cocher in- ra appelée si un contributeur clique sur le CustomAc-
diquant si nous souhaitons chercher un produit corres- tionButton. Cette méthode reçoit 4 paramètres (voir
pondant à notre contenu dans le catalogue Amazon au Tableau 7).
moment de la publication. Cette recherche sera effec- Dans cette méthode, nous allons simplement faire
tuée sur la base du motif renseigné dans la classe de la même opération que dans la méthode onPublish(),
contenu. mais en gardant tous les résultats afin de les afficher
De la même manière que pour le template de vue, il au contributeur afin qu’il choisisse le produit qui lui
est possible de grouper les champs dans un <fieldset> convient le mieux. Les résultats seront stockés dans
en activant le Grouped Input (Listing 23). une variable spéciale de $contentObjectAttribute,

38 2/2011
eZ Publish

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,
• community@ez.no – 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

HTTPValue. Cette variable est directement accessible Nous vous invitons à venir retrouver la communau-
depuis le template du datatype ($attribute.value) té eZ Publish sur son portail : http://share.ez.no, point
et nous pouvons y stocker n’importe quel type de don- d’entrée principal. On y retrouve une vaste base de
nées. Le Listing 25 illustre l’action. connaissances (didacticiels), des blogs d’experts, des
Dans le template, comme le montre le Listing 26, forums hyperactifs, un canal IRC, du twitter, des flux
nous affichons les résultats de la recherche. Le contri- RSS, ainsi que la possibilité de développement colla-
buteur aura juste à cliquer sur le nom du bon produit boratif d’extensions, ainsi que du noyau d’eZ Publish
pour remplir le champ texte avec son ASIN via Javas- même, conjointement avec l’équipe d’eZ (eZ Publish
cript (Listing 27). Voici le résultat final (voir Figure 6). Community Project).

Conclusion A bientôt !
Aprè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- Jérôme Vieilledent
sance d’un datatype eZ Publish. Nous espérons vous Nicolas Pastorino
avoir montré comment vous pourrez l’exploiter dans Jérôme Vieilledent – Software Engineer at eZ Systems – Twitter :
vos cas métiers concrets. @jvieilledent
Jérôme est un développeur web autodidacte. Il a appris le PHP par
Listing 27. Code JS 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
$(document).ready(function() {
$('a.jvamazonid _ result').click(function() { d’ingénierie logicielle d’eZ Systems, depuis Lyon.
var aID = $(this).attr('id').split(' _ '); Nicolas Pastorino – Director Community at eZ Systems – Twitter :
var attributeID = aID[1];
var asin = aID[2]; @jeanvoye – nfrp@ez.no
var nameAttribute = 'ContentObjectAttribute _ Consultant et formateur sur eZ Publish de 2005 à 2008, d’abord en
jvamazonid _ data _ text _ +attributeID;
Norvège puis pour la région Europe de l’Ouest, il a ensuite rejoint
$('input[name="'+nameAttribute+'"]').val(asin); 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.

www.phpsolmag.org 39
Pratique

Écrire
un Web Service en PHP
Des 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 : Ce qu’il faut savoir :


• La création d'un WSDL. • Le lecteur devra avoir des notions sur la technologie SOAP
• L'utilisation des classes SoapClient et SoapServer. et PHP.
• Comment créer un Web Service via PHP.

C
et article vous présentera dans un premier pour la définition de l’interface du service sont définis
temps les services de l’interface par l’intermé- au sein de l’élément types.
diaire du WSDL, abordera la mise en oeuvre du La section message définit deux messages. Le pre-
service en PHP et montrera comment déployer le ser- mier est getResultRequest, qui est une requête pour
vice sur votre serveur Web. relayer le message getResult et prend deux paramè-
tres de type int nommés : integer1, integer2 et un
Définir les services de l’interface paramètre de type string nommé : operation. L’autre
Nous allons essayer d’écrire notre propre Web Servi- message est getResultResponse, qui est la réponse
ce SOAP, qui réalisera l’addition / soustraction de deux au message getResult, contenant une valeur de type
entiers. La première tâche est de créer un document int, nommé result.
WSDL décrivant notre service dans un format compré- La section portType combine plusieurs éléments
hensible par les clients. message pour composer une opération : getResult.
Chaque opération se réfère à un message en entrée
Contenu (input) et à des messages en sortie (output). L’élé-
Dans la mesure où il s’agit d’un standard basé sur XML, ment <binding> décrit les spécifications concrètes de
la structure du WSDL vous semblera familière si vous la manière dont le Web Service sera implémenté et dé-
avez une connaissance un peu approfondie du XML. Le finit la façon dont les messages doivent être transmis. Il
fichier WSDL se divise en plusieurs parties : un ensem- spécifie également les espaces de noms et la valeur de
ble d’éléments décrivant les types de données utilisés l’entête soapAction pour la méthode getResult. Enfin,
par le service, les messages que le service peut rece- le service du document permet d’invoquer le service
voir, ainsi que les liaisons SOAP associées à chaque donné, ce qui sert à regrouper un ensemble de ports
message. reliés (port). La plupart du temps, c’est une URL invo-
Le document WSDL commence par l’entête XML quant un service SOAP.
standard contenant un identificateur de version <?xml
version=»1.0»?>, et que l’élément racine du docu- Conseil de création
ment se nomme <definitions>. Ce dernier, donnant Voici quelques conseils pour créer votre document
le nom du service, peut prendre plusieurs attributs fa- WSDL :
cultatifs, qui le décrivent et déclarent les espaces de
noms utilisés (namespaces) à utiliser dans le reste du • Documentez toujours votre fichier WSDL, à la fois par
document. Ensuite, les types complexes nécessaires le biais de la balise documentation, et en fournissant

40 2/2011
Web Service

de la documentation dans un fichier externe au docu-


Listing 1. Partie 1 : Définitions
ment WSDL.
• Utilisez la recommandation 2001 de XML Schema. <?xml version="1.0"?>

• Utilisez l’élément type, dans tous les cas, ne faites <!-- partie 1 : Definitions -->
pas référence aux types dans l’élément message.
<definitions name='getResult'
targetNamespace='http://example.org/getResult'
Exemples xmlns:tns='http://example.org/getResult'
xmlns:soap='http://schemas.xmlsoap.org/wsdl/soap/'
Je vais vous décrire pas à pas la construction du xmlns:xsd='http://www.w3.org/2001/XMLSchema'
WSDL : xmlns:soapenc='http://schemas.xmlsoap.org/soap/
encoding/'
xmlns:wsdl='http://schemas.xmlsoap.org/wsdl/'
• Le Listing 1 commence par définir l’entête XML, xmlns='http://schemas.xmlsoap.org/wsdl/'>
...
puis l’élément definitions. </definitions>
• Le Listing 2 présente l’élément message.
Listing 2. Partie 2 : Message
Le Listing 3 présente l’élément PortType. Un port est ...
<message name='getResultRequest'>
simplement une suite d’opérations. Dans de nombreux <part name='operation' type='xsd:string'/>
langages de programmation, on appelle ceci une biblio- <part name='integer1' type='xsd:int'/>
<part name='integer2' type='xsd:int'/>
thèque, un module ou une classe, mais dans le mon- </message>
de des Web Services, les points de connexion sont <message name='getResultResponse'>
<part name='result' type='xsd:string'/>
des ports, et la définition abstraite d’un port est appe- </message>
lée Type de Port. Ici, nous créons la définition abstraite ...

d’une procédure getResult, en WSDL, dont l’input est Listing 3. Partie 3 : PortType
le message d’invocation de la méthode, et l’output le
...
message de retour de la méthode. <portType name='getResultPortType'>
Le Listing 4 va décrire l’élément binding du WSDL. <operation name='getResult'>
<input message='tns:getResultRequest'/>
Cette partie va nous permettre de rattacher le port à SOAP. <output message='tns:getResultResponse'/>
Les types de ports sont indépendants du protocole. Ici, </operation>
</portType>
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 Listing 4. Partie 4 : Binding
SOAP (encodingStyle='http://schemas.xmlsoap. ...
org/soap/encoding/'). <binding name='getResultBinding' type='tns:
getResultPortType'>
Le Listing 5 présente l’élément service. Le service, <soap:binding style='rpc' transport='http://
déclaré ci-dessous, est défini avec une seule occurren- schemas.xmlsoap.org/soap/http'/>
<operation name='getResult'>
ce du port getResultPort, ainsi que la définition de ce <soap:operation soapAction='urn:xmethods-
service. Le port est lié à la liaison getResultBinding. delayed-quotes#getResult'/>
<input>
Enfin, on donne l’information sur l’URL du point de sor- <soap:body use='encoded' namespace='urn:
xmethods-delayed-calcul'
tie de l’application (ici l’URL du serveur SOAP). encodingStyle='http://schemas.xmlsoap.org/
Remarque : Notez bien que si on ne trouve qu’un seul soap/encoding/'/>
</input>
élément documentation dans cet exemple, il faudra nor- <output>
malement en déclarer un pour chaque élément… L’exem- <soap:body use='encoded' namespace='urn:
xmethods-delayed-calcul'
ple montre bien le caractère verbeux de WSDL. Cepen- encodingStyle='http://schemas.xmlsoap.org/
dant, il est dans la pratique assez rare d’écrire le corps du soap/encoding/'/>
</output>
fichier à la main : la plupart des environnements de déve- </operation>
loppement savent en effet générer le fichier WSDL sur la </binding>
...
base des caractéristiques du service développé.
Listing 5. Partie 5 : Service
Séparation et capitalisation ...
La facilité de maintenance est toujours au cœur des préoc- <service name='getResultService'>
<documentation>Retourne le résultat de l'opération
cupations des éditeurs. Il se révèle de ce point de vue très </documentation>
bénéfique de distinguer dans un contrat WSDL l’aspect <port name='getResultPort' binding='getResultBindi
ng'>
Abstrait (ce qui est indépendant de la technologie et des <soap:address location='http://votre-adresse.com/
protocoles) et l’aspect Concret (ce qui est dépendant). samples/soap-server.php'/>
</port>
Cette distinction permet de joindre ou réutiliser des sous- </service>
parties de modèles WSDL et de les redéployer à loisir : ...
le WSDL est classiquement séparable en trois parties :

www.phpsolmag.org 41
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 client
Nous 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 service
Aprè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
Figure 1. Définir les services de l’interface 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
Figure 2. Listing des méthodes disponibles par le serveur SOAP 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.
Figure 3. Résultat de l’appel à la méthode de l’addition entre 3 et 4 Dans un premier temps on définit la méthode
getResult($operation, $integer1, $integer2)
• Les schémas d’une part, qui définissent les infor- et le traitement de celle-ci. Le retour sera le résultat de
mations contenues dans les messages reçus/émis l’opération. Ensuite on instancie l’objet SoapServer en
par le service : ces schémas peuvent préexister mode WSDL. Pour cela, on lui passe en premier argu-
avant la notion de service car ils représentent les ment le WSDL operation.wsdl (On suppose qu’il est
entités métier. dans le même répertoire que notre fichier soap-server.
• Une définition abstraite du service, contenant sa php). Si vous voulez travailler en mode non WSDL il
description sans les modalités d’implémentation suffit de définir le paramètre à NULL et déclarer l’URI.
(protocole et déploiement). Remarque : Ici, pour une raison de rapidité, j’ai seu-
• Une partie concrète qui propose les liaisons d’une lement renseigné ce paramètre mais d’autres sont pos-
interface selon un certain format avec un protocole sibles. Je vous propose de lire la documentation : http://
donné à une adresse donné. www.php.net/manual/fr/soapserver.soapserver.php.

42 2/2011
Web Service

Listing 6. Implémentation du SoapServer


Sur Internet
/*
• http://www.blog-nouvelles-technologies.fr/archives/845/ * Fonction getResult sert ŕ addition ou soustraire 2
– Première partie de l’article sur l’écriture d’un Web Ser- entiers et retourne le résultat
* @param $operation Type de l'opération (add/substract)
vice en PHP (3 parties disponibles) – Aspects complé- * @param $integer1 Entier 1
mentaires, * @param $integer2 Entier 2
• http://www.w3.org/TR/wsdl – Description du langage * @return $result Résultat de l'opération
*/
WSDL, function getResult($operation, $integer1, $integer2) {
• http://pear.php.net/ – Une autre implémentation SOAP $result = 0;
pour PHP : PEAR::SOAP, if ($operation == "add") {
• http://dietrich.ganx4.com/nusoap – Une autre implémen- $result = $integer1 + $integer2;
}
tation SOAP pour PHP : NuSOAP, if ($operation == "substract") {
• http://www.poedit.net/ – Site web du logiciel POEdit po- $result = $integer1 -$integer2;
ur l’édition de fichiers de traduction, }
return $result;
• http://www.cakephp-fr.org – Site de la communauté }
francophone de CakePHP (forums …),
• contact@blog-nouvelles-technologies.fr – Adresse de // Désactivation du cache WSDL
l’auteur de l’article pour toutes questions ou remar- ini _ set("soap.wsdl _ cache _ enabled", "0"); 

ques. // Catch l'erreur si l'instanciation la classe SoapServer
// échoue, on retourne l'erreur
try {
$server = new SoapServer('operation.wsdl');
Ensuite on ajoute la méthode getResult au serveur qui // On ajoute la méthode "getResult" que le serveur va
gérer
va gérer les requêtes SOAP. Pour passer plusieurs fonc- $server->addFunction("getResult");
tions au serveur il suffit d’utiliser un tableau de noms de } catch (Exception $e) {
echo 'erreur'.$e;
fonctions : $server->addFunction(array(«getResult», }
«getInt»)); // Si l'appel provient d'une requęte POST (Web Service)
if ($ _ SERVER['REQUEST _ METHOD'] == 'POST') {
Enfin on lance le serveur SOAP lorsque la requête // On lance le serveur SOAP
est supposée être dans les données brutes POST de la $server->handle();
}
requête HTTP. Dans le cas où le serveur ne reçoit pas else {
une requête POST on affiche la liste des fonctions dis- echo '<strong>This SOAP server can handle following
functions : </strong>';
ponibles par le Web Service. Vous enregistrez le fichier, echo '<ul>';
et si tout se passe bien on se donne rendez-vous sur foreach($server -> getFunctions() as $func) {
echo '<li>' , $func , '</li>';
cette adresse : http://localhost/server-soap.php. La Fi- }
echo '</ul>';
gure 2 montre le résultat que vous devez obtenir. Tout }
marche ? HYPERLINK "http://exemple.com/" HYPERLINK "http://
exemple.com/fre

Appel du Web Service par le client Listing 7. Implémentation du SoapClient


Voici un client pour accéder à notre propre serveur
$client = new SoapClient("operation.wsdl");
SOAP. Il suffit de créer un nouveau fichier PHP et de echo $client->getResult("add", 3, 4);
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 Yohann Poiron
nous donne l’affichage du résultat. Autodidacte 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
Conclusion ainsi que j’ai récemment crée un blog me permettant de parler de ma
Dans cet article, j’ai décrit seulement les fonctionnalités veille personnelle et de mon quotidien professionnel autour des ré-
de base de l’extension SOAP pour PHP. En réalité, ce flexions et commentaires sur les thèmes des nouvelles technologies,
dernier est beaucoup plus complexe et permet de faire des services innovants, du développement, de l’interopérabilité dans
bien d’autres choses. Mais il n’est pas possible de le domaine médical, des réseaux sociaux et du buzz marketing.
vous montrer toutes ces caractéristiques dans un article http://www.blog-nouvelles-technologies.fr
aussi court. L’extension SOAP est totalement documen- Je suis actuellement engagé en tant que responsable interopérabilité
tée ici : http://www.php.net/manual/en/book.soap.php et développeur Web pour la société Openxtrem pour laquelle je réali-
Pour commencer le déploiement simple d’un Web se des développements permettant de commercialiser le système en
Service si vous respectez les quelques principes de ba- tant que pur EAI (Enterprise Application Integration) pour les structu-
ses présentés ici vous n’aurez aucun problème. Pour res de santé, en faisant l’extraction des fonctionnalités ‘métier’ (bloc,
mettre en place des Web Services plus complexes, je plan de soins, etc...).
vous propose de bien lire la documentation. http://www.openxtrem.com

www.phpsolmag.org 43
Pour les débutants

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 : Ce qu’il faut savoir :


• Comment se connecter et envoyer des requêtes à un SGBD • Notions de HTML.
depuis PHP. • Bases de PHP.
• Bases de SQL : manipuler et interroger les données.

D
e nombreuses applications web utilisent des cilite le développement, en effet il suffit de connaître
bases de données afin de stocker des infor- les fonctions d’une seule API pour communiquer avec
mations nécessaires à l’application : blogs, fo- les principaux SGBD utilisés dans le développement
rums, paniers de commande, galeries d’images, etc. d’applications web. L’utilisation de PDO facilite éga-
Une application web utilisant une base de données re- lement la migration de base de données dans le code
pose sur une architecture client/serveur mettant en jeu d’une application. Par exemple, si l’application repose
un serveur web permettant l’exécution de programmes sur MySQL et qu’une migration vers PostgreSQL est
(Apache avec le module PHP), un serveur de base de réalisée, les fonctions utilisées dans le code PHP pour
données (MySQL, Oracle, PostgreSQL, …) et un client envoyer des requêtes et les traiter restent les mêmes.
web (navigateur). Le client émet une requête HTTP. Le La migration nécessite uniquement la modification de
serveur web la reçoit et transmet une réponse HTTP la ligne de code indiquant le pilote utilisé, à condition
au client. Lorsque la réponse nécessite l’interrogation bien sûr que les requêtes suivent strictement la norme
d’une base de données, une interface de programma- SQL.
tion (API) est utilisée. Le serveur web et le serveur de Cet article sera illustré par l’exemple d’une biblio-
base de données peuvent être installés sur un même thèque dont le schéma est le même que celui don-
ordinateur (en local) ou sur deux ordinateurs distants né dans les précédents articles de la série SQL, pour
(Figure 1). laquelle deux utilisateurs ont été créés (lecteur
Dans les articles précédents de cette série SQL, et bibliothecaire) avec différents droits d’accès (voir
vous avez appris à créer une base de données, à stoc- la série d’articles sur SQL parus chaque mois depuis oc-
ker des informations et à extraire des données de la tobre 2010). Pour pouvoir reproduire cet exemple, vous
base. Vous allez à présent apprendre à vous connec- devez utiliser un serveur de base de données MySQL,
ter à une base de données et à l’interroger depuis un de préférence la version 5 et un serveur web avec PHP
script PHP avec PDO. Cette extension permet de se version 5. Les distributions XAMPP (Windows, Linux,
connecter à un SGBD, d’envoyer des requêtes et de Mac OS), WAMP (Windows), EasyPHP (Windows) ou
traiter les résultats. C’est une interface dédiée à l’in- MAMP (Mac OS) vous fourniront l’environnement de
terrogation et à la manipulation de bases de données. travail nécessaire.
Elle présente l’avantage de ne pas être spécifique La première partie de cet article présente les termes
à un SGBD, comme le sont les extensions fournies par et la notation objet nécessaires à la compréhension
les éditeurs de bases de données (extensions mys- et à l’utilisation de l’extension PDO. Vous apprendrez
qli et mysql pour MySQL, extension OCI8 pour Ora- ensuite à vous connecter à une base de données, à la
cle, extension pgsql pour PostgreSQL, …). Ceci fa- manipuler et à l’interroger via PDO.

44 2/2011
SQL et PDO

Notation objet
Une 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 Figure 1. Serveur Web et serveur de bases de données
sont spécifiques à une personne du carnet, mais cha-
que objet suit le même modèle. Des opérations peuvent Accéder à une constante
être réalisées sur un objet, telles que le calcul de l’âge Lorsque des constantes de classe sont utilisées, el-
à partir de la date de naissance. Le résultat de ce calcul les doivent être préfixées du nom de la classe suivi de
est spécifique à la valeur de la propriété date de nais- l’opérateur de résolution de portée (::). La classe PDO
sance de chaque contact, mais les instructions pour ef- a une constante ATTR_SERVER_VERSION qui peut être
fectuer le calcul sont identiques pour chaque objet. El- passée en paramètre à la méthode getAttribute afin
les sont donc placées dans une méthode de la classe d’obtenir la version du serveur de base de données :
Contact.
Dans cet article, trois classes prédéfinies seront uti- $cnx->getAttribute(PDO::ATTR _ SERVER _
lisées : VERSION);

• PDO : classe de connexion à la base de données, Se connecter à la base de données


• PDOStatement : classe qui permet notamment de Activer le pilote
récupérer les résultats des requêtes SELECT, Plusieurs pilotes sont disponibles pour PDO : mysql
• PDOException : classe qui gère les exceptions (er- pour MySQL, oci pour Oracle, pgsql pour PostgreSQL,
reurs). etc. Pour se connecter à une base de données, il est
nécessaire que le pilote PDO correspondant soit activé.
Créer un objet Grâce à l’instruction phpinfo(), il est possible de vé-
Pour créer un objet de la classe, il faut utiliser l’opéra- rifier que le pilote est présent sur la ligne PDO drivers
teur new suivi du nom de la classe. L’instruction suivan- du tableau PDO (Figure 2). Dans cet article, vous aurez
te crée un objet de la classe PDO et le stocke dans la besoin du pilote mysql.
variable $cnx : Si le pilote mysql n’est pas activé, la ligne suivante
devra être ajoutée ou décommentée dans le fichier de
$cnx = new PDO($dsn,$login,$passwd); configuration php.ini. Pour Windows :

Parfois, certaines méthodes retournent des objets, il extension=php _ pdo _ mysql.dll


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 Sous Linux et Mac OS X l’extension nécessaire se
crée et retourne un objet de la classe PDOStatement nomme pdo_mysql.so. Après cette étape, il vous faut
(cf Interroger la base). relancer le serveur Web pour que l’extension soit prise
en compte.
Invoquer une méthode
Pour utiliser une méthode sur un objet, il faut la préfixer Etablir la connexion
par le nom de l’objet suivi d’une flèche. Par exemple, Pour établir une connexion vers une base de données,
la classe PDO fournit une méthode getAvailableDri- il faut créer un objet de la classe PDO. Lors de la créa-
vers qui retourne la liste de tous les pilotes disponibles tion, il faut définir des paramètres :
(mysql, oci, pgsql, ...). La variable $cnx stockant un ob-
jet de la classe PDO, il est possible d’invoquer la mé- − le DSN (Data Source Name), c’est une chaîne de
thode sur cet objet en utilisant l’instruction suivante : caractères contenant les informations de connexion
$cnx->getAvailableDrivers(); à la base (pilote, adresse IP ou nom du serveur,

www.phpsolmag.org 45
Pour les débutants

Dans le cas où le serveur de base de données serait


Listing 1. base.php
hébergé par un autre ordinateur (192.168.0.25) qui
<?php écoute sur le port 3312 (Figure 1b), le DSN serait :
/*definition des constantes de connexion*/
define('DSN', 'mysql:host=localhost;port=3306;dbname=bi
blio'); mysql:host=192.168.0.25;port=3312;dbname=biblio
/*identifiants de l'utilisateur*/
define('LOGIN', 'bibliothecaire');
define('PASSWORD', 'motdepasse2'); Deux utilisateurs ont des privilèges sur la base biblio :
/*tableau associatif d'options pour le pilote,
mode d'affichage des erreurs = exceptions, un lecteur qui a des droits en lecture sur la table livre
mode de recuperation des resultats = tableau et un bibliothécaire qui a tous les droits sur toute la ba-
associatif */
$options = array( se. Afin de pouvoir totalement manipuler la base dans
PDO::ATTR _ ERRMODE=> cet article, la connexion doit être établie en tant que bi-
PDO::ERRMODE _ EXCEPTION,
PDO::ATTR _ DEFAULT _ FETCH _ MODE=> bliothecaire dont le mot de passe est motdepasse2.
PDO::FETCH _ ASSOC); Ces informations de connexion ont été placées dans
?>
les constantes DSN, LOGIN et PASSWORD dans le script
Listing 2. insertion.php base.php (Listing 1). La variable $options est un ta-
<?php bleau associatif contenant des options présentées dans
require 'base.php'; la suite de l’article. Les scripts qui communiquent avec
try{
/* connexion PDO vers le SGBD */ la base incluront tous ce script :
$cnx = new PDO(DSN, LOGIN, PASSWORD, $options);
/* requete d'insertion */
$req _ insert = "INSERT INTO auteur (nom, prenom, require 'base.php';
date _ naissance)
VALUES ('Pagnol','Marcel', '1895-02-28')";
$cnx->exec($req _ insert); Séparer les informations de connexion facilite la mainte-
/* recuperer l'id de l'auteur nouvellement inséré nance des applications (modification de login, de mot de
*/
$id _ auteur = $cnx->lastInsertId(); passe, d’adresse du serveur ou changement de SGBD)
echo 'ajout auteur : ', $id _ auteur, '<br />'; et augmente la sécurité (les informations de connexion
/* requete d'affichage des auteurs */
$req _ affich = 'SELECT nom, prenom FROM auteur sont centralisées dans un unique fichier).
ORDER BY nom'; Le Listing 2, qui inclut le Listing 1, établit une
/* envoi de la requete au SGBD */
$res = $cnx->query($req _ affich); connexion vers le SGBD. La connexion est réalisée par
/* parcourir le resultat et l'afficher dans un l’instruction suivante :
tableau HTML */
echo '<table><tr><th>Prenom</th><th>Nom</th></tr>';
while ($ligne = $res->fetch()){
$cnx = new PDO(DSN, LOGIN, PASSWORD,
echo '<tr><td>', $ligne['prenom'], '</td><td>',
$ligne['nom'], '</td></tr>'; $options);
}
echo '</table>';
/* fermer curseur */ En cas de succès, un objet PDO est créé et stocké dans
$res->closeCursor();
/* deconnexion */
la variable $cnx.
$cnx = null; La dernière section de cette partie est consacrée
}catch (PDOException $e){
/* en cas d'erreur afficher un message*/
à la gestion des erreurs. Y sont résumées les principa-
die('echec : '.$e->getMessage()); 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
port, …). Le site php.net indique pour chaque pilote sera présentée à la fin de cet article.
le format attendu. Pour MySQL :
m y s q l:h o st= a d r e ss e;p or t= n u m _ Connexion persistante
port;dbname=nom _ base Les connexions persistantes ne sont pas fermées auto-
− le login (optionnel), matiquement à la fin de l’exécution d’un script. Elles
− le mot de passe (optionnel), sont mises en cache et réutilisées quand d’autres scripts
− un tableau associatif d’options pour le pilote (option- émettent une requête de connexion utilisant les mêmes
nel). identifiants (login/password). Cette mise en cache évite
l’établissement d’une nouvelle connexion à chaque fois
Dans cet article, le serveur MySQL est situé sur le mê- qu’un script veut communiquer avec la base de don-
me ordinateur que le serveur Web (localhost), le port nées, ce qui accélère la communication entre le serveur
est celui par défaut : 3306 (Figure 1a) et la base de don- web et le serveur de bases de données.
nées s'appelle : biblio. Le DSN est donc : Pour que la connexion soit persistante il suffit de
l’indiquer au pilote en donnant la valeur true à l’attribut
mysql:host=localhost;port=3306;dbname=biblio ATTR_PERSISTENT . Ceci est réalisé en ajoutant une

46 2/2011
SQL et PDO

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


Classe Méthode Description
PDO 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 2)

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


Constante Description
FETCH_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 Lorsque le serveur Web et le SGBD ne sont pas situés
du Listing 1 : sur le même ordinateur, il faut s’assurer également que
l’utilisateur est autorisé à se connecter à la base depuis
PDO::ATTR_PERSISTENT => true le serveur Web.
Lorsqu’une connexion échoue, une exception PDO
Gérer les erreurs est levée. L’exception est un objet PDOException qui
Les erreurs peuvent intervenir à différents niveaux lors fournit des informations sur l’erreur survenue : code
de la connexion, et génèrent ainsi des messages spé- d’erreur, fichier, numéro de ligne et texte explicatif en
cifiques : anglais quant à l’exception survenue. Ces informations
sont retournées respectivement par les méthodes get-
− nom d'utilisateur ou mot de passe incorrect : Code, getFile, getLine et getMessage de la classe
Access denied for user ‘bibliothecair’@’localhost’ PDOException (Tableau 1). Le message renvoyé par
(using password: YES) getMessage comporte trois parties, un code d’erreur
− adresse du serveur de bases de données incorrec- de cinq caractères défini dans le standard ANSI SQL
te : (SQLSTATE), un code et un message d’erreur spécifi-
Unknown MySQL server host ‘loclhost’ ques au pilote (ici mysql) :
− port incorrect :
Can’t connect to MySQL server on ‘localhost’ SQLSTATE[28000] [1045] Access denied for user ‘bi-
− nom de base erroné : bliothecair’@’localhost’ (using password: YES)
Access denied for user ‘bibliothecaire’@’%’ to data-
base ‘bibio’ L’utilisation d’exceptions permet de centraliser le traite-
− login et mot de passe de l'utilisateur correct, mais ment des erreurs. Les exceptions doivent être intercep-
aucun privilège sur la base demandée : tées et traitées. Pour ce faire, il faut utiliser deux blocs
Access denied for user ‘pierre’@’%’ to database ‘bi- : try et catch. L’instruction qui est susceptible de pro-
blio’ voquer l’exception est placée dans un bloc try. Si un

www.phpsolmag.org 47
Pour les débutants

Figure 2. Extrait du phpinfo()

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/
Figure 3. Résultat du Listing 2 PDO/insertion.php on line 3.

problème survient lors de la création de l’objet, PHP igno- Cette erreur est retournée au navigateur et/ou stockée
re la suite des instructions dans le bloc try et exécute les dans le fichier de log des erreurs, en fonction des régla-
instructions du bloc catch, où sont centralisés les trai- ges du fichier php.ini.
tements des erreurs. Il faut préciser entre parenthèses
le type d’exceptions à intercepter (PDOException) ainsi Modifier la base
que la variable qui va stocker les informations ($e) : Maintenant que vous êtes connectés à la base de données,
vous pouvez apporter des modifications à celle-ci. Dans l’ar-
try{ ticle SQL : langage de manipulation des données du mois
/* code de connexion à la base et de décembre 2010, vous avez appris à insérer des données
manipulation des données */ (INSERT), les modifier (UPDATE) ou les supprimer (DELETE).
}catch (PDOException $e){ Vous allez maintenant voir comment soumettre ces requê-
/* traitement des erreurs */ 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.
Si la connexion est réalisée sans erreur, alors toutes les
instructions du bloc try sont exécutées et le contenu du Soumettre la requête
bloc catch est ignoré. Le Listing 2 insère un nouvel auteur dans la table auteur
Le bloc catch du Listing 2 intercepte et traite l’excep- de la base biblio. La requête SQL stockée dans la va-
tion. En cas d’erreur, un message est affiché. Il contient le riable $req_insert est la suivante :

48 2/2011
SQL et PDO

INSERT INTO auteur (nom, prenom, date _ de exec retourne la valeur false. Dans ce cas, le déve-
naissance) VALUES ('Pagnol','Marcel', '1895-02-28') loppeur doit faire un test pour vérifier si la modification
de la base a réussi. Attention, une requête qui ne pro-
L’identifiant de l’auteur ne nécessite pas d’être rensei- duit pas d’erreur mais qui n’a affecté aucune ligne dans
gné car il est incrémenté automatiquement par MySQL la base de données retournera la valeur 0 (instructions
lors d’une insertion. UPDATE ou DELETE). Cette valeur est interprétée comme
La méthode exec de la classe PDO envoie au ser- false dans un test, pensez donc à utiliser l’opérateur
veur la requête SQL passée en paramètre : de comparaison tenant compte du type === afin de vous
assurer dans le test que la valeur retournée est le boo-
$cnx->exec($req _ insert); léen false et non pas l’entier 0. Lorsque l’attribut AT-
TR_ERRMODE prend la valeur ERRMODE_EXCEPTION, une
Elle retourne un entier correspondant au nombre de li- exception est générée lorsqu’il y a une erreur. C’est la
gnes affectées par la requête. Dans le cas d’une de- solution choisie dans cet article (Listing 1).
mande de mise à jour ou de suppression, il peut y avoir Il est préférable de tester vos requêtes en mode
zéro, une ou plusieurs lignes concernées en fonction de console ou grâce à phpMyAdmin avant de les insérer
la restriction. dans votre script afin de bien vérifier qu’elles ne com-
portent aucune erreur de syntaxe ou autre.
Gérer les erreurs
Il existe différents types d’erreurs pouvant empêcher Interroger la base
une insertion, une modification ou une suppression de La création d’une page Web présentant des données
données : extraites d’une base de données suit toujours les mê-
mes étapes : le script PHP établit une connexion vers
− nom de table incorrect : le serveur de base de données. La requête SQL est
Table ‘biblio.autur’ doesn’t exist envoyée au serveur de base de données. Celui-ci l’exé-
− nom de colonne incorrect : cute et retourne un résultat au script PHP. Le script ex-
Unknown column ‘prnom’ in ‘field list’ trait les données de chaque ligne de résultat, les met en
− erreur de syntaxe (oubli d'opérateur, de parenthè- forme et les envoie au navigateur.
ses, …) : Dans cette partie, vous allez apprendre à soumettre
You have an error in your SQL syntax; [...] une requête SQL de sélection au SGBD, extraire et pré-
− duplication de la clé primaire (insertion et modifica- senter les données du résultat.
tion) :
Duplicate entry ‘7’ for key ‘PRIMARY’ Soumettre la requête
− enregistrement d'une clé étrangère non référencée Le Listing 2 affiche tous les auteurs de la table auteur
(insertion et modification) : de la base biblio dans l’ordre alphabétique (Figure 3).
Cannot add or update a child row: a foreign key Il soumet la requête suivante stockée dans la variable
constraint fails ('biblio'.'livre', CONSTRAINT 'fk_zo- $req_affich :
ne' FOREIGN KEY ('code_zone') REFERENCES
'zone' ('code_zone') ON DELETE SET NULL ON SELECT nom, prenom FROM auteur ORDER BY nom;
UPDATE CASCADE)
− non renseignement d'une donnée de type NOT NULL La méthode query de la classe PDO envoie la requête
(insertion et modification) : au SGBD :
Field ‘titre’ doesn’t have a default value
− erreur de type de données (insertion et modifica- $res = $cnx->query($req _ affich);
tion) :
Incorrect integer value: ‘tre’ for column ‘nb_pages’ Elle retourne un objet de la classe PDOStatement stoc-
at row 1 ké dans la variable $res dans cet exemple. Cet objet
− nombre de valeurs différentes du nombre de colon- permet de manipuler les lignes du résultat retourné par
nes indiquées (insertion) : le SELECT.
Column count doesn’t match value count at row 1
− utilisateur avec privilèges insuffisants (droit de lec- Extraire le résultat
ture uniquement) : Le résultat de la requête SELECT peut comporter zéro,
INSERT command denied to user ‘lec- une ou plusieurs lignes. Le parcours du résultat est réa-
teur’@’localhost’ for table ‘livre’ lisé ligne par ligne, en utilisant une boucle while. La mé-
thode fetch de la classe PDOStatement extrait la ligne
En cas d’erreur le comportement de PDO est fixé par la suivante du résultat (ici stocké dans $res) et la retourne.
valeur de l’attribut ATTR_ERRMODE. Par défaut, la métho- Lorsque toutes les lignes du résultat ont été extraites, la

www.phpsolmag.org 49
Pour les débutants

ment mais au booléen false. Dans les exemples de


Sur Internet
cet article, la valeur de l’attribut a été fixée à ERRMODE_
• http://php.net/manual/fr/book.pdo.php 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
méthode fetch retourne false et PHP sort de la boucle cas d’exception.
while (Listing 2).
Le type de la donnée retournée par la fonction fetch Se déconnecter
dépend des options PDO : tableau avec accès par in- La valeur null affectée à $cnx, ferme la connexion.
dice, tableau associatif ou objet. Le Tableau 2 indique Si cette étape n’est pas réalisée, PHP ferme automati-
les différents types disponibles. Dans les exemples de quement la connexion à la fin du script (si la connexion
cet article, les données sont récupérées dans un ta- n’est pas persistante).
bleau associatif. Pour ce faire, la valeur de l’attribut AT- Il faut noter que lorsque le script ne récupère pas
TR_DEFAULT_FETCH_MODE est fixée à PDO::FETCH_AS- toutes les lignes d’un résultat, certains pilotes de SGBD
SOC (Listing 1). Préciser ce type lors de la création de la échouent lors d’une requête ultérieure dans le même
connexion dans $options permet de l’appliquer à tou- script. L’instruction $res->closeCursor() permet de
tes les requêtes. Le nom et le prénom de chaque auteur fermer le curseur et d’éviter de tels problèmes.
sont donc obtenus respectivement avec $ligne['nom']
et $ligne['prenom']. Conclusion
Toutes les lignes du tableau peuvent être récupérées Dans cet article, vous avez appris à établir une
en une seule fois en utilisant la méthode fetchAll : connexion à une base de données avec PHP via l’ex-
tension PDO. Vous savez maintenant manipuler et ex-
$lignes = $res->fetchAll(); traire des données depuis un script PHP. Quelle que
soit la complexité de la requête, la méthode reste la
Elle retourne un tableau dont la case 0 comporte un même. Les connaissances en SQL que vous avez ac-
tableau associatif stockant le résultat de la première li- quises dans cette série d’articles vous permettront d’af-
gne, la case 1 le résultat de la deuxième ligne, etc. Cet- finer les requêtes que vous pourrez insérer dans vos
te méthode n’est pas conseillée si vous devez extrai- applications Web. Dans le prochain numéro, vous ver-
re un nombre très important de données car elle peut rez comment construire des requêtes dynamiquement,
consommer beaucoup d’espace mémoire. insérer des données dans la base à partir d’un formu-
laire et sécuriser vos applications grâce à des requêtes
Gérer les erreurs préparées.
Les é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’ Cilia Mauro, Magali Contensin
for table ‘auteur’ Cilia Mauro est gestionnaire de bases de données et développeur
d’applications web au CNRS. Elle enseigne les bases de données
Le comportement du script lors de l’échec d’une re- et PHP à l’université.
quête dépend de la valeur de l’attribut ATTR_ERRMO- Contact : cilia.mro@gmail.com
DE. Par défaut, aucune exception n’est générée et la Magali Contensin est chef de projet en développement d’applica-
méthode query retourne la valeur false. Un test doit tions au CNRS. Elle enseigne depuis plus de dix ans le développe-
alors être effectué pour gérer l’erreur car sinon l’invo- ment d’applications web à l’université et est l’auteur de nombreux
cation de la méthode fetch provoquera une erreur fa- articles sur le développement web en PHP.
tale car elle n’est pas appliquée à un objet PDOState- Contact : http://magali.contensin.online.fr

50 2/2011
Sécurité

La sécurité
de l'information
Cet 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'il faut savoir :


• Ce qu’est la sécurité de l’information. • Aucun pré-requis n’est nécessaire.
• Les moyens techniques et fonctionnels mis en œuvre pour sé-
curiser l’information.

A
ujourd’hui la plupart des données d’une entre- l’information regroupe tous les moyens techniques
prise sont informatisées. Dans la mesure où les et fonctionnels à mettre en œuvre pour protéger les
menaces à l’encontre de la sécurité de l’infor- données d’une société.
mation se veulent chaque jour davantage féroces et po-
lymorphes, il est fondamental de sécuriser au maximum • Disponibilité : l’information doit être continuelle-
ces données. ment disponible.
Les grands acteurs technologiques comme Goo- • Intégrité : l’information ne doit pas être altérée.
gle, Microsoft ou Yahoo sont régulièrement victimes • Confidentialité : l’information doit être uniquement
d’attaques informatiques. Ces dernières ont, depuis accessible aux personnes autorisées.
leur existence au début des années 2000 des visées • Preuve/Traçabilité : Tous les mouvements de don-
intellectuelles, mais pas seulement. Le hacker des nées doivent être tracés.
années 2000, fier d’avoir pénétré un système d’in-
formation sans l’avoir dégradé pour autant, a bien La mise en place d’un processus
évolué. Pour sécuriser l’information, il convient de mettre en
Actuellement, toute attaque informatique, ou pres- place un processus de protection des données. Ce pro-
que, comporte des objectifs lucratifs ou destructeurs. cessus doit suivre les étapes suivantes :
L’espionnage industriel par exemple peut motiver un
hacker. Une tierce personne lui achète les informations • Inspection : Evaluer les besoins en sécurité de
de recherche dans le but de développer la concurrence. l’entreprise…
Une tâche grassement/hautement rémunérée qui attire • Protection : …avant d’appliquer des moyens de
plus d’un pirate informatique. protection de l’information.
Dans ce contexte ô combien hostile, nous définirons • Détection : Mettre en œuvre des moyens pour pré-
d’abord la sécurité de l’information, avant d’aborder les venir l’attaque…
moyens techniques et fonctionnels utilisés à l’heure ac- • Réaction : …afin d’être réactif en cas d’atta-
tuelle pour protéger le système d’information d’une en- que et pouvoir élaborer un plan de reprise d’ac-
treprise. tivité…
• Réflexion : …et procéder à une analyse post-in-
Définition cident.
La sécurité de l’information est un processus qui vise
à protéger les données d’une société. La sécurité de Les moyens techniques de protection

www.phpsolmag.org 51
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- Figure 1. Exemple d’architecture de protection applicative
cative.
• Proxy avec 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. Figure 2. Exemple d’architecture de protection mail
• Les accès WiFi : Les accès Wi-Fi doivent dispo-
ser d’une clé de chiffrement ainsi que d’un algorith- • Le coffre fort numérique : Le coffre fort numéri-
me robuste. Pour ce faire, le mécanisme WPA2 est que permet aux utilisateurs d’archiver leurs docu-
le plus robuste à moins d’utiliser une authentifica- ments de façon sécurisée.
tion par certificat avec une architecture 802.1x ce • La désactivation des périphériques non indis-
qui représente la sécurité la plus robuste en terme pensables : Afin d’éviter l’intrusion de virus au sein
d’accès WiFi. de la société, la désactivation de périphériques non

52 2/2011
Sécurité

Réflexion
Chaque 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.

Normes
Une 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.
Figure 3. Exemple d’architecture de protection des accès ditants. La norme ISO 27001 décrit la mise en œuvre d’un
système de management de la sécurité de l’informa-
indispensables comme les ports USB, les lecteurs tion. Et la norme ISO 27002 prévoit les bonnes prati-
de cartes ou encore les lecteurs de CD/DVD est in- ques de sécurité à mettre en œuvre au sein du système
dispensable. d’information.
Il existe aussi une norme de gestion des risques :
Les moyens fonctionnels de protection l’ISO 27005.
Des mesures fonctionnelles doivent aussi exister afin
de compléter les mesures techniques précédemment Méthodes
décrites. Ces moyens fonctionnels doivent être a mi- En sécurité de l’information, il existe plusieurs métho-
nima les suivants : des pour réaliser des analyses de risques. Parmi les
plus connues, on peut citer les méthodes suivantes :
• Politique de sécurité : une politique de sécurité
du système d’information doit être écrite et respec- • MEHARI,
tée au sein de l’organisation. • EBIOS.
• Sécurité physique : des mesures de sécurité phy-
sique doivent être mises en œuvre (accès aux lo- Conclusion
caux par badge, accès au datacenter, etc.) La sécurité de l’information est aujourd’hui un maillon
• La sensibilisation des utilisateurs : les utilisa- indispensable à la bonne gestion d’une entreprise. Il
teurs étant bien souvent les maillons faibles de la convient donc de ne pas la prendre à la légère et d’en
sécurité, il convient de réaliser des campagnes de respecter, a minima, les bonnes pratiques. Malheureu-
sensibilisation régulières. sement, force est de constater que la sécurité de l’infor-
• La désignation d’un responsable de la sécurité mation est rarement l’une des préoccupations majeures
du système d’information : un RSSI doit être dé- des entreprises.
signé afin de gérer toutes les activités sensibles de
l’entreprise.

Détection et réaction
Des é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 Tony Fachaux
des équipements. L’auteur travaille en tant que consultant sécurité pour la société
Toute détection d’incident doit être résolue et remon- LEXSI (Laboratoire d’EXpertise en Sécurité Informatique). Il se pas-
tée au responsable afin de prendre des mesures cor- sionne pour toutes les problématiques liées à la sécurité dans un
rectrices. contexte professionnel.

www.phpsolmag.org 53
Rejoignez le Club .PRO
Sécurité de l’information
Pour plus de renseignement : editor@phpsolmag.org

Pour les débutants


Ston¿eld Inworld

Rejoignez le Club .PRO


Ston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.

Pour plus de renseignement : editor@phpsolmag.org


COGNIX Systems
Ston¿conception
Conseil, eld Inworld
et développement d’applications évoluées pour les systèmes d’informations Internet/
intranet/extranet.
Stoneld Inworld Alliant
proposelesaux
compétences
entreprisesd’une SSII et d’une
des solutions Webd’intègration
globale Agency, Cognix Systems
d’Internet conçoit
et des des
Univers
applicatifs et portails
Virtuels dans web à l’ergonomie
leur stratégie travaillée Au-delà
de développement. et des sites
de Internet à forte la
ses services, valeur ajoutée.
société consacre 30% de
ses ressources à des travaux de R&D sur le e-Commerce et le e-Learning dans les Mondes Virtuels.
http://www.cognix-systems.com

Anaska
COGNIXFormation
Systems
Anaska
Conseil, est le spécialiste
conception des formations
et développement sur les technologies
d’applications évoluéesOpenSource. En partenariat
pour les systèmes avecInternet/
d’informations MySQL
AB, Mandriva, Zend
intranet/extranet. et d'autres
Alliant acteurs ded’une
les compétences la communauté,
SSII et d’uneAnaska vous propose
Web Agency, Cognixun catalogue
Systems de plus
conçoit des
de 50 formations
applicatifs dédiés
et portails webaux technologies
à l’ergonomie du Libre.
travaillée et des sites Internet à forte valeur ajoutée.
http://www.cognix-systems.com
http://www.anaska.com

Anaska Formation
WEB82
Anaska
Créationestet lehébergements
spécialiste desde
formations surpour
sites web les technologies
particuliers, OpenSource.
associations,En partenariat e-commerce.
entreprises, avec MySQL
AB, Mandriva, Zend
Développement et d'autres
entierement acteurs
aux de la
normes W3C communauté,
(www.w3.org)Anaska vous web
de sites propose un catalogue
de qualité, de plus
au graphisme
de 50 formations
soigné et employantdédiés aux technologies
les dernieres du Libre.
technologies du web (PHP5, MySQL5, Ajax, XHTML, CSS2).
http://www.anaska.com
http://www.web82.net

WEB82
Core-Techs
Création et hébergements de sites web pour particuliers, associations, entreprises, e-commerce.
Expert des solutions
Développement de gestion
entierement et de communication
aux normes d’entreprise
W3C (www.w3.org) de sites en Open
web Source,auCore-Techs
de qualité, graphisme
conçoit,et integre,
soigné déploie
employant et maintient
les dernieres des systemes
technologies de Gestion
du web (PHP5, MySQL5,de Ajax,
Contenu Web,
XHTML, de Gestion
CSS2).
Documentaire, de Gestion de la Relation Client (CRM), d’ecommerce et de travail ollaboratif.
http://www.web82.net
http://www.core-techs.fr

Core-Techs
POP des
Expert FACTORY
solutions de gestion et de communication d’entreprise en Open Source, Core-Techs
conçoit,
PoP integre, déploie
Factory,SSII et maintient
spécialisée des systemes
Web. Développement de de Gestion
solutions de Contenu
applicatives Web,
spéci quesde; offre
Gestion
de
Documentaire,
solutions de Gestion
packagées de la numérique,
: catalogue Relation Client (CRM), d’ecommerce
e-commerce, livre/magazineet numérique,
de travail ollaboratif.
envoi SMS. Nous
http://www.core-techs.fr
accompagnons nos clients tout au long de leur projet : audit, conseil, développement, suivi et gestion.
http://www.popfactory.com / info@popfactory.fr

POP FACTORY
Blue
PoP Note Systems
Factory,SSII
solutions
spécialisée Web. Développement de solutions applicatives spéciques ; offre de
Spécialistes en CRM Open Source, nouse-commerce,
packagées : catalogue numérique, proposons unelivre/magazine numérique,
offre complète envoi SMS.sur
de prestations Nous
la
accompagnons nos clients tout au long de leur projet : audit, conseil, développement, suivi et gestion.
solution SugarCRM. Notre valeur ajoutée réside dans une expertise réactive et une expérience des
http://www.popfactory.com / info@popfactory.fr
problématiques de la GRC. Nous vous aidons à tirer le meilleur parti de votre solution CRM.
http://www.bluenote-systems.com

Blue Note Systems


Spécialistes en CRM Open Source, nous proposons une offre complète de prestations sur la
Intelligence
solution SugarCRM. Power
Notre valeur ajoutée réside dans une expertise réactive et une expérience des
Conseil, Expertises,
problématiques de laFormations
GRC. Nouset Projets
vous E-business
aidons centrés parti
à tirer le meilleur au tour
de du cĞur
votre de métier
solution : la Business
CRM.
Intelligence. Intelligence Power vous propose des solutions innovantes pour aligner la technologie sur
http://www.bluenote-systems.com
la stratégie de votre entreprise.
http://www.intelligencepower.com
Intelligence Power
Conseil, Expertises, Formations et Projets E-business centrés au tour du cĞur de métier : la Business
Web Alliance
Intelligence. Intelligence Power vous propose des solutions innovantes pour aligner la technologie sur
la stratégie
Vous de votre
souhaitez être entreprise.
en première page des moteurs de recherche ? Rejoignez-nous, 100% des clients
http://www.intelligencepower.com
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
Web Alliance

Club .PRO
Vous 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

49
50 5/2010
54 2/2011
Sécurité

www.phpsolmag.org 55

Você também pode gostar