Aller au contenu

Programmation PHP/Version imprimable2

Un livre de Wikilivres.

Ceci est la version imprimable de Programmation PHP.
  • Si vous imprimez cette page, choisissez « Aperçu avant impression » dans votre navigateur, ou cliquez sur le lien Version imprimable dans la boîte à outils, vous verrez cette page sans ce message, ni éléments de navigation sur la gauche ou en haut.
  • Cliquez sur Rafraîchir cette page pour obtenir la dernière version du wikilivre.
  • Pour plus d'informations sur les version imprimables, y compris la manière d'obtenir une version PDF, vous pouvez lire l'article Versions imprimables.


Programmation PHP

Une version à jour et éditable de ce livre est disponible sur Wikilivres,
une bibliothèque de livres pédagogiques, à l'URL :
https://fr.wikibooks.org/wiki/Programmation_PHP

Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la Licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans Texte de dernière page de couverture. Une copie de cette licence est incluse dans l'annexe nommée « Licence de documentation libre GNU ».

Dates

La fonction date() créer une chaîne de caractère contenant la date du jour au format défini par son paramètre, selon la syntaxe suivante[1] :

  • Y (year) : année.
  • m (month) : mois.
  • M : nom des mois (en français si setlocale(LC_TIME, 'fr_FR');)
  • d (day) : jour.
  • w (week) : jour de la semaine sous forme d'un numéro.
0 dimanche
1 lundi
2 mardi
3 mercredi
4 jeudi
5 vendredi
6 samedi

Exemple :

echo date('Y-m-d'); // affiche 2016-07-10

À cela on peut rajouter les options d'horodatage les plus courantes :

  • a (ante meridiem ou post meridiem) : renvoie "am" le matin et "pm" l'après-midi.
  • h (hour) : heure de 0 à 12. À utiliser avec "a".
  • H (Hour) : heure de 0 à 24.
  • i (minute) : minute.
  • s (second) : seconde.

Exemple :

echo date('Y-m-d H:i:s'); // affiche 2016-07-10 20:06:34

Cette fonction transforme un texte (en anglais) en date[2].

Exemples courants :

$demain = date('Y-m-d', strtotime('+1 day'));
$hier = date('Y-m-d', strtotime('-1 day'));

Pour afficher la plage des dates de la semaine précédente[3] :

$previous_week = strtotime('-1 week +1 day');

$start_week = strtotime('last monday midnight', $previous_week);
$end_week = strtotime('next sunday', $start_week);

$start_week = date('Y-m-d', $start_week);
$end_week = date('Y-m-d', $end_week);

echo 'La semaine dernière était du '.$start_week.' au '.$end_week;
// Le vendredi 2016-07-29 cela affiche : La semaine dernière était du 2016-07-18 au 2016-07-24

Il est impératif dans un formulaire de vérifier si une date est au bon format. Pour ce faire il existe checkdate()[4] qui demande de séparer le mois, le jour puis l'année. Exemple :

var_dump(checkdate(0, 0, 2000)); // false
var_dump(checkdate(1, 1, 2000)); // true

Cette classe peut être instanciée en style POO ou en style procédural[5]. Exemple :

$date = new DateTime('2018-01-01');
echo $date->format('Y-m-d H:i:s');
// ou
$date = date_create('2018-01-01');
echo date_format($date, 'Y-m-d H:i:s');

Résultat : 2018-01-01 00:00:00.

Pour générer une date relative à l'actuelle :

 echo (new DateTime('now -2 days'))->format('Y-m-d H:i:s')

Pour date relative à une absolue :

 echo (new DateTime('2018-01-01 -2 days'))->format('Y-m-d H:i:s')

Elle possède également des méthodes pour modifier les dates :

  • Via une string : $date->modify('-1 day');
  • Via un objet de type DateInterval.
  • Pour modifier les heures, par exemple pour obtenir la date du jour à minuit : (new DateTime())->setTime(0, 0);.

La méthode DateTime::add() (et son alias date_add()), permet d'ajouter deux dates[6].

La méthode DateTime::diff() (et son alias date_diff()), permet de soustraire deux dates[7].

Exemple :

$date1 = new DateTime('2017-01-01');
$date2 = new DateTime('2020-11-01');
$dateInterval = $date2->diff($date1);
echo $dateInterval->format('%a jours');

Résultat : 1400 jours.

Conversion Timestamp en DateTime

[modifier | modifier le wikicode]
$date = new DateTime('@1676564671');

DateTimeImmutable

[modifier | modifier le wikicode]

Idem que DateTime mais immutable[8].

Cette classe gère les intervalles entre deux dates et est instanciée avec les paramètres suivants, sensibles à la casse :

  • P : period (période).
  • Y : year (année).
  • M : month (mois).
  • D : day (jour).
  • H : hour (heure).
  • I : minute.
  • S : second (seconde).

Exemple :

$dateInterval = new DateInterval('P1Y2M3D');
echo $dateInterval->format('%y an %m mois %d jours');

Résultat : 1 an 2 mois 3 jours.

Logo

La syntaxe au sein de la méthode "format" est légèrement différente puisqu'elle accepte les minuscules (pour afficher les chiffres sans préfixe "0", ex : "1" au lieu de "1")[9], et aussi :

  • a : nombre de jours au total.
  • N : numéro du jour de la semaine (1 = lundi, 7 = dimanche).

Logo

La syntaxe pour les dates (année, mois, jour) et les temps (heure, minute, seconde) est différente : il faut ajouter un "T" pour les temps. Ex :

 var_dump(new DateInterval('P1D'));  // un jour
 var_dump(new DateInterval('PT1H')); // une heure

Les objets de cette classe peuvent être placés en paramètre des méthodes de DateTime add() et sub(). Ils peuvent aussi être obtenus en résultat de diff() vu ci-dessus.

Pour calculer le nombre de récurrences d'un intervalle au sein d'une période (mais pas la date de fin à partir du début et de la durée)[10].

Cette fonction affiche la date courante au format horodatage Unix, c'est-à-dire en nombre de seconde depuis le premier janvier 1970.

Ce format permet d'additionner ou soustraire deux dates très facilement, est peut être reconverti en date en étant placé en second paramètre de la fonction date().

Cette fonction (make time) crée une chaine de caractères contenant un horodatage Unix, c'est-à-dire un nombre de seconde représentant une date comprise en 1970 et 2038.

Exemple de calcul avec strtotime(), qui accepte les timestamps en second paramètre :

$christmasTimeStamp = mktime(0, 0, 0, 12, 25, 2017); // 1514178000
$FirstDayOfNextMonthTimeStamp = strtotime('first day of next month', $christmasTimeStamp); // 1514782800
echo 'Le premier du mois après Noël 2017 est : '.date('Y-m-d', $FirstDayOfNextMonthTimeStamp); // 2018-01-01

date_default_timezone_set()

[modifier | modifier le wikicode]

Ex :

date_default_timezone_set('Europe/Paris');


Sessions

Comme vous le savez, à la fin d'un script PHP, toutes les variables créées sont détruites et il est impossible d'y accéder depuis un autre script. Alors comment faire pour passer des données entre les pages ?

La réponse : les sessions.

C'est en effet la solution la plus simple pour un webmaster afin de garder des informations sur son visiteur.

[modifier | modifier le wikicode]

Une session est plus ou moins semblable aux cookies HTTP. Les données d'une session sont cependant stockées sur le serveur et non chez le client, ce qui l'empêche de pouvoir les modifier manuellement, comme il peut le faire pour un cookie.

La durée de vie d'une session

[modifier | modifier le wikicode]

Une session est, comme son nom l'indique, une session de navigation. Elle commence donc lors de l'accès à une page les utilisant et se termine à la fermeture du navigateur du client.

Une fois la session terminée, elle est détruite ainsi que toutes les données qu'elle contient. Elle doit donc ne contenir que des données temporaires.

Cependant, la session possède aussi un délai d'expiration. Celui-ci peut être modifié dans la configuration du serveur (directive session.gc_maxlifetime de php.ini), mais vaut généralement une trentaine de minutes. Une fois ce délai dépassé, la session est supprimée.

Comment ça marche ?

[modifier | modifier le wikicode]

Lors de l'accès à une page nécessitant une session, PHP va vérifier si une a déjà été ouverte et la réutiliser ou en créer une nouvelle. Il va donc lui attribuer un identifiant unique et se débrouiller pour que la prochaine page consultée puisse le connaître.

Pour cela, il exploite deux fonctionnalités. Premièrement, si l'utilisation de cookie est possible (le client ne les a pas désactivés), PHP crée un cookie contenant l'identifiant de session. Deuxièmement, si les cookies ne sont pas disponibles, il va réécrire les URL de la page pour inclure cet identifiant dans le lien.

Dans les deux cas, le script ayant besoin d'accéder aux données de la session recevra l'identifiant et sera capable de charger les données qu'elle contient.

Est-ce que c’est sécurisé

[modifier | modifier le wikicode]

Une session est toujours plus sécurisée qu'un cookie puisque le client ne peut pas la modifier manuellement. Un risque subsiste tout de même si l'identifiant de session peut être découvert.

Par exemple, les cookies transitent sur le réseau en clair. Si quelqu’un est capable d'intercepter les communications entre le client et le serveur, il est capable de voir le cookie et de découvrir l'identifiant de session. Il n'aura alors plus qu’à créer un faux cookie et PHP le prendra pour le pauvre internaute s'étant fait voler son cookie. Pour éviter cela, on peut utiliser une connexion cryptée ou configurer le serveur de façon à ce qu’il rende la lecture de ce cookie impossible par JavaScript (Http Only).

Utiliser les sessions

[modifier | modifier le wikicode]

Initialiser une session

[modifier | modifier le wikicode]

Pour pouvoir utiliser la fonctionnalité de session de PHP, il faut lancer le moteur de session en utilisant la fonction session_start().

Cette fonction doit être en mesure d'envoyer des headers HTTP, aucune donnée ne doit donc avoir été transmise au navigateur. Vous pouvez soit placer ce code au tout début de votre script, soit utiliser les fonctions de bufférisation de sortie.

Une session portant un nom personnalisé

[modifier | modifier le wikicode]

Une session porte par défaut le nom "PHPSESSID", c’est lui qui sera utilisé comme nom de cookie ou comme paramètre GET dans les liens... pas très esthétique. Il peut donc vous venir l'envie de changer ce nom.

Pour cela, la fonction session_name() entre en scène.

Début d’un principe
Fin du principe


Ce code va donc charger une session avec l'identifiant provenant du cookie ou du paramètre GET portant le nom nom_de_session.

Noter la position de l'instruction 'session_start()'. Elle doit se trouver AVANT n’importe quel traitement de votre page '.php' .

Changer manuellement l'identifiant de la session

[modifier | modifier le wikicode]

PHP détecte automatiquement l'identifiant à utiliser, cependant, vous pourrez avoir besoin de le changer manuellement, si vous voulez développer un système alternatif pour passer l'identifiant de session entre les pages. Vous pouvez par exemple vous baser sur le hachage (md5() ou sha1()) de l'adresse IP du client pour déterminer l'identifiant de la session. Attention aux proxys et aux internautes dont l'IP change à chaque requête.

Pour définir manuellement l'identifiant de session, la fonction session_id(). Elle prend un paramètre optionnel, qui est le nouvel identifiant de session. Dans tous les cas, la fonction retourne l'identifiant courant de la session.

Exemple
<?php

$identifiant = sha1($_SERVER['REMOTE_ADDR']);
/* Premièrement, nous récupérons l'adresse IP du client,
   puis nous utilisons une fonction de hachage pour
   générer un code valide. En effet, l'identifiant d'une
   session ne peut contenir que des lettres (majuscules
   ou minuscules) et des chiffres. */

$ancien_identifiant = session_id($identifiant);
/* Ensuite, nous modifions manuellement l'identifiant de 
   session en utilisant session_id() et en lui donnant 
   l'identifiant généré plus haut. Comme toujours, la
   fonction retourne l'ancien identifiant, on le place
   dans une variable, au cas où on aurait besoin de le
   réutiliser. */

session_start();
/* On démarre la session, PHP va utiliser le nouvel
   identifiant qu'on lui aura fournis. */


Logo

Dans l'exemple ci-dessus, deux ordinateurs accédant au site par la même adresse IP publique, auront accès à la dernière session ouverte (donc pas forcément la leur).

Lire et écrire des données dans la session

[modifier | modifier le wikicode]

Les données de la session sont très facilement accessibles au travers d'un simple tableau PHP. Depuis PHP 4.1.0, vous pouvez utiliser le tableau super-global $_SESSION. Dans les versions plus anciennes, il s'appelait $HTTP_SESSION_VARS et nécessitait le mot clé global pour y accéder depuis une fonction.

Ces tableaux n'existent qu'une fois la session chargée. Tout ce qui est stocké à l'intérieur est sauvegardé et accessible depuis toutes les pages PHP utilisant les sessions.

Exercice : Une zone d'administration protégée par mot de passe

[modifier | modifier le wikicode]

Dans cet exercice, nous allons fabriquer pas-à-pas une zone d'administration pour votre site web, protégée par un mot de passe. Nous nous occuperons de la partie identification uniquement.

Dans le chapitre précédent, vous avez également créé le même type de script, mais en utilisant un cookie. Nous allons donc adapter le code en utilisant des sessions à la place.

Voici le code du formulaire en HTML, il va afficher une boîte de texte et un bouton "Connexion".

Début d’un principe
Fin du principe


La page de vérification
[modifier | modifier le wikicode]

Le formulaire ci-dessus pointe vers une page nommée verification.php. Cette page va vérifier que le mot de passe est juste et, si c’est le cas, placer un élément dans le tableau de la session pour que la page suivante puisse vérifier que vous êtes bien autorisé à voir la page.

Premièrement, nous devons initialiser la session. Nous laissons PHP choisir le nom.

Début d’un principe
Fin du principe


L'appel à la fonction session_start() a fabriqué le tableau $_SESSION. Pour l'instant celui-ci est vide.

Il faut maintenant vérifier que le mot de passe fourni est le bon. Nous créons ensuite une entrée dans le tableau de la session contenant true (vrai) si le code est le bon. Nous pouvons alors rediriger le navigateur de l'internaute ou afficher un message d'erreur.

Début d’un principe
Fin du principe


Si vous ne comprenez pas la ligne if ($mdp == $_POST['mdp']) {, vous devriez lire (ou relire) le chapitre sur les formulaires.

Pour résumé, le code suivant suffit à l'identification du visiteur :

Début d’un principe
Fin du principe


La page d'administration
[modifier | modifier le wikicode]

Cette page doit se nommer admin.php. Si vous décidez d’utiliser un autre nom, il faudra modifier le script d'identification pour qu’il pointe sur la bonne page.

Comme dans l'autre page, nous devons commencer par initier une session.

Début d’un principe
Fin du principe


Nous avons alors accès au tableau $_SESSION.

Si le visiteur a fourni le bon mot de passe, il existe un élément dans le tableau nommé 'admin' et valant true. Dans le cas contraire, cet élément n'existe pas. Il suffit donc de vérifier sa présence pour savoir si le visiteur est réellement l'administrateur du site. Pour cela, nous utilisons la fonction isset() qui vérifie si la variable (ou l'élément du tableau) existe.

Début d’un principe
Fin du principe


La suite du script n'est plus le sujet de cet exercice, le but est en effet atteint. Vérifier l'identité du visiteur pour lui permettre d'accéder à un espace privé.

Logo

La bonne pratique en terme de sécurité, pour éviter que des attaques puissent énumérer les utilisateurs (avant de chercher leurs mots de passe), et de renvoyer la même erreur 403 si l'utilisateur n'existe pas, ou s'il existe mais que son mot de passe est incorrect.

Fermer une session

[modifier | modifier le wikicode]

De la même façon que d'autre fonctionnalités de PHP, comme les connexions aux bases de données ou un pointeur de fichier, les sessions n'ont pas besoin d’être fermée.

Une fois le script PHP terminé, les données de la session sont automatiquement sauvegardées pour le prochain script.

Cependant, durant toute l'exécution du script, le fichier de la session est verrouillé et aucun autre script ne peut y toucher. Ils doivent donc attendre que le premier arrive à la fin.

Vous pouvez fermer plus rapidement une session en utilisant la fonction session_write_close().

Si vous voulez également détruire la session, vous pouvez utiliser la fonction session_destroy() couplée à la fonction session_unset().


Cookies

Base de données des cookies Firefox, lue avec SQLite.
Cookie ajouté à Firefox après commande PHP setcookie('wiki', 'user');.

Les cookies sont téléchargés du serveur HTTP sur le PC client, stockés dans le répertoire du navigateur de l'utilisateur courant. Par exemple dans Windows 7[1] :

  1. Firefox : C:\Users\%USERNAME%\AppData\Roaming\Mozilla\Firefox\Profiles\xxxxxxxx.default\cookies.sqlite (lisible par exemple avec https://addons.mozilla.org/en-US/firefox/addon/sqlite-manager/).
  2. Chrome C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Safe Browsing Cookies.
  3. Internet Explorer : C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Cookies.

Logo

  • Ne pas mettre d'informations privées (mots de passe du serveur...) dans ces variables car elles sont stockées dans un fichier non protégé, sur le disque dur de l'utilisateur.
  • Le cookie étant défini lors de l'affichage de l'en-tête HTTP, on ne peut pas le modifier puis le relire dans la même exécution. En effet, il est destiné à être lu après rechargement des pages.
  • Ces cookies sont limités à 20 par domaine dans la configuration par défaut de PHP.


D'une manière générale, les cookies prennent la forme suivante :

Set-Cookie: nom=nouvelle_valeur; expires=date; path=/; domain=.exemple.org
  • Le chemin (path) permet de ne les rendre opérants que dans certaines parties d'un site.
  • Le domaine fonctionne même avec d'autres serveurs (voir pixel espion).
<?php
  setcookie('cookie1', 'valeur1');
  echo $_COOKIE['cookie1'];

Pour définir la durée du cookie (à 30 jours) et la page du site qui lui est associée :

<?php
  setcookie('cookie2', 'valeur2', time() + 3600 * 24 * 30, '/');

Pour supprimer un cookie, on lui confère une durée de vie négative :

<?php
  setcookie('cookie2', 'valeur2', time() - 1, '/');
<?php
  if (isset($_COOKIE["cookie1"])) {
    echo 'Authentifié';
  } else {
    echo 'Non authentifié';
  }


Cache

La mémoire cache stocke des données calculées afin de pouvoir y ré-accéder sans les recalculer, donc plus rapidement.

Classification

[modifier | modifier le wikicode]

Il existe plusieurs systèmes de cache en PHP pour accélérer l'exécution du code rappelé[1] :

Nom Données stockées Flush
Cache d'instance Objet PHP. Ex :
if (null === $x) {
    $x = 1;
}
Relancer le script (ex : rafraichir la page Web).
Cache de session Objet PHP[2] Vider les cookies du navigateur.
OPcache Opcode[3] opcache_reset();
APCu Variables utilisateurs dans la RAM[4] apcu_clear_cache();
Cache du navigateur Rendering CTRL + F5
ESI Partie de pages Web Dépend du CDN ou proxy utilisé
Cache de framework Configuration, traductions Exemple de Symfony : php bin/console cache:clear vide les fichiers temporaires de var/cache.
Proxy Page web entière Exemples, voir Varnish, HAProxy
Base de données NoSQL Paire clé-valeur Voir les pages Memcached et Redis ci-après.
Cache d'ORM Annotations, requêtes SQL ou leurs résultats Exemple de Doctrine :
php bin/console doctrine:cache:clear-metadata 
php bin/console doctrine:cache:clear-query  
php bin/console doctrine:cache:clear-result

ou :

bin/console cache:pool:clear doctrine.query_cache_pool doctrine.result_cache_pool doctrine.system_cache_pool

ou en PHP :

        $qb = $entityManager->createQuery();
        $cacheDriver = $qb->getResultCacheDriver();
        $cacheDriver->delete('ma_cle_de_cache');
Chain cache Tout Utiliser les flushs de chaque cache inclus dans la chaine.

Les dépendances des caches gérés en PHP se doivent de respecter la norme PSR6[5], c'est-à-dire de fournir les méthodes de manipulation du cache suivantes :

  • hasItem
  • getItem
  • deleteItem
  • clear
  • save

Normalement chaque item a une durée de rétention (lifetime) avant renouvellement, ce qui évite de chercher à tout invalider régulièrement. Voici les opérations sur les items :

  • getKey
  • get (valeur)
  • isHit (est utilisable)
  • set (valeur)
  • expiresAt
  • expiresAfter

Dans Docker :

RUN docker-php-ext-install opcache
RUN pecl install apcu \
    && docker-php-ext-enable apcu --ini-name 10-docker-php-ext-apcu.ini \
    && docker-php-ext-enable apc --ini-name 20-docker-php-ext-apc.ini \
    && docker-php-ext-enable apc --ini-name 20-docker-php-ext-apc.ini
 Sur PHP < 8.0, ajouter après la première ligne : && pecl install apcu_bc \

Sur machine hôte Linux

[modifier | modifier le wikicode]
 sudo pecl install apcu
 echo "extension=apcu.so" >> php.ini

Sur machine hôte Windows

[modifier | modifier le wikicode]

Télécharger la DLL sur https://pecl.php.net/package/APCu/5.1.21/windows dans le dossier local des extensions PHP (ex : C:\wamp64\bin\php\php7.4.33\ext).

Dans php.ini, ajouter :

extension=apcu
[apcu]
apc.enabled=1
apc.enable_cli=1

Si ce n'est pas pris en compte, on peut avoir l'erreur "APCu is not enabled".

De plus, on peut personnaliser la configuration par défaut de plusieurs manières. Ex[6] :

apc.shm_size=32M
apc.ttl=7200
apc.enable_cli=1
apc.serializer=php

ou[7]

apc.shm_size=64M
apc.shm_segments=1
apc.max_file_size=10M
apc.stat=1


Memcached

Memcached est un système d'usage général servant à gérer la mémoire cache distribuée. Il est souvent utilisé pour augmenter la vitesse de réponse des sites web créés à partir de bases de données. Il gère les données et les objets en RAM de façon à réduire le nombre de fois qu'une même donnée stockée dans un périphérique externe est lue. Il tourne sous Unix, Windows et MacOS et est distribué selon les termes d'une licence libre dite permissive[1].


Memcached s'installe sur un serveur de mémoire cache distribuée, base de données de paires clé-valeur, qui est accessible par ses clients sur le port 11211, en TCP ou UDP[2].

Installation :

 sudo apt-get install memcached

Sur Docker PHP :

 RUN pecl install memcached \
    && docker-php-ext-enable memcache
 telnet localhost 11211

Si ça fonctionne sur le serveur mais pas depuis les autres machines, c'est certainement qu'il écoute 127.0.0.1 au lieu de son IP externe. Pour le vérifier :

netstat -an | grep ":11211"
tcp        0      0 127.0.0.1:11211         0.0.0.0:*               LISTEN

ou

ss -nlt | grep 11211
LISTEN 0 1024 127.0.0.1:11211

Pour le résoudre :

sudo vim /etc/memcached.conf
sudo /etc/init.d/memcached restart
  • Reset mémoire :
 echo "flush_all" | nc -q 1 localhost 11211

Memcached propose plusieurs commandes[3]. Pour tester si le serveur fonctionne avant de l'utiliser en PHP, on peut donc les lancer avec telnet nom_du_serveur 11211.

  • stats : informations sur le cache en cours.
  • set : ajoute une paire clé-valeur dans le cache.
  • add : ajoute une paire clé-valeur uniquement si la clé n'existe pas déjà.
  • get : récupère la valeur à partir de la clé donnée en paramètre.
  • delete : supprime la paire clé-valeur de la clé donnée.
  • flush_all : supprime tout ce qu'il y a dans le cache.

Par exemple pour lire une clé, il faut d'abord voir les descriptions de toutes les clés  :

stats items
STAT items:1:number 1
...
STAT items:2:number 1
...
STAT items:3:number 1
...

Puis l'appeler par son numéro pour voir son nom (le zéro représente l'absence de limite) :

stats cachedump 1 0


Utilisation en PHP

[modifier | modifier le wikicode]

Pour vérifier l'installation de la bibliothèque PHP pour Memcached :

php -i |grep memcached
  • S'il est absent :
sudo pecl install memcached
$memcached = new \Memcached();
$memcached->addServer('127.0.0.1', 11211);
$memcached->set('nom du test', 'valeur du test');
echo $memcached->get('nom du test');


Redis

Redis est comme Memcached, un système de gestion de base de données clef-valeur scalable, très hautes performances. En 2019, il devient plus utilisé que Memcached car il possède plus de fonctionnalités[1]. Par exemple il permet en plus une persistance sur la mémoire morte utile pour les reprises sur panne, autoriser les groupes de paires clé-valeur, et gère mieux le parallélisme[2].

Pour l'installer :

 sudo apt-get install redis-server

Obtenir la version :

redis-server -v
Redis server v=6.2.5 sha=00000000:0 malloc=jemalloc-5.1.0 bits=64 build=19d2f4c94e0b6820
  sudo apt-get install redis

Sur Docker PHP :

 RUN pecl install redis \
    && docker-php-ext-enable redis

Sur WAMP :

  • Télécharger le .dll sur https://pecl.php.net/package/redis
  • L'extraire dans le dossier correspondant à la version de PHP. Ex : bin\php\php8.2.0\ext
  • L'activer dans php.ini.
  • Redémarrer WAMP.

Ensuite on le voit dans phpinfo.

Pour se loguer au serveur Redis :

telnet nom_du_serveur 6379

Les commandes Redis les plus utiles[3] :

  • MONITOR : suivre l'activité du serveur en temps réel.
  • KEYS * : liste des clés.
  • GET : affiche la valeur de la clé en paramètre (ou nil si elle n'existe pas).
  • MGET : affiche les valeurs des clés en paramètre.
  • QUIT : quitter.
  • SET : définit une valeur associée à la clé en paramètre[4].
  • EXPIRE : définit une durée avant expiration pour la clé en paramètre, en secondes.
  • SETEX : SET + EXPIRE[5].
  • DEL : supprimer par le nom complet de la clé.
  • FLUSHALL : vider toute la base de données.

Exemple de reset mémoire depuis le shell :

echo "FLUSHALL" | nc -q 1 localhost 6379

Pour afficher les clés de la base en shell :

redis-cli KEYS '*'

Par défaut, redis-cli pointe sur 127.0.0.1. Pour regarder une autre machine :

redis-cli -h redis.example.com KEYS '*'

Supprimer des clés par leurs noms[6] (exemple de celles qui ont le préfixe "users:") :

 redis-cli KEYS "users:*" | xargs redis-cli DEL

ou :

 redis-cli --scan --pattern users:* | xargs redis-cli DEL

Plusieurs bases

[modifier | modifier le wikicode]

Chaque instance de Redis peut accueillir jusqu'à 16 bases de données[7].

Elles sont accessibles par un suffixe dans leur DSN. Par défaut, redis://localhost:6379 pointe sur la base zéro : redis://localhost/0:6379.


Utilisation en PHP

[modifier | modifier le wikicode]
$redis = new \Redis();
$redis->connect('localhost', 6379);
$redis->set('nom du test', 'valeur du test');
echo $redis->get('nom du test');

Cette bibliothèque permet d'utiliser Redis en clustering, avec des masters et slaves[8].

Dans le framework PHP Symfony.

SncRedisBundle
[modifier | modifier le wikicode]

Avant Symfony 4.1, il fallait passer par un bundle tel que SncRedisBundle[9].

Depuis :

 composer require snc/redis-bundle predis/predis

Pour que les sessions soient stockées dans Redis au lieu de var/cache/, remplacer dans framework.yaml, session.handler_id: null par snc_redis.session.handler. Cela permet par exemple de les partager entre plusieurs conteneurs.

RedisSessionHandler
[modifier | modifier le wikicode]

Depuis Symfony 4.1, le composant HttpFoundation contient une classe RedisSessionHandler[10].

Installation dans services.yaml :

    redis:
        class: Redis
        calls:
            - connect:
                - '%env(REDIS_HOST)%'
                - '%env(int:REDIS_PORT)%'

    Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler:
        arguments:
            - '@redis'

Puis dans config/packages/framework.yaml[11] :

framework:
    session:
        handler_id: Symfony\Component\HttpFoundation\Session\Storage\Handler\RedisSessionHandler

Ensuite le service session utilisera automatiquement Redis.

Pour mettre le cache Doctrine de requête et de résultat dans Redis, on peut utiliser SncRedisBundle[12][13].

Pour vider les caches :

bin/console cache:pool:clear doctrine.query_cache_pool doctrine.result_cache_pool doctrine.system_cache_pool

L'inconvénient de Redis ainsi configuré est que s'il tombe en panne, les applications qui l'utilisent crashent aussi (erreur de connexion à Redis dès l'instanciation des services).

Pour se prémunir de cela, il est recommandé d'inclure Redis dans un cache chainé, qui permet de basculer automatiquement sur des caches de secours sans bloquer l'application. Exemple[14] :

# cache.yaml
framework:
    cache:
        prefix_seed: my_app_
        app: cache.chain
        pools:
            cache.chain:
                adapter: cache.adapter.psr6
                provider: chain_adapter_provider

            cache.redis:
                adapter: cache.adapter.redis
            cache.apcu:
                adapter: cache.adapter.apcu
            cache.filesystem:
                adapter: cache.adapter.filesystem
# services.yaml
services:
    chain_adapter_provider:
        class: Symfony\Component\Cache\Adapter\ChainAdapter
        arguments:
            - ['@cache.redis', '@cache.apcu', '@cache.filesystem']
            - '%env(int:CACHE_DEFAULT_LIFETIME)%'


Formulaire

Le PHP est un langage de traitement. Une page en php pourra analyser et effectuer des opérations suite à un formulaire. Ce formulaire devra être écrit en HTML, dans une page .html (.htm) ou .php. Pour notre exemple, nous allons créer une page avec laquelle un utilisateur pourra se connecter à une zone administrateur.

Notre formulaire (form.html) comprendra deux éléments :

  • le champ du mot de passe ("password")
  • un bouton pour soumettre le formulaire

La page de traitement, en PHP (traitement.php) :

  • vérification si le mot de passe est correct
  • envoi d'un cookie. ceci sera la preuve que l'ordinateur distant est autorisé à accéder aux pages

Une page de la zone administration (admin.php) :

  • vérification si l'utilisateur est autorisé à consulter les pages

Le code source présenté ici est uniquement le formulaire. Pour un affichage agréable de la page il est nécessaire de l'"habiller". Voir comment créer une page en HTML. Ce script ne sera pas expliqué. Pour le comprendre vous devez avoir les bases du formulaire en HTML.

Début d’un principe
Fin du principe


En gros, ce formulaire enverra sur la page traitement.php la valeur de l'entrée "mdp".

Pour comprendre la suite, vous devez avoir en tête la chose suivante sur les variables. Le champ dont le nom est "mdp" (name=mdp) envoie sur la page de traitement la variable $mdp avec pour valeur l'entrée renseignée.

Pour récupérer les valeurs d'un formulaire on utilise $valeur=$_POST["nomvariable"];

Si vous désirez récupérer les valeurs passée via une URL, par exemple http://www.example.com/index.php?mdp=valeur, on utilise $valeur=$_GET["mdp"];

Il est possible de récupérer directement la valeur d'un formulaire via le nom du champ (dans notre exemple $mdp contiendrait la valeur saisie du formulaire) mais il est fortement conseillé d’utiliser $_POST pour des raisons de sécurité et de compatibilité.

Début d’un principe
Fin du principe


La zone administration

[modifier | modifier le wikicode]

La zone administration va vérifier si l'utilisateur est autorisé à consulter ces pages. Il va comparer le mot de passe entré dans le cookie avec le mot de passe réel.

Début d’un principe
Fin du principe


Types de champ

[modifier | modifier le wikicode]

Pour des <input type="checkbox">, on vérifie si leurs valeurs sont 'on' ou 'off'.

Les données d'un formulaire rempli par le client sont envoyées au serveur dans une requête POST avec en en-tête un Content-type x-www-form-urlencoded ou multipart/form-data. La différence est que le premier est semblable aux urlencode() des GET mais dans le body POST, et cette conversion est légèrement plus lente qu'en multipart/form-data[1].


Fichiers

L'utilisation de fichier peut être utile pour exporter des données à archiver ou accélérer un traitement par un système de cache. Cette page explique comment interagir avec un fichier.

Voici les fonctions usuelles de navigation et manipulation du système de fichier.

  • PHP_OS : contient de système d'exploitation courant (ex : WINNT, Linux).
  • basename($path) : extraire le nom du fichier à partir de son chemin.
  • dirname(__DIR__) : extraire le répertoire parent du dossier en paramètre, au niveau précisé en second paramètre (le parent direct par défaut).
  • chdir($dossier) : changer de dossier courant ;
  • opendir($dossier) : ouvrir un dossier ;
  • closedir($dossier) : fermer le dossier ;
  • is_dir($dossier) : vérifier la présence d'un dossier ;
  • is_file($fichier) : vérifier la présence d'un fichier ;
  • file_exists($fichier) : vérifier la présence d'un fichier ou un dossier local[1] ;
  • filesize($fichier) : renvoie la taille du fichier en octet[2] ;
  • readdir($dossier) : lire le contenu du dossier ligne par ligne ;
  • scandir($dossier) : lire le contenu du dossier en une fois ;
  • glob($regex) : lire le contenu du dossier courant, avec filtrage regex (ex : *.txt)[3].

Récupérer certains fichiers d'un dossier

[modifier | modifier le wikicode]

Par exemple, pour trouver tous les fichiers .sql du dossier "sql" :

        chdir('sql');
        $files = glob('*.sql');
        var_dump($files);
  • shell_exec('pwd') : exécuter n'importe quelle commande shell. Le nom du répertoire courant (pwd) dans cet exemple.
  • mkdir() : créer un dossier
  • touch() : créer un fichier (ou le rafraichir s'il existe)
  • rename() : déplacer un fichier

Logo

Les commandes du shell_exec dépendent du système d'exploitation. Par exemple, sur Linux shell_exec('ls') fonctionne mais sur Windows il renvoie null et il faut utiliser shell_exec('dir'). De plus, sachant que les systèmes d'exploitation n'ont pas les mêmes séparateurs de dossiers (sous Windows on utilise "\" et en unixerie c'est "/"), on peut utiliser la constante prédéfinie : DIRECTORY_SEPARATOR qui fonctionne pour les deux[4].

Droits des fichiers

[modifier | modifier le wikicode]

Sous Windows il suffit de se rendre dans l'onglet sécurité des propriétés d'un fichier, pour cocher les cases autorisant le programme à le lire et/ou le modifier.

chmod est le système de droit d'accès a un fichier Unix. Il s'agit d'un nombre à trois chiffres que l’on attribut à un fichier (ex. : 755). Il détermine le droit d'accès au fichier en question, qui peut le modifier.

Selon sa valeur le système d'exploitation autorise ou non la modification du fichier. Sous GNU/Linux, l'utilisateur 'root', (superutilisateur), a tous les droits, c'est-à-dire qu’il peut modifier tous les fichiers.

Lorsque qu'un fichier est créé manuellement, le chmod du fichier en question est 755, avec un tel chmod nous ne pouvons pas modifier le fichier avec un script PHP. Pour pouvoir le modifier, il suffit juste de changer le chmod du fichier, en lui donnant la valeur 766. Sous Windows, cette notion est masquée et il suffit d’être administrateur de la machine (utilisateur par défaut) pour pouvoir modifier un fichier.

  • Pour récupérer les permissions : fileperms($localFilePath) & 0777
  • Le propriétaire : fileowner($localFilePath))

Ouvrir un fichier

[modifier | modifier le wikicode]

Pour déclencher l'ouverture d'un fichier chez celui qui exécute le script, on précise son type puis son emplacement :

header("Content-Type: application/pdf");
header("Content-Disposition: inline; filename='".$fichier."'");

Télécharger un fichier

[modifier | modifier le wikicode]
header("Content-Type: application/pdf");
header("Content-Disposition: attachment; filename='".$fichier."'");

Logo

Le téléchargement ne doit pas être précédé d'affichages (ex : echo ou logs de warning) sinon ils apparaitront dans l'en-tête du fichier, le rendant illisible.

Zipper un fichier

[modifier | modifier le wikicode]

Cette fonctionnalité est fournie nativement depuis PHP 5.2.0. Par contre sur les versions >= 7 sur Linux il faut l'installer :

RUN apt-get update && \
    apt-get install -y \
        libzip-dev \
        && docker-php-ext-install zip

Pour compresser des fichiers, il faut d'abord créer l'archive vide, puis les y ajouter un par un avec la méthode addFile(Fichier source, Nom du fichier dans l'archive)[5] :

$zip = new ZipArchive();
$f = '../Temp/'.$nomFichier . '.zip';

if ($zip->open($f, ZipArchive::CREATE) !== true) {
    exit('Impossible de créer le .zip');
}
$zip->addFile('../Temp/' . $nomFichier . '.xls', $nomFichier . '.xls');
$zip->close();

Dézipper un fichier

[modifier | modifier le wikicode]
$zip = new ZipArchive();
$f = '../Temp/'.$nomFichier . '.zip';

if ($zip->open($f) === true) {
    $zip->extractTo('../Temp/');
    $zip->close();
}

Pour les .gz[6] :

    private function uncompressFile(string $target): void
    {
        $uncompressedFileName = str_replace('.gz', '', $target);

        $file = gzopen($target, 'rb');
        $outFile = fopen($uncompressedFileName, 'wb');

        while (!gzeof($file)) {
            fwrite($outFile, gzread($file, 4096));
        }

        fclose($outFile);
        gzclose($file);

Éditer et fermer un fichier

[modifier | modifier le wikicode]

Créer un fichier avec un attribut chmod de 766. Ensuite il faut ouvrir le fichier en question avant de lire/écrire. Pour cela la fonction fopen est là :

Début d’un principe
Fin du principe


Explication : La fonction fopen à besoin de deux paramètres pour pouvoir s'exécuter :

  • $nomFichier, il s'agit du chemin du fichier
  • $mode, il s'agit du mode de l'ouverture

La fonction fopen utilise le premier paramètre, pour déterminer le chemin du fichier a ouvrir/créer.

Voici les différents modes d'ouvertures pour la fonction fopen :

Mode Description
r (read) Ouvre en lecture seule, et place le pointeur de fichier au début du fichier.
r+ Ouvre en lecture et écriture, et place le pointeur de fichier au début du fichier.
w (write) Ouvre en écriture seule, et place le pointeur de fichier au début du fichier et réduit la taille du fichier à 0. Si le fichier n'existe pas, on tente de le créer.
w+ Ouvre en lecture et écriture, et place le pointeur de fichier au début du fichier et réduit la taille du fichier à 0. Si le fichier n'existe pas, on tente de le créer.
a (append) Ouvre en écriture seule, et place le pointeur de fichier à la fin du fichier. Si le fichier n'existe pas, on tente de le créer.
a+ Ouvre en lecture et écriture, et place le pointeur de fichier à la fin du fichier. Si le fichier n'existe pas, on tente de le créer.
x Créé et ouvre le fichier en lecture seule ; place le pointeur de fichier au début du fichier.

Si le fichier existe déjà, fopen() va échouer, en retournant FALSE et en générant une erreur de niveau E_WARNING. Si le fichier n'existe pas, fopen() tente de le créer. Ce mode est l'équivalent des options O_EXCL|O_CREAT pour l'appel système open(2) sous-jacente. Cette option est supportée à partir de PHP 4.3.2 et fonctionne uniquement avec des fichiers locaux.

x+ Crée et ouvre le fichier en lecture et écriture ; place le pointeur de fichier au début du fichier.

Si le fichier existe déjà, fopen() va échouer, en retournant FALSE et en |générant une erreur de niveau E_WARNING. Si le fichier n'existe pas, fopen() tente de le créer.

c Ouvre le fichier en écriture seule ; place le pointeur de fichier au début du fichier.

Si le fichier existe déjà, il ne le tronque pas, sinon il le crée.

c+ Ouvre le fichier en lecture et écriture, puis se comporte comme "c".

Pour le fermer maintenant, il y a la fonction fclose.

Début d’un principe
Fin du principe


Copier un fichier

[modifier | modifier le wikicode]
if (!copy($ancienFichier, $nouveauFichier)) {
    echo 'La copie a échoué.';
}

Pour les images il existe aussi imagecopyresampled[7].

Supprimer un fichier

[modifier | modifier le wikicode]
if (!unlink($fichier)) {
    echo 'La suppression a échoué.';
}

Interagir avec le fichier

[modifier | modifier le wikicode]

Une fois ouvert, il existe plusieurs manière de lire le contenu d'un fichier : caractère par caractère, ligne par ligne, jusqu'à une certaine taille, ou tout entier.

Tout le fichier

[modifier | modifier le wikicode]

Pour stocker le fichier entier dans un tableau, on peut utiliser file() qui renvoie un tableau séparant chaque ligne du fichier :

Début d’un principe
Fin du principe

Ainsi, $fichier[0] correspond à la première ligne, $fichier[1] à la seconde, etc.

Si le fichier sature la mémoire, utiliser file_lines() avec les générateurs[8].

file_get_contents
[modifier | modifier le wikicode]

Pour stocker le fichier entier dans un scalaire, on utilise cette fonction.

Pour afficher tout le fichier dans la sortie standard[9].

Ligne par ligne

[modifier | modifier le wikicode]

La méthode générale pour lire ligne par ligne avec la fonction fgets, dont la définition est la suivante :

Début d’un principe
Fin du principe


La variable $objetFichier doit être le résultat de l'ouverture d'un fichier avec fopen. Pour lire un fichier en entier, il suffit d’utiliser fgets en boucle ; la condition de sortie de la boucle serait alors l'arrivée à la fin de fichier, évènement notifié par la fonction feof.

Exemple de parcours d'un fichier ligne par ligne
<?php
$nomFichier = "chemin_ou_nom_de_fichier";
$objetFichier = fopen($nomFichier, "r"); //ouverture en lecture

if ($objetFichier) {
    //on lit le fichier tant que la fin n'est pas atteinte
    while (!feof($objetFichier)) {
        $ligne = fgets($objetFichier);
        echo $ligne;
    }
    fclose($objetFichier);
} else {
    echo "Erreur : impossible d'ouvrir le fichier.";
}


La fonction équivalente pour lire caractère par caractère se nomme fgetc :

Début d’un principe
Fin du principe


Notez que cette fonction retourne FALSE arrivée à la fin du fichier. Lors d'un parcours, il faut donc tester impérativement la valeur avant de l’utiliser, avec un test logique de la forme « $caractere !== FALSE ».

Cette fonction fonctionne comme "fgets" sauf qu'en plus elle utilise le séparateur "," (qui peut être changé par le paramètre "delimiter"[10]) pour créer un sous-tableau de champs par ligne.

Une fois le fichier ouvert, l'écriture se fait via la fonction fwrite.

Début d’un principe
Fin du principe


Explication :

  • $objetFichier : variable pointant vers un fichier ouvert avec fopen
  • $chaine : la chaîne à écrire dans le fichier
  • retour : le nombre de caractère écrits, ou FALSE si erreur

L'utilisation est alors la même que pour la lecture : ouvrir le fichier, écrire et fermer.

Exemple d'écriture
<?php
$nomFichier = "chemin_ou_nom_de_fichier";
$chaine = "Je suis une chaine écrite par PHP !\n"
$objetFichier = fopen($nomFichier, "w"); //ouverture en lecture

if ($objetFichier) {
    if(fwrite($objetFichier, $chaine) === FALSE) {
       echo "Erreur : impossible d'écrire dans le fichier.";
    }
    fclose($objetFichier);
} else {
    echo "Erreur : impossible d'ouvrir le fichier.";
}


Attention : Si vous ouvrez le fichier avec l'option w ou w+, le contenu du fichier sera effacé s'il existait. Pour écrire à la fin, il faut ouvrir avec les options a ou a+ (voir ci-dessus). Enfin, si vous pouvez avec l'option r+, le contenu sera écrasé, puisque le pointeur de fichier sera placé au début.


Par ailleurs, la fonction file_put_contents() effectue un fopen(), fwrite() et fclose() successivement[11].

Parfois, il peut être nécessaire de se déplacer dans le fichier, par exemple pour revenir au début. Pour cela, il faut utiliser la fonction fseek comme suit :

Début d’un principe
Fin du principe

Explication :

  • $objetFichier : variable pointant vers un fichier ouvert avec fopen
  • $position : la position à laquelle on veut se déplacer. Pour revenir au début, $position doit valoir zéro.

Pour aller à la fin :

Début d’un principe
Fin du principe


Fichiers uploadés

[modifier | modifier le wikicode]

Copier-coller ces lignes dans un fichier vierge upload.php pour qu’il affiche le nom des fichiers qu'on upload avec :

<?php
echo '
<form method="POST" action="upload.php" enctype="multipart/form-data">
     <input type="file" name="fichier">
     <input type="submit" name="envoyer" value="Uploader">
</form>';
if (isset($_FILES['fichier'])) {
    echo $_FILES['fichier']['name'];
}

Il existe la fonction is_uploaded_file() pour vérifier si un fichier est bien issu d'un upload[12], et move_uploaded_file() pour le déplacer.

Fichiers temporaires

[modifier | modifier le wikicode]

Pour créer des fichiers qui seront automatiquement détruits en fin de connexion, le dossier temporaire est accessible avec : sys_get_temp_dir().

La fonction tempnam() quant-à elle nomme automatiquement un nouveau fichier temporaire avec un nom unique :

$fileName = tempnam(sys_get_temp_dir(), 'MonFichier1');

Fichiers distants

[modifier | modifier le wikicode]

Pour lire un fichier en HTTP (par exemple cette page web) :

<?php
$page = file_get_contents("http://fr.wikibooks.org/wiki/PHP/Fichiers");
echo $page;

ou[13]

<?php
$url = 'http://fr.wikibooks.org/wiki/PHP/Fichiers';
echo htmlspecialchars(implode('', file($url)));

Pour tester si un fichier distant existe, utiliser get_headers()[14].

 fonctionne aussi bien avec HTTPS.

Installation : dans php.ini, décommenter "extension=ftp" (anciennement "extension=php_ftp.dll").

Déposer un fichier (sans vérifier s'il en écrase un)[15] :

<?php
copy('fichier_local.txt', 'ftp://login:password@server/repertoire/fichier_distant.txt');

Souvent le répertoire porte le nom de l'utilisateur, et on écrase le fichier s'il existe :

<?php
$serveur = 'serveur1';
$login = 'utilisateur1';
$password = 'mdp1';
$fichier = 'fichier1';
copy($fichier, 'ftp://'.$login.':'.$password.'@'.$serveur.'/'.$login.'/'.$fichier, stream_context_create(array('ftp' => array('overwrite'=>True))));

Fonctions propres au FTP, pour lire un serveur distant, y télécharger et déposer des fichiers[16] :

$cnx = ftp_connect($serveur);
$loginResult = ftp_login($cnx, $login, $password);

// Liste des noms des dossiers et fichiers du dossier distant (dans le désordre)
$dossierDistant = ftp_nlist($cnx, ".");
var_dump($dossierDistant);

// Liste des éléments du dossier distant avec leurs inodes
$dossierDistant2 = ftp_rawlist($cnx, ".");
var_dump($dossierDistant2);

// Change de répertoire :
var_dump(ftp_pwd($cnx));
ftp_chdir($cnx, 'tmp');
var_dump(ftp_pwd($cnx));

// Téléchargement du dernier fichier distant
sort($dossierDistant);
$distant = $dossierDistant[sizeof($dossierDistant)-1];
$local = 'FichierLocal.txt';
if (!ftp_get($cnx, $local, $distant, FTP_BINARY)) {
    echo "Erreur ftp_get\n";
} else {
    ftp_delete($cnx, $distant);
}

// Téléversement d'un fichier local
$local = 'FichierLocal2.txt';
if (!ftp_put($cnx, $distant, $local, FTP_ASCII)) {
    echo "Erreur ftp_put\n";
}

Pour définir le timeout, voir ftp_set_option()[17].

On utiliser les trois fonctions suivantes pour construire l'URL ouverte par fopen[18] :

$connexion = ssh2_connect('tools-login.wmflabs.org', 22);
ssh2_auth_password($connexion, 'monLogin', 'MonMotDePasse');
$sftp = ssh2_sftp($connexion);
$stream = fopen("ssh2.sftp://$sftp/monFichier", 'r');

Ce protocole donne accès aux flux (entrant et sortant) de PHP[19].

  • php://fd : file descriptor.
  • php://filter
  • php://input : lecture de ce qui est posté.
  • php://memory
  • php://output : écriture.
  • php://stderr
  • php://stdin
  • php://stdout
  • php://temp

Fichiers structurés

[modifier | modifier le wikicode]

Les fichiers structurés comme les PDF, XML, DOCX et XLSX peuvent facilement être manipulés par des bibliothèques et frameworks existant, qui seront abordés dans les chapitres suivants.

  1. http://php.net/manual/fr/function.file-exists.php
  2. https://www.php.net/manual/en/function.filesize.php
  3. http://php.net/manual/fr/function.glob.php
  4. http://php.net/manual/fr/dir.constants.php
  5. http://php.net/manual/fr/ziparchive.addfile.php
  6. https://stackoverflow.com/questions/11265914/how-can-i-extract-or-uncompress-gzip-file-using-php
  7. http://php.net//manual/fr/function.imagecopyresampled.php
  8. https://www.startutorial.com/articles/view/php-generator-reading-file-content
  9. https://www.php.net/manual/en/function.readfile.php
  10. http://php.net/manual/fr/function.fgetcsv.php
  11. http://php.net/manual/fr/function.file-put-contents.php
  12. https://www.php.net/manual/en/function.is-uploaded-file.php
  13. developpez.net
  14. http://php.net/manual/fr/function.get-headers.php
  15. http://php.net/manual/fr/book.ftp.php
  16. http://php.net/manual/fr/function.ftp-nlist.php
  17. http://php.net/manual/fr/function.ftp-set-option.php
  18. http://php.net/manual/fr/function.ssh2-sftp.php
  19. http://php.net/manual/fr/wrappers.php.php



Objets COM

Le système d'exploitation Windows fournit des objets COM (Component Object Model) pour manipuler des fichiers dans divers langages de programmation dont PHP.

Le serveur Web IIS charge déjà ce composant par défaut, pour Apache par contre il faut l'ajouter au php.ini : extension=php_com_dotnet.dll.

Pour être sûr qu'il soit activé ensuite, on peut utiliser[1] :

 ini_set('com.allow_dcom','1');

Création d'un .xls :

$Excel = new COM('excel.application');
$Classeur = $Excel->Workbooks->Add();
$Feuille = $Classeur->Worksheets(1);
$Cellule = $Feuille->Cells(1,1);

$Cellule->Value = 'Hello World!';

$Classeur->SaveAs('Monclasseur.xls');
$Classeur->Close();
$Excel->Quit();

On peut aussi créer des .doc.


Images

PHP peut créer et modifier dynamiquement des images, par exemple avec la bibliothèque GD, inclue depuis PHP 3.0.

La création d'une nouvelle image se déroule généralement ainsi :

  1. Chargement en mémoire d'une image, nouvelle ou existante.
  2. Chargement éventuel des couleurs à y apporter.
  3. Modifications éventuelles de ses composants (création de lignes, points, remplissages, ajout de textes...).
  4. Restitution de l'image après avoir posté son type dans l'en-tête.
  5. Libération mémoire.

Créer une nouvelle image

[modifier | modifier le wikicode]

Pour créer une image ex-nihilo, on utilise la fonction :

imagecreatetruecolor($hauteur, $largeur)

qui crée en mémoire une nouvelle image de hauteur et largeur définies en pixel, et restitue une référence à l'image crée.

Il existe aussi une autre fonction pour cela, mais elle n'est pas recommandée car son amplitude de couleurs est moindre[1] :

imagecreate($largeur, $hauteur)

Pour charger en mémoire une image sauvegardée sur le disque :

imagecreatefrom<type>($chemin)

Exemple :

$img = imagecreatefrompng('image.png');

Autre fonction :

imagecreatefromstring($texte)

qui crée une image à partir de son format texte, spécifié en paramètre.

S'il survient une erreur, ces fonctions renvoient false.

Travailler avec les couleurs

[modifier | modifier le wikicode]

Pour allouer une couleur il faut en définir les paramètres RVB :

$couleur = imagecolorallocate($image, $r, $v, $b)

Pour définir une transparence dans un PNG :

imagecolortransparent($image, $couleur)

$couleur est le résultat de imagecolorallocate.

Il est également possible de déterminer la transparence, comprise entre 0 et 127 (qui représente la transparence totale) à l'aide de la fonction :

imagecolorallocatealpha($image, $r, $v, $b, $transparence)
 la première couleur allouée définit le fond de toute l'image.

Sinon, un fond transparent peut également être assuré par les deux fonctions ci-dessous :

    imageAlphaBlending($image, false);
    imageSaveAlpha($image, true);

Une fois l'image créée et colorisée, on peut y appliquer les opérations :

  • Dessiner des pixels (ex : créer des lignes).
  • Travailler sur les pixels existants en désignant des zones.

Dessiner des formes

[modifier | modifier le wikicode]

Pour dessiner un pixel, on utilise ses coordonnées (x, y ci-dessous) :

imagesetpixel(image, x, y, couleur)

Pour tracer une ligne entre deux points :

imageline(image, x1, y1, x2, y2, couleur)

Pour créer un rectangle par sa diagonale :

imagerectangle(image, x1, y1, x2, y2, couleur)

Pour représenter une ellipse par son centre, sa hauteur et sa largeur :

imageellipse(image, x, y, h, l, couleur)

ou en précisant son arc par ses angles en gradient (numérotés dans le sens horaire) :

imagearc(image, x, y, h, l, angle1, angle2, couleur)

Retravailler les pixels existants

[modifier | modifier le wikicode]

Une des fonctions les plus utilisées pour retravailler des images comme des photos, est certainement imagecopyresized, qui permet de copier une zone rectangulaire pour la coller dans une autre image[2]. Exemple :

imagecopyresized(dst_image, src_image, dst_x, dst_y, src_x, src_y, dst_w, dst_h, src_w, src_h);

où :

  • src_image est l'image source ;
  • dst_image est l'image de destination ;
  • dst_x, dst_y sont les coordonnées de dst_image ;
  • src_x, src_y sont les coordonnées de src_image en partant d'en haut à gauche ;
  • dst_w, dst_h, src_w, src_h sont les largeurs et hauteurs des rectangles de source et destination.

On peut ensuite comprendre que si dst_w est égal à src_w, et dst_h à src_h, la portion rectangulaire de l'image restera de la même taille. Dans le cas contraire on allonge ou on élargit.

La fonction imagecopyresampled reçoit les mêmes paramètres que imagecopyresized, mais avec la différence que, en cas de redimensionnement, la qualité est améliorée.

Puis il existe la fonction imagefilter, qui permet de nombreux effets tels que les niveaux de gris, le relief, ou la recoloration[3].

Imprimer l'output

[modifier | modifier le wikicode]

Le format de l'image obtenue ("png", "jpeg" ou "gif") peut être spécifié par la fonction header, via le content-type (par défaut text/html) ainsi :

header("Content-type: image/<type>");

Pour visualiser l'image ensuite, la placer en paramètre dans une fonction dépendant de son type : imagepng, imagejpeg ou imagegif.

Puis, libérer la mémoire avec imagedestroy($image). Cette étape facultative est particulièrement recommandée pour les images volumineuses.

Le code suivant affiche dans le navigateur, un carré rouge de 50 pixels dans un noir de 100.

$image = imagecreatetruecolor(100, 100);
$couleur = imagecolorallocate($image, 255, 0, 0);
imagefilledrectangle($image,0,0,50,50,$couleur);
header("Content-type: image/png");
imagepng($image);
imagedestroy($image);


Mails

Le code suivant se connecte à localhost:25 pour envoyer un mail[1] :

<?php
     $to      = 'Destinataire@gmail.com';
     $subject = 'Sujet du mail';
     $message = 'Contenu du message';
     $headers = 'From: Expediteur@gmail.com' . "\r\n" .
       'Reply-To: Expediteur@gmail.com' . "\r\n" .
       'X-Mailer: PHP/' . phpversion();
     mail($to, $subject, $message, $headers);

Si la machine hébergeant le script n'est pas pourvue d'un serveur SMTP, le freeware portable Simple Mail Server[2] peut jouer ce rôle rapidement sans installation.

Utilisation de Mail() plus complexe...

[modifier | modifier le wikicode]
Exemple
<?php
$mail = 'test@test.fr'; // Déclaration de l'adresse de destination.
if (!preg_match("#^[a-z0-9._-]+@(hotmail|live|msn).[a-z]{2,4}$#", $mail)) { // On filtre les serveurs qui rencontrent des bogues.
    $passage_ligne = "\r\n";
} else {
    $passage_ligne = "\n";
}
//=====Déclaration des messages au format texte et au format HTML.
$message_txt = "Bonjour, voici un e-mail envoyé par un script PHP.";
$message_html = "<html><head></head><body><b>Bonjour</b>, voici un e-mail envoyé par un <i>script PHP</i>.</body></html>";
//==========
 
//=====Création de la boundary
$boundary = "-----=".md5(rand());
//==========
 
//=====Définition du sujet.
$sujet = "Hey mon ami !";
//=========
 
//=====Création du header de l'e-mail.
$header = "From: \"WeaponsB\"<test@test.fr>".$passage_ligne;
$header.= "Reply-to: \"WeaponsB\" <test@test.fr>".$passage_ligne;
$header.= "MIME-Version: 1.0".$passage_ligne;
$header.= "Content-Type: multipart/alternative;".$passage_ligne." boundary=\"$boundary\"".$passage_ligne;
//==========
 
//=====Création du message.
$message = $passage_ligne."--".$boundary.$passage_ligne;
//=====Ajout du message au format texte.
$message.= "Content-Type: text/plain; charset=\"ISO-8859-1\"".$passage_ligne;
$message.= "Content-Transfer-Encoding: 8bit".$passage_ligne;
$message.= $passage_ligne.$message_txt.$passage_ligne;
//==========
$message.= $passage_ligne."--".$boundary.$passage_ligne;
//=====Ajout du message au format HTML
$message.= "Content-Type: text/html; charset=\"ISO-8859-1\"".$passage_ligne;
$message.= "Content-Transfer-Encoding: 8bit".$passage_ligne;
$message.= $passage_ligne.$message_html.$passage_ligne;
//==========
$message.= $passage_ligne."--".$boundary."--".$passage_ligne;
$message.= $passage_ligne."--".$boundary."--".$passage_ligne;
//==========
 
//=====Envoi de l'e-mail.
mail($mail,$sujet,$message,$header);
//==========


Explication:

  • Ouverture boundary.(sert à séparer les différentes parties de notre e-mail)
  • Déclaration de type (exemple texte, par défaut les clients mail tentent de convertir l'HTML en texte).
  • Texte.
  • Ouverture boundary.
  • Déclaration de type (exemple HTML,par défaut les clients mail tentent de convertir l'HTML en texte)).
  • HTML.
  • Fermeture boundary.
  • Fermeture boundary.

Avec la mise en place de pièce jointe

[modifier | modifier le wikicode]
Exemple
<?php
$mail = 'test@test.fr'; // Déclaration de l'adresse de destination.
if (!preg_match("#^[a-z0-9._-]+@(hotmail|live|msn).[a-z]{2,4}$#", $mail)) { // On filtre les serveurs qui présentent des bogues.
    $passage_ligne = "\r\n";
} else {
    $passage_ligne = "\n";
}
//=====Déclaration des messages au format texte et au format HTML.
$message_txt = "Bonjour, voici un e-mail envoyé par un script PHP.";
$message_html = "<html><head></head><body><b>Bonjour</b>, voici un e-mail envoyé par un <i>script PHP</i>.</body></html>";
//==========
 
//=====Lecture et mise en forme de la pièce jointe.
$fichier   = fopen("background.jpg", "r");
$attachement = fread($fichier, filesize("background.jpg"));
$attachement = chunk_split(base64_encode($attachement));
fclose($fichier);
//==========
 
//=====Création de la boundary.
$boundary = "-----=".md5(rand());
$boundary_alt = "-----=".md5(rand());
//==========
 
//=====Définition du sujet.
$sujet = "Fichier important";
//=========
 
//=====Création du header de l'e-mail.
$header = "From: \"WeaponsB\"<test@test.fr>".$passage_ligne;
$header.= "Reply-to: \"WeaponsB\" <weaponsb@mail.fr>".$passage_ligne;
$header.= "MIME-Version: 1.0".$passage_ligne;
$header.= "Content-Type: multipart/mixed;".$passage_ligne." boundary=\"$boundary\"".$passage_ligne;
//==========
 
//=====Création du message.
$message = $passage_ligne."--".$boundary.$passage_ligne;
$message.= "Content-Type: multipart/alternative;".$passage_ligne." boundary=\"$boundary_alt\"".$passage_ligne;
$message.= $passage_ligne."--".$boundary_alt.$passage_ligne;
//=====Ajout du message au format texte.
$message.= "Content-Type: text/plain; charset=\"ISO-8859-1\"".$passage_ligne;
$message.= "Content-Transfer-Encoding: 8bit".$passage_ligne;
$message.= $passage_ligne.$message_txt.$passage_ligne;
//==========
 
$message.= $passage_ligne."--".$boundary_alt.$passage_ligne;
 
//=====Ajout du message au format HTML.
$message.= "Content-Type: text/html; charset=\"ISO-8859-1\"".$passage_ligne;
$message.= "Content-Transfer-Encoding: 8bit".$passage_ligne;
$message.= $passage_ligne.$message_html.$passage_ligne;
//==========
 
//=====On ferme la boundary alternative.
$message.= $passage_ligne."--".$boundary_alt."--".$passage_ligne;
//==========
 
 
 
$message.= $passage_ligne."--".$boundary.$passage_ligne;
 
//=====Ajout de la pièce jointe.
$message.= "Content-Type: image/jpeg; name=\"background.jpg\"".$passage_ligne;
$message.= "Content-Transfer-Encoding: base64".$passage_ligne;
$message.= "Content-Disposition: attachment; filename=\"background.jpg\"".$passage_ligne;
$message.= $passage_ligne.$attachement.$passage_ligne.$passage_ligne;
$message.= $passage_ligne."--".$boundary."--".$passage_ligne; 
//========== 
//=====Envoi de l'e-mail.
mail($mail,$sujet,$message,$header);
 
//==========


Explication:

  • Ouverture boundary.
  • Déclaration du nouveau content-type et de la seconde boundary.
  • Ouverture boundary_2.
  • Déclaration de type (exemple texte).

Texte.

  • Ouverture boundary_2.
  • Déclaration de type (exemple HTML).

HTML.

  • Fermeture boundary_2.
  • Ouverture boundary.
  • Déclaration de la pièce jointe 1.
  • Ouverture boundary.
  • Déclaration de la pièce jointe 2.
  • Ouverture boundary.
  • Déclaration de la pièce jointe [...].
  • Ouverture boundary.
  • Déclaration de la pièce jointe n.
  • Fermeture boundary.

Bibliothèques

[modifier | modifier le wikicode]

Ce livre abordera dans un chapitre ultérieur, des bibliothèques contenant des fonctions d'envoi d'email permettant plus d'options dans une syntaxe nettement plus concise :



Sécurité

Sécuriser les en-têtes HTTP

[modifier | modifier le wikicode]

Pour commencer, il est préférable d'utiliser HTTPS à HTTP pour complexifier l'attaque de l'homme du milieu, en garantissant l'intégrité et la confidentialité du flux réseau.

Ceci peut être forcé par le mécanisme HSTS, qui utilise l'en-tête HTTP Strict-Transport-Security.

Masquer les versions

[modifier | modifier le wikicode]

Pour se prémunir des exploitations des failles de sécurité liées à sa version de PHP ou de serveur Web, on peut chercher régulièrement si certaines ont été détectées et ont une mise à jour sur https://www.exploit-db.com/.

Mais pour éviter d'être la cible de celles qui fonctionneraient, il est préférable de masquer ces versions dans les en-têtes HTTP.

Ceci se fait généralement au niveau des fichiers de configuration du serveur, mais peut aussi être réalisé en PHP ainsi :

ini_set('expose_php', 'Off');
header('X-Powered-By: UnknownWebServer');

Restreindre les connexions

[modifier | modifier le wikicode]

De plus, il existe d'autres sécurisations des en-têtes HTTP pour se prémunir des attaques de type Cross-site request forgery (CSRF).

Exemple de couche de protection utilisant le Content Security Policy (CSP) :

ini_set('register_globals', 'Off');

header('Content-Security-Policy "default-src \'self\'; style-src \'self\' \'unsafe-inline\'; script-src \'self\' \'unsafe-inline\'; img-src \'self\' data:"');
header('X-Frame-Options "SAMEORIGIN" always');
header('X-Content-Type-Options nosniff');
header('Referrer-Policy: origin');
header('Permissions-Policy "geolocation=(),midi=(),sync-xhr=(),microphone=(),camera=(),magnetometer=(),gyroscope=(),fullscreen=(self),payment=()"');

Voir aussi Cross Origin Ressource Sharing (CORS) avec Access-Control-Allow-Origin.

Protéger l'accès à l'application

[modifier | modifier le wikicode]

Pour éviter que des robots spammeurs polluent l'application via ses formulaires et ses API, ou la saturent par une attaque par déni de service (DoS attack), il y a plusieurs solutions complémentaires :

  • Filtrer l'accès par un whitelistage d'IP (ex : via iptables).
  • Configurer un throttle dans le serveur Web, qui va empêcher un trop grand nombre de connexions de la même provenance (via iptables aussi).
  • Chiffrer la connexion par un VPN (impossible les utilisateurs prévus sont inconnus et proviennent des moteurs de recherche).
  • Échapper les caractères interprétés lors des attaques de type injection SQL (généralement les apostrophes), et cross-site scripting (alias XSS, qui injecte du JavaScript entre balises HTML "script"), ou clickjacking (balises "iframe"). En échappant les chevrons cela suffit)[1].
  • Imposer aux utilisateurs des mots de passe complexes (c'est-à-dire impossible à deviner par robots avant plusieurs années de tentatives infructueuses).
  • Installer un captcha sur les formulaires accessibles aux utilisateurs non connectés. Par exemple reCAPTCHA, ou sur MediaWiki ConfirmEdit.
  • Filtrer les messages postés sur l'application en les comparant aux listes partagées de spams connus, comme l'API Akismet[2], ou sur MediaWiki TitleBlacklist.
  • Utiliser PHP >= 8 qui protège par défaut contre les attaques XML external entity attack (XXE)[3].

Chiffrer les données sensibles

[modifier | modifier le wikicode]

Le fait de stocker les mots de passe en clair (non chiffrés), dans un fichier ou une base de connées constitue une faille de sécurité.

Il est donc obligatoire de les chiffrer. Pour réaliser cela, il existe plusieurs fonctions PHP.

Chiffrement symétrique

[modifier | modifier le wikicode]

Dans le cadre d'échange de données censées être déchiffrées par tout le monde, on peut utiliser base64, ce qui prend 33 % de place en plus que les données encodées[4] :

  • base64_encode()
  • base64_decode()

Mais si on souhaite restreindre le décodage à certains, une cryptographie symétrique peut être assurée avec OpenSSL[5] :

  • openssl_encrypt()
  • openssl_decrypt()

Chiffrement asymétrique

[modifier | modifier le wikicode]

Le cryptographie asymétrique est plutôt utilisé pour garantir une identité, via une clé privée et une publique. En PHP, selon se traduit par :

  • openssl_public_encrypt()[6]
  • openssl_private_decrypt()
  • hash($algo, $data)[7] : avec un algorithme de la liste hash_algos()[8]. Ex : hash('sha256', $maChaine).
  • crypt()[9] est une fonction de hachage à sens unique d'une chaine, qui accepte en deuxième paramètre optionnel un salage.
  • sha1() : déconseillé car facile à cracker[10].
  • md5() : déconseillé car facile à cracker[11].

Certaines sociétés proposent des pentests payant (en boite noire ou blanche).

Mais un bon outil gratuit permet déjà de se faire une idée : https://observatory.mozilla.org.



Programmation orientée objet

Une classe est un format de variable non scalaire, comprenant trois types de composants :

  1. des constantes, accessibles par réflexion avec $maClasse::getConstants().
  2. des variables appelées "propriétés", accessibles avec $maClasse::getProperties().
  3. des fonctions appelées "méthodes", accessibles avec $maClasse::getMethods().

La programmation orientée objet s’effectue en deux étapes : la définition des classes, puis leur utilisation. Une fois la classe définie, il est en effet possible de créer des objets, appelés "instances", au format de la classe définie. Toutefois, les composants déclarés avec le mot static sont persistants, et accessibles sans instanciation préalable.

Opérateur objet

[modifier | modifier le wikicode]

Pour accéder aux propriétés et méthodes d'un objet, on utilise l'opérateur object : ->.

Opérateur de résolution de portée

[modifier | modifier le wikicode]

Pour accéder aux constantes, propriétés et méthodes statiques d'une classe, on utilise l'opérateur de résolution de portée : ::.

Cet opérateur peut également être précédé de noms de classes ou des mots réservés suivants[1] :

  • $this : l'objet courant.
  • parent : la classe parente.
  • static : la classe courante.
  • self : la classe parente puis la courante s'il n'y a rien[2].

Logo

Le mot-réservé static a donc deux sens : un pour les déclarations et un pour les appels.

À l'instar d'une bibliothèque de fonctions, une classe est généralement stockée dans un fichier dédié, qui peut porter son nom.

Elle s'inclut donc dans un programme de la même manière qu'une bibliothèque :

include('ma_classe.php');
// ou
require('ma_classe.php');
// ou
require_once('ma_classe.php');

Mais la syntaxe à privilégier est celle par espace de nom :

use mon_namespace/ma_classe;

Logo

En PHP, l'inclusion doit précéder les appels du code qui y figure.

 Les classes et fonctions globales peuvent être appelées directement dans le code, ou avec le préfixe "\" (signifiant "namespace global"). Mais il existe aussi use function ma_fonction pour déclarer l'utilisation d'une fonction.
 Avant PHP7.4, on pouvait mettre une URL dans ces fonctions, si la configuration allow_url_include=1[3]. Ex : require_once("http://example.com/");

Une fois la classe incluse, on peut l'appeler.

  • Directement pour une classe statique.
  • Après instanciation sinon. Elle est réalisée par le mot-clé "new".

Par défaut, PHP fournit déjà la classe suivante pour créer des objets anonymes :

$c = new stdClass();
var_dump($c);

Définition des classes

[modifier | modifier le wikicode]

Définir une nouvelle classe adopte la syntaxe suivante :

class nomObjet
{
  var $variable1;
  var $variable2;
  ...


  function maFonction1()
  {
    ...code
  }

  function maFonction2()
  {

  }

}

Il est possible d’attribuer une valeur par défaut. Le code dans la classe est alors var $variable1 = valeur;. Cette syntaxe est économe puisqu'elle évite d'initialiser la variable à chaque appel des méthodes qui l'utilisent.

La définition de méthodes de classe est identique à celle de n’importe quelle fonction à la différence que lorsqu’elle fait référence à une variable de sa classe, $variable doit être :

  • $this->variable pour cibler l'objet instancié (et $this::constante, $this->méthode()).
  • self::variable pour cibler la classe statique.


De même pour exécuter une autre méthode de sa classe. ex :

class client
{
  var $aDitBonjour = false;

  function direBonjour()
  {
    $this->message("Bonjour");
  }

  function message($message)
  {
    echo $message;
    $this->aDitBonjour = true;
  }

}

Pour utiliser une variable qui n'est pas dans la classe ou exécuter les méthodes d'une autre classe, il faut les redéclarer avec global :

class client
{
  function message($message)
  {
    global $InstanceAutreClasse;
    $InstanceAutreClasse->aDitBonjour = true;
  }

}

Utilisation d’un objet

[modifier | modifier le wikicode]

Attention : la classe est la définition d’un format de variable personnalisable. Le code n’est pas exécuté et il est impensable d’introduire le code suivant qui n’aurait aucun sens :

class client
{

   for ($i=0; $i<5; $i++)
   echo "$i\n";

}

Une fois la classe définie, il va falloir créer des variables objet du format de la classe définie. On crée un objet par le code suivant :

$objet = new client();

Il faut bien entendu avoir préalablement défini la classe client. La variable $objet contient donc un objet. Pour accéder à une variable pour lui faire subir des modifications, il suffit d’entrer le code suivant :

$objet->variable1 = "Hello world";

Il est possible de lui faire subir les mêmes opérations qu’à une variable normale. De même pour exécuter une fonction :

$objet->maFonction();

Autant les méthodes une fois définies ne peuvent pas être modifiées, autant il est possible d’ajouter ou de supprimer des variables dans l’objet :

$objet->variable = "valeur"; // définition de variable

unset($objet->variable); // suppressions

L’objet est unique, de sorte que s’il est enregistré dans une autre variable et qu’une modification lui est faite, elle sera visible pour les deux variables :

//Le code reprend l'ancien script

$objet = new client();
$objet2 = $objet;
$objet2->direBonjour();
echo $objet->aDitBonjour;

//affiche true

Pour dupliquer une variable de type objet, il faut donc entrer le code suivant :

$objet2 = clone $objet;

La nouvelle variable sera différente de l’ancienne mais aura les mêmes valeurs.


Il est également possible d'exécuter la méthode d'un objet sans avoir créé de variable auparavant :

class Message
{
   function direBonjour()
   {
     echo "salut";
   }
}


/* Exécute la méthode */
Message::direBonjour();

PHP était initialement un langage à héritage simple[4], c'est-à-dire qu'une classe ne peut hériter que d'au plus une seule autre classe.

L'héritage consiste à transmettre les propriétés et méthodes d’une classe mère à une classe fille, en déclarant cette dernière avec extends. Ex :

class parent1
{
    var $varParent;

    function méthodeParente()
    {
        print 'Je connais méthodeParente' . PHP_EOL;
    }
}

class enfant extends parent1
{
    var $varEnfant;

    function méthodeEnfant()
    {
        print 'Je connais méthodeEnfant' . PHP_EOL;
    }
}

$Enfant1 = new enfant();
$Enfant1->méthodeParente();

L'héritage permet le polymorphisme, qui consiste à utiliser des variables ou méthodes dans des classes de plusieurs types, grâce à l'héritage.

Les classes filles bénéficieront automatiquement de toutes les propriétés et des méthodes de leur classe mère (qui n'a pas de limite dans le nombre de ses filles[5]).

Logo

Les interfaces peuvent par contre bénéficier d'un héritage multiple.

On peut aussi invoquer les méthodes parentes depuis la classe enfant :

class enfant2 extends parent1
{
    var $varEnfant;

    function méthodeEnfant()
    {
        parent::méthodeParente();
        print 'Je connais méthodeEnfant2' . PHP_EOL;
    }
}

$Enfant2 = new enfant2();
$Enfant2->méthodeEnfant();

Depuis PHP 5.4.0, une structure de données appelée "trait" permet l'héritage multiple. Exemple d'utilisation :

<?php
trait MonTrait1
{
    function Hello()
    {
        print 'Hello';
    }
}

trait MonTrait2
{
    function World()
    {
        print 'World';
    }
}

class MaClasse1
{
    use MonTrait1;
    use MonTrait2;

    function __construct()
    {
        $this->Hello();
        $this->World();
    }
}

$Test = new MaClasse1;

Logo

Les traits sont limités par rapport aux classes :

  • Un trait ne peut pas contenir de constante.
  • Un trait ne peut pas hériter d'une classe, il doit utiliser un autre trait à la place.

De plus, ce type d'injection de dépendance est contraire au principe SOLID d'inversion des dépendances.

Pour empêcher une classe ou une méthode d'être étendue (et en faire donc une classe finale ou une méthode finale), on peut la déclarer avec le mot-clé final. Ex :

final class MaClasseFinale
{
    ...
}

Classes abstraites

[modifier | modifier le wikicode]

La classe abstraite ne peut pas être instanciée, mais elle peut être appelée en statique. Comme pour l'héritage classiques, ses classes filles accèdent à ses attributs et méthodes publics et protégés.

Voici un exemple de classe abstraite :

abstract class MaClasseAbstraite
{
    public $var="Bonjour";

    abstract protected function MaMethode($var1, $var2);

    protected function MaMethode2($var1, $var2)
    {
        return 'TODO';
    }
}

Dans cet exemple, on voit que les méthodes d'une classe abstraite peuvent contenir du code, mais les méthodes abstraites non (elles ne définissent que les arguments[6]

Logo

Les méthodes abstraites sont obligatoirement à implémenter par les classes filles.

Apparues avec PHP 5.3[7], les closures sont des classes avec des méthodes gérant les fonctions anonymes.

Classes anonymes

[modifier | modifier le wikicode]

Apparues avec PHP 7[8], les classes anonymes sont des classes sans nom, déclarées lors de l'exécution.

Voici un exemple d'interface :

interface MonInterface
{
    public function setName($name);
    public function getName();
}

Et son utilisation : la classe doit reprendre les méthodes de l'interface sous peine d'erreur.

class MaClasse implements MonInterface
{
    private $myName;

    public function setName($name)
    {
        print 'Définition de '.$name;
        $myName = $name;
    }

    public function getName()
    {
        print 'Récupération de '.$myName;
    }
}

Logo

  • Les méthodes d'une interface ne peuvent pas contenir de code.
  • Une classe ou une interface ne peut implémenter qu'une ou plusieurs interfaces (donc pas d'implémentation de classe).
  • Une interface ne peut hériter que d'une autre interface[9].
  • Toutes les méthodes d'une interface doivent être publiques.
  • Si un objet hérite et implémente, toujours le déclarer en plaçant le extends avant le implements.

Exemple d'espace de noms :

namespace MonEspace\Nom;

class MaClasse {}
function MaMethode() {}
const MYCONST = 1;

$a = new MaClasse;
$c = new \MonEspace\Nom\MaClasse;
$d = new \ClasseGlobale;

Pour utiliser un namespace, "use" conserve son nom mais on peut le changer avec "as" :

use MonEspace\Nom;
use SonEspace\Nom as NomExterne;

Depuis PHP7 on peut même importer plusieurs classes, fonctions ou constantes sur la même ligne :

   use MonEspace\{MaClasseA, MaClasseB as B};

Portée des variables

[modifier | modifier le wikicode]

Il est possible depuis PHP5 de préciser l'accès à certaines variables ou méthodes, en les déclarant à la place de var avec :

  • public : visible dans tout le programme.
  • protected : visible uniquement dans les instances de la classe et de ses sous-classes.
  • private : visible uniquement dans les instances de la classe.

Exemple :

class CompteEnBanque
{
    private $argent = 0;

    private function ajouterArgent($valeur)
    {
        $this->argent += $valeur;
    }

    function gagnerArgent($valeur)
    {
        $this->ajouterArgent($valeur);
    }
}


$compte = new CompteEnBanque()

//les actions suivantes sont impossibles :

$compte->argent = 3000;
$compte->ajouterArgent(3000);

//l'action suivante est possible

$compte->gagnerArgent(3000);

En effet, il faut gagner de l’argent avant d’en ajouter à la banque (quoique...).

Logo

Ce code retournera un message d’erreur s'il est exécuté sous PHP5 ou une version ultérieure.

Les méthodes prédéfinies

[modifier | modifier le wikicode]

Il existe quelques méthodes prédéfinies qui s’exécutent automatiquement à des périodes de la vie de l’objet. Elles sont appelées méthodes magiques[10], et leurs noms commencent toujours par deux underscores :

  1. __call() : à chaque appel d'une méthode de la classe.
  2. __callStatic() : à chaque appel statique d'une méthode de la classe.
  3. __clone() : lors du clonage de l'objet (via la fonction "clone").
  4. __construct() : à l'instanciation de la classe.
  5. __debugInfo() : modifie les résultats des var_dump().
  6. __destruct() : à la suppression de l'objet instancié.
  7. __get() : à la lecture de propriétés inexistantes ou interdites.
  8. __invoke() : à l'appel de l'objet comme une fonction (ex : echo $object(1)).
  9. __isset() : à l'appel de isset() (ou empty()) sur des propriétés inexistantes ou interdites.
  10. __serialize() : à l'appel de serialize().
  11. __set() : à l'écriture de propriétés inexistantes ou interdites.
  12. __set_state() : modifie les résultats des var_export().
  13. __sleep() : à l'appel de serialize(), pour en modifier le résultat.
  14. __toString() : à l'appel de l'objet comme une chaine de caractères (ex : echo $object).
  15. __unserialize() : à l'appel de serialize().
  16. __unset() : à l'appel de unset() sur des propriétés inexistantes ou interdites.
  17. __wakeup() : à l'appel de unserialize(), pour en modifier le résultat.

Constructeur et destructeur

[modifier | modifier le wikicode]
__construct()
Cette méthode s’exécute lors de la création de l’objet. On entre alors les attributs potentiels de la fonction lors de sa création. Cette méthode est appelée "le constructeur"
__destruct()
Cette méthode s’exécute au contraire au moment de la destruction de la variable. Elle est appelée "le destructeur".

Voici un exemple utilisant les méthodes :

//Définition de la classe

class Humain
{
    public $homme = false;
    public $femme = false;

    function __construct($type)
    {
        if ($type=="homme")
            $this->homme=true;
        if ($type=="femme")
            $this->femme=true;
    }

    function extremeOnction()
    {
        echo 'Amen';
    }


    function __destruct()
    {
        $this->extremeOnction();
    }

}


//C'est un garçon !
$homme = new Humain("homme");

if ($homme->homme) {
    echo "C'est un homme";
} elseif ($homme->femme) {
    echo "C'est une femme";
}

//mort de l'homme
unset($homme);


/*
La sortie sera

C'est un homme
Amen
*/

Sous php4, le constructeur avait pour nom celui de la classe. Sous php5, si la fonction __construct() n’est pas trouvée, l’interpréteur cherchera une méthode du même nom que la classe.

Copie en profondeur

[modifier | modifier le wikicode]

Il existe une méthode qui s’exécute lors d’une duplication de l’objet. Son nom est __clone().

En effet, elle est utile car par défaut si $x = $y, $x n'est qu'une référence à $y et changer $x changera $y.

__get, __set, __call

[modifier | modifier le wikicode]

Ces méthodes permettent de rendre dynamique l'utilisation de la classe, et permettent la surcharge magique[11].

La méthode __call() s'exécute quand une méthode appelée est inaccessible ou inexistante. Exemple :

    function __call($method,$arguments)
    {
        echo "On a appelé $method sans succès avec les paramètres :<br/>";
        var_dump($arguments);
    }

Cette méthode s'exécute quand une variable appelée est inaccessible ou inexistante. L'exemple ci-dessous lui permet de retourner une donnée dépendant du contenu de la variable $nom. Important : le contenu de la variable $nom ne sera pas prioritaire sur le nom d'une variable interne à la classe.

class test
{
    public $a;
    private $b;

    function __construct($a,$b)
    {
        $this->a=$a;
        $this->b=$b;
    }

    function __get($nom)
    {
        echo "On a appelé __get(\$$nom)";
    }
}

// Utilisation
$var=new test(5,10);

echo $var->a; // affiche : "5"
echo '<br/>';
echo $var->b; // affiche : "On a appelé __get($b)". En effet, b est privée et ne peut donc pas être accédée.
echo '<br/>';
echo $var->__get('a'); // affiche : "On a appelé __get($a)"
echo '<br/>';
echo $var->c; // affiche : "On a appelé __get($c)"

On voit ici que PHP va chercher en priorité à retourner une variable interne, mais si elle est privée ou inexistante, il prendra le résultat du __get.

Logo

Ne jamais accéder à une variable de classe privée dans son __get() sous peine de boucle infinie.

Exemple de réécriture de la méthode __get ci-dessus pour accéder à la variable privée :

function __get($nom)
{
    if ($nom == 'b') {
        echo $this->b;
    }
}

Cette méthode s'exécute quand on modifie une variable inaccessible ou inexistante. Exemple :

class test2
{
    public $a;
    private $b;

    function __construct($a,$b)
    {
        $this->a=$a;
        $this->b=$b;
    }

  function __get($nom)
    {
        echo 'get '.$nom;echo '<br/>';
    }

    function __set($nom,$value)
    {
        echo 'set '.$nom.' '.$value;echo '<br/>';
    }
}

$var=new test2(5,10);
$var->a=6;
echo $var->a;	// affiche 6
echo '<br/>';
$var->b=11;		// appelle __set('b',11)
echo $var->b;	// appelle __get('b')

Cette méthode se déclenche lors de l'autochargement, c'est-à-dire quand le programme charge une autre classe (lors de son instanciation ou invocation statique). Elle permet donc de lever les exceptions si par exemple la classe demandée n'existe pas.

Le code ci-dessous affiche :

  • Avant PHP 5.3.0, Fatal error: Class 'ClasseDistante' not found in C:\Program Files (x86)\EasyPHP\data\localweb\WL.php on line 7.
  • Après, ClasseDistante est introuvable !
<?php
function __autoload($ClasseDistante)
{
    throw new Exception($ClasseDistante . ' est introuvable !');
}

try {
    $ClasseDistante1 = new ClasseDistante();
} catch (Exception $ex) {
    echo $ex->getMessage(), "<br/>";
}

try {
    $ClasseDistante2 = ClasseDistante::MethodeStatique();
} catch (Exception $ex) {
    echo $ex->getMessage(), "<br/>";
}
?>

__sleep() et __wakeup()

[modifier | modifier le wikicode]

Ces méthodes ne fonctionnent plus avec l'interface Serializable depuis PHP 8.1, au profit de serialize et unserialize[12].

Elles permettent respectivement de sauvegarder et restaurer l'état d'un objet, pour qu'il soit fonctionnel après une sérialisation / désérialisation. C'est utile par exemple pour se reconnecter à une base de données.

Cette méthode rend la classe invocable, c'est-à-dire qu'après instanciation, elle s'exécute si on l'appelle comme une méthode. Ex :

$maClasse = new MaClasse();
return $maClasse();

Quelques fonctions intégrées

[modifier | modifier le wikicode]

Voici quelques fonctions en relation avec la programmation orientée objet qui peuvent vous être utiles.

Une classe peut s'instancier elle-même avec new self();.

class_exists()

[modifier | modifier le wikicode]

Vérifie qu’une classe existe. Renvoie une valeur booléenne. ex :

if (class_exists('maClasse'))
    $var = new maClasse();

get_class_methods()

[modifier | modifier le wikicode]

Retourne toutes les méthodes d’une classe sous forme de tableau. Ex :

$maClasse = new Classe();
$methodes = get_class_methods($maClasse);
print_r($methodes);

get_class_vars()

[modifier | modifier le wikicode]

Retourne tous les attributs d'une classe (dont la portée est accessible, donc généralement les publiques), ainsi que leurs valeurs par défaut sous forme de tableau. Ex :

$attributs = get_class_vars('Classe');
print_r($attributs);

// Fonctionne aussi avec les instances :
$maClasse = new Classe();
$attributs = get_class_vars(get_class($maClasse));
print_r($attributs);

Peut donc servir pour un "foreach" propriétés de la classe.

Pour récupérer ou filtrer les attributs privés, utiliser \ReflectionClass::getProperties[13] :

$reflecttion = new \ReflectionClass('Classe');
print_r($reflection->getProperties());

get_object_vars()

[modifier | modifier le wikicode]

Idem avec les valeurs courantes de l'objet instance de classe.

method_exists($classe, $méthode)

[modifier | modifier le wikicode]

Teste sur une méthode existe dans une classe.

serialize() et unserialize()

[modifier | modifier le wikicode]

Assurent la transformation du flux de données, en précisant les types des variables et index des tableaux. Exemple :

    $Hello = 'Hello World';
    var_dump($Hello); // string(11) "Hello World" 
    $Hello = serialize($Hello);
    print $Hello;	   // s:11:"Hello World";

    $Hello = array('Hello', 'World');
    var_dump($Hello);      // array(2) { [0]=> string(5) "Hello" [1]=> string(5) "World" }
    $Hello = serialize($Hello);
    print $Hello;	        // a:2:{i:0;s:5:"Hello";i:1;s:5:"World";}

Le préfixe "a:2" signifie "array of 2 lines", et il est obligatoire pour désérialiser.



Bases de données

Soit la base de données BDDNAME contenant la table NOMTABLE qui sera utilisée pour la suite du livre. Voici la table :

  • ID : id
  • NOM : chaine de caractères
  • PRENOM : chaine de caractères
  • ADRESSE1 : chaine de caractères
  • ADRESSE2 : chaine de caractères
  • TEL1 : entier long
  • TEL2 : entier long

Cette base de données contient les deux enregistrements suivants :

0 "DUPOND" "LOUIS" "1,Petite rue" "2,Petite rue" 0543454654 0543454352
1 "DUSS" "Jean-Claude" "1,Grande rue" "2, Grande rue" null null

Constantes propres à la base utilisées par la suite :

Nom de la BDD : "BDDNAME"
Adresse de la BDD : "BDDADRESSE"
Login d'accès à la BDD : "BDDUSER"
Mot de passe pour accéder à la BDD : "BDDPASS"

SQL imbriqué en PHP

[modifier | modifier le wikicode]

À l'instar du HTML, on peut trouver du code SQL imbriqué dans du code PHP. Dans ce cas il faut que les limitateurs de chaines soient bien échappés. Ex :

$sql = sprintf('
    SELECT *
    FROM ma_table
    WHERE (mon_champ1 = "%1$s" AND mon_champ2 != "") OR mon_champ2 = "%1$s"
', $maChaine);

NB : \'%1$s\' serait équivalent à "%1$s".

Documentation sur http://php.net/manual/fr/book.oci8.php.

Documentation sur http://php.net/manual/fr/book.pgsql.php.



Microsoft SQL Server

On distingue plusieurs pilotes PHP pour MS-SQL Server :

  • mssql (désuet en PHP7).
  • sqlsrv
  • PDO

Pour se connecter au serveur MS-SQL à partir d'un tout-en-un comme EasyPHP, il suffit de télécharger les pilotes .dll[1] correspondant à sa version de PHP, puis d'indiquer leurs chemins dans le PHP.ini :

  • Sous PHP 4, copier le fichier php_mssql.dll dans les extensions.
  • Pour PHP 5.4 :
    1. Télécharger les .dll SQL30
    2. Les copier dans C:\PROGRA~2\EasyPHP\binaries\php\php_runningversion\ext
    3. Les ajouter dans C:\PROGRA~2\EasyPHP\binaries\php\php_runningversion\php.ini via les lignes suivantes[2] :
      extension=php_sqlsrv_54_ts.dll
      extension=php_pdo_sqlsrv_54_ts.dll
  • Dans PHP 5.5 on obtient toujours Fatal error: Call to undefined function sqlsrv_connect(), donc upgrader ou downgrader PHP.
  • Dans PHP 7 : cela fonctionne.

Pour vérifier l'installation, redémarrer le serveur Web, puis vérifier que la ligne pdo_sqlsrv s’est bien ajoutée dans la configuration (ex : http://127.0.0.1/home/index.php?page=php-page&display=extensions).

Pour se connecter au serveur MS-SQL, il suffit de télécharger les pilotes .so[3] correspondant à sa version de PHP, puis d'indiquer leurs chemins dans le PHP.ini[4] :

extension=php_pdo_sqlsrv_7_nts.so
extension=php_sqlsrv_7_nts.so

Pour vérifier l'installation, redémarrer le serveur Web, puis vérifier que la ligne pdo_sqlsrv s’est bien ajoutée dans la configuration (ex : php -r "phpinfo();" |grep sql).


Pilote 1.1 SQL Server pour PHP

[modifier | modifier le wikicode]

Ce pilote fournit des fonctions d’interaction avec Microsoft SQL Server. Il peut s'utiliser avec les version de SQL Server postérieure à 2005[5].

Début d’un principe
Fin du principe


Alternative désuète

[modifier | modifier le wikicode]

Les fonctions suivantes sont supprimées depuis PHP 7.0[6] :

Début d’un principe
Fin du principe


Alternative Doctrine

[modifier | modifier le wikicode]

Les bibliothèques Doctrine utilisent cette syntaxe[8] :

Début d’un principe
Fin du principe


// ... connexion

$sql = "DELETE FROM ma_table WHERE colonne = '1' LIMIT 0,30";
if (!sqlsrv_query($connect, $sql)) {
  die (sqlsrv_errors());
}

// ... déconnexion

Récupération des données

[modifier | modifier le wikicode]
// ... connexion

$sql = "SELECT ville, pays, code_postal FROM table_clients WHERE code_postal = '78000' AND client_id = '44' LIMIT 1";
if (!$result = sqlsrv_query($connect, $sql)) {
  die (sqlsrv_errors());
}
$client_data = sqlsrv_fetch_array($result);
extract($client_data);

echo $ville;
echo $pays;
echo $code_postal;

// ... déconnexion

Logo

Par défaut, sqlsrv_fetch_array() renvoie deux tableaux imbriqués : le tableau itératif et l'associatif. Pour n'en sélectionner qu'un, il faut remplir son deuxième paramètre avec[9] :

  • SQLSRV_FETCH_ASSOC : pour le tableau associatif.
  • SQLSRV_FETCH_NUMERIC : pour le tableau itératif.
  • SQLSRV_FETCH_BOTH : pour les deux (déjà par défaut).



MySQL

PHP offre plusieurs fonctions d’interaction avec MySQL.

mysql_connect()

[modifier | modifier le wikicode]

Logo

La fonction mysql_connect() est obsolète en PHP7, et remplacée par la classe mysqli.

Exemple d'accès à une base de données MySQL
$user="BDDUSER";
$pass="BDDPASS";
$db="BDDNAME";
$table="demande_intervention";
$link=mysql_connect("localhost",$user,$pass); // Préproduction locale de BDDADRESSE
if (!$link)
die("Impossible de se connecter à mysql");
mysql_select_db($db,$link)
or die ("Impossible d'ouvrir $db :".mysql_error());
$query="insert into $table values($znom,$ztel,$znomach,zlieu,zdate)";
mysql_query($query,$link)
or die("Impossible d'ajouter des nouvelles données".mysql_error());
mysql_close($link);

Exécution d'une requête

[modifier | modifier le wikicode]

Maintenant que nous sommes connectés à notre base de données, il est possible d’exécuter des requêtes dessus. En voici un exemple

Exploiter une requête de type SELECT (avec mysql_fetch_assoc)
    $requete = "SELECT * FROM NOMTABLE";
    $res = mysql_query($requete);
    //On obtient alors tous les enregistrements présents dans la table nom table, et pour exploiter les enregistrements, on peut boucler de la manière        suivante :
    while ($enregistrement = mysql_fetch_assoc ($res) ) {
        $nom = $enregistrement['NOM'];
        $prenom = $enregistrement['PRENOM'];
        $adresse1 = $enregistrement['ADRESSE1'];
        $adresse2= $enregistrement['ADRESSE2'];
        $tel1 = $enregistrement['TEL1'];
        $tel2 = $enregistrement['TEL2'];
    }

De cette manière on récupère un tableau associatif sous la forme Clé->Valeur pour chacun des enregistrements retournés par la requête.

Il existe d'autres fonctions pour cela :

  • mysql_num_rows() : retourne le nombre de lignes données par la requête.
  • mysql_fetch_row() : identique à mysql_fetch_assoc mais retourne un tableau simple indice->valeur.
  • mysql_fetch_object() : identique à mysql_fetch_assoc mais retourne un objet.
  • mysql_fetch_array() : retourne les tableaux mysql_fetch_assoc() et mysql_fetch_row().

Exemple :

Exploiter une requête de type SELECT (avec mysql_fetch_object)
    $requete = "SELECT * FROM NOMTABLE";
    $res = mysql_query($requete);

    while ($enregistrement = mysql_fetch_object ($res) ) {
       $nom = $enregistrement->NOM;
       $prenom = $enregistrement->PRENOM;
       $adresse1 = $enregistrement->ADRESSE1;
       $adresse2= $enregistrement->ADRESSE2;
       $tel1 = $enregistrement->TEL1;
       $tel2 = $enregistrement->TEL2;
    }

Logo

Par défaut, mysql_fetch_array() renvoie deux tableaux imbriqués : le tableau itératif et l'associatif. Pour n'en sélectionner qu'un, il faut remplir son deuxième paramètre avec[1] :

  • MYSQL_ASSOC : pour le tableau associatif.
  • MYSQL_NUM : pour le tableau itératif.
  • MYSQL_BOTH : pour les deux (déjà par défaut).

Fermeture d'une connexion

[modifier | modifier le wikicode]
 //Ferme la connexion MySQL
 mysql_close ($ressource);

mysqli_connect()

[modifier | modifier le wikicode]

Cette fonction fonctionne un peu comme mysql_connect()[2] :

$link = mysqli_connect("BDDADRESSE","BDDUSER","BDDPASS","BDDNAME");
if (!$link) { die(mysqli_connect_errno()); }
mysqli_query($link, $query);
mysqli_close($link);

Les fonctions de manipulation de données ont pour préfixe "mysqli" avec les mêmes suffixes que celles du paragraphe précédent :

  • mysql_num_rows()
  • mysql_fetch_assoc()
  • mysql_fetch_row()
  • mysql_fetch_object()

La classe mysqli permet les mêmes opérations que les fonctions précédentes, avec l'avantage de la programmation objet en plus (ex : héritage, introspection...) :

$query = 'SELECT * FROM NOMTABLE';
$link = new mysqli("BDDADRESSE","BDDUSER","BDDPASS","BDDNAME");
if ($link->connect_error) {
    die(mysqli_connect_errno());
}
$result = $link->query($query);
while ($row = $result->fetch_assoc()) {
    var_dump($row);
}
$result->close();
$link->close();

query accepte les valeurs suivantes en second paramètre :

  • MYSQLI_STORE_RESULT : récupère tous les résultats du serveur (valeur par défaut).
  • MYSQLI_USE_RESULT : récupère ligne par ligne.
  • MYSQLI_ASYNC : requête SQL asynchrone (non bloquante).



PDO

PDO (PHP Data Objects) est une extension définissant l'interface pour accéder à plusieurs types de base de données, fournie automatiquement depuis PHP 5.1. Avec ce système, pour utiliser plusieurs SGBD il n'est pas nécessaire de changer les fonctions de communication dans tout le code, mais seulement les arguments envoyés au constructeur.

Les pilotes suivants sont disponibles[1] :

  1. 4D
  2. CUBRID
  3. Firebird
  4. IBM DB2
  5. Informix
  6. Microsoft SQL Server
  7. MySQL
  8. Oracle Database
  9. PostgreSQL
  10. SQLite

Et aussi via ODBC.

 Pour PHP 5.0, l'extension est disponible en tant qu'extension PECL, et doit être activée[2] en ajoutant ou décommentant la ligne de "php_pdo.dll" dans php.ini.

Pour activer les différents SGBD qui doivent communiquer avec PDO, il faut ajouter ou décommenter les lignes extension=php_pdo_[SGBD utilisé].dll dans php.ini, que ce soit sur Apache ou IIS. Exemple :

extension=php_pdo_firebird.dll
extension=php_pdo_mysql.dll
extension=php_pdo_oci.dll
extension=php_pdo_odbc.dll
extension=php_pdo_pgsql.dll
extension=php_pdo_sqlite.dll
extension=php_pdo_sqlsrv_54_ts.dll

Les .dll de Microsoft SQL Server sont téléchargeables sur le site officiel[3].

MySQL :

RUN docker-php-ext-install mysqli && docker-php-ext-enable mysqli

PostgreSQL :

RUN apt-get update && apt-get install -y libpq-dev
RUN docker-php-ext-install pdo_pgsql && docker-php-ext-enable pdo_pgsql

Les classes de l'extension PDO

[modifier | modifier le wikicode]

L'extension PDO comporte trois classes[4] :

  • La classe PDO correspond à une connexion à la base de données.
  • La classe PDOStatement représente d'une part une requête préparée et d'autre part le jeu de résultats de la requête une fois qu'elle est exécutée.

Cette classe offre des méthodes de parcours, de comptage, d'informations.

  • La classe PDOException représente une erreur émise par PDO.

Accès à la base de données avec PDO

[modifier | modifier le wikicode]

L'accès à la base de données se fait en instanciant un objet PDO. Les paramètres à indiquer au constructeur sont :

  • la source de la base de données ;
  • optionnellement, le nom d'utilisateur et le mot de passe.

Par exemple, pour accéder à une base de données de nom ma_bdd accessible sur le port mon_port du serveur mon_serveur avec l'utilisateur mon_id associé au mot de passe mon_mot_de_passe, le code sera le suivant :

  • Pour MySQL[5] :
$connexion = new PDO('mysql:host=mon_serveur;dbname=ma_bdd;port=3306','mon_id','mon_mot_de_passe');
  • Pour MS-SQL[6] les noms des paramètres diffèrent :
$connexion = new PDO('sqlsrv:server=mon_serveur:mon_port;Database=ma_bdd','mon_id','mon_mot_de_passe');
  • MS-SQL via une source de données ODBC (à créer dans C:\Windows\SysWOW64\odbcad32.exe) :
$connexion = new PDO('odbc:nom_de_la_source','mon_id','mon_mot_de_passe');
  • Pour PostgreSQL[7] :
$connexion = new PDO('pgsql:host=mon_serveur;dbname=ma_bdd;port=5432;user=mon_id;password=mon_mot_de_passe');

Logo

Par rapport à mysqli, PDO indique le nom réseau de la machine qui l'utilise en suffixe du compte MySQL, ce qui change le nom du compte et donc les permissions sur les bases. Par exemple si 'mon_id@%' est autorisé, ce n'est pas forcément le cas de 'mon_id@192.168.1.10', ce qui provoque l'erreur Access denied for user.

Passer des requêtes en PDO

[modifier | modifier le wikicode]

Des méthodes permettent de passer des requêtes à l'objet récupéré lors de la connexion.

  • PDO::exec(string $statement)

La méthode exec() permet de passer et exécuter une requête SQL de type INSERT, UPDATE, DELETE.
Elle retourne le nombre de lignes affectées par la requête.

$requete="DELETE * FROM matable WHERE champ1='mavaleur'";
$resultat=$connexion->exec($requete);
echo $resultat.' suppressions effectuées';
  • PDO::query(string $statement)

La méthode query() permet de passer et exécuter une requête SQL de type SELECT.
Elle retourne le jeu de résultats (s'il y en a) sous forme d'objet PDOStatement.

$requete="SELECT champ1, champ2 FROM matable";
$resultat=$connexion->query($requete);

Quelques méthodes de PDOStatement

[modifier | modifier le wikicode]
  • PDOStatement::fetch() récupère la ligne suivante d'un jeu de résultats PDO.
  • PDOStatement::fetchAll() retourne un tableau contenant toutes les lignes du jeu d'enregistrements PDO.
  • PDOStatement::fetchObject() récupère la ligne suivante et la retourne en tant qu'objet.
  • PDOStatement::fetchColumn() retourne une colonne depuis la ligne suivante d'un jeu de résultats PDO.
  • PDOStatement::rowCount() retourne le nombre de lignes affectées par le dernier appel à la fonction.
  • PDOStatement::closeCursor() libère la connexion au serveur, permettant ainsi à d'autres requêtes SQL d'être exécutées. La requête reste dans un état lui permettant d'être de nouveau exécutée. Cette fonction retourne TRUE en cas de succès ou FALSE si une erreur survient.
  • PDOStatement::execute() exécute une requête préparée.

Les paramètres de la méthode fetch

[modifier | modifier le wikicode]

La méthode fetch() peut avoir en paramètre le type de retour du résultat. Par défaut, sans paramètre, le paramètre implicite est PDO::FETCH_BOTH.

  • PDO::FETCH_ASSOC retourne le jeu de résultats sous forme d'un tableau associatif, dont la clé est le nom de colonnes.

Exemple d'utilisation :

while ($ligne = $resultat ->fetch(PDO::FETCH_ASSOC)) {    
 echo $ligne['champ3'].' '.$ligne['champ1'].'<br/>';   
}
  • PDO::FETCH_NUM retourne le jeu de résultats sous forme d'un tableau indexé par numéro commençant à 0.

Exemple d'utilisation :

while ($ligne = $resultat ->fetch(PDO::FETCH_NUM)) {    
 echo $ligne[2].' '.$ligne[0].'<br/>';   
}
  • PDO::FETCH_BOTH retourne un tableau indexé et un tableau associatif.
  • PDO::FETCH_OBJ retourne le jeu de résultats sous forme d'un objet dont les noms de propriétés correspondent aux noms des colonnes.

Faire une requête préparée avec PDO

[modifier | modifier le wikicode]

PDO::prepare() permet de préparer une requête que l'on exécutera ensuite avec la méthode PDOStatement::execute().

Les variables (parties de la requête spécifiques au moment de l'exécution) seront passées à la requête préparée grâce à cette méthode execute(). Ce dispositif protège l'application d'attaque de type injection SQL.

Exemple :

        $pdoStatement = PDO::prepare('SELECT * FROM people WHERE name = :name');
        $pdoStatement->bindParam(':name', $name, PDO::PARAM_STR);
        $pdoStatement->execute();

        return $pdoStatement->fetchAll(PDO::FETCH_ASSOC);

Jongler avec plusieurs SGBD en même temps

[modifier | modifier le wikicode]

Pour exécuter alternativement des requêtes sur plusieurs SGBD, par exemple pour comparer leurs bases :

Notes et références

[modifier | modifier le wikicode]


SQLite

SQLite est le moteur de base de données intégré à PHP5.

// On se connecte à la base
// CHEMIN_BDD constitue de chemin physique de la base de données
$db = new SQLiteDatabase(CHEMIN_BDD);

Exécution d'une requête

[modifier | modifier le wikicode]

Maintenant que nous sommes connectés à notre base de données, il est possible d’exécuter des requêtes dessus. En voici un exemple

$requete = "SELECT * FROM NOMTABLE";
$res = $db->arrayQuery ($requete, SQLITE_ASSOC);

On obtient alors tous les enregistrements présents dans la table nom table, et pour exploiter les enregistrements, on peut boucler de la manière suivante :

foreach($res as $enregistrement) {
    $nom = $enregistrement['NOM'];
    $prenom = $enregistrement['PRENOM'];
    $adresse1 = $enregistrement['ADRESSE1'];
    $adresse2= $enregistrement['ADRESSE2'];
    $tel1 = $enregistrement['TEL1'];
    $tel2 = $enregistrement['TEL2'];
}

De cette manière on récupère un tableau associatif sous la forme Clé->Valeur pour chacun des enregistrements retournés par la requête. C'est la constante SQLITE_ASSOC qui permet cela. La constante SQLITE_NUM permet de retourner un tableau indexé numériquement. Il existe d'autres méthodes...

  • numRows () : retourne le nombre de lignes données par la requête.

Fermeture d'une connexion

[modifier | modifier le wikicode]
 $db->close();


Expressions rationnelles

En informatique, une expression régulière ou expression rationnelle ou expression normale ou motif, est une chaîne de caractères, qui décrit, selon une syntaxe précise, un ensemble de chaînes de caractères possibles. Les expressions régulières sont également appelées regex (de l'anglais regular expression). Elles sont issues des théories mathématiques des langages formels. Les expressions régulières sont aujourd’hui utilisées pour la lecture, le contrôle, la modification, et l'analyse de textes ainsi que la manipulation des langues formelles que sont les langages informatiques.

L'exemple d'expression régulière suivant permet de valider qu'une chaîne de caractère correspond à la syntaxe d'un nombre entier non signé, c'est à dire une suite non vide de chiffres :

[0-9]+

En détails :

  • Les crochets spécifient l'ensemble des caractères auquel doit appartenir le caractère courant de la chaîne. Dans cet exemple, l'ensemble est celui des chiffres de 0 à 9 inclus.
  • Le caractère plus indique de répéter le motif précédent au moins une fois (suite non vide).


En PHP, la validation d'une chaîne de caractères peut se faire en utilisant la fonction preg_match :

<?php
$chaine = '12345'; // ou '12ABC'

if (preg_match('`[0-9]+`', $chaine)) {
    print('Le texte est un entier positif');
} else {
    print('Le texte n\'est pas un entier positif');
}

PHP utilise la norme PCRE.


Les expressions rationnelles peuvent être analysées et testées via un débogueur en ligne comme https://regex101.com/.

Expressions rationnelles courantes
Caractère Type Explication
. Point N'importe quel caractère
[...] crochets classe de caractères : tous les caractères énumérés dans la classe, avec possibilité de plages dont les bornes sont séparées par "-". Ex : [0-9a-z] pour tout l'alphanumérique en minuscule, ou [0-Z] pour tous les caractères de la table Unicode entre "0" et "Z", c'est-à-dire l'alphanumérique majuscule plus ":;<=>?@"[1].
[^...] crochets et circonflexe classe complémentée : tous les caractères sauf ceux énumérés.
[...[...]] union Union des deux ensembles
[...&&[...]] intersection Intersection des deux ensembles
^ circonflexe Marque le début de la chaîne ou de la ligne.
$ dollar Marque la fin de la chaîne ou de la ligne.
| barre verticale Alternative - ou reconnaît l'un ou l'autre
(...) parenthèses groupe de capture : utilisé pour limiter la portée d'un masque ou de l'alternative, grouper un motif répété ou capturer une séquence
\n référence Même séquence que celle capturée précédemment par le nème groupe de capture
\g{n} référence Même séquence que celle capturée précédemment par le nème groupe de capture
(?P<nom>pattern) Sous-motif nommé Nomme le résultat d'un groupe de capture par un nom.
\g{nom} référence Même séquence que celle capturée précédemment par le groupe de capture nommé nom.
\k<nom> référence Même séquence que celle capturée précédemment par le groupe de capture nommé nom.

Par défaut, les caractères et groupes ne sont pas répétés. Les quantificateurs permettent de spécifier le nombre de répétitions et sont spécifiés immédiatement après le caractère ou groupe concerné.

Quantificateurs
Caractère Type Explication
* astérisque 0, 1 ou plusieurs occurrences
+ plus 1 ou plusieurs occurrences
? interrogation 0 ou 1 occurrence
{...} accolades nombre de répétitions : spécifie le nombre de répétitions du motif précédent (minimum et maximum). Avec la présence de la virgule, quand le minimum est absent la valeur par défaut est zéro, quand le maximum est absent la valeur pas défaut est l'infini. Sans virgule (un seul nombre) il s'agit du nombre exact (minimum et maximum ont la même valeur). Exemples :
  • a{2} deux occurrences de "a",
  • a{1,10} (sans espace) entre une et dix,
  • a{,10} jusqu'à 10 fois (de 0 à 10),
  • a{3,} au moins 3 fois (de 3 à l'infini).

Par défaut les quantificateurs ne recherchent pas forcément la plus longue séquence de répétition possible. Il est possible de les suffixer avec un caractère pour modifier leur comportement.

Modificateurs de quantificateurs
Caractère Type Explication
? réticent Le quantificateur qui précède recherchera la plus petite séquence possible.
+ possessif Le quantificateur qui précède recherchera la plus grande séquence possible.

Remarques :

  • Les caractères de début et fin de chaîne (^ et $) ne fonctionnent pas dans [] où ils ont un autre rôle.
  • Les opérateurs * et + sont toujours avides, pour qu'ils laissent la priorité il faut leur apposer un ? à leur suite[2].
Classes de caractères POSIX[3]
Classe Signification
[[:alpha:]] n'importe quelle lettre
[[:digit:]] n'importe quel chiffre
[[:xdigit:]] caractères hexadécimaux
[[:alnum:]] n'importe quelle lettre ou chiffre
[[:space:]] n'importe quel espace blanc
[[:punct:]] n'importe quel signe de ponctuation
[[:lower:]] n'importe quelle lettre en minuscule
[[:upper:]] n'importe quelle lettre capitale
[[:blank:]] espace ou tabulation
[[:graph:]] caractères affichables et imprimables
[[:cntrl:]] caractères d'échappement
[[:print:]] caractères imprimables exceptés ceux de contrôle
Expressions rationnelles Unicode[4]
Expression Signification
\\ Antislash
\C Caractère spécial C non interprété : [ ] { } ( ) ? * . : \ & - ^ $
\Q...\E Séquence littérale non interprétée
\0xxx Caractère Unicode (1 à 3 chiffres octaux)
\a Alarme (ASCII 07)
\A Début de chaîne
\b Caractère de début ou fin de mot
\B Caractère qui n'est pas début ou fin de mot
\cX Caractère de contrôle ASCII (X étant une lettre)
\d Chiffre
\D Non chiffre
\e Escape (ASCII 1B)
\f Form-feed (ASCII 0C)
\G Fin de la correspondance précédente
\h Espace blanc horizontal [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]
\H Non espace blanc horizontal [^\h]
\n Fin de ligne
\pL, \p{L}, \p{Letter} Lettre (dans tout langage)
\r Retour charriot
\R Retour à la ligne, équivaut à \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]
\s Caractères espace [ \t\n\x0B\f\r]
\S Non caractères espace [^\s]
\t Tabulation
\uxxxx Caractère Unicode (4 chiffres hexadécimaux)
\v Espace blanc vertical [\n\x0B\f\r\x85\u2028\u2029]
\V Non espace blanc vertical [^\v]
\w Caractère alphanumérique : lettre, chiffre ou underscore
\W Caractère qui n'est pas lettre, chiffre ou underscore
\xxx Caractère Unicode (2 chiffres hexadécimaux)
\x{xx...x} Caractère Unicode (chiffres hexadécimaux)
\X Caractère Unicode du groupe de graphèmes étendu
\z Fin de chaîne

Constructeurs spéciaux : Ces fonctions précèdent l'expression à laquelle elles s'appliquent, et le tout doit être placé entre parenthèses.

  • ?: : groupe non capturant. Ignorer le groupe de capture lors de la numérotation des backreferences. Exemple : ((?:sous-chaine_non_renvoyée|autre).*).
    La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant ?: juste après la parenthèse ouvrante, et en décalant les numéros des groupes référencés.
  • ?> : groupe non capturant indépendant.
  • ?<= : positive lookbehind, vérifier (sans consommer) que ce qui précède correspond au motif spécifié. Exemple :
    Chercher une lettre u précédée d'une lettre q : (?<=q)u
  • ?<! : negative lookbehind, vérifier (sans consommer) que ce qui précède ne correspond pas au motif spécifié.
  • ?= : positive lookahead, vérifier (sans consommer) que ce qui suit correspond au motif spécifié.
  • ?! : negative lookahead, vérifier (sans consommer) que ce qui suit ne correspond pas au motif spécifié. Exemples :
    Chercher une lettre q non suivie d'une lettre u : q(?!u)
    ((?!sous-chaine_exclue).)
    <(?!body).*> : pour avoir toutes les balises HTML sauf "body".
    début((?!mot_exclu).)*fin[5] : pour rechercher tout ce qui ne contient pas un mot entre deux autres.
    (?!000|666) : pour exclure 000 et 666[6].

Options :

Les options d'interprétation sont en général spécifiées à part. Mais certaines API ne permettent pas de les spécifier. Il est possible d'insérer ces options dans l'expression régulière[7].

(?optionsactivées-optionsdésactivées)

Exemples :

  • Chercher un mot composé de voyelles sans tenir compte de la casse :
    (?i)[AEIOUY]+
  • Chercher un mot composé de voyelles en tenant compte de la casse, ici en majuscules :
    (?-i)[AEIOUY]+

Les options s'appliquent à toute l'expression quelle que soit leur position dans l'expression.


  • $1 : résultat du premier groupe de capture dans les remplacements ($2 correspond au deuxième, etc.).

Logo

En PHP, pour chercher un dollar, "\$" ne fonctionne pas car c'est le format de certaines variables. Il faut donc utiliser des apostrophes au lieu des guillemets : '\$'.

En PHP, les motifs d'expression régulière doivent toujours être entourés d'un symbole délimiteur. On utilise généralement l'accent grave (`), mais on trouve aussi souvent / et #. Ceci sous peine de ne pas fonctionner avec Warning: no ending delimiter found.

De plus, on peut ajouter des options après ces délimiteurs[8] :

i insensibilité à la casse
s le "." inclut les retours à la ligne
m les symboles ^ et $ deviennent valables une fois par ligne (au lieu d'une fois pour la chaine)
x ignorer les espaces
o lors d'un remplacement, ne traite que la première correspondance (pas disponible en PHP7.2)
u compte les caractères Unicode (en multi-octet)

La fonction ereg() qui permettait de rechercher en regex a été remplacée par preg_match() depuis PHP 5.3.

La fonction preg_match()[9] est la principale fonction de recherche[10]. Elle renvoie un booléen et demande deux paramètres obligatoires : l'expression rationnelle et la chaine à scanner. Le troisième paramètre correspond à la variable dans laquelle stocker le tableau des résultats. Enfin, le quatrième accepte un flag PHP, modifiant le comportement de base de la recherche.

  • Exemple minimal :
<?php
$chaine = 'Test regex PHP pour Wikibooks francophone.';

if (preg_match('`.*Wikibooks.*`', $chaine)) {
    print('Le texte parle de Wikibooks');
} else {
    print('Le texte ne parle pas de Wikibooks'); 
}
  • Exemple avancé :
<?php
$chaine = 'Test regex PHP pour Wikibooks francophone.';

if (preg_match('`.*Wikibooks.*`', $chaine, $resultats, $flag)) {
    var_dump($resultats);
} else {
    print('Le texte ne parle pas de Wikibooks'); 
}

Exemples de flags[11] :

  • PREG_OFFSET_CAPTURE : affiche la position de la sous-chaine recherchée dans la chaine.
  • PREG_GREP_INVERT : affiche l'inverse dans preg_grep().

Exemples élaborés

[modifier | modifier le wikicode]
  • Recherche des balises images HTML sans attribut "alt"[12] : /(<img(?!.*?alt=(['"]).*?\2)[^>]*)(>)/
  • Savoir si la chaine est une année (à quatre chiffres) : '/^[0-9]{4}$/'
  • Comparer une URL et un host sans tenir compte du protocole ou du slash de fin : '`^https?://'.$hostUrl.'/?$`'
  • Caractères ASCII et ASCII étendu (ex : "e" ou "E" ou "é") : '/^[a-z\x7f-\xff]+$/i*'

Logo

preg_match() peut ne pas marcher sur un résultat de file_get_contents() (avec plusieurs lignes), car il n'a pas de flag global (/g)[13]. Il faut alors utiliser preg_match_all() (voir ci-dessous).

Cette fonction recherche dans les tableaux[14].

preg_match_all()

[modifier | modifier le wikicode]

Pour obtenir tous les résultats dans un tableau, remplacer preg_match par preg_match_all[15], et print par print_r.

Pour filtrer le contenu d'un fichier, par exemple récupérer tout ce qui se trouve entre parenthèses dans un tableau :

$regex = "/\(([^)]*)\)/";
preg_match_all($regex, file_get_contents($nomFichier), $matches);
print_r($matches);


preg_replace()

[modifier | modifier le wikicode]

La fonction preg_replace comprend trois paramètres : remplacé, remplaçant, chaine à traiter.

<?php
// Remplace tous les espaces par des underscores
$chaine="Test regex PHP pour Wikibooks francophone.";
$chaineTriee=preg_replace('`( )`','_',$chaine);
echo $chaineTriee;

Logo

Seul le dernier groupe de capture sera pris en compte.

Idem que preg_replace() mais son résultat ne contient que ce qui est effectivement remplacé.

Décompose une chaine de caractères.


Concevoir du code de haute qualité

La programmation est accessible à tout le monde. En revanche, créer du code de qualité demande une rigueur et une organisation qui n'est pas toujours au rendez-vous. Voici donc un tutoriel pour apprendre à programmer du code de haute qualité. Les exemples seront pris au PHP mais leur application est toujours valable quel que soit le langage de programmation.

Pourquoi bien programmer ?

[modifier | modifier le wikicode]

Il est important d'aborder cette question dès le début pour légitimer ce cours, la motivation étant un facteur non négligeable de l'apprentissage.

Le bien-programmer est tout d'abord indispensable pour travailler en équipe. Lorsque vous travaillez dans un projet avec d'autres développeurs, il est important de coordonner votre action, d'éditer du code lisible... ce qui augmentera considérablement votre rendement de travail.

Vous vous apercevrez rapidement qu'un code bien programmé simplifie la vie : Lorsque vous avez créé un script une année auparavant et que vous devez vous replonger dessus, vous serez heureux de gagner du temps en retrouvant plus facilement le sens du code grâce à sa lisibilité. De plus, du bon code est plus compatible et générera moins d'erreurs ...

La quantité toujours grandissante de développeurs risque d'entraîner une saturation du marché. Vous serez jugés sur votre qualité de programmation. Un code aux normes est un sceau garantissant votre compétence de développeur.

Les critères de qualité

[modifier | modifier le wikicode]

La programmation doit posséder des qualités qui ne sont pas arbitraires. Chaque critère sera accompagné d'une rapide description de son utilité.

La portabilité

[modifier | modifier le wikicode]

La portabilité du code est son degré d'indépendance à son environnement.

Cette qualité est surtout requise pour des scripts destinés à être utilisés par le grand public. Par exemple, lorsqu'en PHP, vous utilisez des balises <?, vous réduisez la portabilité du code. En effet, l'usage des balises de ce type nécessite l'activation de la fonction SHORT_TAGS, donc dépend de l'environnement du script. Par définition, cela réduit la portabilité du code. Il vaut mieux utiliser la balise <?php qui marche quel que soit l'environnement.

La lisibilité

[modifier | modifier le wikicode]

Il n'est pas besoin de chercher loin pour trouver les avantages que confèrent un code lisible : il permet aux autres développeurs de modifier plus facilement votre script, mais également à vous-même de comprendre plus rapidement un de vos anciens programmes. La lisibilité du code permet également d'éviter les erreurs de logique qui apparaissent plus distinctement.

La lisibilité est très liée à la définition de normes.

La lisibilité du code inclut un bon usage de la commentarisation. Les commentaires sont faits pour augmenter la rapidité de compréhension du script par le développeur, mais il ne doit pas en être fait un usage excessif, lequel serait néfaste pour la lisibilité. Voici un exemple de mauvaise utilisation des commentaires :

/* Code qui va afficher "salut" par un echo. Ce programme est fait en PHP compatible php3*/
echo "salut"; //affiche "salut"
#fin du programme

Il devient difficile de distinguer le code parmi les commentaires. Cet exemple était volontairement exagéré, mais n'est pas si loin de quelques scripts que l'on peut trouver sur le Net.

La définition de normes

[modifier | modifier le wikicode]

Les normes sont des décisions le plus souvent arbitraires sur des méthodes de programmation. L'important n'est pas ce que définissent les normes, mais que des normes soient définies.

La définition de normes confère une continuité logique au code. Par exemple, en PHP, il est possible de nommer des variables de deux manières différentes :

$maSuperVariable   // Écriture en "CamelCase"
$ma_super_variable // Écriture avec des underscores

Vous serez rapidement désorienté si tantôt vous utilisez une méthode, tantôt une autre, et il se peut que vous perdiez des heures à chercher la cause d'une erreur d'écriture de variable.

De plus, lorsque vous travaillez en commun avec d'autres développeurs, si vous n'avez pas défini le nom du fichier de connexion à la base de données, lors de la fusion des scripts, vous devrez tout remodifier et perdre ainsi un temps précieux.

L'unicité du code (DRY)

[modifier | modifier le wikicode]

L'unicité du code (ou DRY pour "Don't repeat yourself", Ne vous répétez pas) est le principe selon lequel aucun code ne doit être double dans le script. Ce critère a un enjeu pratique et vise à augmenter la rapidité des modifications d'un script. Prenons un exemple :

Vous faites un programme de comptabilité d'entreprise et vous vous connectez à une base de données. Si dans chaque page où vous vous connectez, vous entrez le code suivant :

<?php
$connect = mysql_connect('host','account','password');

//actions sur la BDD

Le jour où vous voudrez changer le mot de passe de la base de données, vous devrez modifier chaque script, tâche qui s'avère laborieuse. Si en revanche lorsque vous vous connectez à la base de données vous entrez le code suivant :

include 'connect.php';

Et que dans le fichier connect.php vous entrez les informations de votre base de données, vous n'aurez qu'à modifier ce seul fichier lors d'un changement de mot de passe.

La duplication de code est donc considérée comme un anti-patron de programmation.

Exemple concret

[modifier | modifier le wikicode]

Créez un fichier conf/config.php dans lequel vous mettez vos informations de connexion à la base de données :

define('HOST', 'localhost');
define('USER', 'moi');
define('PASS', 'mon_mdp');
define('DB', 'mon_site');
define('PREFIX', 'mon_site_'); // Préfixe des tables SQL

Lorsque vous ferez une requête, vous la ferez de la manière suivante :

mysql_connect(HOST, USER, PASS);
mysql_select_db(DB);

$temp = mysql_query('SELECT * FROM '. PREFIX .'ma_table');

La gestion des erreurs

[modifier | modifier le wikicode]

Votre programme ne doit pas afficher au client de message d'erreur. Cela ne signifie pas non plus qu'il faille les étouffer. Il faut, par du code de qualité, réduire au maximum les erreurs possibles, puis traiter les autres en les enregistrant par exemple dans un fichier.

Voir le chapitre suivant pour la mise en œuvre : Exceptions.

Les conflits de critères

[modifier | modifier le wikicode]

Réaliser un programme rassemblant toutes les qualités présentées précédemment est extrêmement difficile. La plupart du temps, certains critères entrent en conflit, par exemple entre la compatibilité et la lisibilité. Il va alors falloir établir une hiérarchie.

Il n'existe pas de hiérarchie absolue. Elle dépend du type de projet que vous menez. Voici quelques exemples de hiérarchisation de critères en fonction du projet :


Projet destiné au grand public

[modifier | modifier le wikicode]

Par exemple, vous décidez un jour de concevoir un CMS. Les qualités requises seront :

  • La portabilité du code (car le CMS doit fonctionner sous le plus grand nombre de serveurs, donc doit dépendre le moins possible de sa configuration)
  • La gestion des erreurs (sachant que les personnes qui vont utiliser le script ne l'ont pas fait, il ne doit pas retourner de message d'erreur car ils ne pourraient pas l'arranger)
  • La définition de normes (vous le programmerez certainement en équipe, donc il faut coordonner votre action)
  • La lisibilité du code (toujours pour des raisons de coordination)

Script pour un particulier

[modifier | modifier le wikicode]

Si on vous demande de programmer un système de restriction d'accès pour un site particulier, il va falloir que le code possède les critères suivants :

  • La lisibilité du code (un autre développeur doit pouvoir facilement modifier votre script si le client le lui demande)

Erreurs à éviter

[modifier | modifier le wikicode]

Attention à ne pas vous laisser avoir par des idées fausses d'autant plus dangereuses qu'elles sembleraient logiques. En voici quelques exemples :

Le code optimisé

[modifier | modifier le wikicode]

Parfois le code le plus court n'est pas le plus rapide d'exécution[1]. En voici un exemple :

//Code le plus court
for ($i = 0; $i < count($array); $i++) {
    echo $array[$i];
}

//Code le plus rapide
$count = count($array);
for ($i = 0; $i < $count; $i++) {
    echo $array[$i];
}

En effet, avec le premier code, à chaque itération, la taille du tableau est recalculée, ce qui ralentit le script. Le code le plus court n'est donc pas le plus rapide d'exécution.

Guillemets simples pourquoi ?

[modifier | modifier le wikicode]

Toutes les chaînes peuvent être écrites autant avec des guillemets simples ('foo') qu'avec des guillemets doubles ("bar"). Cependant, on conseille généralement d'employer les guillemets simples parce qu'ils sont plus rapides[2]. En voici la raison : à l'intérieur des guillemets doubles, les variables sont interprétées et substituées correctement.

Exemple :

echo "Votre nom est $nom et vous êtes $etatUser. Il vous reste $pointAction point(s) d'action.";
//Fait ce qu'on s'attend qu'il fasse. Alors que :
echo 'Votre nom est $nom et vous êtes $etatUser. Il vous reste $pointAction point(s) d\'action.';
//écrira bêtement la chaine avec les nom des variables. Il faudra additionner les chaines ensembles :
echo 'Votre nom est '. $nom .' et vous êtes '. $etatUser .'. Il vous reste '. $pointAction .' point(s) d\'action.';

Si à première vue ça semble être un avantage pour les guillemets doubles et qu'il est vrai que dans certaines conditions particulières, ça puisse augmenter la lisibilité du code, ça a aussi son désavantage : chacune des chaînes écrites avec des guillemets doubles doit d'abord être traversée par PHP pour y chercher de telles variables. En y regardant bien, le plus souvent c'est à des endroits où il n'y a aucune chance que se retrouvent de telles variables. Cette opération est en soi extrêmement rapide, presque qu'imperceptible, mais les chaînes se retrouvent souvent dans des endroits clés : des boucles, des fonctions exécutées des milliers des fois. Par ailleurs, même si on doit ajouter des variables dans une chaîne, ce sont les guillemets simples qui seront les plus rapides (Sauf cas extrême : echo "$a - $b,$c + $d $e $sigma $epsilon";). C'est pourquoi on conseille de s'habituer et d'employer les guillemets simples le plus souvent possible.

Par ailleurs, lorsqu'on avance dans le niveau de programmation avec les objets et les tableaux (qui ne sont pas interprétés correctement dans les guillemets doubles), les occasions de se servir de ceux-ci vont en s'amenuisant considérablement.

Logo

En cas de migration des guillemets vers les apostrophes, il faut remplacer tous les retours à la ligne \n qui ne sont plus interprétés, par des <br/>.

Conventions de codage

[modifier | modifier le wikicode]

Lors d'une comparaison entre une variable et un littéral, on place ce dernier en premier. Ex : if (1 == $x). Cette technique sert à éviter de confondre les assignations avec les égalités, et les déréférencements de pointeurs null.

La programmation objet en PHP est régie par des recommandations nommées PSR (pour PHP Standards Recommendations) publiées sur http://www.php-fig.org/psr/.

En 2022, 13 de ces recommandations ont été acceptées officiellement :

PSR-1 : conventions de codages basiques

[modifier | modifier le wikicode]

Les voici résumées ici[3] :

  • Un fichier .php doit être encodé en UTF8 sans BOM.
  • Les noms de classe doivent être rédigés en StudlyCaps (commencer par une majuscule).
  • Les noms des variables et méthodes de classe doivent être écris en camelCase (en commençant par une minuscule).
  • Les noms des constantes doivent être en lettres capitales et snake_case (en séparant les mots par des underscores). Comme par exemple la native DIRECTORY_SEPARATOR.

PSR-3 : interface du logger

[modifier | modifier le wikicode]

Définit une méthode par niveau de criticité du log :

Numéro Graylog Niveau
0 emergency
1 alert
2 critical
3 error
4 warning
5 notice
6 informational
7 debug
 Généralement la production n'affiche pas les debug.

PSR-4 : Autoloading

[modifier | modifier le wikicode]

Régit le fait que les séparateurs dans les namespaces, et les underscores dans les noms de classe, représentent des séparateurs de dossier du système de fichier.

PSR-6 : interface du cache

[modifier | modifier le wikicode]

PSR-7 : interface des messages HTTP

[modifier | modifier le wikicode]

PSR-11 : interface des conteneurs

[modifier | modifier le wikicode]

PSR-12 : guide de style étendu

[modifier | modifier le wikicode]

Cette norme remplace la PSR-2 et prolonge le PSR-1 avec :

  • Les alinéas doivent faire quatre espaces. Presser la touche "tabulation" peut le faire automatiquement en réglant les IDE.
  • Les lignes ne doivent pas dépasser 120 caractères (les IDE peuvent dessiner une ligne verticale à ce niveau).

Les normes suivantes proposent des implémentations d'architectures logicielles (voir PHP Standard Recommendation sur Wikipédia (en anglais) Article sur Wikipédia).

PSR-13 : liens hypermédia

[modifier | modifier le wikicode]

PSR-14 : Event Dispatcher

[modifier | modifier le wikicode]

PSR-15 : HTTP Handlers

[modifier | modifier le wikicode]

PSR-16 : interface des éléments du cache

[modifier | modifier le wikicode]

PSR-17 : interface des fabriques de requêtes HTTP

[modifier | modifier le wikicode]

PSR-18 : interface des clients HTTP

[modifier | modifier le wikicode]

Les principes de programmation objet SOLID permettent des codes avec une bonne couverture en tests unitaires et peu de conflits de commits entre les branches du SGV.

Une fonction ne doit faire qu'une seule chose

[modifier | modifier le wikicode]

Afin de comprendre tout ce que fait une fonction par son nom sans avoir à la relire, et de réaliser facilement ses tests unitaires, il convient de lui confier un seul rôle, et de ne pas lui injecter plus de deux arguments (en les remplaçant par une classe de configuration à plusieurs attributs[4], ou un tableau).

Séparer le code SQL dans un dossier "repository"

[modifier | modifier le wikicode]

À l'instar de Doctrine, il convient de ranger les classes contenant du DQL ou de l'SQL dans un dossier séparé (qui sera le seul à évoluer en cas de changement de SGBD). Ceci est conforme au patron de conception MVC.

Optimisation des performances

[modifier | modifier le wikicode]

PHP permet d'aboutir à un même résultat de plusieurs manières différentes, il s'agit donc de privilégier les plus performantes. Voici donc les manières de coder préconisées :

Plusieurs outils de profilage permettent de classer les codes par temps d'exécution[5].

Xdebug's Profiler

[modifier | modifier le wikicode]

Le profilage Xdebug a été décrit dans le chapitre Programmation PHP/Xdebug.

Données XHProf lues par Grafana

XHProf est une extension PHP dédiée au profilage du code[6], développée par Facebook et open source depuis mars 2009.

pecl install -f xhprof

Dans php.ini :

extension=xhprof.so
RUN pecl install -f xhprof \
 && docker-php-ext-enable xhprof
RUN echo 'xhprof.output_dir = "/usr/src/app/traces"' > /usr/local/etc/php/conf.d/xhprof.ini

Modifier le .php à analyser, en précisant les flags à analyser parmi les trois disponibles :

xhprof_enable(XHPROF_FLAGS_CPU + XHPROF_FLAGS_MEMORY + XHPROF_FLAGS_NO_BUILTINS);
// Code à profiler
$data = xhprof_disable();

Ensuite les data peuvent être lues via un fichier :

file_put_contents('trace.xhprof', serialize($data));
Affichage du résultat
[modifier | modifier le wikicode]

Plusieurs solutions parmi lesquelles Grafana, Graphviz ou des conteneurs Docker avec leurs propres URLs.

Le résultat est présenté sous la forme d'un tableau de chaque fonction avec leur consommation de CPU et RAM en valeurs et pourcents. On peut aussi voir le graphe séquentiel de leurs appels avec les plus consommatrices de ressources mises en évidence.

Outil payant développé par le concepteur du framework Symfony[7] qui fournit des organigrammes des exécutions.

Choisir la condition sans négation

[modifier | modifier le wikicode]

Cela permet de gagner une opération NOT dans le processeur, et cela simplifie également la lecture du code en simplifiant la condition.

Avant :

if (x !== 1) {
	y = 1;
} else {
	y = 0;
}

Après :

if (x === 1) {
	y = 0;
} else {
	y = 1;
}

L'évitement des else (et else if) par des return dans les conditions, permet de gagner en performances, en lisibilité et en taille de lignes[8]. Cela peut permettre également d'éviter trop d'imbrications de blocs de code, et la grande indentation que cela entraîne.

Mais le plus important est aussi de ne pas lancer d'instructions inutiles. Exemple si on va chercher des enfants par un appel à une base de données ou une API :

  • Pas bien :
$parent = $this->getParent();
$children = $this->getChildren($parent);

if (empty($parent) || empty($children)) {
    return 404;
}
  • Bien :
$parent = $this->getParent();
if (empty($parent)) {
    return 404;
}

$children = $this->getChildren($parent);
if (empty($children)) {
    return 404;
}

Passage par référence des tableaux

[modifier | modifier le wikicode]

Utiliser les références dans les arguments tableaux volumineux (function fonction(&$tableau)), pour éviter sa duplication en mémoire.

Tester les invariants avant les boucles

[modifier | modifier le wikicode]

Une condition est testée dans une boucle comme dans l'exemple ci-dessous.

for ($i=0; $i<$count; $i++) {
    if ($mode === 'display') {
        echo $array[$i];
    }
}

Cependant, la boucle n'a pas d'influence sur la valeur de la condition. La condition peut donc être testée avant la boucle pour éviter de la retester plusieurs fois.

if ($mode === 'display') {
    for ($i=0; $i<$count; $i++) {
        echo $array[$i];
    }
}

Tests de charge

[modifier | modifier le wikicode]

Plusieurs outils existent :

Autres bonnes pratiques

[modifier | modifier le wikicode]
  • Pour nommer une variable, éviter $data car trop générique : choisir un nom le plus descriptif possible.
  • Dans les sprintf(), numéroter les paramètres (ex : remplacer "%s" par "%9$s).
  • Arrondir les float pour éviter les erreurs d'imprécisions dues à la virgule flottante.
  • En POO, ne pas appeler les variables superglobales directement dans les classes, mais les injecter dans le constructeur comme les autres dépendances.
  • Ne pas lancer de SELECT * en SQL car son résultat peut être récupéré en PHP par indice numérique et donc être perturbé par des modifications de schéma en base.
  • Ne pas lancer de SELECT d'un élément dans une boucle si on peut la remplacer par un seul SELECT de tous les éléments.
  • Dans le cas d'un projet à plusieurs, la revue par les pairs permet d'éviter les écueils les plus évidents. Si les spécifications métier sont simples, on peut même étendre cette pratique par un test par les pairs (sinon il faut les faire par un PO).



Exceptions

Tester l'existence

[modifier | modifier le wikicode]

Pour éviter les warnings de variables inexistantes il vaut mieux les initialiser au début. Si toutefois cela s'avère impossible, on recourt à des tests sur les fonctions suivantes pour le faire :

if (!isset($maVariable)) { $maVariable = ''; }
if (!defined('MA_CONSTANTE')) { define('MA_CONSTANTE', ''); }
if (!function_exists('maFonction')) { function maFonction() {} }

Permet de savoir si une variable est définie et si contient une valeur. Son comportement s'adapte au type de la variable. Cela équivaut à :

  • String : isset($v) and $v == ''.

ou

  • Booléen : isset($v) and $v == false.

ou

  • Tableau : isset($v) and sizeof($v) == 0.

Tout comme en Java, la levée d'exception est assurée par un bloc try... catch. Cela permet à un script de poursuivre son exécution malgré les erreurs (ex : panne réseau), et ainsi de ne pas bloquer l'utilisateur sur une page blanche ou en anglais destinée au développeur.

Ces interruptions sont représentées par des classes qui héritent toutes du type Throwable, qui a deux classes filles : Error et Exception[1].

try {
  echo '1 / 2 = ' 1/2;
  echo '3 / 0 = ' 3/0; // instruction qui déclenchera l'exception
  echo '2 / 1 = ' 2/1; // cette instruction ne sera pas exécutée à cause de la précédente
}
catch (Exception $e) {
  echo $e->getMessage(); // afficher le message lié à l'exception
}

Il n'est donc pas nécessaire de prévoir ce qui peut interrompre le programme pour s'en prémunir et poursuivre l'exécution en fonction.

La classe de gestion des erreurs nommée Exception est gracieusement mise à votre disposition par l’interpréteur dans les versions ultérieures à PHP 5.0.

Autre exemple d’utilisation :

class Humain
{
  var $age;

  function __construct($combien)
  {
    $this->age = $combien;
   
    try {
      if ($this->age<0)
      throw new Exception('Un peu jeune');
      if ($this->age>200)
      throw new Exception('Un peu vieux');
    } catch (Exception $e) {
      echo $e->getMessage();
      return;
    }
  }

}

//Retournera un message d'erreur
$humain = new Humain(700);
$humain = new Humain(-3);

//Sans erreur
$humain = new Humain(16);
 Le bloc finally ajouté après les catch sera exécuté après les instructions du try et des catch.

Logo

La bonne pratique est de ne jamais envoyer \Exception directement mais d'utiliser ses sous-classes, qui ont de plus généralement le bon code HTTP (au lieu de erreur 500). Par exemple sur Symfony, les erreurs inhérente à l'utilisateur (400) sont accessibles dans le namespace Symfony\Component\HttpKernel\Exception.

Par ailleurs, il est possible de créer ses propres classes d'exceptions, et de modifier les exceptions natives PHP avec set_exception_handler()[2].

Recréer une exception ou une erreur

[modifier | modifier le wikicode]

Les Throwable PHP sont immutables, pour les modifier il faut donc les recréer. Pour faciliter cela, ils possèdent depuis PHP7 une méthode getPrevious(), dont le résultat injecté en troisième argument du constructeur d'un autre, permet de cloner l'objet. Ex :

        try {
            crash();
        } catch (\Exception $e) {
            throw new \Exception('Nouveau message + ancien : '.$e->getMessage(), $e->getCode(), $e->getPrevious());
        }

NB : la classe Throwable n'est pas instanciable directement.

trigger_error()

[modifier | modifier le wikicode]

Cette fonction lance une exception du type placé en second paramètre, dont certains peuvent stopper l'exécution du programme[3]. Par exemple :

  trigger_error('Message d'erreur', E_USER_ERROR);

Affiche : ErrorException. User Error: Message d'erreur.

La liste des types d'erreur est disponible sur http://php.net/manual/fr/errorfunc.constants.php.

Affichage d'une trace lisible

[modifier | modifier le wikicode]

Afin de clarifier la trace de l'exécution des scripts, il peut être utile de formater celle-ci avec la balise <pre> :

if ($debogage) {
  print '<pre>';
  var_dump(scandir('.'));
  print '</pre>';
}

NB : on rappelle que les commandes ini_set('display_errors', 1); et error_reporting(E_ALL); permet d'afficher à l'écran toutes les erreurs et avertissements.


Voici une manière de n'afficher les erreurs que lors du débogage :

if ($debogage) {
  error_reporting(E_ALL);
} else {
  error_reporting(0);
}

Pour créer une bonne gestion des erreurs, partez du principe que l'utilisateur est imprévisible, et qu'il va justement faire ce qu'il ne faut pas (Loi de Murphy). Envisagez toutes les possibilités et trouvez-y une solution, en testant les exceptions (ex : caractères d'échappements ou symboles non ASCII comme des sinogrammes pour voir s'ils s'affichent bien).


debug_backtrace et debug_print_backtrace

[modifier | modifier le wikicode]

Ces fonction affichent automatiquement la trace de l'exécution menant à elles.

Logo

Sur un framework cela peut être trop long à exécuter, il faut donc ne faire afficher que les noms des fichiers exécutés avec l'argument suivant :

echo '<pre>'; debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);echo '</pre>';
$logFile = fopen('debug.log', 'a+');
fwrite($logFile, json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS)).PHP_EOL);
fclose($logFile);

Si l'affichage de la trace doit être suivi d'une interruption du programme, utiliser :

throw new \Exception('Trace');

Exception::getTraceAsString()

[modifier | modifier le wikicode]

Si l'exception ne surgit pas avec un throw, on peut afficher la trace depuis avec sa méthode :

echo $e->getTraceAsString();

Fichier de log maison

[modifier | modifier le wikicode]

Bien souvent on ne peut pas afficher les logs dans le navigateur ou rapidement via le logeur existant, donc il faut recourir à la création d'un nouveau fichier de log temporaire :

$logFile = fopen('debug.log', 'a+');
fwrite($logFile, $maVariable.PHP_EOL);
fclose($logFile);

Depuis une commande Symfony, on peut afficher des logs en console :

$output->writeln('Update complete');

Mais le mieux est d'utiliser la bibliothèque Monolog[4] (compatible PSR-3[5]), pour que les logs soient horodatés et s'enregistrent dans var/log/ (puisque les commandes peuvent être lancées par cron et donc sans affichage en console). De plus, Monolog est configurable pour afficher en plus ces logs en console[6] (ce qui est fait par défaut sur la v3.3 dans config/packages/dev/monolog.yaml).

composer require symfony/monolog-bundle

Pour booster ses performances, on peut le régler ainsi dans monolog.yaml :

monolog:
    use_microseconds: false

Logo

Quand l'application tourne sur plusieurs serveurs frontaux, il vaut mieux centraliser les logs dans un agrégateur de logs comme Kibana ou Graylog. Or, dedans il est possible que tous les logs ne soient pas visibles. Par exemple en cas de caractères spéciaux dans les clés des tableaux, tels que ">" ou "$", ou en cas de valeur NULL :

$a = 'HelloWorld!';
$this->logger->info('Test d\'affichage.', [
    '$a' => $a,  // absent de Graylog
    'a' => $a,   // visible
    'b' => null, // absent de Graylog
]);
 C'est une raison pour ne pas inclure de variable dans le message et de ne les mettre que dans le tableau, en plus de pouvoir retrouver tous les logs similaires plus facilement.


Xdebug

Xdebug est un système qui permet de calculer le taux de couverture de code, de le profiler, ou de l'exécuter pas à pas. Cela montre donc l'exécution plus précisément qu'en relecture seule, et élimine donc le risque de sauvegarder des "echo", des "print" ou des "var_dump" en production.

Installation sur Linux[1][2] :

sudo apt-get install php8.2-xdebug

ou

pecl install xdebug

Puis dans php.ini :

xdebug.remote_enable = On

ou sur PHP8.2 via Apache :

sudo vim /etc/php/8.2/apache2/conf.d/20-xdebug.ini

Ajouter :

xdebug.remote_enable=1

Configuration complète

[modifier | modifier le wikicode]

Exemple en V3[3] :

sudo apt-get install php7.4-xdebug
&& echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.discover_client_host=0" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.client_port=9000" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.mode=debug" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/php.ini
 Le client_port par défaut est passé de 9000 à 9003 entre Xdebug 2 et 3[5].

Avec Docker, il faut aussi spécifier le "remote_host"[6].

Exemple d'installation et activation :

RUN pecl install xdebug-2.9.8 \
  && docker-php-ext-enable xdebug

RUN echo "xdebug.client_host = 172.170.0.1" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.discover_client_host=0" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.client_port=9000" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.mode=debug" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini \
    && echo "xdebug.start_with_request=yes" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini

Wamp fournit Xdebug en mode "develop". Pour activer le débogage pas à pas, il faut donc éditer le php.ini :

xdebug.mode=debug
xdebug.client_host=127.0.0.1
xdebug.start_with_request=yes

Dans un navigateur

[modifier | modifier le wikicode]

Il faut installer un module sur son navigateur (ex : Xdebug-ext sur Firefox[7]) pour pouvoir activer ou désactiver le débogage d'une page. Ce module s'interface avec les principaux IDE, par exemple PhpStorm[8], pour leur faire lancer le débogage lors du chargement d'une page.

En ligne de commande

[modifier | modifier le wikicode]

On peut ajouter un argument à PHP[9]. Ex :

php -d xdebug.profiler_enable=1 bin/console MaCommande.php

Débogage pas à pas

[modifier | modifier le wikicode]

Lors du débogage, PhpStorm fera apparaitre un menu "Debug" avec trois sous-menus dont :

  1. la liste des variables du script et leurs valeurs (modifiables à la volée)
  2. les warnings PHP
  3. la sortie HTML.

Quand on clique dans la marge, un point d’arrêt est créé et représenté par une ligne rouge.

Raccourcis clavier (voir le menu "Run" en haut) :

  • F7 (step into) : mode pas à pas détaillé.
  • F8 (step over) : mode pas à pas sans sauter dans les dépendances.
  • F9 (resume) : poursuivre l'exécution du programme sans s'arrêter.
  • Alt + F9 : poursuivre jusqu'au prochain point d'arrêt.
  • Shift + F7 : pas à pas intelligent.

Ajouter le mode au précédent[10] :

xdebug.mode=debug,profile

Un ensemble de vidéos explicatives existent en anglais[11].


Mots réservés

Mots du langage

[modifier | modifier le wikicode]

Les mots qui suivent ont un sens spécial en PHP. Si vous les utilisez hors de leur contexte, des problèmes de confusions peuvent arriver.

Liste des mots spécifiques en PHP
Uniquement sous PHP 5
and or xor __FILE__ exception
__LINE__ array as break final
case class const continue php_user_filter
declare default die do public
echo else elseif empty private
enddeclare endfor endforeach endif catch
endswitch endwhile eval exit try
extends for foreach function clone
global if include include_once implements
isset list new print interface
require require_once return static throw
switch unset use var protected
while __FUNCTION__ __CLASS__ __METHOD__ abstract
extends cfunction* old_function* yield[1]
* : depuis PHP4 seulement

Liste des 72 mots réservés par ordre alphabétique[2] :

__CLASS__ 
__DIR__
__FILE__ 
__FUNCTION__ 
__LINE__ 
__METHOD__ 
__NAMESPACE__
abstract
and 
array() 
as 
break 
case 
catch
cfunction ''(PHP 4)''
class 
clone
const 
continue 
declare 
default 
die() 
do 
echo() 
else 
elseif 
empty() 
enddeclare 
endfor 
endforeach 
endif 
endswitch 
endwhile 
eval() 
exit()
explode()
extends 
final
for 
foreach 
function 
global 
goto
if 
implements
include_once() 
include() 
instanceof
interface
isset() 
list() 
namespace
new 
old_function ''(PHP 4)''
or 
print() 
private
protected
public
require_once() 
require() 
return() 
split() ''(PHP < 5.3)''
static 
switch 
throw
try
unset() 
use 
var 
while 
xor

Nouveautés PHP 7

[modifier | modifier le wikicode]

Plusieurs opérateurs composés permettent de réduire la syntaxe d'opérations courantes :

De plus, on peut maintenant utiliser :

  • plusieurs classes dans le même use.
  • define() pour définir un tableau de constantes.

Depuis PHP 7.4, les propriétés typées. Ex :

public int $id;

Liste des 48 bibliothèques natives PHP 5.5.0 avec EasyPHP[3] :

Core
PDO
Phar
Reflection
SPL
SimpleXML
apache2handler
bcmath
bz2
calendar
ctype
curl
date
dom
ereg
filter
ftp
gd
hash
iconv
json
libxml
mbstring
mcrypt
mhash
mysql
mysqli
mysqlnd
odbc
openssl
pcre
pdo_mysql
pdo_sqlite
pdo_sqlsrv
session
sockets
sqlite3
sqlsrv
standard
tokenizer
wddx
xdebug
xml
xmlreader
xmlwriter
xsl
zip
zlib

Voir aussi List of PHP extensions sur Wikipédia (en anglais) Article sur Wikipédia.


GFDL GFDL Vous avez la permission de copier, distribuer et/ou modifier ce document selon les termes de la licence de documentation libre GNU, version 1.2 ou plus récente publiée par la Free Software Foundation ; sans sections inaltérables, sans texte de première page de couverture et sans texte de dernière page de couverture.