Archives de catégorie : PHP

Exemple simple avec SplObjectStorage

La classe SplObjectStorage est comme son nom l’indique issue de la SPL. Elle permet à l’instar des tableaux de stocker des collections d’objets. Cependant SplObjectStorage est nettement plus rapide qu’un simple tableau.Elle implémente de plus les interfaces Countable , Iterator , Traversable , Serializable , ArrayAccess qui rendent sont utilisation vraiment confortable.Voici un petit exemple pratique d’utilisation de cette class.Admettons que nous ayons une classe Groupe qui est charger de contenir plusieurs instance d’une classe Personne (un groupe composé de plusieurs personnes pour ceux qui suivent pas)
On commence par une classe Personne très simple :

class Personne
{
    private $nom;
    private $prenom;
    private $age;

    public function __construct($nom,$prenom,$age)
    {
        $this->nom      = $nom;
        $this->prenom   = $prenom;
        $this->age      = intval($age);
    }

    public function getAge()
    {
        return $this->age;
    }

}

Notre classe groupe étant supposée être une collection d’objet nous allons la faire dériver de SplObjectStorage :

class Groupe extends SplObjectStorage
{
    public function attach(Personne $personne)
    {
        parent::attach($personne);
    }

    public function detach(Personne $personne)
    {
        parent::detach($personne);
    }

    public function getMoyenneAge()
    {
        $nbObjet = $this->count();
        $total = null;
        foreach($this as $personne) {
            $total += $personne->getAge();
        }

        return floor($total / $nbObjet);
    }
}

On redéfini attach et detach pour n’accepter que des objet de type Personne (au lieu de n’importe quel objet).On ajoute ensuite une méthode qui va calculer la moyenne d’age du groupe.Pour celà il faut parcourir tous les objets de la collection. Un simple foreach suffit pour itérer sur le contenu de Groupe tout comme un simple count() nous retourne le nombre de Personne !
Ne reste ensuite plus qu’a créer quelques personnes et former un groupe :

$pierre = new Personne('Dupond','Pierre',30);
$paul   = new Personne('Dupont','Paul',28);
$jack   = new Personne('Dupon','Jack',60);

$groupe = new Groupe();
$groupe->attach($pierre);
$groupe->attach($paul);
$groupe->attach($jack);
echo $groupe->getMoyenneAge().'<br />';
//Jack décède tragiquement
$groupe->detach($jack);

echo $groupe->getMoyenneAge();

PDO et sql server

Le driver PDO pour sqlserver étant en phase expérimental et carrément non supporté en PHP 5.3.x comment profiter de PDO pour se connecter à des bases sql server ?
Et bien tout simplement en passant par le driver ODBC qui lui est pleinement fonctionnel :
$dsn = "odbc:Driver={SQL Server Native Client 10.0};Server=127.0.0.1;Database=myDB;Uid=myUser;Pwd=myPass;"
$sql = new PDO($dsn);
$sql->query(...);
Les identifiants de connexion étant inclus dans le DSN il ne faut pas les inclures dans l’instanciation de PDO.

Webservice REST récupération des données

Lorsque l’on souhaite mettre en place un webservice REST en php on se heurte souvent au problème de la récupération des données quand on utilise une méthode autre que POST ou GET.
En effet PHP dispose des superglobales $_GET et $_POST mais qu’en est-il pour PUT,HEAD ou DELETE ?

Ces données sont bien transmises , il faut simplement passer par le flux php://input pour les retrouver :

 echo file_get_contents('php://input');

va retourner les données sous forme de query string (comme les paramètres en GET). Il ne reste donc qu’a parser cette chaine pour avoir un tableau facilement exploitable :

$method = $_SERVER['REQUEST_METHOD'];
if($method == 'GET')
    $datas = $_GET;
elseif($method == 'POST')
    $datas = $_POST;
else
    parse_str(file_get_contents('php://input'),$datas);

De cette manière quelque soit le type de méthode utilisée vous serez en mesure de récupérer les données envoyées.

Constante de Date

Je viens de tomber tout à fait par hasard sur des constantes fort utiles en php.
De nombreux standard tel que le RSS nécessite un format de date particulier, format généralement imbuvable totalement impossible à mémoriser …
Et c’est là que les constantes DATE_* interviennent :

echo date(DATE_RSS);// Affichera par exemple  Thu, 05 Aug 2010 13:45:58 +1000

Toutes les constantes sont visibles sur la documentation et disponibles depuis PHP 5.1.1

preg_replace et modificateur #e

Une petite info qui peut se révéler utile (parce que perso j’ai mis une heure à trouver d’ou venait le problème !).
Lorsque l’on utilise preg_replace avec un modificateur d’évaluation #e , comme par exemple  :

preg_replace('#\[liste\]([^\[]*?)\[\liste\]#e','$this-&gt;parseList(\'$1\')',$chaine);

Et bien la fonction rajoute des slashes sur les doubles quote comme pourrait le faire addslashes. Je n’ai pas trouver de réelle solution si ce n’est un gros :

str_replace('\"','"',$chaine);

Sqlite Encryption Extension avec PHP

PHP intègre de base sqlite3 , cependant cette version ne supporte pas les bases cryptées avec SEE. La seule solution pour que PHP puisse intéragir avec des bases cryptée c’est de re-compiler l’extension sqlite3 avec l’extension SEE fournie par sqlite (après s’être acquitté de la licence à 2000$ ).
Nous verrons donc ici comment réaliser ceci sous Windows. Je n’ai effectuer la manipulation qu’avec un PHP VC9 , mais celà doit être plus ou moins équivalent avec un VC6.

Prérequis :

Divers outils sont nécessaire avant de commencer quoi que ce soit

  • Visual Studio 2008 (nom de code Vc9) version pro ou express.
  • Le SDK Windows 6.1 : Voir ici
  • Les Binary tools de PHP : Télécharger
  • Les sources PHP : Voir ici
  • Les librairies PHP annexes. Dans le doute je les ai toutes prises, c’est pas très gros , autant pas se priver. Voir ici
  • Les sources de SEE téléchargées sur le dépôt sqlite. Faites attention à la version utiliser dans PHP pour être en adéquation (3.6.19 avec un PHP 5.3.1).
  • Apache version VC9 si besoin : Apachelounge

Procédure :

1- Dans un premier temps installer Visual Studio puis le SDK windows. C’et sans doute la partie la plus longue de l’opération….
2- Pendant ce temps préparez le dossier qui va recevoir les sources à compiler. J’ai personnellement crée un dossier D:/php-sdk.
3- Extraire les binary tools dans le dossier php-sdk. Vous devriez vous retrouver avec un dossier bin et script.

A partir de ce point l’installation du SDK et Visual doit être terminée.

4- Depuis le menu démarrer ouvrir le shell Windows SDK puis taper :
setenv /x86 /xp /release
cd d:\php-sdk\
bin\phpsdk_setvars.bat
bin\phpsdk_buildtree.bat php531

Le nom php531 est bien évidemment à choisir en fonction de votre version de php / humeur.
5- Extraire les sources PHP dans \php531\vc9\x86\
6- Modifiez L’extension sqlite de php (\php531\vc9\x86\ext\sqlite3\libsqlite\) avec la version SEE. Voir la doc SEE pour les lignes de code à copier et insérer.
7- Extraire les librairies annexes dans \php531\vc9\x86\deps\ si vous en avez besoin.
8- Lancez la commande : D:\php-sdk\php53dev\vc9\x86\php5.3.1\buildconf pour créer le fichier de configuration
9- configure –help . Vous donnera les différentes options possible pour votre compilation. Gardez en tête que pour obtenir une dll les extension doivent être compilée en shared.
Par exemple : configure –enable-pdo –with-pdo-sqlite3=shared
10- Une fois la configuration terminée (sans erreur) lancez : nmake
11- Une fois la compilation terminée lancez : nmake install
Par défaut vous trouverez les binaire et dll dans C:/php5/
12- Copiez la dll sqlite3 nouvellement compilé et remplacez là dans vos binaires de production.

Attention : Il est indispensable que vos binaires soit compilé avec le même compilateur que votre extension ! Extension VC9 = binaire VC9.

Après un redémarrage de php vous pouvez desormais profiter de la version cryptée de sqlite directement dans php.
On peut par exemple ouvrir une base cryptée comme ceci :

$sql->query('PRAGMA key=\''aes256:maclesecrete\'');

Conversion ressource image vers string

La bibliothèque GD permet beaucoup de chose mais pas de sauvegarder une image en chaine de caractère. Ça peut pourtant être pratique pour sérialiser une image (les ressources ne sont pas sérialisables) ou encore ne pas avoir un créer un fichier sur le disque.

La solution est d’utiliser la bufferisation de la sortie standard de php pour récupérer la ressource sous forme de string :

ob_start();imagejpeg($imageBase);$string = ob_get_contents();ob_end_clean();

Un petit exemple de mise en situation : Créer une archive zip contenant des images issues d’une fusion de plusieurs images contenues dans un BLOB d’une base de données :

$zip = new ZipArchive();

$op = $zip->open('test.zip',ZipArchive::CREATE);

foreach($ids as $id){ ob_start(); $data  = $bd->getById($id); $imageBase  = $bd->generateImage($data); imagejpeg($imageBase);

$zip->addFromString($data->id.'.jpg',ob_get_contents()); ob_end_clean(); }$zip->close();

Il est ainsi possible de créer à la volée l’archive sans avoir à écrire les fichiers sur le disque. La consommation mémoire est en revanche plus élevée. A titre d’exemple , une archive avec 380 images d’environs 20Ko aura consommé 18Mo de mémoire.

Se prémunir des failles CSRF en PHP

Quand on parle sécurité pour un site web, la première chose qui nous vient à l’esprit c’est l’injection sql, ou encore le vol de sessions qui sont les grand classique du hack.
Cependant une autre faille moins connue mais tout aussi dangereuse vous guète : La faille Cross-Site Request Forgeries (CSRF).

Le principe

Il est très simple , on utilise l’utilisateur pour exécuter un fichier auquel on à normalement pas accès. Voici un exemple très simple :
Admettons que sur votre site , dans votre admin vous ayez une page du type supprimer_utilisateur.php?idUser=xxx . Vous vous pensez à l’abri car il faut être admin pour accéder à cette page et c’est là toute la subtilité de la faille.
Un de vos membres pas très sympathique poste un message sur votre forum, ce message contient un lien vers une image ou toute chose capable d’exécuter du code. Vous allez donc naïvement consulter le lien de votre membre et sans vous en rendre compte vous aurez exécuté votre page de suppression avec les paramètres voulu par le hacker.
C’est ce qui rend cette faille très dangereuse, c’est totalement invisible pour la personne piégée !

Comment limiter les risques ?

  • La première chose est de limiter les paramètre en GET et leur préférer le POST plus discret (mais tout aussi modifiable).
  • Demander des confirmations à l’utilisateur sur les actions critiques. Avant de lancer une suppression demander à l’utilisateur si il est réellement certains , voir même lui demander une passphrase secrète.
  • Utiliser des tokens pour toutes les requêtes.

Ce qui ne marche pas : Ce baser sur le référant contenu dans la requête HTTP. C’est modifiable comme toute la requête.

Les Tokens

Ou jetons en bon français est la solution la plus robuste. Elle consiste à attribuer un jeton à l’utilisateur avant d’effectuer une requête (typiquement en arrivant sur le formulaire) et de vérifier ce jeton au moment d’exécuter l’action. Les jetons étant regénéré constamment et ayant une durée de vie courte il est impossible de le prédire à l’avance et donc d’exécuter des requêtes sur votre site depuis des sites/domaines qui ne sont pas prévus pour.

Une classe PHP à la rescousse :

class Util_Token
{
 /**
  * Type d'erreur retournée à la vérification du token
  * 1 = Token non passé en paramètre
  * 2 = token recu != token généré
  * 3 = token expiré
  * @var int
  */
 static public $error = 0;
 
 /**
  * Génère un token et le stocke en session
  *
  * @param int $ttl Durée de vie du token en minute
  */
 static public function genToken($ttl = 15)
 {
  if(!isset($_SESSION))
   session_start();
   
  $token  = hash('sha1',uniqid(rand(),true));
  $rand   = rand(1,20);
  //Sha1  = 40 caractères => 20 de longeur max
  $token  = substr($token,$rand,20);
  $ttl *=60;
  $_SESSION['csrf_protect']    = array();
  $_SESSION['csrf_protect']['ttl']  = time()+$ttl;
  $_SESSION['csrf_protect']['token']  = $token;
 }
 
 /**
  * Récupère le token
  *
  * @return string
  */
 static public function getToken()
 {
  if(isset($_SESSION['csrf_protect']) && !empty($_SESSION['csrf_protect']))
   return $_SESSION['csrf_protect']['token'];
  else
   throw new Util_ExceptionHandler('No token available');
 }
 
 /**
  * Récupère le timestamp de durée de vie
  *
  * @return int
  */
 static public function getTTL()
 {
  if(isset($_SESSION['csrf_protect']) && !empty($_SESSION['csrf_protect']))
   return $_SESSION['csrf_protect']['ttl'];
  else
   throw new Util_ExceptionHandler('No token available'); 
 }
 
 /**
  * Vérifie la validité du token
  *
  * @return boolean
  */
 static public function checkToken()
 {
  if(!isset($_SESSION))
   throw new Util_ExceptionHandler('Can\'t check token if there is no session available');
   
  if(isset($_REQUEST['csrf_protect']) && !empty($_REQUEST['csrf_protect']))
  {
   if($_REQUEST['csrf_protect'] == $_SESSION['csrf_protect']['token'])
   {
    if($_SESSION['csrf_protect']['ttl']-time()>0)
    {
     return true;
    }
    else
    {
     self::$error = 3;
    }
   }
   else
   {
    self::$error = 2;
   }
  }
  else
  {
   self::$error = 1;
  }
  return false;
 }
 
 /**
  * Retourn le code erreur
  *
  * @return int
  */
 static public function getError()
 {
  return self::$error;
 }
}

Concrètement comment ça marche ?
Tout d’abord il faut avoir une session d’active , puis générer un token :

Util_Token::genToken();

On récupère ainsi une chaine basée sur un hash d’une id unique. Ce token est enregistré en session.
Il vous reste ensuite à le passer en paramètre lors de vos requêtes dans input hidden ou directement dans votre chaine de paramètre dans le cas de requête ajax par exemple.

Puis sur la page exécutant la requête il vous suffit de vérifier le token avant toute action :

if(Util_Token::checkToken())    //Executer du codeelse    // Mauvais token ou token expiré

Notez que dans son état actuelle la classe nécessite que le paramèter du token sois nommé csrf_protect.

Configurer Xdebug et eclipse PDT avec Wamp

Eclipse PDT est un EDI pour PHP très intéressant. Au coté de Zend studio qui lui est payant c’est un peu le visual studio de PHP.
Outre les options habituelles , on peut avec PDT debugger du code pas à pas comme on le ferait avec n’importe quel langage compilé. PDT prend en charge deux débuggers en natif : Xdebug et Zend Debugger , c’est le premier qui nous intéressera .

Installation de XDebug :

La première chose à faire est de télécharger la Dll de xdebug sur le site officiel. Placez alors le fichier récupéré dans le dossier d’extension de PHP, par défaut C:\wamp\bin\php\php-x.x.x\ext\.
Dans le menu contextuel de wamp demandez l’affichage du php.ini
A la fin du fichier (mais avant les dernières lignes commentées) rajoutez ces lignes de config :

[xdebug]
zend_extension_ts= »D:/wamp/bin/php/php5.2.5/ext/php_xdebug-2.0.5-5.2.dll »
xdebug.remote_enable=1
xdebug.remote_host= »127.0.0.1″
xdebug.remote_port=9000
xdebug.remote_handler=dbgp
xdebug.remote_mode=req
xdebug.show_local_vars=1
xdebug.profiler_enable_trigger=1
xdebug.profiler_output_dir= »D:/wamp/logs/xdebug/ »

Veillez à bien changez les différents chemins pour qu’il correspondent à votre installation. Attention à ne pas modifier la directive xdebug.remote_host. Si vous mettez autre chose que 127.0.0.1 (même localhost) vous risquez de ne rien voir fonctionner.

Configuration de PDT

La première chose à faire est de configurer l’executable php que PDT va utiliser. Pour celà rendez vous dans Windows -> préférences -> PHP ->PHP Executables.
Si vous n’en n’avez pas de configurer , créer en un nouveaux. La seule subtilité est qu’il faut spécifier le chemin du php.ini qui se trouve dans le dossier d’apache car c’est celui ci que wamp utilise.


Toujours dans la configuration php de PDT rendez vous dans la rubrique debug. Configurez alors PDT comme suit :
PHPDebugger : XDebug
Server : Votre serveur par défaut
Executables : l’executables précédemment crée.


A cet instant la configuration est terminée. Si avec un phpinfo(); vous voyez bien une entrée pour Xdebug c’est quasi gagné.

Debugger

Pour débugger rien de bien difficile. il suffit de faire un clic droit sur un fichier puis « Debug as » et choisir l’une des deux options. Vous devriez alors passer en vue debug dans eclipse et avoir accès au point d’arrêt, au mode pas à pas, à l’espion de variables … :


Si jamais au moment de lancer un debug vous voyez que le chargement (en bas à droite) est bloqué sur « Waiting a Xdebug session » c’est très probablement que la navigateur par défaut est mal configuré dans eclipse (ou que xdebug n’est pas installé). Pour être tranquille choisissez le navigateur par défaut du système.

Bon debug !

Distance entre deux villes

Il peut être utile de devoir calculer une distance entre deux villes , pour par exemple savoir si tel ou tel service est disponible dans un rayon de X kilomètres du lieu de votre choix.
Je me suis donc mis à chercher comment réaliser cela , je suis finalement tombé sur la formule de calcul orthodromique sur Lion1906. C’est une méthode qui prend en compte la sphéricité de la terre et fournie donc un calcul plutôt proche de la réalité.

La formule est la suivante :

6371*acos(cos(LatitudeA)*cos(LatitudeB)*cos(longitudeB-longitudeA)+sin(LatitudeA)*sin(latitudeB))

Elle fournit donc la distance en kilomètre entre un point A et un point B. Les coordonnées correspondent aux angles décimaux convertis en radian.
Vous pouvez très simplement obtenir les angles décimaux d’une position via Google maps par exemple.

Et pour finir une petite application de la formule en php :


//Calcul de distance entre Paris et Dijon
$longA = 2.3458*(M_PI/180);
$latA = 48.8608*(M_PI/180);

$longB = 5.0356*(M_PI/180);
$latB = 47.3225*(M_PI/180);

$subBA = bcsub ($longB, $longA, 20);
$cosLatA = cos($latA);
$cosLatB = cos($latB);
$sinLatA = sin($latA);
$sinLatB = sin($latB);

$distance = 6371*acos($cosLatA*$cosLatB*cos($subBA)+$sinLatA*$sinLatB);
echo $distance ;