Programmation PHP/Version imprimable2
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
Dates
date()
[modifier | modifier le wikicode]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
strtotime()
[modifier | modifier le wikicode]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
checkdate()
[modifier | modifier le wikicode]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
DateTime
[modifier | modifier le wikicode]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);
.
add
[modifier | modifier le wikicode]La méthode DateTime::add()
(et son alias date_add()
), permet d'ajouter deux dates[6].
diff
[modifier | modifier le wikicode]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].
DateInterval
[modifier | modifier le wikicode]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
.
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).
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.
DatePeriod =
[modifier | modifier le wikicode]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].
Timestamps
[modifier | modifier le wikicode]time()
[modifier | modifier le wikicode]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()
.
mktime()
[modifier | modifier le wikicode]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');
Références
[modifier | modifier le wikicode]- ↑ http://php.net/manual/fr/function.date.php
- ↑ http://php.net/manual/fr/function.strtotime.php
- ↑ http://stackoverflow.com/questions/21644002/how-can-i-get-last-week-date-range-in-php
- ↑ http://php.net/manual/fr/function.checkdate.php
- ↑ http://php.net/manual/fr/datetime.format.php
- ↑ https://www.php.net/manual/fr/function.date-add.php
- ↑ https://www.php.net/manual/fr/function.date-diff.php
- ↑ http://php.net/manual/fr/class.datetimeimmutable.php
- ↑ http://php.net/manual=/fr/dateinterval.format.php
- ↑ https://www.php.net/manual/en/class.dateperiod.php
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.
Un cookie et une session, quelles différences
[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.
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. */
|
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.
Le formulaire
[modifier | modifier le wikicode]Voici le code du formulaire en HTML, il va afficher une boîte de texte et un bouton "Connexion".
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.
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.
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 :
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.
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.
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é.
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
Introduction
[modifier | modifier le wikicode]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] :
- 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/). - Chrome
C:\Users\%USERNAME%\AppData\Local\Google\Chrome\User Data\Safe Browsing Cookies
. - Internet Explorer :
C:\Users\%USERNAME%\AppData\Roaming\Microsoft\Windows\Cookies
.
- 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).
Syntaxe
[modifier | modifier le wikicode]<?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, '/');
Exemples
[modifier | modifier le wikicode]<?php
if (isset($_COOKIE["cookie1"])) {
echo 'Authentifié';
} else {
echo 'Non authentifié';
}
Références
[modifier | modifier le wikicode]- http://php.net/manual/fr/features.cookies.php
- http://www.php.net/manual/fr/function.setcookie.php
- http://fr2.php.net/explode/
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
Installation
[modifier | modifier le wikicode]OPcache
[modifier | modifier le wikicode]Dans Docker :
RUN docker-php-ext-install opcache
APCu
[modifier | modifier le wikicode]Dans Docker
[modifier | modifier le wikicode]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
&& 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
Références
[modifier | modifier le wikicode]- ↑ http://www.php-cache.com/en/latest/
- ↑ https://www.php.net/manual/fr/session.security.php
- ↑ https://www.php.net/manual/fr/intro.opcache.php
- ↑ https://www.php.net/manual/fr/intro.apcu.php
- ↑ https://www.php-fig.org/psr/psr-6/
- ↑ https://stackoverflow.com/questions/24448261/how-to-install-apcu-in-windows
- ↑ https://phpflow.com/php/how-to-install-apc-cache-on-wamp-and-xampp/?utm_content=cmp-true
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].
Installation
[modifier | modifier le wikicode]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
Client
[modifier | modifier le wikicode]Sur Docker PHP :
RUN pecl install memcached \
&& docker-php-ext-enable memcache
Test
[modifier | modifier le wikicode] 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
Commandes
[modifier | modifier le wikicode]- Reset mémoire :
echo "flush_all" | nc -q 1 localhost 11211
Utilisation
[modifier | modifier le wikicode]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]Installation
[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
Utilisation
[modifier | modifier le wikicode]$memcached = new \Memcached();
$memcached->addServer('127.0.0.1', 11211);
$memcached->set('nom du test', 'valeur du test');
echo $memcached->get('nom du test');
Références
[modifier | modifier le wikicode]- ↑ (en) « License of memcached »
- ↑ « Amplification d'attaque DDoS : Memcached fait exploser les compteurs » (consulté le 19 octobre 2018)
- ↑ https://github.com/memcached/memcached/wiki/Commands
Redis
Installation
[modifier | modifier le wikicode]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].
Serveur
[modifier | modifier le wikicode]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
Client
[modifier | modifier le wikicode]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.
Commandes
[modifier | modifier le wikicode]Pour se loguer au serveur Redis :
telnet nom_du_serveur 6379
Les commandes Redis les plus utiles[3] :
Lecture
[modifier | modifier le wikicode]- 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.
Écriture
[modifier | modifier le wikicode]- 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
redis-cli
[modifier | modifier le wikicode]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');
predis
[modifier | modifier le wikicode]Cette bibliothèque permet d'utiliser Redis en clustering, avec des masters et slaves[8].
Dans Symfony
[modifier | modifier le wikicode]Dans le framework PHP Symfony.
Session
[modifier | modifier le wikicode]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.
Doctrine
[modifier | modifier le wikicode]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
Cache chainé
[modifier | modifier le wikicode]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)%'
Références
[modifier | modifier le wikicode]- ↑ https://aws.amazon.com/fr/elasticache/redis-vs-memcached/
- ↑ https://www.disko.fr/reflexions/technique/redis-vs-memcached/
- ↑ https://redis.io/commands
- ↑ https://redis.io/commands/set/
- ↑ https://redis.io/commands/setex/
- ↑ https://rdbtools.com/blog/redis-delete-keys-matching-pattern-using-scan/
- ↑ https://www.mikeperham.com/2015/09/24/storing-data-with-redis/
- ↑ https://github.com/predis/predis
- ↑ https://github.com/snc/SncRedisBundle/blob/master/Resources/doc/index.md
- ↑ https://github.com/symfony/symfony/blob/4.1/src/Symfony/Component/HttpFoundation/Session/Storage/Handler/RedisSessionHandler.php
- ↑ https://symfony.com/doc/current/session/database.html
- ↑ https://kunicmarko20.github.io/2017/07/20/Doctrine-Second-Level-Cache-with-Translations-and-Redis.html
- ↑ https://github.com/snc/SncRedisBundle/issues/554
- ↑ https://symfony.com/doc/current/components/cache/adapters/chain_adapter.html
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.
Présentation
[modifier | modifier le wikicode]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 formulaire
[modifier | modifier le wikicode]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.
En gros, ce formulaire enverra sur la page traitement.php la valeur de l'entrée "mdp".
Le traitement
[modifier | modifier le wikicode]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é.
<?php //traitement.php
$motdepasse = 'qwerty';
/* voici le mot de passe à envoyer si l’on veut être
connecté */
if (empty($_POST["mdp"]) OR $_POST["mdp"] != $motdepasse) {
/* si la valeur envoyée est vide ou différente de la valeur
demandée */
exit;
/* interruption du script (voir php/interrompre_un_script) */
}
setcookie("wiki",$_POST["mdp"],time()+3600);
/* le serveur envoie un cookie à l'utilisateur pour
permettre l'accès aux pages administration */
header("Location: admin.php");
/* la page redirige l'utilisateur vers la page de la
zone d'administration (cette fonction doit être
utilisée avant tout code HTML) */
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.
<?php // admin.php
$motdepasse = 'qwerty';
/* le (vrai) mot de passe */
$mdp = $_COOKIE["wiki"];
/* le mot de passe enregistré sur le cookie.
L'accès aux cookies se fait au travers du
tableau super-global $_COOKIE. Il fonctionne
comme $_POST ou $_GET. */
if ($mdp != $motdepasse) {
/* si le mot de passe n'est pas correct */
exit('Haha ! Tu voulais voir l\'admin sans mot de passe ?!');
/* interruption du script avec un joli message ^^
notez l'antislash devant l'apostrophe qui permet
de ne pas interrompre la chaine de caractères */
}
echo "affichage de admin.php";
/* la page peut s'afficher correctement. Si le script
arrive ici, c’est que le mot de passe est correct,
autrement le script aurait été arrêté (appel à exit
plus haut. */
Types de champ
[modifier | modifier le wikicode]Pour des <input type="checkbox">
, on vérifie si leurs valeurs sont 'on' ou 'off'.
Types MIME
[modifier | modifier le wikicode]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].
Références
[modifier | modifier le wikicode]
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.
Dossiers
[modifier | modifier le wikicode]Voici les fonctions usuelles de navigation et manipulation du système de fichier.
Lecture
[modifier | modifier le wikicode]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);
Écriture
[modifier | modifier le wikicode]shell_exec('pwd')
: exécuter n'importe quelle commande shell. Le nom du répertoire courant (pwd) dans cet exemple.mkdir()
: créer un dossiertouch()
: créer un fichier (ou le rafraichir s'il existe)rename()
: déplacer un fichier
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]Windows
[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.
Unix
[modifier | modifier le wikicode]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."'");
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]Installation
[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
Utilisation
[modifier | modifier le wikicode]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à :
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.
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]Lecture
[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]file
[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 :
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.
readfile
[modifier | modifier le wikicode]Pour afficher tout le fichier dans la sortie standard[9].
Ligne par ligne
[modifier | modifier le wikicode]fgets
[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 :
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.";
}
|
fgetc
[modifier | modifier le wikicode]La fonction équivalente pour lire caractère par caractère se nomme fgetc :
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 ».
fgetcsv
[modifier | modifier le wikicode]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.
Écriture
[modifier | modifier le wikicode]Une fois le fichier ouvert, l'écriture se fait via la fonction fwrite.
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].
Se déplacer
[modifier | modifier le wikicode]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 :
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 :
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]HTTP
[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].
FTP
[modifier | modifier le wikicode]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].
SFTP
[modifier | modifier le wikicode]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');
php://
[modifier | modifier le wikicode]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.
Références
[modifier | modifier le wikicode]- ↑ http://php.net/manual/fr/function.file-exists.php
- ↑ https://www.php.net/manual/en/function.filesize.php
- ↑ http://php.net/manual/fr/function.glob.php
- ↑ http://php.net/manual/fr/dir.constants.php
- ↑ http://php.net/manual/fr/ziparchive.addfile.php
- ↑ https://stackoverflow.com/questions/11265914/how-can-i-extract-or-uncompress-gzip-file-using-php
- ↑ http://php.net//manual/fr/function.imagecopyresampled.php
- ↑ https://www.startutorial.com/articles/view/php-generator-reading-file-content
- ↑ https://www.php.net/manual/en/function.readfile.php
- ↑ http://php.net/manual/fr/function.fgetcsv.php
- ↑ http://php.net/manual/fr/function.file-put-contents.php
- ↑ https://www.php.net/manual/en/function.is-uploaded-file.php
- ↑ developpez.net
- ↑ http://php.net/manual/fr/function.get-headers.php
- ↑ http://php.net/manual/fr/book.ftp.php
- ↑ http://php.net/manual/fr/function.ftp-nlist.php
- ↑ http://php.net/manual/fr/function.ftp-set-option.php
- ↑ http://php.net/manual/fr/function.ssh2-sftp.php
- ↑ http://php.net/manual/fr/wrappers.php.php
Objets COM
Installation
[modifier | modifier le wikicode]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');
Exemple
[modifier | modifier le wikicode]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.
Références
[modifier | modifier le wikicode]
Images
Introduction
[modifier | modifier le wikicode]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 :
- Chargement en mémoire d'une image, nouvelle ou existante.
- Chargement éventuel des couleurs à y apporter.
- Modifications éventuelles de ses composants (création de lignes, points, remplissages, ajout de textes...).
- Restitution de l'image après avoir posté son type dans l'en-tête.
- 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)
où $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)
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 dedst_image
;src_x, src_y
sont les coordonnées desrc_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.
Exemple
[modifier | modifier le wikicode]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);
Références
[modifier | modifier le wikicode]
Mails
Mail()
[modifier | modifier le wikicode]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 :
- PHPMailer[3]
- PEAR Mail[4], Net SMTP[5] et Net Socket[6], et Mail Mime[7] pour ajouter un fichier joint.
Références
[modifier | modifier le wikicode]- ↑ http://php.net/manual/fr/function.mail.php
- ↑ https://sourceforge.net/projects/simplemailsvr/
- ↑ http://sourceforge.net/projects/phpmailer/
- ↑ http://pear.php.net/package/Mail/download
- ↑ http://pear.php.net/package/Net_SMTP/download
- ↑ http://pear.php.net/package/Net_Socket/download
- ↑ http://pear.php.net/package/Mail_Mime/download
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()
Hachage
[modifier | modifier le wikicode]- 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].
Scanners
[modifier | modifier le wikicode]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.
Références
[modifier | modifier le wikicode]- ↑ https://github.com/WebGoat/WebGoat
- ↑ https://symfony.com/doc/current/the-fast-track/fr/16-spam.html
- ↑ https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html#php
- ↑ https://www.php.net/manual/fr/function.base64-encode.php
- ↑ http://php.net/manual/fr/function.openssl-encrypt.php
- ↑ https://www.php.net/manual/fr/function.openssl-public-encrypt.php
- ↑ https://www.php.net/manual/fr/function.hash.php
- ↑ https://www.php.net/manual/fr/function.hash-algos.php
- ↑ http://php.net/manual/fr/function.crypt.php
- ↑ https://www.php.net/manual/fr/function.sha1.php
- ↑ https://www.php.net/manual/fr/function.md5.php
Programmation orientée objet
Introduction
[modifier | modifier le wikicode]Une classe est un format de variable non scalaire, comprenant trois types de composants :
- des constantes, accessibles par réflexion avec
$maClasse::getConstants()
. - des variables appelées "propriétés", accessibles avec
$maClasse::getProperties()
. - 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].
Le mot-réservé static
a donc deux sens : un pour les déclarations et un pour les appels.
Inclusion
[modifier | modifier le wikicode]À 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;
En PHP, l'inclusion doit précéder les appels du code qui y figure.
use function ma_fonction
pour déclarer l'utilisation d'une fonction.Instanciation
[modifier | modifier le wikicode]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();
Héritage
[modifier | modifier le wikicode]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]).
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();
Traits
[modifier | modifier le wikicode]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;
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.
Final
[modifier | modifier le wikicode]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]
Les méthodes abstraites sont obligatoirement à implémenter par les classes filles.
Closures
[modifier | modifier le wikicode]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.
Interfaces
[modifier | modifier le wikicode]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;
}
}
- 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 leimplements
.
Namespaces
[modifier | modifier le wikicode]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...).
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 :
- __call() : à chaque appel d'une méthode de la classe.
- __callStatic() : à chaque appel statique d'une méthode de la classe.
- __clone() : lors du clonage de l'objet (via la fonction "clone").
- __construct() : à l'instanciation de la classe.
- __debugInfo() : modifie les résultats des
var_dump()
. - __destruct() : à la suppression de l'objet instancié.
- __get() : à la lecture de propriétés inexistantes ou interdites.
- __invoke() : à l'appel de l'objet comme une fonction (ex :
echo $object(1)
). - __isset() : à l'appel de
isset()
(ouempty()
) sur des propriétés inexistantes ou interdites. - __serialize() : à l'appel de
serialize()
. - __set() : à l'écriture de propriétés inexistantes ou interdites.
- __set_state() : modifie les résultats des
var_export()
. - __sleep() : à l'appel de
serialize()
, pour en modifier le résultat. - __toString() : à l'appel de l'objet comme une chaine de caractères (ex :
echo $object
). - __unserialize() : à l'appel de
serialize()
. - __unset() : à l'appel de
unset()
sur des propriétés inexistantes ou interdites. - __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].
__call()
[modifier | modifier le wikicode]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);
}
_get()
[modifier | modifier le wikicode]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
.
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;
}
}
__set()
[modifier | modifier le wikicode]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')
__autoload()
[modifier | modifier le wikicode]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.
__invoke()
[modifier | modifier le wikicode]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.
self()
[modifier | modifier le wikicode]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.
Références
[modifier | modifier le wikicode]- ↑ http://php.net/manual/fr/language.oop5.paamayim-nekudotayim.php
- ↑ https://www.php.net/manual/fr/language.oop5.static.php#104823
- ↑ https://www.php.net/manual/fr/filesystem.configuration.php
- ↑ Sébastien Rohaut, Algorithmique : Techniques fondamentales de programmation (avec des exemples en PHP), Editions ENI, (lire en ligne)
- ↑ http://php.net/manual/fr/language.oop5.basic.php
- ↑ http://php.net/manual/fr/language.oop5.abstract.php
- ↑ http://php.net/manual/fr/class.closure.php
- ↑ https://secure.php.net/manual/fr/language.oop5.anonymous.php
- ↑ https://www.safaribooksonline.com/library/view/php-5-power/013147149X/013147149X_ch03lev1sec14.html
- ↑ http://php.net/manual/fr/language.oop5.magic.php
- ↑ http://php.net/manual/fr/language.oop5.overloading.php
- ↑ https://www.php.net/manual/en/class.serializable.php
- ↑ https://www.php.net/manual/fr/reflectionclass.getproperties.php
Bases de données
Introduction
[modifier | modifier le wikicode]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.
Voir aussi
[modifier | modifier le wikicode]
Microsoft SQL Server
Installation
[modifier | modifier le wikicode]On distingue plusieurs pilotes PHP pour MS-SQL Server :
Windows
[modifier | modifier le wikicode]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 :
- Télécharger les .dll SQL30
- Les copier dans
C:\PROGRA~2\EasyPHP\binaries\php\php_runningversion\ext
- 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
).
Linux
[modifier | modifier le wikicode]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
).
Connexion
[modifier | modifier le wikicode]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].
Alternative désuète
[modifier | modifier le wikicode]Les fonctions suivantes sont supprimées depuis PHP 7.0[6] :
$serverName = "(local)";
$connect = mssql_connect($serverName, "login", "password");
mssql_select_db("my_db", $connect)
// ... votre script
mssql_close($connect);
Alternative Doctrine
[modifier | modifier le wikicode]Les bibliothèques Doctrine utilisent cette syntaxe[8] :
$config = new \Doctrine\DBAL\Configuration();
$connectionParams = array(
'driver' => 'sqlsrv', // ou pdo_sqlsrv selon le pilote
'host' => $this->serverName,
'dbname' => $this->databaseName,
'user' => $this->userName,
'password' => $this->password,
);
$this->connection = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
$this->connection->query($query);
$this->connection->close();
Requête SQL
[modifier | modifier le wikicode]// ... 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
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).
Références
[modifier | modifier le wikicode]- ↑ http://www.microsoft.com/en-us/download/details.aspx?id=20098
- ↑ http://www.php.net/manual/fr/ref.pdo-sqlsrv.php
- ↑ https://docs.microsoft.com/en-us/sql/connect/odbc/linux-mac/installing-the-microsoft-odbc-driver-for-sql-server
- ↑ https://www.barryodonovan.com/2016/10/31/linux-ubuntu-16-04-php-and-ms-sql
- ↑ http://msdn.microsoft.com/fr-fr/library/cc296152%28v=sql.90%29.aspx
- ↑ http://php.net/manual/fr/function.mssql-connect.php
- ↑ http://www.php.net/manual/fr/function.mssql-select-db.php
- ↑ http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html
- ↑ http://php.net/manual/fr/function.sqlsrv-fetch-array.php
MySQL
PHP offre plusieurs fonctions d’interaction avec MySQL.
mysql_connect()
[modifier | modifier le wikicode]
La fonction mysql_connect()
est obsolète en PHP7, et remplacée par la classe mysqli
.
Connexion
[modifier | modifier le wikicode]$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
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 tableauxmysql_fetch_assoc()
etmysql_fetch_row()
.
Exemple :
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;
}
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()
Classe mysqli
[modifier | modifier le wikicode]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).
Références
[modifier | modifier le wikicode]
PDO
Introduction
[modifier | modifier le wikicode]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] :
Et aussi via ODBC.
Installation
[modifier | modifier le wikicode]Machine hôte
[modifier | modifier le wikicode]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].
Docker
[modifier | modifier le wikicode]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');
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]- ↑ http://php.net/manual/fr/pdo.drivers.php
- ↑ http://fr2.php.net/manual/fr/pdo.installation.php
- ↑ https://www.microsoft.com/en-us/download/details.aspx?id=20098
- ↑ http://php.net/manual/fr/class.pdo.php
- ↑ http://php.net/manual/fr/ref.pdo-mysql.php
- ↑ http://php.net/manual/fr/ref.pdo-sqlsrv.connection.php
- ↑ https://www.php.net/manual/fr/ref.pdo-pgsql.php
SQLite
SQLite est le moteur de base de données intégré à PHP5.
Connexion
[modifier | modifier le wikicode]// 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');
}
Syntaxe
[modifier | modifier le wikicode]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/.
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. |
^
|
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. |
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é.
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 :
|
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.
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].
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 |
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 |
\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.
- 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
?>
: 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
- Chercher une lettre u précédée d'une lettre q :
?<!
: 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 :
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.).
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) |
Recherche
[modifier | modifier le wikicode]La fonction ereg()
qui permettait de rechercher en regex a été remplacée par preg_match()
depuis PHP 5.3.
preg_match()
[modifier | modifier le wikicode]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*'
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).
preg_grep()
[modifier | modifier le wikicode]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);
Remplacement
[modifier | modifier le wikicode]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;
Seul le dernier groupe de capture sera pris en compte.
preg_filter()
[modifier | modifier le wikicode]Idem que preg_replace()
mais son résultat ne contient que ce qui est effectivement remplacé.
preg_split()
[modifier | modifier le wikicode]Décompose une chaine de caractères.
Références
[modifier | modifier le wikicode]- ↑ https://unicode-table.com/fr/
- ↑ https://docstore.mik.ua/orelly/webprog/pcook/ch13_05.htm
- ↑ https://www.regular-expressions.info/posixbrackets.html
- ↑ https://www.regular-expressions.info/unicode.html
- ↑ https://www.regextester.com/15
- ↑ Jan Goyvaerts, Steven Levithan, Regular Expressions Cookbook, O'Reilly Media, Inc., (lire en ligne)
- ↑ Les options sont appelées modificateurs (modifiers en anglais), voir https://www.regular-expressions.info/modifiers.html
- ↑ http://php.net/manual/fr/reference.pcre.pattern.modifiers.php
- ↑ http://php.net/manual/en/function.preg-match.php
- ↑ http://php.net/manual/fr/ref.pcre.php
- ↑ http://php.net/manual/fr/pcre.constants.php
- ↑ https://stackoverflow.com/questions/4031948/using-regular-expressions-to-find-img-tags-without-an-alt-attribute
- ↑ https://stackoverflow.com/questions/3578671/unknown-modifier-g-in-when-using-preg-match-in-php
- ↑ http://php.net/manual/fr/function.preg-grep.php
- ↑ http://www.expreg.com/pregmatchall.php
Concevoir du code de haute qualité
Introduction
[modifier | modifier le wikicode]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.
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.
PSR
[modifier | modifier le wikicode]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 |
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) ).
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 :
Profilage
[modifier | modifier le wikicode]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.
XHProf
[modifier | modifier le wikicode]XHProf est une extension PHP dédiée au profilage du code[6], développée par Facebook et open source depuis mars 2009.
Installation
[modifier | modifier le wikicode]Linux
[modifier | modifier le wikicode]pecl install -f xhprof
Dans php.ini :
extension=xhprof.so
Docker
[modifier | modifier le wikicode]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
Lancement
[modifier | modifier le wikicode]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.
Blackfire
[modifier | modifier le wikicode]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;
}
Return early
[modifier | modifier le wikicode]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).
Références
[modifier | modifier le wikicode]- ↑ http://www.phpbench.com/
- ↑ https://www.keycdn.com/blog/php-performance#11-stick-with-single-quotes
- ↑ http://www.php-fig.org/psr/psr-1/
- ↑ https://github.com/jupeter/clean-code-php
- ↑ https://www.novaway.fr/blog/tech/les-outils-de-profiling-php-open-source-en-2017
- ↑ https://www.php.net/manual/fr/book.xhprof.php
- ↑ https://blackfire.io/docs/up-and-running/installation
- ↑ https://pear.php.net/manual/en/standards.bestpractices.php
- ↑ https://github.com/locustio/locust
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() {} }
empty()
[modifier | modifier le wikicode]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
.
try... catch
[modifier | modifier le wikicode]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].
Exemples
[modifier | modifier le wikicode]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);
finally
ajouté après les catch sera exécuté après les instructions du try et des catch.
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.
Logs
[modifier | modifier le wikicode]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.
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);
throw
[modifier | modifier le wikicode]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);
Symfony
[modifier | modifier le wikicode]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
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
]);
Références
[modifier | modifier le wikicode]- ↑ https://www.php.net/manual/fr/language.errors.php7.php
- ↑ http://php.net/manual/fr/function.set-exception-handler.php
- ↑ http://php.net/manual/fr/function.trigger-error.php
- ↑ https://symfony.com/doc/current/logging.html
- ↑ https://www.php-fig.org/psr/psr-3/
- ↑ https://symfony.com/doc/current/logging/monolog_console.html
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
[modifier | modifier le wikicode]Linux
[modifier | modifier le wikicode]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
Exemple en V2
On peut aussi forcer d'autres paramètres[4] :
sudo apt-get install php7.4-xdebug
&& echo "xdebug.remote_handler=dbgp" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.remote_connect_back=0" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.remote_port=9000" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.remote_enable=1" >> /usr/local/etc/php/php.ini \
&& echo "xdebug.remote_autostart=1" >> /usr/local/etc/php/php.ini
Docker
[modifier | modifier le wikicode]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
Windows
[modifier | modifier le wikicode]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
Lancement
[modifier | modifier le wikicode]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 :
- la liste des variables du script et leurs valeurs (modifiables à la volée)
- les warnings PHP
- 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.
Profilage
[modifier | modifier le wikicode]Ajouter le mode au précédent[10] :
xdebug.mode=debug,profile
Un ensemble de vidéos explicatives existent en anglais[11].
Références
[modifier | modifier le wikicode]- ↑ « PHPStorm : Xdebug »
- ↑ « Tutoriel PHP : Xdebug, l'exécution pas à pas »
- ↑ https://xdebug.org/docs/upgrade_guide
- ↑ https://xdebug.org/docs/all_settings
- ↑ https://www.jetbrains.com/help/phpstorm/2023.2/configuring-xdebug.html#5a0181d2
- ↑ https://blog.eleven-labs.com/fr/debug-run-phpunit-tests-using-docker-remote-interpreters-with-phpstorm/
- ↑ https://addons.mozilla.org/fr/firefox/addon/xdebug-ext-quantum/?src=search
- ↑ https://www.jetbrains.com/help/phpstorm/configuring-xdebug.html
- ↑ https://stackoverflow.com/questions/2288612/how-to-trigger-xdebug-profiler-for-a-command-line-php-script
- ↑ https://xdebug.org/docs/profiler
- ↑ https://xdebug.org/docs/profiler#related_content
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.
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 | 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 :
??
: opérateur de fusion null. Équivalent d'un opérateur ternaire avec unis_null()
.<=>
: opérateur vaisseau spatial. Équivalent d'unswitch
à trois cas : inférieur, égal et supérieur.intdiv()
: division entière. Équivalent de/
+intval()
.
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;
Extensions
[modifier | modifier le wikicode]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) .
Références
[modifier | modifier le wikicode]
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. |