# Programmation PHP/Version imprimable2

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

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 :
* 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 :
À 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 :

## strtotime

Cette fonction transforme un texte (en anglais) en date.
Exemples courants :
Pour afficher la plage des dates de la semaine précédente :

## checkdate

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

## DateTime

Cette classe peut être instanciée en style POO ou en style procédural. Exemple :
Résultat : 2018-01-01 00:00:00.
Pour générer une date relative à l'actuelle :
Pour date relative à une absolue :
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

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

### diff

La méthode DateTime::diff (et son alias date_diff), permet de soustraire deux dates.
Exemple :
Résultat : 1400 jours.

### Conversion Timestamp en DateTime
## DateTimeImmutable

Idem que DateTime mais immutable.

## DateInterval

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 :
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"), 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 :
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.

## Timestamps
### time

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

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 :

## date_default_timezone_set

Ex :

## Références

* ↑ 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

# 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

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

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 ?

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é

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
### Initialiser une session

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é

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

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

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

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

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

##### La page de vérification

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

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

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

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 :
* 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 :
* 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

Pour définir la durée du cookie (à 30 jours) et la page du site qui lui est associée :
Pour supprimer un cookie, on lui confère une durée de vie négative :

## Références

* ↑ https://openclassrooms.com/forum/sujet/ou-sont-stockes-les-cookies-87753
* 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

Il existe plusieurs systèmes de cache en PHP pour accélérer l'exécution du code rappelé :
| 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 | Vider les cookies du navigateur. |
| OPcache | Opcode | opcache_reset; |
| APCu | Variables utilisateurs dans la RAM | 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, 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
### OPcache

Dans Docker :

### APCu
#### Sur machine hôte Windows

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 :
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 :
ou

## Références

* ↑ 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.

## Installation

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.
Installation :

### Client

Sur Docker PHP :

### Test

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 :
ou
Pour le résoudre :

### Commandes

* Reset mémoire :

## Utilisation

Memcached propose plusieurs commandes. 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 :
Puis l'appeler par son numéro pour voir son nom (le zéro représente l'absence de limite) :

## Utilisation en PHP
### Installation

Pour vérifier l'installation de la bibliothèque PHP pour Memcached :
* S'il est absent :

### Utilisation
## Références

* ↑ (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

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

### Serveur

Pour l'installer :
Obtenir la version :

### Client

Sur Docker PHP :
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

Pour se loguer au serveur Redis :
Les commandes Redis les plus utiles :

### Lecture

* 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

* SET : définit une valeur associée à la clé en paramètre.
* EXPIRE : définit une durée avant expiration pour la clé en paramètre, en secondes.
* SETEX : SET + EXPIRE.
* 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 :

## redis-cli

Pour afficher les clés de la base en shell :
Par défaut, redis-cli pointe sur 127.0.0.1. Pour regarder une autre machine :
Supprimer des clés par leurs noms (exemple de celles qui ont le préfixe "users:") :
ou :

## Plusieurs bases

Chaque instance de Redis peut accueillir jusqu'à 16 bases de données.
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
### predis

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

### Dans Symfony

Dans le framework PHP Symfony.

#### Session
##### SncRedisBundle

Avant Symfony 4.1, il fallait passer par un bundle tel que SncRedisBundle.
Depuis :
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

Depuis Symfony 4.1, le composant HttpFoundation contient une classe RedisSessionHandler.
Installation dans services.yaml :
Puis dans config/packages/framework.yaml :
Ensuite le service session utilisera automatiquement Redis.

#### Doctrine

Pour mettre le cache Doctrine de requête et de résultat dans Redis, on peut utiliser SncRedisBundle.
Pour vider les caches :

#### Cache chainé

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 :

## Références

* ↑ 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

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

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

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é.

## La zone administration

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.

## Types de champ

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

## Types MIME

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.

## Références

* ↑ https://javarevisited.blogspot.com/2017/06/difference-between-applicationx-www-form-urlencoded-vs-multipart-form-data.html#axzz71dnIiWZu

# 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

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

### Lecture

* 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 ;
* filesize($fichier) : renvoie la taille du fichier en octet ;
* 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).

#### Récupérer certains fichiers d'un dossier

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

### Écriture

* shell_exec('pwd') : exécuter n'importe quelle commande shell. Le nom du répertoire courant (pwd) dans cet exemple.
* mkdir : créer un dossier
* touch : créer un fichier (ou le rafraichir s'il existe)
* rename : déplacer un fichier
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.

## Droits des fichiers
### Windows

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

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

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

## Télécharger un 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
### Installation

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

### Utilisation

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) :

### Dézipper un fichier

Pour les .gz :

## Éditer et fermer un fichier

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

Pour les images il existe aussi imagecopyresampled.

## Interagir avec le fichier
### Lecture

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
##### file

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.

##### file_get_contents

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

##### readfile

Pour afficher tout le fichier dans la sortie standard.

#### Ligne par ligne
##### fgets

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

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

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

### Écriture

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.

### Se déplacer

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

Copier-coller ces lignes dans un fichier vierge upload.php pour qu’il affiche le nom des fichiers qu'on upload avec :
Il existe la fonction is_uploaded_file pour vérifier si un fichier est bien issu d'un upload, et move_uploaded_file pour le déplacer.

## Fichiers temporaires

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 :

## Fichiers distants
### HTTP

Pour lire un fichier en HTTP (par exemple cette page web) :
ou
Pour tester si un fichier distant existe, utiliser get_headers.

### FTP

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) :
Souvent le répertoire porte le nom de l'utilisateur, et on écrase le fichier s'il existe :
Fonctions propres au FTP, pour lire un serveur distant, y télécharger et déposer des fichiers :
Pour définir le timeout, voir ftp_set_option.

### SFTP

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

### php://

Ce protocole donne accès aux flux (entrant et sortant) de PHP.
* 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

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

* ↑ 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

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 :

## Exemple

Création d'un .xls :
On peut aussi créer des .doc.

## Références

* ↑ http://php.net/manual/fr/com.configuration.php

# Images
## Introduction

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

Pour créer une image ex-nihilo, on utilise la fonction :
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 :
Pour charger en mémoire une image sauvegardée sur le disque :
Exemple :
Autre fonction :
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

Pour allouer une couleur il faut en définir les paramètres RVB :
Pour définir une transparence dans un PNG :
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 :
Sinon, un fond transparent peut également être assuré par les deux fonctions ci-dessous :
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

Pour dessiner un pixel, on utilise ses coordonnées (x, y ci-dessous) :
Pour tracer une ligne entre deux points :
Pour créer un rectangle par sa diagonale :
Pour représenter une ellipse par son centre, sa hauteur et sa largeur :
ou en précisant son arc par ses angles en gradient (numérotés dans le sens horaire) :

## Retravailler les pixels existants

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. Exemple :
où :
* src_image est l'image source ;
* dst_image est l'image de destination ;
* dst_x, dst_y sont les coordonnées de dst_image ;
* src_x, src_y sont les coordonnées de src_image en partant d'en haut à gauche ;
* dst_w, dst_h, src_w, src_h sont les largeurs et hauteurs des rectangles de source et destination.
On peut ensuite comprendre que si dst_w est égal à src_w, et dst_h à src_h, la portion rectangulaire de l'image restera de la même taille. Dans le cas contraire on allonge ou on élargit.
La fonction imagecopyresampled reçoit les mêmes paramètres que imagecopyresized, mais avec la différence que, en cas de redimensionnement, la qualité est améliorée.
Puis il existe la fonction imagefilter, qui permet de nombreux effets tels que les niveaux de gris, le relief, ou la recoloration.

## Imprimer l'output

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 :
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

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

## Références

* ↑ http://php.net/manual/fr/function.imagecreate.php
* ↑ http://php.net/manual/fr/function.imagecopyresized.php
* ↑ http://fr.php.net/manual/fr/function.imagefilter.php

# Mails
## Mail

Le code suivant se connecte à localhost:25 pour envoyer un mail :
Si la machine hébergeant le script n'est pas pourvue d'un serveur SMTP, le freeware portable Simple Mail Server peut jouer ce rôle rapidement sans installation.

## Utilisation de Mail plus complexe...

* 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

* 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

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
* PEAR Mail, Net SMTP et Net Socket, et Mail Mime pour ajouter un fichier joint.

## Références

* ↑ 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

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

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 :

### Restreindre les connexions

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) :
Voir aussi Cross Origin Ressource Sharing (CORS) avec Access-Control-Allow-Origin.

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

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).
* 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, ou sur MediaWiki TitleBlacklist.
* Utiliser PHP >= 8 qui protège par défaut contre les attaques XML external entity attack (XXE).

## Chiffrer les données sensibles

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

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 :
* base64_encode
* base64_decode
Mais si on souhaite restreindre le décodage à certains, une cryptographie symétrique peut être assurée avec OpenSSL :
* openssl_encrypt
* openssl_decrypt

### Chiffrement asymétrique

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
* openssl_private_decrypt

### Hachage

* hash($algo, $data) : avec un algorithme de la liste hash_algos. Ex : hash('sha256', $maChaine).
* crypt 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.
* md5 : déconseillé car facile à cracker.

## Scanners

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

* ↑ 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

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

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

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 :
* $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.
Le mot-réservé static a donc deux sens : un pour les déclarations et un pour les appels.

## Inclusion

À 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 :
Mais la syntaxe à privilégier est celle par espace de nom :
En PHP, l'inclusion doit précéder les appels du code qui y figure.

## Instanciation

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 :

## Définition des classes

Définir une nouvelle classe adopte la syntaxe suivante :
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 :
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 :

## Utilisation d’un objet

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 :
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 :
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 :
Il est possible de lui faire subir les mêmes opérations qu’à une variable normale. De même pour exécuter une fonction :
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 :
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 :
Pour dupliquer une variable de type objet, il faut donc entrer le code suivant :
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 :

## Héritage

PHP était initialement un langage à héritage simple, 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 :
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).
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 :

### Traits

Depuis PHP 5.4.0, une structure de données appelée "trait" permet l'héritage multiple. Exemple d'utilisation :
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

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 :

## Classes abstraites

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 :
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
Les méthodes abstraites sont obligatoirement à implémenter par les classes filles.

## Closures

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

## Classes anonymes

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

## Interfaces

Voici un exemple d'interface :
Et son utilisation : la classe doit reprendre les méthodes de l'interface sous peine d'erreur.
* 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.
* Toutes les méthodes d'une interface doivent être publiques.
* Si un objet hérite et implémente, toujours le déclarer en plaçant le extends avant le implements.

## Namespaces

Exemple d'espace de noms :
Pour utiliser un namespace, "use" conserve son nom mais on peut le changer avec "as" :
Depuis PHP7 on peut même importer plusieurs classes, fonctions ou constantes sur la même ligne :

## Portée des variables

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 :
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

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, 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 (ou empty) 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
#### __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 :
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

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

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

#### __call

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

#### _get

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.
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 :

#### __set

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

### __autoload

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 !

### __sleep et __wakeup

Ces méthodes ne fonctionnent plus avec l'interface Serializable depuis PHP 8.1, au profit de serialize et unserialize.
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

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 :

## Quelques fonctions intégrées

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

### self

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

### class_exists

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

### get_class_methods

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

### get_class_vars

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 :
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 :

### get_object_vars

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

### method_exists($classe, $méthode)

Teste sur une méthode existe dans une classe.

### serialize et unserialize

Assurent la transformation du flux de données, en précisant les types des variables et index des tableaux. Exemple :
Le préfixe "a:2" signifie "array of 2 lines", et il est obligatoire pour désérialiser.

## Références

* ↑ 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, 2009 (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

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

À 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 :
NB : \'%1$s\' serait équivalent à "%1$s".

## Oracle

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

## PostgreSQL

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

## Voir aussi

* Bibliothèque DB du framework PEAR
* http://php.net/manual/fr/book.mysqli.php

# Microsoft SQL Server
## Installation

On distingue plusieurs pilotes PHP pour MS-SQL Server :
* mssql (désuet en PHP7).
* sqlsrv
* PDO
** pdo_sqlsrv
** pdo_dblib (Sybase)
** pdo_odbc (ODBC).

### Windows

Pour se connecter au serveur MS-SQL à partir d'un tout-en-un comme EasyPHP, il suffit de télécharger les pilotes .dll 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 :
    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

Pour se connecter au serveur MS-SQL, il suffit de télécharger les pilotes .so correspondant à sa version de PHP, puis d'indiquer leurs chemins dans le PHP.ini :
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
### Pilote 1.1 SQL Server pour PHP

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

### Alternative désuète

Les fonctions suivantes sont supprimées depuis PHP 7.0 :

### Alternative Doctrine

Les bibliothèques Doctrine utilisent cette syntaxe :

## Requête SQL
### Récupération des données

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 :
* 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

* ↑ 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

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

### Exécution d'une requête

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
De cette manière on récupère un tableau associatif sous la forme Clé->Valeur pour chacun des enregistrements retournés par la requête.
Il existe d'autres fonctions pour cela :
* mysql_num_rows : retourne le nombre de lignes données par la requête.
* mysql_fetch_row : identique à mysql_fetch_assoc mais retourne un tableau simple indice->valeur.
* mysql_fetch_object : identique à mysql_fetch_assoc mais retourne un objet.
* mysql_fetch_array : retourne les tableaux mysql_fetch_assoc et mysql_fetch_row.
Exemple :
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 :
* 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
## mysqli_connect

Cette fonction fonctionne un peu comme mysql_connect :
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

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 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

* ↑ http://php.net/manual/fr/function.mysql-fetch-array.php
* ↑ http://www.w3schools.com/php/func_mysqli_connect_errno.asp

# PDO
## Introduction

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 :
* 4D
* CUBRID
* Firebird
* IBM DB2
* Informix
* Microsoft SQL Server
* MySQL
* Oracle Database
* PostgreSQL
* SQLite
Et aussi via ODBC.

## Installation
### Machine hôte

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 :
Les .dll de Microsoft SQL Server sont téléchargeables sur le site officiel.

### Docker

MySQL :
PostgreSQL :

## Les classes de l'extension PDO

L'extension PDO comporte trois classes :
* 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

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 :
* Pour MS-SQL les noms des paramètres diffèrent :
* MS-SQL via une source de données ODBC (à créer dans C:\Windows\SysWOW64\odbcad32.exe) :
* Pour PostgreSQL :
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

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.
* 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.

## Quelques méthodes de PDOStatement

* 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

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 :
* PDO::FETCH_NUM retourne le jeu de résultats sous forme d'un tableau indexé par numéro commençant à 0.
Exemple d'utilisation :
* 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

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 :

## Jongler avec plusieurs SGBD en même temps

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

## Notes et références

* ↑ 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.

### Exécution d'une requête

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
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 :
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
# 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 :
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 :

## Syntaxe

PHP utilise la norme PCRE.
Les expressions rationnelles peuvent être analysées et testées via un débogueur en ligne comme https://regex101.com/.
Expressions rationnelles courantes :
| Caractère | Type | Explication |
| . | Point | N'importe quel caractère |
| [...] | crochets | classe de caractères : tous les caractères énumérés dans la classe, avec possibilité de plages dont les bornes sont séparées par "-". Ex : [0-9a-z] pour tout l'alphanumérique en minuscule, ou [0-Z] pour tous les caractères de la table Unicode entre "0" et "Z", c'est-à-dire l'alphanumérique majuscule plus ":;<=>?@". |
| [^...] | crochets et circonflexe | classe complémentée : tous les caractères sauf ceux énumérés. |
| [...[...]] | union | Union des deux ensembles |
| [...&&[...]] | intersection | Intersection des deux ensembles |
| ^ | circonflexe | Marque le début de la chaîne ou de la ligne. |
| $ | dollar | Marque la fin de la chaîne ou de la ligne. |
| | | barre verticale | Alternative - ou reconnaît l'un ou l'autre |
| (...) | parenthèses | groupe de capture : utilisé pour limiter la portée d'un masque ou de l'alternative, grouper un motif répété ou capturer une séquence |
| \n | référence | Même séquence que celle capturée précédemment par le nème groupe de capture |
| \g{n} | référence | Même séquence que celle capturée précédemment par le nème groupe de capture |
| (?P<nom>pattern) | Sous-motif nommé | Nomme le résultat d'un groupe de capture par un nom. |
| \g{nom} | référence | Même séquence que celle capturée précédemment par le groupe de capture nommé nom. |
| \k<nom> | référence | Même séquence que celle capturée précédemment par le groupe de capture nommé nom. |
Par défaut, les caractères et groupes ne sont pas répétés. Les quantificateurs permettent de spécifier le nombre de répétitions et sont spécifiés immédiatement après le caractère ou groupe concerné.
Quantificateurs :
| Caractère | Type | Explication |
| * | astérisque | 0, 1 ou plusieurs occurrences |
| + | plus | 1 ou plusieurs occurrences |
| ? | interrogation | 0 ou 1 occurrence |
| {...} | accolades | nombre de répétitions : spécifie le nombre de répétitions du motif précédent (minimum et maximum). Avec la présence de la virgule, quand le minimum est absent la valeur par défaut est zéro, quand le maximum est absent la valeur pas défaut est l'infini. Sans virgule (un seul nombre) il s'agit du nombre exact (minimum et maximum ont la même valeur). Exemples : a{2} deux occurrences de "a", a{1,10} (sans espace) entre une et dix, a{,10} jusqu'à 10 fois (de 0 à 10), a{3,} au moins 3 fois (de 3 à l'infini). |
Par défaut les quantificateurs ne recherchent pas forcément la plus longue séquence de répétition possible. Il est possible de les suffixer avec un caractère pour modifier leur comportement.
Modificateurs de quantificateurs :
| Caractère | Type | Explication |
| ? | réticent | Le quantificateur qui précède recherchera la plus petite séquence possible. |
| + | possessif | Le quantificateur qui précède recherchera la plus grande séquence possible. |
Remarques :
* Les caractères de début et fin de chaîne (^ et $) ne fonctionnent pas dans où ils ont un autre rôle.
* Les opérateurs * et + sont toujours avides, pour qu'ils laissent la priorité il faut leur apposer un ? à leur suite.
Classes de caractères POSIX :
| Classe | Signification |
| [[:alpha:]] | n'importe quelle lettre |
| [[:digit:]] | n'importe quel chiffre |
| [[:xdigit:]] | caractères hexadécimaux |
| [[:alnum:]] | n'importe quelle lettre ou chiffre |
| [[:space:]] | n'importe quel espace blanc |
| [[:punct:]] | n'importe quel signe de ponctuation |
| [[:lower:]] | n'importe quelle lettre en minuscule |
| [[:upper:]] | n'importe quelle lettre capitale |
| [[:blank:]] | espace ou tabulation |
| [[:graph:]] | caractères affichables et imprimables |
| [[:cntrl:]] | caractères d'échappement |
| [[:print:]] | caractères imprimables exceptés ceux de contrôle |
Expressions rationnelles Unicode :
| Expression | Signification |
| \\ | Antislash |
| \C | Caractère spécial C non interprété : [ ] { } ( ) ? * . : \ & - ^ $ |
| \Q...\E | Séquence littérale non interprétée |
| \0xxx | Caractère Unicode (1 à 3 chiffres octaux) |
| \a | Alarme (ASCII 07) |
| \A | Début de chaîne |
| \b | Caractère de début ou fin de mot |
| \B | Caractère qui n'est pas début ou fin de mot |
| \cX | Caractère de contrôle ASCII (X étant une lettre) |
| \d | Chiffre |
| \D | Non chiffre |
| \e | Escape (ASCII 1B) |
| \f | Form-feed (ASCII 0C) |
| \G | Fin de la correspondance précédente |
| \h | Espace blanc horizontal [ \t\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000] |
| \H | Non espace blanc horizontal [^\h] |
| \n | Fin de ligne |
| \pL, \p{L}, \p{Letter} | Lettre (dans tout langage) |
| \r | Retour charriot |
| \R | Retour à la ligne, équivaut à \u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029] |
| \s | Caractères espace [ \t\n\x0B\f\r] |
| \S | Non caractères espace [^\s] |
| \t | Tabulation |
| \uxxxx | Caractère Unicode (4 chiffres hexadécimaux) |
| \v | Espace blanc vertical [\n\x0B\f\r\x85\u2028\u2029] |
| \V | Non espace blanc vertical [^\v] |
| \w | Caractère alphanumérique : lettre, chiffre ou underscore |
| \W | Caractère qui n'est pas lettre, chiffre ou underscore |
| \xxx | Caractère Unicode (2 chiffres hexadécimaux) |
| \x{xx...x} | Caractère Unicode (chiffres hexadécimaux) |
| \X | Caractère Unicode du groupe de graphèmes étendu |
| \z | Fin de chaîne |
Constructeurs spéciaux : Ces fonctions précèdent l'expression à laquelle elles s'appliquent, et le tout doit être placé entre parenthèses.
* ?: : groupe non capturant. Ignorer le groupe de capture lors de la numérotation des backreferences. Exemple : ((?:sous-chaine_non_renvoyée|autre).*).
    La présence d'un groupe capturant peut engendrer une allocation mémoire supplémentaire. Si une expression régulière particulièrement complexe provoque une erreur de mémoire, essayez de remplacer les groupes capturant non référencés et inutilisés par des groupes non-capturant en ajoutant ?: juste après la parenthèse ouvrante, et en décalant les numéros des groupes référencés.
* ?> : groupe non capturant indépendant.
* ?<= : positive lookbehind, vérifier (sans consommer) que ce qui précède correspond au motif spécifié. Exemple :
    Chercher une lettre u précédée d'une lettre q : (?<=q)u
* ?<! : negative lookbehind, vérifier (sans consommer) que ce qui précède ne correspond pas au motif spécifié.
* ?= : positive lookahead, vérifier (sans consommer) que ce qui suit correspond au motif spécifié.
* ?! : negative lookahead, vérifier (sans consommer) que ce qui suit ne correspond pas au motif spécifié. Exemples :
    Chercher une lettre q non suivie d'une lettre u : q(?!u)
    ((?!sous-chaine_exclue).)
    <(?!body).*> : pour avoir toutes les balises HTML sauf "body".
    début((?!mot_exclu).)*fin : pour rechercher tout ce qui ne contient pas un mot entre deux autres.
    (?!000|666) : pour exclure 000 et 666.
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.
    (?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 :
| 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

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

### preg_match

La fonction preg_match est la principale fonction de recherche. 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 :
* Exemple avancé :
Exemples de flags :
* 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

* Recherche des balises images HTML sans attribut "alt" : /(<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). Il faut alors utiliser preg_match_all (voir ci-dessous).

### preg_grep

Cette fonction recherche dans les tableaux.

### preg_match_all

Pour obtenir tous les résultats dans un tableau, remplacer preg_match par preg_match_all, 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 :

## Remplacement
### preg_replace

La fonction preg_replace comprend trois paramètres : remplacé, remplaçant, chaine à traiter.
Seul le dernier groupe de capture sera pris en compte.

### preg_filter

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

### preg_split

Décompose une chaine de caractères.

## Références

* ↑ 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., 22 mai 2009 (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
* ↑ http://php.net/manual/fr/function.preg-grep.php
* ↑ http://www.expreg.com/pregmatchall.php

# Concevoir du code de haute qualité
## Introduction

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 ?

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é

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é

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é

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 :
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

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 :
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)

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 :
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 :
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

Créez un fichier conf/config.php dans lequel vous mettez vos informations de connexion à la base de données :
Lorsque vous ferez une requête, vous la ferez de la manière suivante :

### La gestion des erreurs

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

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

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

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

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é

Parfois le code le plus court n'est pas le plus rapide d'exécution. En voici un exemple :
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 ?

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. En voici la raison : à l'intérieur des guillemets doubles, les variables sont interprétées et substituées correctement.
Exemple :
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
### Condition Yoda

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

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

Les voici résumées ici :
* 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

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

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-12 : guide de style étendu

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-18 : interface des clients HTTP
### SOLID

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

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, ou un tableau).

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

À 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

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

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

#### Xdebug's Profiler

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

#### XHProf

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

##### Installation
###### Linux

Dans php.ini :

###### Docker
##### Lancement

Modifier le .php à analyser, en précisant les flags à analyser parmi les trois disponibles :
Ensuite les data peuvent être lues via un fichier :

##### Affichage du résultat

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

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

### Choisir la condition sans négation

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

### Return early

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. 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 :
* Bien :

### Passage par référence des tableaux

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

Une condition est testée dans une boucle comme dans l'exemple ci-dessous.
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.

### Tests de charge

Plusieurs outils existent :
* Gatling (en Java)
* Postman (en JavaScript)
* Locust (en Python)

## Autres bonnes pratiques

* 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

* ↑ 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

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 :

## empty

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

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.

### Exemples

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 :
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.

### Recréer une exception ou une erreur

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 :
NB : la classe Throwable n'est pas instanciable directement.

## trigger_error

Cette fonction lance une exception du type placé en second paramètre, dont certains peuvent stopper l'exécution du programme. Par exemple :
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
### Affichage d'une trace lisible

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

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 :

#### throw

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

#### Exception::getTraceAsString

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

### Fichier de log maison

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 :

### Symfony

Depuis une commande Symfony, on peut afficher des logs en console :
Mais le mieux est d'utiliser la bibliothèque Monolog (compatible PSR-3), 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 (ce qui est fait par défaut sur la v3.3 dans config/packages/dev/monolog.yaml).
Pour booster ses performances, on peut le régler ainsi dans monolog.yaml :
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 :

## Références

* ↑ 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
### Linux

Installation sur Linux :
ou
Puis dans php.ini :
ou sur PHP8.2 via Apache :
Ajouter :

### Configuration complète

Exemple en V3 :
Exemple en V2
On peut aussi forcer d'autres paramètres :

### Docker

Avec Docker, il faut aussi spécifier le "remote_host".
Exemple d'installation et activation :

### Windows

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

## Lancement
### Dans un navigateur

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

### En ligne de commande

On peut ajouter un argument à PHP. Ex :

### Débogage pas à pas

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

Ajouter le mode au précédent :
Un ensemble de vidéos explicatives existent en anglais.

## Références

* ↑ « 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

Les mots qui suivent ont un sens spécial en PHP. Si vous les utilisez hors de leur contexte, des problèmes de confusions peuvent arriver.
Liste des mots spécifiques en PHP :
| | | | | Uniquement sous PHP 5 |
| and | or | xor | __FILE__ | exception |
| __LINE__ | array | as | break | final |
| case | class | const | continue | php_user_filter |
| declare | default | die | do | public |
| echo | else | elseif | empty | private |
| enddeclare | endfor | endforeach | endif | catch |
| endswitch | endwhile | eval | exit | try |
| extends | for | foreach | function | clone |
| global | if | include | include_once | implements |
| isset | list | new | print | interface |
| require | require_once | return | static | throw |
| switch | unset | use | var | protected |
| while | __FUNCTION__ | __CLASS__ | __METHOD__ | abstract |
| extends | cfunction* | old_function* | | yield |
| * : depuis PHP4 seulement |||||
Liste des 72 mots réservés par ordre alphabétique :

## Nouveautés PHP 7

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 un is_null.
* <=> : opérateur vaisseau spatial. Équivalent d'un switch à 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 :

## Extensions

Liste des 48 bibliothèques natives PHP 5.5.0 avec EasyPHP :
Voir aussi List of PHP extensions sur Wikipédia (en anglais) .

## Références

* ↑ http://php.net/manual/fr/language.generators.syntax.php
* ↑ http://www.php.net/manual/fr/reserved.keywords.php
* ↑ https://www.php.net/manual/fr/extensions.alphabetical.php