Michel Llibre - Mai 2025
Les informaticiens ont détourné le sens du mot interpolation dans le cas des chaines de caractères (strings). Pour eux ce mot signifie "remplacer une variable par ce qu'elle représente dans un string".
Ainsi en javasscript dans un string de gabarit (entre apostrophes inversées `):
`pi = ${Math.PI}`
la variable MATH.PI est interpolée en 3,1416.. et le string devient
`i = 3.1416`
En JS, dans les strings de gabarit les variables var situées entre les accolades dans ${var} sont remplacées par leur valeur. Cette interpolation ne se fait pas dans les strings entre guillemets doubles " ou entre apostrophes '.
Pour comprendre ce qui suit, il faut savoir qu'en PHP les noms des variables commencent par le caractère $ (sans rapport avec le $ de l'exemple précédent).
En PHP cette interpolation ne se fait pas dans les strings entre apostrophes ', mais elle se fait dans les strings entre guillemets double ". Ainsi, en supposant $x = "toto" et $y="titi", le string :
"blabla $x balbla '$y' blabla"
sera entièrement interpolé et produira le string :
"blabla toto blabla 'titi' blabla"
où les variables $x et $y sont remplacées par ce qu'elles représentent, même la variable $y qui est entre apostrophes '.
Exemples de code : https://www.geeksforgeeks.org/php-examples/
Référence du langage : https://www.php.net/manual/fr/langref.php
Cours et Manuel : http://www.lephpfacile.com/
Un document PHP est un document sauvegardé avec l'extension *.php qui comporte des instructions en PHP généralement destinées à générer du code html, mais qui peuvent aussi être des utilitaires purement php exécutés par l'interpréteur : C:\wamp64\bin\php\php7.3.21\php.exe
dans mon cas. tpot simplement par la commande suivante dans une "Invite de commandes" :
>php monscrip.php
Les instructions en php sont encadrées dans les balises :
<?php .... ?>
Elles peuvent apparaitre même à l'extérieur des balises html, mais là elle ne servent pas à générer du code html, mais à faire d'autres opérations. On peut tester et exécuter du code php pur dans un navigateur, sans rien d'autre. La commande echo écrivant ce qu'elle envoie dans la page.
Si le document est sauvegargé en *.html, le contenu entre les balises php sera ignoré.
La commande echo str; est utilisée pour écrire le contenu du string str sur le canal de sortie standard.
•dans le cas de PHP pur le contenu du string str est affiché dans la console comme un puts(str) en langage C.
•Si le code PHP se trouve dans un body HTML ou script JS, le contenu de str corresspond à du code HTML ou JS ajouté dans la page en remplacement du code PHP.
•Si le code PHP est extérieur à un body (éventuellement seul) et correspond à la réponse à une requête HTTP, le contenu de str est envoyé à destination du demandeur.
Le texte de str est un string entre guillemets simple ou double, au choix, mais les guillemets doubles " ont l'avantage de permettre l'interpolation des variables PHP.
Exemple :
<?php echo "let x = $x;"; ?>
Si $x = 1234, la ligne précédente écrite dans une section <script> sera remplacée par la ligne :
let x = 1234;
Un des grands intérêts de cette méthode est que les variables PHP contenus dans les strings à guillemets doubles sont interpolées et leur valeur peut être técupérées par javascript.
Attention : Ne pas oublier le ; à la fin d'une instruction JS envoyée par echo, (juste avant le guillement double " fermant) sinon le string sera collé au suivante qui sera envoyée par echo, sans espacement, virgule, point virgule, le dernier caractère étant accolé au premièr caractère suivant, ce qui rendra le tout incompréhensible.
Si le script est du pur PHP exécuté dans une invite de commande par >php monprog.php, on introduit les retours à la ligne normalement avec les caractères fin de ligne \n dans le string, mais si on l'exécute dans un navigateur les \n sont considérés comme des espaces. Dans ce cas il faut utiliser la balise html <br> dans le string.
Le langage PHP a d'énormes parentés avec le C/C++, mais grande différence : le nom des fonctions et mots clés n'est pas sensible à la casse ! strlen() === sTRLen()
[ANNOTATION: ON '2021-08-07T14:16:45.402000000' NOTE: '<!--StartFragment-->']Paramètres |
Similitudes |
Syntaxe |
|
Les opérateurs |
Tous les types d'opérateurs en C se comportent de la même manière en PHP, tels que les opérateurs arithmétiques de base, booléens (mais aussi AND, and, OR, or, XOR, xor qui ont une priorité encore plus faible que && et ||), d'affectation et de comparaison. |
Structure de contrôle |
|
Prototype de fonction |
Les fonctions définies par l'utilisateur sont nommées de la même manière dans les deux langues. |
Différences entre PHP et C
Paramètres |
PHP |
C |
Types de données |
PHP n'a que deux types de données numériques :
Les chaînes sont de longueur aléatoire et aucun type de données caractère n'est disponible en PHP. |
C a :
|
Conversion de type |
Les types de données ne sont pas vérifiés pendant la compilation et les erreurs de type de données sont rares. Les valeurs et les variables sont automatiquement converties dans le type de données approprié. |
En C, les variables sont déclarées avec des types de données. Alors que certains types de données ont une conversion implicite, d'autres doivent être transtypés. |
Tableaux |
Superficiellement, ils ressemblent aux tableaux de C.
|
|
Structure |
Aucun type de structure nécessaire en raison du tableau et des objets. |
Le type de structure existe en C. |
Prototype |
Il n'y a pas de prototype en PHP car la fonction n'a pas besoin d'être déclarée avant son implémentation. |
Il existe des prototypes en C car la fonction doit être déclarée avant son implémentation. |
Permissivité |
PHP est beaucoup plus clément par rapport à C. Plus que des erreurs, il y a des résultats inattendus générant de nouvelles erreurs. |
C donnera des erreurs si le type de données n'est pas déclaré, la taille du tableau n'est pas déclarée, etc. |
L'accès aux membres des classes se fait uniquement par -> car la variable instance est toujours considérée comme un pointeur. On verra que le . est utilisé comme opérateur de concaténation des chaines de caractères.
or semblable à || , mais de priorité plus faible, et de plus permet d'exécuter la deuxième instruction si la proposition précédente est fausse.
=== pour identique strict avant toute convesion dans le même type)
!== non identique
Par exemple "1" et "+1" sont == car convertis en nombre avant test, alors qu'ils ne sont pas identiques.
Boucle for : idem C/C++, mais aussi boucle foreach($groupe as $x) {.. travail sur $x..)
Switch-case : idem C/C++
Idem javascript
Importation de code :
include('fonctions.php'); ne vérifie pas les inclusions multiples, et qui est donc déconseillé d'utilisation.
include_once('fonctions.php'); idem mais évite les inclusions multiples.
Ces deux fonctions ne vérifient pas le succès de l'importation et continuent même si rien n'a été trouvé.
require('fonctions.php'); idem include mais vérifie le succès de l'imporation.
require_once('fonctions.php'); idem include_once et de plus vérifie le succès d'une imporation.
Dans du code HTML/JS, une zone PHP comportant un bloc classique entre { } qui suit un if, while, for, switch peut être divisé pour retourner à l'HTML/JS ou revenir au PHP. Pour cela on utilise un syntaxe proche du Python :
•en remplaçant l'accolade ouvrante du PHP { par un caractère :
•en remplaçant l'accolade fermante } du PHP par endqqchose
Entre les deux on peut revenir au code HTML/JS, ou remettre du code php entre <?php ?> ou pour remettre une variable $v PHP unique utiliser le raccourci <?= $v ?>
Exemple IF :
<?php if ($heure < 12) : ?>
<p>Bonjour !</p>
<?php else : ?>
<p>Bonsoir !</p>
<?php endif; ?>
Exemple WHILE :
<?php while ($compteur < 5) : ?>
<p>Compteur : <?= $compteur ?></p>
<?php $compteur++; ?>
<?php endwhile; ?>
Exemple FOR :
<?php for ($i = 0; $i < 3; $i++) : ?>
<p>i vaut <?= $i ?></p>
<?php endfor; ?>
Exemple SWITCH : (pas très approprié)
<?php switch ($jour) :
case 'lundi': ?>
<p>Début de la semaine.</p>
<?php break; ?>
<?php case 'vendredi': ?>
<p>Presque le week-end !</p>
<?php break; ?>
<?php default: ?>
<p>Un jour ordinaire.</p>
<?php endswitch; ?>
Exemple FOREACH :
<?php foreach ($stations as $station) : ?>
<option><?= htmlspecialchars($station['name']) ?></option>
<?php endforeach; ?>
__LINE__ : numéro de la ligne en cours dans le fichier php
__FILE__ : nom du fichier php
__DIR__ : nom du répertoire le contenant (=dirname(__FILE__))
La fonction get_defined_constants() fournit la liste de toutes les constantes définies et la fonction defined(nomCte) permet de savoir si une constante existe.
Le #define n'existe pas en PHP. A la place il existe une fonction define qui permet de définir une chaine constante, généralement écrite en majuscule sans $ en tête. Son nom est case-sensitive.
define("ROOT_LOCATION", "/usr/local/www");
Pas contre on peut créer un alias du nom de fonction comme ceci, à l'aide de la primitive use :
// Raccourci pour htmlspecialchars
use function htmlspecialchars as hs;
Mais cet opérateur use offre d'autres possibilités. PHP peut aliaser(/importer) avec l'opérateur use les constantes, fonctions, classes, interfaces, et les espaces de noms. Un alias est créé avec l'opérateur use.
En particulier :
use truc\machin\chose; // crée un raccourci chose pour truc\machin\chose
Dans une classe, mettre use maFonction ; ajoute maFonction aux méthodes de la classe.
Des constantes peuvent être définies par le mot clé const, mais seulement au plus haut niveau du contexte (pas dans les boucles, tests, fonctions, etc.
const Pi = 3.1415926535;
Ce mot clé const est également utilisé pour définir des constantes dans les classes.
Les variables sont des variants comme en javaScript, mais leur nom commence par la lettre $.
Deux variables peuvent référencer la même instance à l'aide de l'opérateur &.
$a = &$b;
Les types ne sont pas déclarés. Il y a 10 types de base :
bool : booléens : TRUE, true ou 1 et FALSE, false, NULL, null, "", 0, 0.00, [], ....
int : entiers
float (ou double) : réels, correspond au double du C++
string
array
callable : les pointeurs sur les fonctions (de rappel), appelées callback en C/C++
iterable
resource
NULL
La fonction gettype(var) retourne le nom commun (boolean par exemple et non pas bool) du type. Pour tester, utiliser les fonctions is_bool(v), is_int(v), etc... qui permettent de tester le type.
PHP fait automatiquement des conversions implicites vers le type attendu, mais on peut imposer des conversion avec l'opérateur de cast (type) $var qui transforme $var (si possible) vers le type type.
La fonction is_array($var) permet de savoir si une variable est un tableau.
La fonction count($c) donne le nombre d'éléments du tableau $c.
Il existe deux types de tableaux : les tableaux indexés et les tableaux associatifs.
Dans un tableau indexé $t on accède à ses éléments par u index entier $i : $t[$i]
Tableaux indexés
On le définit comme ceci :
$tableau = ["ab", "cd", "ef"];
ou un tableau plus hétéroclite comme ceci :
$c = array(1, false, "abcd", array(4,7));
ou le même sans la déclaration array préalable :
$c[] = 1; $c[] = false; $c[] = "abcd"; $c[] = array(4,7);
ce qui conduit au même résultat, car chaque affectation sans indice se comporte comme un append.
Pour plus de clarté, on peut signaler au préalable que $c est un tableau, comme ceci : $c = array();
On accède aux éléments par un indice entre crochets partant de 0 (zéro) et inférieur à count($c).
Dans les 2 cas précédents $c[2] fournit "abcd" , $c[2][1] donne 'b' et $c[3][1] fournit 7.
count($c) ou count($c,0) donne 4 (éléments de premier niveau) et count($c,1) donne 5 nombre total d'éléments.
On le définit comme ceci :
$adresse = array('nom' => 'DUPONT', 'prenom' => 'Mickaël', 'num' => 12,...);
ou bien sans aucune déclaration préalable :
$adresse['nom']='DUPONT';
$adresse['prenom']='Mickaël';
$adresse['num'] = 12;
$adresse['rue'] = 'rue des églantines';
$adresse['cp'] = 93000;
$adresse['ville'] = 'SAINT-DENIS';
bien qu'il soit judicieux de déclarer au préalable adresse = array();
Définition directe plus moderne :
$urls = ['stations.txt' => 'https://celestrak.org/NORAD/elements/stations.txt',
'amateur.txt' => 'https://celestrak.org/NORAD/elements/amateur.txt'];
On accède aux éléments par la clé.
echo $adresse['num'];
count($adresse) donne le nombre d'éléments du tableau, mais l'accès via un index produit une erreur : $adresse[0] => Undefined index.
Le parcours d'un tableau indexé peut se faire avec une boucle classique :
for ($i=0; $i < count($tab); $i++) { ... $tab[$i]...}
Parcours des deux types de tableau (indexé ou associatif) ave une boucle foreach :
foreach($tab as $item) {... $item ...} / Dans la boucle $item à la valeur de chaque item, l'un après l'autre.
Parcours d'un tableau associatif avec une boucle foreach
foreach($tab as $cle => $item) {... $item ...}
Dans la boucle $cle à la valeur de la cle et $item à la valeur de l'item, couple après couple.
Exemple de ce dernier cas :
$c['a']="aaa";
$c['b']="zzz";
foreach ($c as $i => $v) echo "item[$i] = $v<br>";
fournit :
item[a] = aaa
item[b] = zzz
list($a, $b, $c) = $tab; affecte aux variables $a, $b, $c les 3 premiers élément du tableau indexé $tab.
foutrnit lors de ses appels successifs toutes les paires (index, valeur) ou bien (clé, valeur) du tableau, sous forme d'un tableau à 2 éléments qui est généralement affecté à list(ix, val) ou list(cle, val). Exemple :
while(list($cle, $val) = each($tab)) echo "item[$cle] = $val<br>";
Le while s'arrête car each renvoie FALSE quand il n'y a plus d'éléments.
Cette fonction est obsolète et supprimée de PHP 8.0.0.
$encours = reset($tab); $first = reset($tab); La fonction reset renvoie l'élément encours, puis remet le pointeur sur le 1er élément du tableau, ce qui fait que l'appel suivant fournit le 1er élément.
$encours = end($tab); $last = end($tab); La fonction end renvoie l'élément encours, puis met le pointeur sur le dernier élément du tableau, ce qui fait que l'appel suivant fournit le dernier élément.
$tabAB = $tabA + $tabB;
ok = sort($tab [, type]) : tri $tab en place (passé par référence) avec type = SORT_NUMERIC (par défaut) ou SORT_STRING (tous les éléments doivent être des strings).
ok = rsort($tab [, type]) : tri en ordre inverse.
ok = shuffle($tab) : mélange les éléments.
$tab = explode(separ, chaine[, lim]) : génère un tableau à partir des éléments de la chaine séparés par la sous-chaine separ. Si lim est positif, le nombre d'éléments est limité à lim. S'il y en a davantage, le dernier contiendra tout ce qui reste. Si lim est négatif, -2 par exemple, les 2 derniers éléments ne seront pas pris en compte. lim=0 a le même effet que lim=1.
Exemple :
$b = explode('XY', "coucouXYtitiXYmerleXYhelloXYaaaXYbbbXYccc",-2);
print_r($b); echo "<br>";
donne :
Array ( [0] => coucou [1] => titi [2] => merle [3] => hello [4] => aaa )
$nv = extract($tab [, flag, $prefix]); A partir du tableau associatif $tab la fonction extract génère des variables ayant pour nom les clés et pour valeur, la valeur associée. Si une variable de même nom existe déjà, le comportement dépendra du flag :
•EXTR_OVERWRITE : la variable pré-existante est écrasée par la nouvelle
•EXTR_SKIP : la variable existante est conservée, la clé entrant en collision est ignorée
•EXTR_PREFIX_SAME : la variable existante est conservée et pour la clé entrant en collision une nouvelle varible est crée avec pour nom la clé précédée de la chaine $prefix donnée.
•EXTR-PREFIX_ALL : Crée des noms ayant tous la chaine $prefix avant la clé. Permet de faire un extract de tableaux indexés.
•EXTR_PREFIX_INVALID. Utilise la chaine $prefix uniquement pour des noms invalides ou numériques. Permet de faire un extract de tableaux indexés.
•EXTR_IF_EXIST
•EXTR_REFS : extrait les variables sous forme de références. Cette option peut être utilisée avec les autres avec l'opérateur OR.
Pour EXTR_PREFIX_SAME, EXTR_PREFIX_ALL, EXTR_PREFIX_INVALID et EXTR_PREFIX_IF_EXISTS, la variable créée sera préfixée de la chaine $prefix et du caracère "_".
Exemple :
$taille = "large";
$var_array = array("couleur" => "bleu",
"taille" => "étroit",
"forme" => "sphere");
$n = extract($var_array, EXTR_PREFIX_SAME, "nlle");
echo "extract a converti $n éléments<br>";
echo "$couleur, $nlle_taille, $forme, $taille";
Résultat :
extract a converti 3 éléments
bleu, étroit, sphere, large
Cette fonction est intéressante pour générer automatiquement les variables associées aux tableaux $_GET, $POST, $_FILES..., mais faire attention à ne pas écraser des variables existantes, car ces tableaux sont susceptibles d'être piratés. Il vaut donc mieux dans ce cas utiliser EXTR-PREFIX_ALL afin de maitriser les noms des variables créés.
$tabass = compact("clé1", array("clé2", "clé3"), "clé4",...); A partir de la liste de clés en argument, données une par une ou en tableau, ou un mixage des deux, qui doivent toutes correspondre à des variables existantes ayant une valeur, la fonction compact crée un tableau associatif associant ces clés à leur valeur.
Exemple :
$taille = "un"; $couleur = "bleu"; $forme = "sphere"; $autre = "deux";
$tabcle = array("couleur", "forme");
$tabass = compact("taille", $tabcle, "autre");
print_r($tabass);
Résultat :
Array ( [taille] => un, [couleur] => bleu, [forme] => sphere, [autre] => deux )
Remarquer que les nom des variables dans la liste des arguments ne sont pas préfixés par le $, car c'est bien leur nom qui est l'argument et non pas leur valeur.
Cette fonction est utilisée en debogage pour imprimer rapidement les valeurs d'une liste de variables, comme ceci :
$var1 = "un"; $var2 = "bleu"; $var3 = "sphere"; $var4 = "deux";
//print_r(compact("var1", "var2", "var3", "var4"));
//print_r(compact(Array("var1", "var2", "var3", "var4")));
print_r(compact(explode(",","var1,var2,var3,var4")));
Les print_r commentés sont aussi valides, mais moins intéressants si on a un grand nombre de variables. On peut utiliser une autre chaine comme séparateur, mais la virgule est bien pratique, seulement attention à ne pas mettre d'espace après la virgule car le caractère espace serait intégré au nom de la clé et la variable ne serait pas reconnue.
On peut accéder aux caractères individuels d'une chaine de caractère $str par son index entre crochet comme pour un tableau de caractère $str[$i], mais on ne peut pas le modifier car les string sont immuables. Il faut créer une nouvelle chaine pour effectuer des modifications.
Concaténation de chaines : opérateur "." (point) :
$x = "toto".'titi';
•Dans une chaine litérale à apostrophe verticale ' $x n'est pas interpolé et est traité comme le mot $x,
•dans une chaine à guillemets " $x est interpolé : remplacé par sa valeur s'il en a une.
Ainsi si $x = 5 :
•'1 + $x' => 1 + $x
•"1 + $x" => 1 + 5,
ce qui est plus facile à coder que '1 + '.$x qui donnerait la même chose.
Résumé :
- 'chaine entre apostrophes' c'est vraiment du littéral, tout est reproduit tel que sauf \ et ' que l'on peut obtenir par \\ et \'.
- "chaine entre guillemets" c'est à moitié littéral car \n, \t, .... $x... sont remplacés par leur valeur newline, tabulation, ..., valeur de $x ...
De plus les retours à la ligne effectifs :
"aaa
bbb"
sont respectés en interprétation invite de commande, mais ignorés dans un navigateur ou on obtiendra :
"aaa bbb".
Les " sont affichés dans un string entre ' et les ' sont affichés dans un string entre ", sinon il faut les échapper avec \.
En PHP, il y a conversion automatique des nombres vers les chaines et inversement selon le contexte d'utilisation. Mais FALSE est converti en un string vide. :
<?php
$a = TRUE; $b = FALSE;
echo "FAUX = $b et VRAI = $a<br>"
donnera : FAUX = et VRAI = 1
Une construction spéciale permet de définir une chaine interpolée sur plusieurs lignes, comme si on concaténait des chaines de guillemets doubles ", mais dans laquelle on peut utiliser ces guillemets doubles sans les échapper, comme les simples.
Dans cette chaine les constructions spéciales seront interpolées :
•les variables $x, ... sont systématiquement remplacées par leurs valeurs,
•\x41 remplacé par la lettre A, etc...
La chaine littérale est insérée entre les constructions <<<XYZ et XYZ;. ou XYZ est un nom quelconque qui suit les règles habituelles des identificateurs. Il va servir de repère de début et fin.
Le repère de début <<<XYZ doit être directement suivie par un retour à la ligne effectif et le repère final XYZ doit être en tout début d'une nouvelle ligne et suivi du ; qui cloture l'instruction, comme ceci :
<?php
$auteur = "Michel";
$out = <<<_MYVERBATIM
L'hiver il fait "très froid".
L'été il fait "très chaud".
- $auteur.
_MYVERBATIM;
echo $out;
?>
ou ici XYZ est la chaine _MYVERBATIM. On obtiendra :
L'hiver il fait "très froid". L'été il fait "très chaud". - Michel.
Bien entendu la chaine XYZ ne doit pas être dans le texte enserré entre les deux marqueurs. Cela facilite l'insertion d'un nombre important de lignes HTML dans du PHP sans se préoccuper des apostrophes et guillemets.
Depuis php 7.3 le repère de fin peut être indenté, mais à la plus faible valeur d'indentation du bloc.
Dans la chaine Heredoc les variables PHP sont remplacées par leur valeur . Pour l'éviter il faut protéger le $, comme dans cet exemple, à l'avant dernière ligne :
<?php
$F = __FILE__;
$D = __DIR__;
$D1 = dirname(__FILE__);
$D2 = dirname($D);
$L = __LINE__;
echo <<<M_L_L
Ceci est la ligne __LINE__ du fichier __FILE__<br>
Ceci est la ligne $L du fichier $F<br>
du répertoire $D<br>
Remarques :<br>
dirname("__FILE__") -> $D1<br>
dirname("\$D") -> $D2<br>
M_L_L;
?>
dont la sortie est :
Ceci est la ligne __LINE__ du fichier __FILE__
Ceci est la ligne 6 du fichier D:\MesProgs_Langages\html&javascript&PHP\encours\constantes.php
du répertoire D:\MesProgs_Langages\html&javascript&PHP\encours
Remarques :
dirname("__FILE__") -> D:\MesProgs_Langages\html&javascript&PHP\encours
dirname("$D") -> D:\MesProgs_Langages\html&javascript&PHP
On remarquera que pour obtenir dirname("$D") à la dernière ligne, on a mis dirname("\$D") dans le string multiligne.
C'est le pendant de Heredoc sans interpolation.
La construction est identique sauf que le repère repère d'entrée 'XYZ' est entre apostrophes ' (pas celui de fin). Cette construction génère une chaine multiligne sans interpoler les variables PHP et en traitant le caractère d'échappement \ et les guillemets simples ou doubles comme des carcatères ordinaires. Voici un exemple avec les 2 constructions et le résultat :
<html><head></head><body><?php
$a = "Coucou";
$b = <<<DOCHERE
$a "Michel". Comment vas-tu<br>
Entre les parenthèses (\x41) il 'devrait' y avoir la lettre A !<br><br>
DOCHERE;
$c = <<<'DOCNEW'
$a 'Michel'. Comment vas-tu<br>
Entre les parenthèses (\x41) 'il y a' le code de la lettre A !<br>
DOCNEW;
echo $b;
echo $c;
?></body></html>
Résultat :
Coucou "Michel". Comment vas-tu
Entre les parenthèses (A) il 'devrait' y avoir la lettre A !
$a 'Michel'. Comment vas-tu
Entre les parenthèses (\x41) 'il y a' le code de la lettre A !
Dans les éditeurs à coloration syntaxique la partie interne aux contructions HereDoc et NewDoc est vue comme une simple chaine de caractère. Elle n'est donc pas analysée en tant que code HTML, ce qui est assez génant si elle est assez importante. Pour vérifier cette partie on peut l'écrire dans un fichier chaine.html qui sera bien analysé par notre éditeur favori, et dans le cas d'une partie Newdoc, récupérer ce texte en entier dans le PHP, en l'affectant à une variable :
$partie_html = file_get_contents('chaine.html');
puis faire l'echo de cette chaine : echo $partie_html;
Dans le cas d'une partie Heredoc, cela ne marche pas car les variables PHP contenues dans le fichier chaine.html ne seront pas reconnues : Exemple :
<?php
$x = 1;
echo "x = $x\n";
$s = file_get_contents("test.html");
echo $s;
?>
et le fichier test.html contient :
<p>x = $x</p>
Dans une invite de commande on obtient :
x = 1
<p>x = $x</p>
Pour éviter cela , il est préférable on remettra le fichier vérifié dans sa section Heredoc.
$GLOBALS tableau de toutes les variables gloables definies. On accède à leur valeur en utilisant leur nom comme clé.
$_SERVER informations (éventuellement) fournies par le serveur.
$_GET variables passée au script par un HTTP GET
$_POST variables passée au script par un HTTP POST
$_FILES Tableau des éléments chargés dans le script par un HTTP POST généralement généré par un <input type="file"...
$_COOKIE Variables transmises au script par un cookies
$_SESSION Variables de session
$_REQUEST Contenu des info envoyées par le navigateur (duplique $_GET, $_POST et $_COOKIE)
$_ENV La variable d'environnement.
Variable superglobale $_SERVER
Script d'affichage de la table $_SERVER :
<?php
$indices = array(
'PHP_SELF',
'argv',
'argc',
'GATEWAY_INTERFACE',
'SERVER_ADDR',
'SERVER_NAME',
'SERVER_SOFTWARE',
'SERVER_PROTOCOL',
'REQUEST_METHOD',
'REQUEST_TIME',
'REQUEST_TIME_FLOAT',
'QUERY_STRING',
'DOCUMENT_ROOT',
'HTTP_ACCEPT',
'HTTP_ACCEPT_CHARSET',
'HTTP_ACCEPT_ENCODING',
'HTTP_ACCEPT_LANGUAGE',
'HTTP_CONNECTION',
'HTTP_HOST',
'HTTP_REFERER',
'HTTP_USER_AGENT',
'HTTPS',
'REMOTE_ADDR',
'REMOTE_HOST',
'REMOTE_PORT',
'REMOTE_USER',
'REDIRECT_REMOTE_USER',
'SCRIPT_FILENAME',
'SERVER_ADMIN',
'SERVER_PORT',
'SERVER_SIGNATURE',
'PATH_TRANSLATED',
'SCRIPT_NAME',
'REQUEST_URI',
'PHP_AUTH_DIGEST',
'PHP_AUTH_USER',
'PHP_AUTH_PW',
'AUTH_TYPE',
'PATH_INFO',
'ORIG_PATH_INFO') ;
echo "<b>Liste table SERVER nominale :</b><br> ";
foreach ($indices as $arg)
{
if (isset($_SERVER[$arg])) echo "$arg = $_SERVER[$arg]<br>" ;
else echo "$arg= ?<br>" ;
}
echo "<br><br><b>Liste additionnelle</b><br>";
foreach ($_SERVER as $parm => $value)
{
$out = true;
foreach ($indices as $arg)
if($arg == $parm) {$out = false; break;}
if ($out) echo "$parm = $value<br>";
}
?>
Résultat WAMPSERVER :
Liste table SERVER nominale :
PHP_SELF = /PHP/global_server.php
argv= ?
argc= ?
GATEWAY_INTERFACE = CGI/1.1
SERVER_ADDR = ::1
SERVER_NAME = mestests
SERVER_SOFTWARE = Apache/2.4.46 (Win64) PHP/7.3.21
SERVER_PROTOCOL = HTTP/1.1
REQUEST_METHOD = GET
REQUEST_TIME = 1645468159
REQUEST_TIME_FLOAT = 1645468159.8296
QUERY_STRING =
DOCUMENT_ROOT = D:/MesProgs_Langages/htmJavascriptPHP
HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
HTTP_ACCEPT_CHARSET= ?
HTTP_ACCEPT_ENCODING = gzip, deflate
HTTP_ACCEPT_LANGUAGE = fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
HTTP_CONNECTION = keep-alive
HTTP_HOST = mestests
HTTP_REFERER = http://mestests/PHP/
HTTP_USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
HTTPS= ?
REMOTE_ADDR = ::1
REMOTE_HOST= ?
REMOTE_PORT = 59436
REMOTE_USER= ?
REDIRECT_REMOTE_USER= ?
SCRIPT_FILENAME = D:/MesProgs_Langages/htmJavascriptPHP/PHP/global_server.php
SERVER_ADMIN = wampserver@wampserver.invalid
SERVER_PORT = 80
SERVER_SIGNATURE =
Apache/2.4.46 (Win64) PHP/7.3.21 Server at mestests Port 80
PATH_TRANSLATED= ?
SCRIPT_NAME = /PHP/global_server.php
REQUEST_URI = /PHP/global_server.php
PHP_AUTH_DIGEST= ?
PHP_AUTH_USER= ?
PHP_AUTH_PW= ?
AUTH_TYPE= ?
PATH_INFO= ?
ORIG_PATH_INFO= ?
Liste additionnelle (variables environnement locales, a priori)
HTTP_UPGRADE_INSECURE_REQUESTS = 1
HTTP_CACHE_CONTROL = max-age=0
PATH = C:\Program Files\Eclipse Foundation\jdk-8.0.302.8-hotspot\bin;C:\ImageMagick-7.1.0-Q16-HDRI;C:\Program Files\Java\Java3D\1.5.1\bin;C:\Program Files (x86)\Common Files\Oracle\Java\javapath;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\Windows\System32\OpenSSH\;C:\Program Files (x86)\NVIDIA Corporation\PhysX\Common;C:\Program Files (x86)\7-Zip;C:\Python\Python38-32;C:\Python\Python38-32\Scripts;D:\Applis\LyX 2.3\Perl\bin;C:\Program Files\PuTTY\;C:\Program Files\Microsoft SQL Server\130\Tools\Binn\;C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\;C:\Program Files\Git\cmd;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\WINDOWS\System32\OpenSSH\;D:\MesProgs_Langages\opencv\build\x64\vc15\bin;C:\Program Files\OpenSSL-Win64\bin;C:\ProgramData\chocolatey\bin;C:\Program Files\dotnet\;C:\Program Files\NVIDIA Corporation\NVIDIA NvDLISR;C:\WINDOWS\system32\config\systemprofile\AppData\Local\Microsoft\WindowsApps
SystemRoot = C:\WINDOWS
COMSPEC = C:\WINDOWS\system32\cmd.exe
PATHEXT = .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
WINDIR = C:\WINDOWS
REQUEST_SCHEME = http
CONTEXT_PREFIX =
CONTEXT_DOCUMENT_ROOT = D:/MesProgs_Langages/htmJavascriptPHP
RESULTAT SERVEUR ONO&ONE
Liste table SERVER nominale :
PHP_SELF = /affichage_server.php
argv = Array
argc = 0
GATEWAY_INTERFACE = CGI/1.1
SERVER_ADDR = 82.165.84.181
SERVER_NAME = llibre.fr
SERVER_SOFTWARE = Apache
SERVER_PROTOCOL = HTTP/1.1
REQUEST_METHOD = GET
REQUEST_TIME = 1645468577
REQUEST_TIME_FLOAT = 1645468577.3203
QUERY_STRING =
DOCUMENT_ROOT = /kunden/homepages/43/d719179520/htdocs
HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
HTTP_ACCEPT_CHARSET= ?
HTTP_ACCEPT_ENCODING = gzip, deflate, br
HTTP_ACCEPT_LANGUAGE = fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
HTTP_CONNECTION= ?
HTTP_HOST = www.llibre.fr
HTTP_REFERER= ?
HTTP_USER_AGENT = Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:97.0) Gecko/20100101 Firefox/97.0
HTTPS = on
REMOTE_ADDR = 2a01:cb19:8263:3b00:4058:309d:6a6f:bc2d
REMOTE_HOST= ?
REMOTE_PORT = 10812
REMOTE_USER= ?
REDIRECT_REMOTE_USER= ?
SCRIPT_FILENAME = /kunden/homepages/43/d719179520/htdocs/affichage_server.php
SERVER_ADMIN = webmaster@llibre.fr
SERVER_PORT = 443
SERVER_SIGNATURE =
PATH_TRANSLATED= ?
SCRIPT_NAME = /affichage_server.php
REQUEST_URI = /affichage_server.php
PHP_AUTH_DIGEST= ?
PHP_AUTH_USER= ?
PHP_AUTH_PW= ?
AUTH_TYPE= ?
PATH_INFO= ?
ORIG_PATH_INFO = /affichage_server.php
Liste additionnelle
REDIRECT_UNIQUE_ID = YhPboYJ2fZ0RwnES0BKvQgAAAAQ
REDIRECT_DOCUMENT_ROOT = /kunden/homepages/43/d719179520/htdocs
REDIRECT_HTTPS = on
REDIRECT_HANDLER = x-mapp-php5.5
REDIRECT_STATUS = 200
UNIQUE_ID = YhPboYJ2fZ0RwnES0BKvQgAAAAQ
HTTP_UPGRADE_INSECURE_REQUESTS = 1
HTTP_SEC_FETCH_DEST = document
HTTP_SEC_FETCH_MODE = navigate
HTTP_SEC_FETCH_SITE = none
HTTP_SEC_FETCH_USER = ?1
PATH = /bin:/usr/bin
REQUEST_SCHEME = https
CONTEXT_PREFIX = /system-bin/
CONTEXT_DOCUMENT_ROOT = /kunden/usr/lib/cgi-bin/
REDIRECT_URL = /affichage_server.php
STATUS = 200
ORIG_PATH_TRANSLATED = /kunden/homepages/43/d719179520/htdocs/affichage_server.php
On crée une classe avec ses propriétés et méthodes à l'intérieur comme en java :
class Utilisateur
{
const VERSION = 2;
public $nom, $passw;
function __contruct($par1, $par2) {... } // Constructeur
function enregistrer() {....}
static function version (){ echo self::VERSION; }
function __destruct() {...} // Destructeur
}
Propriétés constantes const : On peut définir des propriétés constantes (sans le $ devant), obligatoirement initialisées et non modifiables, avec le mot clé const.
Mot clé $this : Dans les méthodes $this est utilisable pour accéder à l'instance en cours, et ainsi accéder à ses propriétés (sans le $ devant leur nom) et ses méthodes.
Mot clé static : Il permet de créer des fonctions indépendantes des instances, directement accessible avec le nom de la classe suivie de "::". Exemple : Utilisateur::version();
Mot clé self : Il permet d'accéder aux constantes. et aux membres statiques.
Création d'instances : On crée un nouvel objet de cette classe avec new :
$a = new Utilisateur;
$b = new Utilisateur('Toto','sesame');
Accès aux propiétés et méthodes : Les propriétés et les méthodes s'appellent avec -> entre le nom de l'instance et leur nom, comme pour un pointeur (en effet le . est utilisé pour concatener les strings) :
$a->nom; $b->enregister();
La portée des propriétés et méthodes est définie par les mots clés public (par défaut, synonyme de var), protected et private :
•protected : accessibles uniquement dans la classe ET sous-classes.
•private : accessible uniquement dans la classe.
Dans une classe, mettre use maFonction ; ajoute maFonction aux méthodes de la classe, en supposant que maFonction existe et peut être exécutée.
Exemple :
<?php
$un = new Animal("chien", 4);
if ($un->peut_courir()) echo "Un ", $un->nom, " peut courir !";
else echo "Un ", $un->nom, " ne peut pas courir !";
class Animal {
public $nom, $pattes;
function __construct($n, $p){$this->nom = $n; $this->pattes = $p;}
function peut_courir() { return ($this->pattes >= 2);}
}
?>
Dans le cas des objets, l'affectation se fait par référence : $c = $a;. Les deux références pointent le même objet.
clone permet de créer un vrai nouvel objet identique au premier.
$d = clone $a;
Pratiques bizarres :
•Les classes peuvent être définies après leur utilisation, c'est-à-dire class Toto{} peut être placé après un $t = new Toto;
•Un membre propriété d'un objet (mais pas de sa classe) peut être défini implicitement par son utilisation : $t->force = "grande";
Héritage : Une sous-classe fait référence à sa mère par le mot clé extends
class Abonne extends Utilisateur {... }
Si une sous-classe surcharge une méthode, elle peut accéder à la méthode de la classe parente à l'aide du mot clé parent, comme ceci : parent::methode();
Par ailleurs, le constructeur du parent n'est pas automatiquement appelé dans le constructeur enfant. pour ce faire, utiliser le mot clé parent. Exemple :
class Abonne extends Utilisateur
{
function __contruct($par1,$par2,$par3){parent::__construct($par1,$par2);...}
...
}
Elles sont définies comme en javascript.
function multiply($num1,$num2) {
result = $num1 * $num2;
return $result;
}
Comme en C++ on peut définir des valeurs par défauts à certains arguments (à placer après ceux qui n'en ont pas et qui doivent obligatoirement être présents).
Pour les arguments en nombre variable la construction ...$tabargs est préférable à la technique C++ également acceptée. Exemple :
<?php
function sum(...$numbers) {
$acc = 0; foreach ($numbers as $n) {$acc += $n;}
return $acc;
}
echo sum(1, 2, 3, 4);
?>
Autre exemple :
<?php
function add($a, $b) {return $a + $b;}
echo add(...[1, 2])."<br>";
$a = [3, 4];
echo add(...$a);
?>
Un argument $a est normalement passé par copie. Si on le modifie à l'intérieur de la fonction, il n'est pas modifié à l'extérieur.
On peut le passer par référence avec le péfixe &, comme ceci &$a. Dans ce cas il sera également modifié à l'extérieur.
Si une fonction attend une référence, on ne peut lui passer en argument qu'une variable dont elle prendra la référence ou une fonction qui renvoie une référence.
Pour qu'une fonction renvoie une référence, il faut faire précéder son nom du préfixe &.
Exemple :
<?php
// Fonction retournant une référence
function &myabs($a) {$x = abs($a); return $x;}
// Fonction prenant en argument des référence
function somme(&$a, &$b) { return $a + $b;}
// Test
echo somme(myabs(-5), myabs(2)); // Renvoie bien 7
?>
A partir de PHP 8.0 on peut passer les arguments en ordre quelconque, avec pour chaque argument la syntaxe nomarg:valeur, où nomarg n'est pas préfixé par le $. On peut placer de manière classique les arguments obligatoires, puis de manière nommée les arguments optionnels que l'on fournis.
Par défaut toutes les variables sont de portée locale. Une fonction ne connait pas les variables de code principal et réciproquement, mais, à l'intérieur du corps principal, on peut déclarer une variable par le mot clé global, ce qui lui donné une portée globale (même dans les fonctions) :
global $myname;
Une variable non globale, peut être accédée dans une fonction, si elle est déclarée loocalement globale dans cette fonction, également par le mot clé global.
Pour qu'une variable à l'intérieur d'une fonction conserve sa valeur d'un appel sur l'autre (pour faire un compteur d'appel par exemple), il faut la déclarer static :
static $compteur = 0; // initialisé par valeur prédéfinie uniquement
...
$compteur++;
phpversion(); // Renvoi le n° de la version php
unset($v) : delete la variable
trim($str) : supprime les caractères espaces " ", tab "\t", retour ligne "\n", retour chariot "\r", NULL "\0" et tab verticale "\v" de début et fin de la chaine,
ltrim($str) uniquement ceux du début de la chaine,
rtrim($str) uniquement ceux de la fin de la chaine.
Avec un deuxième argument on peut spécifier d'autres caractères à éliminer :
rtrim($str,"/\\") pour supprimer aussi les / et les \.
echo : Nous avons déjà vu la construction echo qui permet d'insérer un string dans la page html. echo peut prendre plusieurs arguments séparés par des virgules, qui sont tous sortis l'un après l'autre.
print : La construction print permet exactement la même chose, mais elle ne prend qu'un seul argument (la fin de la ligne jusqu'au point-virgule) et de plus, comme une fonction, elle renvoie un résultat, toujours la valeur 1.
print_r : La commande print_r($toto) (_r pour human readable) va insérer dans la page html un description la plus compréhensible et la plus complète possible de la variable $toto, mais comme l'html ignore les sauts de lignes, le texte est toujours très dense.
•Très utile pour voir les propriétés d'un objet quelconque.
•Très utile car peut être utilisé pour un tableau quelconque non défini.
var_dump($v) : écrit le type et le contenu de la variable
printf envoie le résultat directement dans la page. Pour que le formatage soit bien pris en compte (retour ligne, espaces supplémentaires...) il y a intérêt à entourer le printf de echo "<pre>"; et echo "</pre>";
printf et sprintf s'utilisent comme en C/C++. Les spécifications de formatage sont assez proches. Pour son formatage un nombre est remplacé par %a dans la chaine de formatage où a est le spécificateur figurant dans la 1ère colonne du tableau ci-après :
La précision est indiquée comme en C/C++ sous la forme %10.3f où 10 est le nombre total de caractères utilisés et 3 le nombre de décimales. De plus 10 peut être précédé d'un caractère précisant le pré-remplissage 0 ou '# (# ou tout autre caractère) :
Pour les chaines de caractères, on peut également préciser des justifications assez élaborées. par exemple avec $x = "Rasmus" on obtient les sorties suivantes (< et > ajoutés pour préciser la justification):
%s |
<Rasmus> |
normal |
%12s |
< Rasmus> |
6 espaces avant |
%-12s |
<Rasmus > |
6 espaces après: justifié à gauche. |
%012ss |
<000000Rasmus> |
6 zéros avant |
%'#12s |
<######Rasmus> |
6 dièses avant (dièse ou n'importe quel autre caractère) |
On peut faire des justifications de chaine avec coupure. Supposons $x = "Rasmus Lerdrorf". On peut obtenir :
%12.8s |
< Rasmus L> |
8 caractères pris en compte placés dans 12, justifiés à droite |
%-12.12s |
<Rasmus Lerdo> |
12 caractères pris en compte placés dans 12, justifiés à gauche |
%-'@12.10s |
<Rasmus Ler@@> |
10 caractères pris en compte, placés dans 12 justifiées à gauche, complétés par des @ (ou n'importe quel autre caractère) |
En résumé :
•"-" après % => justification à gauche
•0 après % => remplissage par 0 en tête
•'X après % ou après "-" => remplissage par des X
•.N après le nombre de places donne le nombre de chiffres après le point (format f) ou le nombre de caractères pris dans la chaine (format s).
La fonction ts = time() fournit le temps Unix : nombre de secondes écoulées depuis le 1er janvier 1970 à 00h00, appelé timestamp.
La fonction ts = mktime(h, m, s, mois, jour, annee) avec 0 ≤ h ≤ 23, 0 ≤ m ≤59, 0 ≤ s ≤59, 1 ≤ mois ≤ 12, 1 ≤ jour ≤ 31 et 1970 ≤ annee ≤ 2038 génère le timestamp correspondant.
La fonction str = date($format, $timestamp) génère la date correspondante à la valeur $timestamp sous forme d'une chaine compréhensible que l'on agence à l'aide de l'argument $format. Les éléments heure, jour, mois,... sont présentés selon les spécificateurs suivants :
Exemple :
$ts = mktime(3,15,0,3,30,1973);
$tabjours = Array("dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi");
$js = $tabjours[date("w",$ts)];
$str = date("d/m/Y à G:i", $ts);
echo "Florence est née le $js $str";
donne pour résultat :
Florence est née le vendredi 30/03/1973 à 3:15 .
function_exist("array_combine"): // TRUE si la fonction array_combine(..) existe
isset($v) : true si $v est défini
is_array($v)
is_bool($v)
is_numeric($v)
is_null($v) : true
•int, float : si $v == 0
•string : si $v == "" ou "0" ou "0.0"
•boolean : si $v = false
•array : si $v est vide
Bien que certaines conversions sont effectuées automatiquement par défaut, en les forçant à l'aide de fonctions de conversions spécifiques on évite que du code malveillant soit exécuté.
intval($a) -> entier permet de s'assurer que la valeur traitée est bien un entier. La fonction inverse, utile dans certains cas est strval($a) -> string
$line = iconv("CP850","UTF-8", $line);
Convertit le string $line de la page de code 850 (CP850) en UTF-8. Utile pour incorporer les sorties de DOS.
$output = mb_convert_encoding($input, "UTF-8", "CP850");
Idem, mais plus puissant : $input peut être un string ou un tableau de strings. Le code d'origine (dernier argument ici) peut être omis. Dans ce cas les caractères non ASCII seront remplacés par des ?.
PHP offfre 2 fonctions utiles pour crypter une chaine de caractère et vérifier la correspondance :
L'instruction $hash = password_hash($text, PASSWORD_DEFAULT);
transforme la chaine $text = "coucou" en chaine $hash = "$2y$10$ujlscZZkYeDiDaiJr2qP5OtFX35WklXO1eLoZX.sBemH7z9ZvaySa",
L'instruction password_verify($text, $hash) renvoie true ou false suivant que le couple correspond ou non.
Les fonctions exec, shell_exec, system sont utilisés pour faire exécuter des commandes, données en argument ($cde) par le système. La fonction escapeshellcmd(string $command): string est utilisée pour éviter que les caractères spéciaux des shell soit échappés.
Exemple shell_exec :
<?php // On fait un dir du répertoire contenant ce fichier php.
$cmd = "dir";
$output = shell_exec($cmd);
$output = mb_convert_encoding($output, "UTF-8", "CP850");
echo "<pre>";
echo $output;
echo "</pre>";
?>
shell_exec retourne null si pas de sortie ou erreur, ce qui pose un pb pour les commandes sans sortie, car on ne sait pas si elles ont réussies. Pour récupérer le code de sortie, il faut utiliser exec.
Remarque : Version courte, mais alambiquée de shell_exec avec des guillemets inverse ˋ ˋ
$output = shell_exec($cmd); <==> $output = `$cmd`;
Exemple exec
<?php // On fait un dir du répertoire contenant ce fichier php.
$cmd = "dir";
exec(escapeshellcmd($cmd), $output, $status);
$output = mb_convert_encoding($output, "UTF-8", "CP850");
if ($status) echo "Échec de la commande exec";
else
{
echo "<pre>";
foreach($output as $line)
{
echo htmlspecialchars("$line\n");
}
echo "</pre>";
}
?>
La commande system peut également être utilisée, mais sa sortie est plus délicate à exploiter :
$output = system(escapeshellcmd($cmd), $status);
base64_encode() |
Encode une chaîne en base64 (pour transmettre des binaires comme des images). |
base64_decode() |
Décode une chaîne base64. |
serialize() |
Convertit un tableau/objet PHP en chaîne (pour stockage, moins utilisé que JSON). |
unserialize() |
Reconstruit un tableau/objet PHP depuis une chaîne serialisée. |
urlencode() |
Encode une chaîne pour une URL (espaces → %20, etc.). |
rawurlencode() |
Version plus stricte (encode aussi !, ', (). |
urldecode() |
Décode une chaîne encodée pour URL. |
Pour vérifier qu'une variable string acquise ne contient pas une url (avec ://) on pourra faire :
if (strpos($variable, '://') !== FALSE || strpos($variable, '../') !== FALSE)
die('Illegal string');
mais c'est assez lour et peu efficace. On utiilise surtout les fonctions suivantes pour sécuriser des données provenant provenant de l'extérieur : htmlspecialchars, htmlentities et strip-tags.
Deux utilitaires basiques, à tout faire :
$b = addslashes($a) ajoute des \ devant les caractères risquant de poser des problèmes :
"l'été" => "l\'été"
$b stripslashes($a) enlève les \ qui protègent des caractères :
"l\'été" => "l'été"
Cette méthode supprime les balises HTML d'une chaine, sans remplacement. C'est une protection anti-XSSqui vient de cross-site scriptint qui consiste à faire exécuter du code malveillant dans une page).
strip_tags renvoie un string ou toutes les balises sont supprimées. Seul le texte est conservé. En fournissant un deuxième argument, on peut préciser des balises qui sont à conserver dans le texte. Par exemple, si on veut conserver les balises <p> et <a> on fera : strip_tags($text, '<p><a>') ou bien strip_tags($text, ['p', 'a']). Exemple :
<?php
$str = "<p>Coucou Michel</p>";
echo $str."\n";
echo strip_tags($str)."\n";
echo strip_tags($str,"<p>")."\n";
?>
Sortie invite de commande :
<p>Coucou Michel</p>
Coucou Michel
<p>Coucou Michel</p>
Cette fonction transforme les caractères HTML (<, >, ", ', &) pour qu'ils perdent leur signification d'élément de balisage, tout en conservant leur valeur textuelle en HTML.
Elle remplace :
•& par &
•" par " sauf si $flags = ENT_NOQUOTES
•' par ' si $flags = ENT_QUOTES
•< par <
•> par >
Elle permet d'afficher dans une page html du texte généré à partir d'éléments externes en s'assurant qu'il ne sera pas interprété comme du code.
Prototype :
htmlspecialchars(string $string, int $flags = ENT_COMPAT, ?string $encoding = null, bool $double_encode = true): string.
La fonction htmlentities(str) est identique à htmlspecialchars, mais convertit aussi tous les caractères spéciaux comme à, é, ... en équivalent HTML.
Exemple de fonction de sécurisation de Robin Nixon :
function SanitizeString($var)
{
$var = strip_tags($var); // supprime les balises
$var = htmlentities($var); //
return stripslashes($var);
}
html_entity_decode() fait l'inverse de htmlentities. Je n'ai pas testé !
JSON est ici un Format Multi-Types qui supporte les 6 types de valeurs suivants :
number : 42, 3.1416
string JSON : "hello" // Entre guillets double " uniquement
boolean : true, false
null : null
array JSON : [1, 2, "toto"]
object JSON : {"eleve":{"nom":"Jean", "age":30, "ville":"Paris"}}
Il ne supporte ni les fonctions, ni les dates, ni undefined...
Il ne faut pas le confondre avec le type XML constitué uniquement des paires clé:valeur.
C'est un format qui sert d'intermédiaire entre les types PHP et JAVASCRIPT. Des fonction PHP convertissent les types PHP en JSON (dans les 2 sens) et des méthodes javascript convertissent les types JSON en JS (dans les 2 sens).
json_encode() est une fonction qui convertit les types PHP en types JSON :
int/float → number
string → string (avec guillemets, pas des apostrophes)
array indexé → array JSON ([])
array associatif → object JSON ({})
object PHP → object JSON (si propriétés publiques)
Exemple :
echo json_encode([
"nombre" => 42,
"tableau" => [10, 20],
"texte" => "Bonjour"
]);
Le tableau associatif de type PHP est converti en tableau associatif de type JSON (qui ressemble à celui de JS). L'echo enverra par HTTP la chaine : {"nombre":42,"tableau":[10,20],"texte":"Bonjour"}
Les données transmises par un echo json_encode(..)); sont réceptionnées en JS dans un objet response qui doit être décodé par la méthode response.json() pour générer les objets javascript équivalents :
const data = await response.json();
La méthode response.json() aurait pu s'appeler response.json_decode() pour faciliter la compréhension, mais c'est un nom court qui a été préféré.
Attention : echo json_encode(..)); envoie un string. Dans javascript, la méthode response.json() le retransforme en l'objet JS attendu, alors que response.text() qui est une autre méthode utilisée en réception, montrerait le string JSON non modifié, qui n'aurait pas de sens en javascript.
Rappel : il est recommendé d'utiliser htmlspecialchars avant d'envoyer des données de provenance inconnue. On fera :
echo htmlspecialchars(json_encode($phpData));
pour éviter que dans javascript des chaines de caractères comme "<a>" qui sont sans signification dans le PHP soient interprétées comme une balise dans le html.
json_decode(string, tabass = false) est une fonction qui nécessite une chaîne JSON valide en premier argument.
En particulier on fera attention aux strings. Par exemple '{"age":30,"nom":"Alice"}', doit avoir les strings entre guillemets doubles ", car si l'objet a une provenance hors PHP avec des strings entre apostrophes ' il ne sera pas valide.
Le deuxième argument, faculatif (false par défaut). Exemples des 2 cas :
$jsonString = '{"age":30,"nom":"Alice"}';
$objet = json_decode($jsonString); // tabass = false => Objet stdClass
$tab = json_decode($jsonString, true); // true => Tableau associatif
Sans second argument, un objet JSON de type "clé":valeur est converti en objet PHP de type stdClass, alors qu'avec true il est converti en tableau associatif :
echo $objet->nom; // "Alice" (accès comme un objet stdClass)
echo $tab['nom']; // "Alice" (accès comme un tableau associatif)
Le passage de la valeur d'une variable numérique de PHP à JS est relativement facile : $x peut être affecté à une variable x JS et réciproquement.
Exemple 1 : HTML -> PHP -> SCRIPT
<body>...
<?php
echo "<script>var a = $a;</script>";
?>
...</body>
Ici les balises <script> sont mises par PHP dans le string envoyé par echo pour générer une zone javascript.
Exemple 2 : HTML -> SCRIPT -> PHP
<body>...<script>...
<?php
echo "var a = $a;";
?>
...</script>...</body>
Ici le string est écrit par echo dans la zone JS déjà présente.
Remarque :
Dans le cas où dans le string écho, il n'y a qu'une simple variable numérique on peut utiliser le raccourci suivant :
<?= $index ?> qui est équivalent à <?php echo $index; ?>
Dans le cas des autres variables c'est plus compliqué car echo est une fonction rustique qui transforme tout en chaine de caractères, sauf si c'en est déjà une, sans faire de différence. Ainsi à titre d'exemple :
<?php
$a = "abc";
echo "<script>var a = $a;</script>";
?>
va produire l'erreur abc indéfini car le html reçoit "<script>var a = abc;</script>" et abc n'est pas une variable définie (alors qu'il devrait recevoir "<script>var = \"abc\"</script>;" ), car echo est une fonction rustique qui interpole les strings recursivement. Pour s'accomoder de cette interpolation récursive il faut rajouter les apostrophes ' dans la chaine pour entourer le contenu abc qui sera généré par $a.
<?php
$a = "abc";
echo "<script>var a = '$a';</script>";
?>
Dans une zone Heredoc on pourra aussi utiliser des guillemets autour de $a.
Dans une zone de code Javascript inutile d'ajouter les balises<script>, on ouvre une zone php dans laquelle on fait l'echo où la variable JS reçoit la valeur de la variable php.
<?PHP echo "var a = '$a';"; ?>
ATTENTION : Utiliser les guillemets doubles dans la chaine echo si on veut que les variables PHP soient interpolées et mettre les variables string entre apostrophes.
La création d'un texte multiligne global dans php destiné à être affiché dans un élément html n'est pas simple. On peut bien entendu l'afficher ligne par ligne avec un <br> à la fin de chaque ligne.
La méthode suivante permet de créer un string global qui conservera les sauts de lignes.
Elle suppose :
1) L'utilisation du style="white-space:pre-line" qui conserve les sauts de lignes.
2) La création de la chaine avec les caractères sauts de ligne \n échappés => \\n, mais sans saut de ligne cachés, ce qui signifie :
•soit on concatène les lignes
•soit on échappe les fin dans les bloc heredoc et newdoc en les terminant par \
3)On affecte la chaine crée en obligeant interprétation en tant que chaine : entourée de guillemets doubles et non de guillemets simples.
Exemple de création de variable php multiligne :
Par un bloc Heredoc en échappant les retours lignes finaux
$txt = <<<_litteral
Ligne 1\\n\
Ligne 2\\n\
Ligne 3\\n
_litteral;
ou bien en concaténant des lignes :
$txt = "Ligne 1\\n";
$txt .= "Ligne 2\\n";
$txt .= "Ligne 3\\n";
Ensuite affectation à une variable javascript :
echo "var txt = '$txt';";
ou bien
echo "var txt =\"$txt\";";
ou encore :
echo <<<_litteral
var txt = "$txt";
_litteral;
Exemple complet :
<p id="out" style="white-space:pre-line"></p>
<script>
<?php
$txt = "Texte passé dans de php vers javascript :\\n";
$txt .= "Première ligne\\n";
$txt .= "Deuxième ligne\\n";
echo <<<_PHP2JS
var txt = "$txt";
_PHP2JS;
?>
document.getElementById("out").textContent = txt;
</script>
Pour que l'élément html p, ou div, ou autre conserve les saut de ligne on utilise la propriété css style="white-space:pre-line" qui permet de gérer l'interprétation des espaces surnuméraires et des sauts de ligne. Voir https://developer.mozilla.org/fr/docs/Web/CSS/white-space.
Le passage d'un tableau nécessite le passage par l'intermédiaire des types JSON à l'aide de la fonction json_encode de PHP. Comme les tableaux JSON sont identiques aux tableax JS, il n'y a pas besoin de décodage coté JS. La procédure se limite donc, dans la zone PHP, a génèrer le string qui contient le descriptif du tableau à l'aide de json_encode, puis à enserrer par un echo une zone javascript dans laquelle on affecte ce string à une variable javascript. Exemple :
<?php
/* Passage tableau PHP vers Javascript */
$a = ["aa", "bb", "cc"];
$ja = json_encode($a);
echo <<<_END
<html><head></head>
<body>
<p id='affich1'></p>
<p id='affich2'></p>
<script> var tabJS = $ja;
document.getElementById('affich1').textContent = tabJS; // affichera aa,bb,cc
document.getElementById('affich2').textContent = tabJS[1]; // affichera bb
</script>
</body>
_END;
?>
L'utilisation d'une zone Heredoc facilite grandement l'écriture, car dans cette zone les variables PHP sont interprétées et on peut utiliser les apostrophes et guillemets pour les chaines HTML ou javascript.
Nous venons de voir l'utilisation de json_encode pour un tableau. En fait json_encode peut être utilisé pour tous les types convertibles en un des six types JSON : nombre, string, booléen, null, tableau indexé et object JSON. Et il est même recommandé de l'utiliser.
La plupart sont utilisables directement en JS, ou peuvent être convertis en JS par la méthode .json() de l'objet response reçu. Remarque : response est un nom conventionnel donné à l'argument de la callback de traitement. On peut en utiliser un autre.
Le passage de javascript vers PHP est plus complexe car il nécessite l'envoi d'une requête du navigateur client au serveur de la page.
Généralement cette valeur n'est pas connu initialement, sinon on l'aurait déclarée ou calculée dans PHP. Elle résulte a priori d'une action de l'usager, par exemple suite à un clic sur un bouton.
<!-- Input où est entré la valeur et bouton dans le code HTML -->
Définir la variable<input type="text" id="varin">
<button onclick="envoi()">Clic pour envoi</button>
// le code PHP est à insérer ici dans cet exemple
<script>
function envoi() // la callback du bouton
{
let invar = document.getElementById("varin");
window.location.href = "js2phpGET.php?var1=" + invar.value;
}
</script>
L'instruction window.location.href = ... redemande la page et en plus elle fournit le couple ( var1 = invar.value) que le serveur va trouver dans le tableau associatif $_GET[].
Au niveau du serveur le code PHP suivant est exécuté :
<?php // Réception de la variable dans le code php
if (isset($_GET["var1"]))
{
$a = $_GET["var1"];
echo "<br>La variable vaut $a<br>";
}
?>
Dans cet exemple on suppose que le code PHP est dans le body à l'emplacement indiqué par le commentaire. Si le serveur reçoit un requête de la page avec ce couple dans le tableau $_GET[], PHP va insérer dans le body, à l'emplacement où il se trouve le texte La variable vaut avec la valeur reçue.
Si le serveur doit réaliser un travail, sans intervenir dans la page, il n'y a pas besoin de la redessiner et dans ce cas au lieu d'envoyer la requête avec une demande de changement de page par l'instruction window.location.href = ... on utilise l'instruction fetch (bizarement non présentée dans le bouquin de Robin Nixon).
- A la place d'envoyer "js2phpGET.php?var1=" + invar.value; on envoie :
"?var1=" + invar.value; car l'url de la page en cours (js2phpGET.php) est sous-entendu par défaut avant le ?.
- Au lieu de recharger la page après le traitement de la requête par window.location.href =.. on envoie, dans javascript, la requête comme ceci :
const response = await fetch(`?var1='+ invar.value); // ici response n'est pas utilisé.
Voir paragraphe 3.2.2.
Exemple envoi par POST
<?php // Réception de la variable dans le code php
if (isset($_POST["var1"])) { $a = $_POST["varin"]; echo "La variable vaut $a<br>";}
else echo "La variable n'est pas définie<br>";
?>
<!-- Envoi de la variable dans le code HTML -->
<form method="post" action="js2phpPOST.php">
Définir la variable<input type="text" name="varin"><input type="submit" name="var1" value="Clic pour envoi">
</form>
La méthode javascript :
fetch(arg).then((a)=>{ methode1}).catch(e=>alert("Erreur : "+e));
•envoie une requête décrite par arg.
•Si le serveur renvoie une réponse elle sera contenue dans a, ce qui fait que dans javascript on positionne une callback méthode1 sur cet objet qui éventuellemnt génère un nouveau résultat pour un then suivant..
•A la première une rerreur qui intervient dans cette séquence le contenu du catch est exécuté sur le string e décrivant l'erreur.
Pour envoyer un GET avec le mot clé toto associé à la valeur val, on utilise comme arg '?toto=val'
La méthode fetch est souvent utilisée en combinaison avec le mot clé await ce qui permet un programmation plus intuitive sans callback positionnées par then et catch, mais la methode fetch doit alors être située dans une fonction déclarée async, ou bien si c'est au niveau racine, le script doit être du type="module".
<?php // formtest2.php
if (isset($_POST['nom'])) $nom = $_POST['nom'];
else $nom = "(Inconnu)";
echo <<<_END
<html>
<head>
<title>Formulaire de test</title>
</head>
<body>
Ton nom est : $nom<br>
<form method="post" action="formtest2.php">
Quel est ton nom?
<input type="text" name="nom">
<input type="submit">
</form>
</body>
</html>
_END;
?>
PHP construit automatiquement un tableau associatif dès que l'on soumet un formulaire.
Ce tableau se nomme $_POST, chaque élément a pour index le 'name' d'un élément du formulaire, et chaque valeur, la valeur entrée par l'utilisateur dans chaque champ avant de cliquer sur le bouton 'Envoyer'. On donne un nom différent à ce bouton Ok par exemple avec le champ value="Ok".
On peut donc égrener le tableau de variables POST de cette façon :
foreach($_POST as $index => $valeur){
echo "$index = $valeur<br>";
}
?>
Ici $index parcourt tous les indices ou clé du tableau et $valeur toutes les valeurs correspondantes
Le protocole de communication HTTP (Hypertext Transfer Protocol), est un protocole qui facilite les échanges de communication entre les clients Web (par exemple, un navigateur) et les serveurs Web. Ces échanges concernent généralement une action à effectuer sur une ressource identifiée par une URL qui suit la méthode associée à l'action. Les principales méthodes sont :
•Get : communication de donnée simples
•Head : demande d'informations
•Post : transmission de données
•et aussi : Options, Connect, Trcae, Put, Patch, Delete
https://www.ionos.fr/digitalguide/hebergement/aspects-techniques/http-header/
Lorsqu'une page Web, www.exemple.com par exemple, est consultée, le serveur Web renvoie non seulement la page elle-même, mais aussi l’en-tête suivant (invisible pour les utilisateurs) :
HTTP/1.1 200 OK
Content-Encoding:gzip
Age: 521648
Cache-Control: max-age=604800
Content-type: text/html; charset=UTF-8
Date: Fri, 06 Mar 2020 17:36:11 GMT
Last-Modified: Thu, 17 oct 2019 07:18:26 GMT
Server: ECS (dcb/7EC9)
Vary: Accept-Encoding
X-Cache: Hit
Content-Length: 648
A part la 1ère ligne les autres sont des paires nom: valeur
Explications pour les différentes lignes :
•HTTP/1.1 est la version valide du protocole HTTP et 200 OK est le code de statut. Il indique que le serveur a reçu, compris et accepté la demande.
•Content-Encoding et Content-Type fournissent des informations sur le type de fichier.
•Age, Cache-Control, Expires, Vary et X-Cache font référence à la mise en cache du fichier.
•Etag et Last-Modified sont utilisés pour le contrôle de la version du fichier livré.
•Le terme « server » désigne le logiciel du serveur Web.
•Content-Length est la taille du fichier en octets.
Prototype :
void header(string $str [,bool $replace = true [,int $http_response_code]])
La fonction header() doit être appelée avant que le moindre contenu ne soit envoyé, soit par des lignes HTML habituelles dans le fichier, soit par des affichages PHP. Elle permet d'envoyer le string $str dont la signification est défini dans la Spécification HTTP/1.1. Le deuxième paramètre $replace (true/false) indique si ce header doit écraser ou laisser le header précédemment émis. On utilisera false lorsque qu'il y a besoin de plusieurs lignes header pour préciser un même premier $str. Ci-après les exemples les plus courants :
Redirection locale :
<?php
header("Location: ../test.php"); /* Redirection relative par rapport à this.php */
exit;
?>
Dans certains cas, (lesquels ?) il faut reconstruire le chemin local :
<?php
// Redirection vers une page différente du même dossier
$host = $_SERVER['HTTP_HOST']; // Le nom de l'hote
$self = $_SERVER['PHP_SELF']; // Le chemin de la page dans l'hote
$dirn = dirname($self); // Uniquement le nom du répertoire contenant la page
$uri = rtrim(dirname($self), '/\\'); // Supprime les / et \ de fin de chaine
$extra = 'exemples1&1/dirhome.php'; // La nouvelle page
header("Location: http://$host$uri/$extra");
exit;
?>
Redirection externe
<?php
header("Location: https://www.llibre.fr/rando/rando.php");/*Redirection externe */
exit;
?>
Dans le cas d'une redirection on peut préciser le troisième argument ( int $http_response_code) aux valeurs suivantes :
301 // pour Moved Permanently (la nlle page sera mise en cache pour une longue période)
302 // pour Found (la nlle page sera mis en cache pour la session) qui est la valeur par défaut.
303 // pour See Other
307 // pour Temporary redirect (la nlle page ne sera pas mise en cache)
Affichage boite de téléchargement
<?php
// on désire gérer un pdf
header('Content-type: application/pdf');
// Il sera présenté sous le nom "downloaded.pdf"
header('Content-Disposition: attachment; filename="downloaded.pdf"');
readfile('manuel.pdf'); // Le vrai nom de la source du PDF
?>
file_exist("toto.txt") vérifie l'existence de toto dans le répertoire du fichier php en cours. Vérifie aussi l'existence d'un répertoire.
Date d'enregistrement d'un fichier :
$filetime = filemtime($filename);
Verification de l'age du fichier :
$days_old = (time() - $filetime) / (60 * 60 * 24); // conversion en jours
dirname($path, $niveau=1) -> renvoi le nom du répertoire parent de niveau $niveau.
basename($path, $ext) -> renvoi le nom final du fichier, avec l'extentension $ext supprimée (mettre "", pour la conserver)
realpath($path) -> renvoi le chemin canonique (complet et sans fioritures).
scandir($dir) : Liste des fichiers d'un répertoire.
Exemple :
<?php
$dir = $_SERVER["DOCUMENT_ROOT"].'/michel';
$files1 = scandir($dir);
echo "Fichiers de /michel :<br>";
foreach($files1 as $cle => $val)
echo $val.'<br>';
?>
Résultat :
Fichiers de /michel :
.
..
astro
chmots3
copie_cert
index.html
orange
pdfs
php.ini
publis
publis.html
robot
$fh = fopen($nomfich,'w'); // idem en C, ouverture en 'r', 'r+', ou 'w', 'w+', 'a', 'a+'...
$fh vaut FALSE en cas d'échec
fclose($fh);
fwrite($fh, $page);
fputs($fh, $ligne);
$page = fread($fh);
$ligne = fgets($fh);
$txt = fread($f,$n); //ne lit que $n octets
fseek($fh, $pos, $depart); // Déplace le pointeur à la position $pos mesurée à partir :
•du début si $depart = SEEK_SET
•de la position courante si $depart = SEEK_CUR
•de la fin si $depart = SEEK_END
copy("src.txt", "dest.txt");
if (flock($fh, LOCK_EX)){// Verrouille le fichier. A faire avant d'écrire
{ .. ecrire ... puis flock($fh, LOCK_UN); }// Dévérouille
Ce verrouillage ne marche que si les autres programme qui accèdent au fichier utilisent aussi cette méthode.
rename("nom_avant.txt", "nom_apres.txt"); // Equivalent à move. Il semble que ça ne marche que dans le même répertoire.
unlink("toto.txt"); // supprime le fichier
$txt = file_get_contents("fichier.txt"); // Charge tout le contenu du fichier. Peut être utilisé pour afficher une page web, mais les liens ne seront pas suivis :
<?php echo file_get_contents("http://www.llibre.fr"); ?>
Érire tout le contenu de $data dans le fichier $stationsFile :
file_put_contents($stationsFile, $data);
$lines = file(nomfich) et $lines = file (url) permettent de lire le contenu sous forme d'un tableau de lignes $lines[$i] pour ($i = 0; $i < count($lines < $i++)
Différence principale entre file_get_contents et file est que le premier fournit une seule chaine contenant tout le contenu et que le second fournit un tableau de chaines avec une chaine par ligne.
Exemple :
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
</head>
<body><div>
<?php
$url = 'https://www.llibre.fr/hello.txt';
$tle_lines = file($url);
foreach ($tle_lines as $line) {
echo htmlspecialchars($line) . "<br>";
}
?>
</div></body></html>
Lorsque le contenu d'un fichier n'est pas à traiter par le php, et qu'il est destiné à être envoyé directement au navigateur, la séquence :
echo file_get_contents($filename); exit;
peut-être remplacée par :
readfile($filename); exit;
Ce contenu qui, par exemple, avait étét demandé dans javascript par :
const response = await fetch(`?demande=${encodeURIComponent(fichier)}`);
sera obtenu par :
const contenu = await response.text();
dans le cas où il s'agit de texte.
file_get_contents et readfile permettent d'accéder, par le biais d'une url à des fichiers distants si allow_url_fopen = On dans le fichier PHP.ini. Mais avec ces deux fonctions on ne peut exercer aucune vérification sur le contenu ce qui est très dangereux. C'est pour cela qu'il exsite une autre méthode securisé offerte par cURL.
Exemple d'enregistrement d'un fichier url distant sur le serveur :
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); //en secondes
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); //suivre les redirections
$data = curl_exec($ch);
if ($data !== false) {
file_put_contents($filename, $data);
echo "Mise à jour réussie pour \"$filename\".";
} else { echo 'Erreur cURL : ' . curl_error($ch);}
exit;
On initialise cuRL, on fixe 3 options, on exécute et en reception on obtient les données distantes.
cURL risque de produire l'erreur suivante : SSL certificate problem: unable to get local issuer certificate lors d'une exécution locale (sous Wampserver64 par exemple) car cURL ne trouve pas de certicat d'autorité racine (CA) pour valider le certificat SSL du site distant. Dans ce cas, on peut :
•soit désactiver temporaitement la vérification SSL (à faire uniquement pour un test local) en ajoutant : curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); option qui est à true par défaut.
•soit (solution propre recommandée pour un environnement sécurisé) :
1.Télécharger le fichier cacert.pem depuis le site officiel de cURL : https://curl.se/docs/caextract.html
2.Le placer dans un dossier accessible, par exemple C:/wamp64/bin/php/phpX.Y.Z/extras/ssl/cacert.pem. (X.Y.Z = 7.3.21 dans mon cas)
3.Ajouter dans la section [curl] de php.ini de Wampserver (C:/wamp64/bin/apache/apache2,4,46\bin\php.ini) la ligne :
curl.cainfo = "C:/wamp64/bin/php/phpX.Y.Z/extras/ssl/cacert.pem"
4.Redémarre WampServer.
Exemple d'upload avec enregistrement et affichage d'une image :
<?php // upload.php
// echo défini l'entête, avec la méthode post d'envoie de fichier
// (lors du submit). L'action rejoue ce script.
// L'input type='file' ouvre un sélecteur de fichier
echo <<<_END
<html><head><title>Formulaire de téléversement en PHP</title></head>
<body>
<form method='post' action='upload.php' enctype='multipart/form-data'>
Sélectionnez le fichier : <input type='file' name='nomfichier' size='10'>
<input type='submit' value='Déposer'>
</form>
_END;
// Ici au départ il n'y a rien dans $_FILES, mais après le submit il y a
// les indication concernant le fichier transmis dont on n'exploite que le nom
if ($_FILES)
{
$nom = $_FILES['nomfichier']['name'];
// Endroit choisi pour conserver le fichier reçu. ici/ doit exister.
$dest = "ici/".$nom;
/*
$typ = $_FILES['nomfichier']['type']; // image/jpeg selon le cas
$siz = $_FILES['nomfichier']['size']; // taille en octet du fichier reçu
$loc = $_FILES['nomfichier']['tmp_name']; // path temporaire de stockage
$err = $_FILES['nomfichier']['error']; // 0 si pas d'erreur
*/
// Recopie du fichier du répertoire temporaire vers l'endroit choisi
move_uploaded_file($_FILES['nomfichier']['tmp_name'], $dest);
// On affiche le nom initial et l'image enregistrée
echo <<<AAA
Nom image téléchargée : $nom<br>
<img src= '$dest' >
AAA;
}
// On ferme la page html
echo "</body></html>";
?>
Pour aller plus loin, voir : http://www.lephpfacile.com/manuel-php/features.file-upload.php
https://www.php.net/manual/fr/features.file-upload.multiple.php
Les cookies sont des données (4 Ko maximum par cookie) que l'on stocke sur l'ordinateur de l'usager et que l'on positionne par l'instruction setcookie('nom_du_cookie', saValeur,...); avant tout envoie de HTML. Cette instruction doit être exécutée avant toute écriture dans la page.
Lors d'une nouvelle réception de demande de la page web, la valeur du cookie est renvoée par le navigateur et il accessible via le tableau $_cookie[].
L'exemple suivant utilise un cookie pour le nom de l'usager et un autre pour le nombre de ses visites de la page. A l'origine ces 2 cookies ne sont pas définis (isset($_COOKIE['no_visit'] est faux), on met donc en place une FORM pour interroger l'usager sur son nom, uniquement cette première fois. Au début du PHP on teste la réception de la réponse à cette forme que l'on utilise pour initialiser la valeur des cookies.
Exemple :
<?php
if(isset($_POST["submitnv"]) && isset($_POST["nom_visit"]))
{ // Reception réponse 1ere visite : on memorise les réponses dans les cookies
setcookie('Nom_Usager', $_POST["nom_visit"]);
setcookie('no_visit', 2);
}
else
{
if(isset($_COOKIE['Nom_Usager']) && isset($_COOKIE['no_visit']))
{ // Ce n'est pas la première visite : on memorise son n° de visite
setcookie('no_visit', $_COOKIE['no_visit']+1);
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Cours PHP & MySQL</title>
<meta charset="utf-8">
<link rel="stylesheet" href="cours.css">
</head>
<body>
<h1>Titre principal</h1>
<?php
if(isset($_COOKIE['Nom_Usager']) && isset($_COOKIE['no_visit']))
{ // Ce n'est pas la première visite : Message d'accueil circonstancié
$user = htmlentities($_COOKIE['Nom_Usager']);
$no = intval($_COOKIE['no_visit']);
echo <<<_MLL_HTML1
Bonjour $user<br>
C'est votre visite n° $no
_MLL_HTML1;
}
else if(isset($_POST["submitnv"]) && isset($_POST["nom_visit"]))
{ // Première visite après remplissage cartouche : Message d'accueil circonstancié
$user = htmlentities($_POST["nom_visit"]);
echo <<<_MLL_HTML2
Bonjour $user pour votre première visite
_MLL_HTML2;
}
else
{ // Première visite : On met en place le cartouche questionnaire
echo <<<_MLL_HTML3
<form method="post" action="cookie.php">
Quel est votre nom ?<input type="text" name="nom_visit">
<input type="submit" name="submitnv">
</form>
_MLL_HTML3;
}
?>
</body>
</html>
La fonction setcookie renvoie normalement true, ou false si elle est placée trop tard (après une écriture dans la page). Elle a deux forme au niveau des arguments :
1.setcookie(string $name, string $value = "", array $options = [])
et
2.setcookie(string $name, string $value = "", int $expires = 0, string $path = "", string $domain = "", bool $secure = false, bool $httponly = false )
Dans la première forme, $options est un tableau associatif qui peut avoir comme clés les noms des arguments de la deuxième forme : expires, path, domain, secure, httponly et un paramètre supplémentaire samesite dont la valeur doit être None, Lax ou Strict.
Signification des arguments :
•name : Le nom du cookie.
•value : La valeur du cookie.
•expires : La date d'expiration en timestamp Unix. Utiliser mktime() ou par ex time()+ 30*24*3600 pour expiration dans un mois. La valeur 0, fait expirer le cookie à la fermeture du navigateur.
•path : Le chemin sur le serveur sur lequel le cookie sera disponible : '/' pour l'ensemble du domaine, '/foo/' pour uniquement dans le répertoire /foo/ et ses sous-répertoires comme /foo/bar/ dans le domaine domain. La valeur par défaut est le répertoire courant où le cookie a été défini.
•domain : Le domaine pour lequel le cookie est disponible.
•secure : Si true le cookie ne sera envoyé que si la connexion est sécurisée (problématique en simulation locale). Côté serveur, c'est au développeur d'envoyer ce genre de cookie uniquement sur les connexions sécurisées (par exemple, en utilisant la variable $_SERVER["HTTPS"]).
•httponly : Laisser sur false car le comportement avec true est sujet à discussions.
Ainsi pour supprimer un cookie (précédemment créé) chez le client, depuis le serveur on peut le recréer avec une date d'expiration située dans le passé, ce qui fait que le navigateur supprimera ce cookie :
setcookie('Nom_Usager', $_POST["nom_visit"], time() - 30*24*3600, '/');
setcookie('no_visit', 2, time() - 30*24*3600, '/');
Les valeurs transmises sont sans importance, la date transmise est la date actuelle moins un mois et le path de disponibilité est tout le domaine.
Une session est un mécanisme qui permet de mémoriser des données de l'usager sur le serveur via un tableau $_SESSION['nomparam'], plutôt que chez le client via des cookies, mais elle mémorise quand même un cookie chez le client associé au nom de la session (accessible via la fonction session_name()), mais sans qu'on ait à le faire explicitement. Ce cookie de session permet de retrouver les paramètres mémorisées lorsque la page ou une autre page associée à la session est appelée, sans qu'on ait à s'en préoccuper. C'est automatique : toutes les pages désirant utiliser le mécanisme doivent mettre l'instruction session_start(); avant tout envoi de code HTML.
Ensuite des valeurs quelconques, par exemple $valparam1 récupérés n'importe où (dans d'autres tableaux globaux GET, POST, SERVER,...) peuvent être mémorisés de la manière suivante :
$_SESSION['nomparam1'] = $valparam1;
Si la page, appelle une autre page, dans cette autre page, en mettant session_start(); avant tout code HTML, on pourra accéder ensuite aux valeurs mémorisées :
$valparam1 = $_SESSION['nomparam1'] ;
Si dans une page (de fin de travail par exemple) on désire faire une RAZ des informations mémorisées, on fera $_SESSION = array(); et on demandera la destruction du cookie de session comme ceci : setcookie(session_name(), '', time() - 30*24*3600, '/'); en limitant sa validité au mois précédent.
On peut définir une durée limite de validité des données d'une session, pour s'assure de leur destruction dans le cas ou l'utilisateur ne passerait pas par une étape prévue pour cette destruction. On appellera la fonction :
ini_set('session.gc_maxlifetime', 24*3600);
pour entrainer la suppression automatique des données de session au bout d'un jour.
A un moment donné, la urée courante restante peut être obtenue par la fonction ini_get('session.gc_maxlifetime');
Si les échanges ne sont pas sécurisés en mode SSL, on peut dans la première page mémoriser l'adresse IP du client :
$_SESSION['ip'] = $_SERVER['REMOTE_ADD'] ;
et vérifier dans les autres page que c'est toujours la même adresse :
if ($_SESSION['ip'] != $_SERVER['REMOTE_ADD']) // => IL Y A UN PROBLEME
Pour éviter qu'un pirate profite de ce système par le biais d'un identifiant de session qu'il aurait piraté à sa création, il existe une fonction session_regenerate_id(); qui modifie cet identifiant.
On l'appellera comme suit :
session_start();
if (!isset($_SESSION['initiated']))
{ session_regenerate_id(); $_SESSION['initiated'] = 1; }
Comme cela l'identifiant sera modifié une seule fois au 1er appel.
Il est possible, via le fichier php.ini, de modifier des paramètres de l'environnement PHP. Nous vous avons rassemblé dans cet article les directives les plus souvent utilisées pour personnaliser votre environnement PHP.
Remarque : Ce paramètre a été supprimé dans PHP 5.4.
Augmente la sécurité des scripts PHP lorsque cette directive est DÉSACTIVÉE car la désactivation empêche que certaines variables puissent être modifiées de manière involontaire.
Définit si l'accès à des fichiers sur des URLs externes est permis ou non. Le fait d'interdire cet accès permet d'augmenter la sécurité car il devient très complexe pour les éventuels détraqueurs de faire interférer des fichiers malveillants avec vos scripts. (Pb : on ne peut plus accéder à des url externes, par exemple pour faire un update des TLE).
Même si les messages d'erreur sont très pratiques pour trouver les origines des éventuelles erreurs dans vos scripts, cette fonctionalité peut avoir comme incovénient de révéler des informations privées à des personnes malveillantes. Pour cette raison, il est recommandé de la désactiver.
Permet de définir une liste de fonctions à désactiver. Cette directive n'est pas souvent utilisée dans les logiciels PHP mais elle l'est souvent par les détraqueurs.
Exemple : disable_functions = exec,system,passthru,shell_exec, popen,escapeshellcmd,proc_open, proc_open, proc_nice,ini_restore
En activant le "safe-mode", PHP va fonctionner en mode sécurisé. Cela signifie que les opérations sur les fichiers vont être contrôlées et que les accès aux variables d'environnement vont être restreints.
Remarque : Les valeurs limites de la mémoire de travail (RAM) sont données ci-dessous dans la notation php.ini-typique. Par exemple, une valeur de 256M limite la mémoire disponible à 256 mégaoctets (Mo).
Définit la valeur maximale de mémoire qu'un script est autorisé à consommer. Cette valeur est donnée en Mo.
ATTENTION : Quelle que soit la valeur saisie via cette directive, la limite de mémoire propre à votre version de PHP prévaut : des valeurs plus élevées sont donc ignorées par le serveur Web.
Définit la taille maximale des données qui peuvent être reçues par la méthode POST. Cette directive affecte également les fichiers chargés. Pour charger de gros fichiers, cette valeur doit être plus grande que la valeur de upload_max_filesize. De façon générale, memory_limit doit être plus grand que post_max_size.
Définit la taille maximale que peuvent avoir les fichiers qui sont chargés par formulaire.
Fixe le temps maximal d'exécution d'un script, en secondes. Cela permet d'éviter que des scripts en boucles infinies saturent le serveur.
ATTENTION : Indépendamment de la valeur saisie dans cette directive, la valeur globale memory-limit propre à votre version de PHP prévaut. Des valeurs plus élevées sont donc ignorées par le serveur Web.
zlib.output_compression = désactivé
Comprime les fichiers PHP au format gzip
Spécifie si le module de session doit démarrer automatiquement au début de chaque script PHP. Par défaut, c'est 0 (désactivé).
Définit le nombre de variables d'entrée pouvant être acceptées (cette limite est appliquée aux variables superglobales $_GET, $_POST et $_COOKIE, séparément). L'utilisation de cette directive permet de limiter les possibilités d'attaque par déni de service utilisant des collisions de hashages. S'il y a plus de variables en entrée que le nombre spécifié par cette directive, une alerte de type E_WARNING sera émise, et les variables en trop seront supprimées de la requête.
Spécifie la durée maximale pour analyser les données d'entrée, via POST et GET. Cette durée est mesurée depuis le moment où PHP est invoqué sur le serveur jusqu'au début de l'exécution du script.
Pour tester j'utilise la base MySQL qui est gérée par WampServer (plate-forme de développement Web sous Windows à l’aide du serveur Apache2, du langage de scripts PHP et d’une base de données MySQL qui offre également PHPMyAdmin pour gérer les bases de données).
Sur Wampserver les base sont accessibles par root mot de passe onera. Il y a les bases suivantes :
•xxx_schema : tables système
•mysql : table système
•sys : table système
•wordpress : table wordpress
•mabase01 : à supprimer
•mesusers : à supprimer
•publications : tables essai exos Nate Robin.
La base publication est également accessible par michel, mot de passe pistache. Elle comporte les tables suivantes (exos Nate Robin) classiques (auteurs, bouquin, isbn...), clients, comptes, felins, utilisateurs (nom, prenom, id, passwd crypté).
Crée à l'origine par Michael Widenius MySQL est passé dans le giron de Sun Microsystems, puis Oracle Corporation. Michael Widenius a ensuite créé MariaDB qui demeure une version libre.
Lien sur le tutoriel IONOS concernant MySQL : https://www.ionos.fr/digitalguide/serveur/know-how/apprendre-mysql-en-toute-simplicite/
Pour l'utilisation de MariaDB et MySQL voir aussi le fichier : wamp(64)\mariadb_mysql.txt
phpMyAdmin est une application interface utilisateur fenêtrée permet de gérer plus simplement les bases MySQL et MariaDB.
Lorsque Wampserver est en service on peut accéder à MySQL en mode console :
•directement dans le menu de l'icône Wamp dans la barre d'outils de windows (en bas à droite), ligne MySql, puis ConsoleMySql
•Dans une fenêtre de commande ouverte dans C:\wamp64\bin\mysql\mysql5.7.31\bin taper la commande "mysql -u root -p"
ou bien par le biais de phpMyAdmin en mode page web :
•dans le menu de l'icône Wamp choisir ligne phpMyAdmin,
•dans le menu de l'icône Wamp choisir ligne Localhost et cliquer sur le lien phpMyAdmin
•ou bien dans le navigateur web taper l'url localhost/phpmyadmin
Dans WampServer64 l'utilisateur initial de MySQL est "root" sans mot de passe. Changé pour mon mot de passe Linux.
Dans Ubuntu, faire sudo mysql -u root.
MySQL permet de gérer de nombreuses bases de données identifiées des noms différents.
Une base de donner peut comporter de nombreuses tables identifiées par des noms différents.
Les tables comportent des champs identifiés par leur noms (des colonnes) et des enregistrements (des lignes) dans lesquels chaque champ prend (ou ne prend pas) une valeur.
char(n) : chaine de n <= 255 caractères, de longueur fixe (complétée par des espaces)
varchar(n) : chaine de n <= 65535 caractères de longueur variable, limitée à n.
text(n) : idem varchar, mais pas de valeur par défaut
tinytext(n) : n <= 255
text(n) : n <= 65535 (216 - 1)
mediumtext(n) : n <= 224 - 1
longtext(n) : n <= 232 - 1
char, varchar et _text sont associés à un jeu de caractères.
binary(n) : chaine de n <= 255 octets
varbinary(n) : chaine de n <= 65535 octets de longueur variable, limitée à n.
blob(n) : idem varbinary, mais pas de valeur par défaut.
tinyblob(n) : n <= 255
blob(n) : n <= 65535 (216 - 1)
mediumblob(n) : n <= 224 - 1
longblob(n) : n <= 232 - 1
binary, varbinary et _blob ne sont pas associés à un jeu de caractères.
tinyint : 1 octet
smallint : 2 octet
mediumint : 3 octets
int ou integer : 4 octets
bigint : 8 octets
float : 4 octets
double ou real : 8 octets
Les entiers peuvent être signés (par défaut) ou non signés si qualifié de unsigned.
Si le type décimal est suivi d'une taille, par exemple int(4) la taille indique le nombre de caractères (4) à utiliser pour l'affichage du champ. La qualificatif ZEROFILL indique dans ce cas que si l'affichage de la valeur comporte moins de n caractères , on le complètera par des 0.
date : 'AAAA-MM-JJ'
time : 'HH:MM:SS'
datetime : 'AAAA-MM-JJ HH:MM:SS'
timestamp : 'AAAA-MM-JJ HH:MM:SS'
year : AAAA (de 1901 à 2155 uniquement)
Pour datetime les années vont jusqu'à 9999 alors que pour timestamp elle sont limitées de 1970 à 2037.
Attribut not null : pour indiquer que le champ n'a pas le droit d'être non initialisé.
L'attribut auto_increment permet de génerer un champ qui sera automatiquement incrémenté dans la table ce qui permet d'attribuer un numéro unique à chaque ligne. Le type de ce champ sera généralement un "int unsigned not null auto_increment key".
key signifie que cette valeur sera utilisée dans le champ key comme index de recherche dans la table.
Dans la console phpMyAdmin toutes les commandes sont envoyées par un CTRL+ Return car Return seul est utilisé pour continuer le texte de la commande sur la ligne suivante.
Une commande se termine par un ; (point-virgule). Si on l'a oublié, une invite de commande du type -> attend qu'on le frappe. Une invite '> ou "> attend qu'on ferme la chaine de caractère et une invite /*> attend que l'on ferme le commentaire.
La case (majuscule ou minuscule) est sans importance pour les mots clés.
Les commandes de haut niveau concernent la gestion des bases de données (création, accès) et des utilisateur (création, privilèges) :
ALTER, BACKUP, CREATE, DELETE, DESCRIBE, DROP, EXIT, GRANT, HELP, INSERT, LOCK, QUIT, RENAME, SHOW, SOURCE, STATUS, TRUNCATE, UNLOCK, UPDATE, USE.
>SHOW DATABASES;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mabase01 |
| mesusers |
| mysql |
| performance_schema |
| sys |
| wordpress |
+--------------------+
>CREATE DATABASE publications;
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mabase01 |
| mesusers |
| mysql |
| performance_schema |
| publications |
| sys |
| wordpress |
+--------------------+
>DROP DATABASE mabase01;
mysql> drop database mabase01;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mesusers |
| mysql |
| performance_schema |
| publications |
| sys |
| wordpress |
+--------------------+
7 rows in set (0.00 sec)
Sous Windows on crée l'utilisateur en lui affectant ses privilèges par GRANT.
>GRANT privilèges ON bd.object TO 'user'@'hote' IDENTIFIED BY 'passe';
Parmi les privilèges il y a :
•ALL,
•CREATE,
•CREATE USER,
•DELETE,
•DROP,
•EVENT,
•EXECUTE,
•FILE,
•INDEX,
•INSERT,
•SELECT,
•USAGE (= pas de privilèges), ...
.object permet de limiter les privilèges à une partie de la base, sinon les privilèges sont valables pour toutes les données de la base. Utiliser .* pour accéder à toute la base.
Si IDENTIFIED BY est omis, l'utilisateur de la base n'a pas de mot de passe.
Exemple :
>GRANT ALL ON publications.* TO 'michel'@'localhost' IDENTIFIED BY 'pistache';
Ce qui fait que la base publications sera accessible à root sans mot de passe et à michel, passe : pistache.
Sous Ubuntu, il faut d'abord créer l'usager par create user et ensuite lui donner ses privilèges par grant :
create database rnixon;
create user 'michel'@'localhost' identified by 'pistache';
grant all on rnixon.* to 'michel'@'localhost';
REWOKE permet de supprimer des privilèges.
Avant de pouvoir travailler sur des tables il faut accéder à une base de données par
>USE publications;
mysql> use publications;
Database changed
Dans la base de données, celles-ci sont constituées en tables organisé en :
•colonnes généralement appelées les champs.
•lignes généralement appelées les enregistrements ou les entrées.
Dans les bases MySQL chaque champ est caractérisé par les propriétés suivantes :
•son nom (Field dans MySQL) de la donnée concernée par ce champ,
•le Type de sa donnée (char, int, blob...)
•la possibilité que la donnée soit absente (NULL, NOT NULL)
•l'indexabilité (Key) par une clé primaire(PRIMARY KEY),
•l'examen d'une portion du texte (INDEX), l'examen d'un résumé du texte (FULLTEXT),
•la donnée par défaut (Default)
•une propriété extra (?)
CREATE TABLE classiques (
auteur varchar(128),
titre varchar(128),
type varchar(16),
annee char(4)
) ENGINE InnoDB charset utf8;
La précision ENGINE InnoDB est inutile avec un MySQL récent (>= 5.6)
CREATE TABLE felins (
id SMALLINT NOT NULL AUTO_INCREMENT,
famille VARCHAR(32) NOT NULL,
nom VARCHAR(32) NOT NULL,
age TINYINT NOT NULL,
PRIMARY KEY (id)
);
>SHOW TABLES;
+------------------------+
| Tables_in_publications |
+------------------------+
| classiques |
| felins |
+------------------------+
Notre base publications n'a pour le moment que deux tables : classiques et felins
Créons une table bidon, puis supprimons la.
>Create table bidon(truc int);
>Show tables;
+------------------------+
| Tables_in_publications |
+------------------------+
| bidon |
| classiques |
| felins |
+------------------------+
>DROP TABLE bidon;
>Show tables;
+------------------------+
| Tables_in_publications |
+------------------------+
| classiques |
| felins |
+------------------------+
>DESCRIBE classiques;
Résultat :
+--------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | | NULL | |
| titre | varchar(128) | YES | | NULL | |
| type | varchar(16) | YES | | NULL | |
| annee | char(4) | YES | | NULL | |
+--------+--------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> describe felins;
+---------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+----------------+
| id | smallint(6) | NO | PRI | NULL | auto_increment |
| famille | varchar(32) | NO | | NULL | |
| nom | varchar(32) | NO | | NULL | |
| age | tinyint(4) | NO | | NULL | |
+---------+-------------+------+-----+---------+----------------+
>ALTER TABLE classiques RENAME anciensAuteurs;
>ALTER TABLE anciensAuteurs RENAME classiques;
>ALTER TABLE classiques MODIFY annee smallint;
Les données du champ annee qui était des char(4) vont être convertis par MySQL en smallint
>ALTER Table classiques ADD id int unsigned not null auto_increment key;
Remarquer que dans les commandes les qualificatifs suivent le type (int unsigned ....).
mysql> DESCRIBE classiques;
+--------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------------+------+-----+---------+----------------+
| auteur | varchar(128) | YES | | NULL | |
| titre | varchar(128) | YES | | NULL | |
| type | varchar(16) | YES | | NULL | |
| annee | char(4) | YES | | NULL | |
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
+--------+------------------+------+-----+---------+----------------+
Comme le champ type peut prétrer à confusion avec un mot clé de MySQL on le renomme catégorie :
>ALTER TABLE classiques CHANGE type categorie varchar(16);
On doit repréciser le type du champ, varchar(16) ici, même si on ne le modifie pas.
Résultat :
mysql> DESCRIBE classiques;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | | NULL | |
| titre | varchar(128) | YES | | NULL | |
| categorie | varchar(16) | YES | | NULL | |
| annee | smallint(6) | YES | | NULL | |
+-----------+--------------+------+-----+---------+-------+
mysql> SELECT * FROM classiques;
+---------------------+-----------------------------+--------------+-------+
| auteur | titre | categorie | annee |
+---------------------+-----------------------------+--------------+-------+
| Jane Austen | Orgueil et préjugés | Roman | 1811 |
| Mark Twain | Les aventures de Tom Sawyer | Roman | 1876 |
| Charles Darwin | De l'origine des espèces | Scientifique | 1856 |
| Charles Dickens | Le magasin d'antiquités | Roman | 1841 |
| William Shakespeare | Roméo et Juliette | Tragédie | 1594 |
+---------------------+-----------------------------+--------------+-------+
Ajoutons un champ pages (pas très utile) : >ALTER Table classiques ADD pages smallint unsigned;
et supprimons le car finalement, on n'en a pas besoin :
>ALTER Table classiques DROP pages;
ATTENTION : PAS DE DEMANDE DE CONFIRMATION : IRREVERSIBLE
>INSERT INTO classiques(auteur, titre, type, annee) VALUES('Jane Austen','Orgueil et préjugés','Roman','1811');
Exemple :
> INSERT INTO classiques(auteur, titre, type, annee) VALUES('Jane Austen','Orgueil et préjugés','Roman','1811');
> INSERT INTO classiques(auteur, titre, type, annee) VALUES('Mark Twain','Les aventures de Tom Sawyer','Roman','1876');
> INSERT INTO classiques(auteur, titre, type, annee) VALUES('Charles Darwin',"De l'origine des espèces",'Scientifique','1856');
> INSERT INTO classiques(auteur, titre, type, annee) VALUES('Charles Dickens',"Le magasin d'antiquités",'Roman','1841');
> INSERT INTO classiques(auteur, titre, type, annee) VALUES('William Shakespeare',"Roméo et Juliette",'Tragédie','1594');
Remarquer l'encadrement du texte par des guillemets double (") lorsqu'on veut introduire des apostrophes (') dans le texte. L'inverse fonctionne aussi. Alternativement on peut doubler l'apostrophe ou le guillemet pour l'introduire dans le texte.
Si on instancie tous les champs dans l'ordre, il est inutile de préciser leur liste :
> INSERT INTO classiques VALUES('William Shakespeare',"Roméo et Juliette",'Tragédie','1594');
Quand on inserre un enregistrement contenant un champ index AUTO_INCREMENT, on lui donne la valeur null, c'est le système qui se charge de sa valeur :
INSERT INTO felins VALUES(NULL, 'Lion', 'Léo', 4);
> SELECT * FROM classiques;
+---------------------+-----------------------------+--------------+-------+
| auteur | titre | type | annee |
+---------------------+-----------------------------+--------------+-------+
| Jane Austen | Orgueil et préjugés | Roman | 1811 |
| Mark Twain | Les aventures de Tom Sawyer | Roman | 1876 |
| Charles Darwin | De l'origine des espèces | Scientifique | 1856 |
| Charles Dickens | Le magasin d'antiquités | Roman | 1841 |
| William Shakespeare | Roméo et Juliette | Tragédie | 1594 |
+---------------------+-----------------------------+--------------+-------+
Nous avons déja présenté trois types d'indexation pouvant être associées à un champ :
•l'indexation créée par le mot clé INDEX qui permet une recherche ordinaire sur le texte de la donnée contenue dans ce champ, et généralement on limite cette recherche aux n premiers caractères de la donnée quand ce nombre n a été précisé.
•l'indexation créée par les mots clés PRIMARY KEY qui permet une recherche rapide à l'aide d'un champ généralement dédié au tri.
•l'indexation créee par le mot clé FULLTEXT qui permet une recherche sur un résumé spécial du texte.
On peut à la création de la table lui adjoindre un champ qui servira de clé primaire de tri comme suit :
CREATE TABLE essai (
auteur VARCHAR(128),
titre VARCHAR(128),
categorie VARCHAR(16),
annee SMALLINT,
isbn char(13) PRIMARY KEY) ENGINE InnoDB;
Ce qui conduit à :
mysql> describe essai;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | | NULL | |
| titre | varchar(128) | YES | | NULL | |
| categorie | varchar(16) | YES | | NULL | |
| annee | smallint(6) | YES | | NULL | |
| isbn | char(13) | NO | PRI | NULL | |
+-----------+--------------+------+-----+---------+-------+
On peut également, au moment de la création de la table, préciser les clés primaire ou ordinaires qui sont associées aux champs, comme ceci :
CREATE TABLE essai (
auteur VARCHAR(128),
titre VARCHAR(128),
categorie VARCHAR(16),
annee SMALLINT,
isbn CHAR(13),
INDEX(auteur(20)),
INDEX(titre(20)),
INDEX(categorie(4)),
INDEX(annee),
PRIMARY KEY (isbn)) ENGINE InnoDB;
Ce qui conduit à :
mysql> describe essai;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | MUL | NULL | |
| titre | varchar(128) | YES | MUL | NULL | |
| categorie | varchar(16) | YES | MUL | NULL | |
| annee | smallint(6) | YES | MUL | NULL | |
| isbn | char(13) | NO | PRI | NULL | |
+-----------+--------------+------+-----+---------+-------+
Les clés précisées par INDEX(champ(taille)) limitent à taille le nombre de caractères qui seront testés lors des recherches, pour accélérer celles-ci. Remarquer que ce nombre n'apparait pas dans le Describe.
Ces clés de tri peuvent être précisées après-coup par :
>ALTER TABLE classiques ADD INDEX(titre(20));
Considérons la table vide issue de la création suivante :
>CREATE TABLE classics1(
auteur VARCHAR(128),
titre VARCHAR(128),
categorie VARCHAR(16),
annee SMALLINT,
INDEX(auteur(20)),
INDEX(titre(20)),
INDEX(categorie(4)),
INDEX(annee)) ENGINE InnoDB;
où on a précisés les index étendus pour chaque champ, mais qui ne comporte pas de champ avec un index de tri primaire :
> DESCRIBE classics1;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | MUL | NULL | |
| titre | varchar(128) | YES | MUL | NULL | |
| categorie | varchar(16) | YES | MUL | NULL | |
| annee | smallint(6) | YES | MUL | NULL | |
+-----------+--------------+------+-----+---------+-------+
On peut ajouter après coup un champ servant de clé primaire de tri isbn comme suit :
> ALTER TABLE classics1 ADD isbn char(13) PRIMARY KEY;
ce quit conduit à :
> DESCRIBE classics1;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | MUL | NULL | |
| titre | varchar(128) | YES | MUL | NULL | |
| categorie | varchar(16) | YES | MUL | NULL | |
| annee | smallint(6) | YES | MUL | NULL | |
| isbn | char(13) | NO | PRI | NULL | |
+-----------+--------------+------+-----+---------+-------+
Mais si la table possède déjà des enregistrements, comme notre Table classiques, cette même commande génère une erreur :
mysql> ALTER TABLE classiques ADD isbn char(13) PRIMARY KEY;
ERROR 1062 (23000): Duplicata du champ '' pour la clef 'PRIMARY'
car le champ isbn n'étant pas définie dans les enregistrements déjà présents dans la table, va se voir attribuer une valeur NULL ce qui est interdit pour une clé primaire.
Pour ajouter ce champ avec l'attribut PRIMARY à la table classiques qui possède déjà des enregistrements, on va tricher et procéder en 3 temps :
1 - Ajouter le champs isbn, sans l'attribut primary
>ALTER TABLE classiques ADD isbn CHAR(13);
2 - Remplir tous les champs isbn des enregistrements existants
>UPDATE classiques SET isbn='9781598184891' WHERE annee='1876';
>UPDATE classiques SET isbn='9780582506206' WHERE annee='1811';
>UPDATE classiques SET isbn='9780517123201' WHERE annee='1856';
>UPDATE classiques SET isbn='9780099533474' WHERE annee='1841';
>UPDATE classiques SET isbn='9780192814968' WHERE annee='1594';
3 - Transformer ce champ (dont les valeurs sont maintenant non NULL) en clé primary.
>ALTER TABLE classiques ADD PRIMARY KEY(isbn);
>DESCRIBE classiques;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | | NULL | |
| titre | varchar(128) | YES | | NULL | |
| categorie | varchar(16) | YES | | NULL | |
| annee | smallint(6) | YES | | NULL | |
| isbn | char(13) | NO | PRI | NULL | |
+-----------+--------------+------+-----+---------+-------+
Pour faire des recherches textuelles plus tolérantes on attribue aux champs concernés une clé d'indexation FULLTEXT qui contient une version simplifiée du texte sans les mots vides de sens (le, la , de , est, à.. en français, appelés stopwords en anglais : the , as, is, of...). Cette clé n'est applicable qu'aux champs char, varchar ou text.
On ajoute des index FULLTEXT de la même manière que les INDEX ordinaires, en remplaçant le mot clé INDEX par FULLTEXT et sans préciser de taille.
> ALTER TABLE classiques ADD FULLTEXT(auteur);
> ALTER TABLE classiques ADD FULLTEXT(titre);
> ALTER TABLE classiques ADD INDEX(titre);
mysql> describe classiques;
+-----------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+--------------+------+-----+---------+-------+
| auteur | varchar(128) | YES | MUL | NULL | |
| titre | varchar(128) | YES | MUL | NULL | |
| categorie | varchar(16) | YES | | NULL | |
| annee | smallint(6) | YES | MUL | NULL | |
| isbn | char(13) | NO | PRI | NULL | |
+-----------+--------------+------+-----+---------+-------+
Certaines versions acceptent ADD FULLTEXT(auteur, titre); permettant l'ajout simultané de plusieurs champs, mais pas la mienne.
Pour voir la nature des index, utiliser la commande SHOW INDEX FROM ..
> show index from classiques;
+------------+------------+----------+--------------+-------------+-----------+-------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality |
+------------+------------+----------+--------------+-------------+-----------+-------------+
| classiques | 0 | PRIMARY | 1 | isbn | A | 6 |
| classiques | 1 | annee | 1 | annee | A | 6 |
| classiques | 1 | titre | 1 | titre | NULL | 6 |
| classiques | 1 | auteur | 1 | auteur | NULL | 6 |
+------------+------------+----------+--------------+-------------+-----------+-------------+
+----------+--------+------+------------+---------+---------------+
| Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+--------+------+------------+---------+---------------+
| NULL | NULL | | BTREE | | |
| NULL | NULL | YES | BTREE | | |
| NULL | NULL | YES | FULLTEXT | | |
| NULL | NULL | YES | FULLTEXT | | |
+----------+--------+------+------------+---------+---------------+
où on voit que le champ isbn doit avoir une donnée unique (non-unique = 0), type PRIMARY, trié par l'algorithme BTREE, alors que les champs annee, titre et auteur peuvent contenir de données identiques (non-unique = 1). Le champ annee est trié par BTREE et les champs auteur et titre sont triés par l'algorithme FULLTEXT.
Quelquesoit le type de l'attribut l'indexation :
>ALTER TABLE essai DROP INDEX annee;
Remarque : ... ADD INDEX(annee); avec parenthèses mais ... DROP INDEX annee; sans parenthèses !
La syntaxe de base de la sélection d'information est :
>SELECT qqchose FROM nomTable;
où qqchose peut être, par exemple :
•* : pout tous les enregistrements
•auteur, titre : seulement
•COUNT(*) : renvoi le nombre d'enregistrements
•DISTINCT qqchose : ne renvoi qu'une seule instance des réponse identiques
Lorsque dans une requête le nom d'un champ est suivi de AS truc, le nom truc devient un alias (synonyme) du nom du paramètre.
... nomParametre AS par .... => par devient un alias de nomParametre.
Dans l'affichage de la réponse le nom du paramètre sera remplacé par son alias.
mysql> select auteur, categorie AS cat from classiques;
+---------------------+--------------+
| auteur | cat |
+---------------------+--------------+
| Charles Dickens | Bouquin |
| Charles Dickens | Bouquin |
| William Shakespeare | Tragédie |
| Charles Darwin | Scientifique |
| Jane Austen | Bouquin |
| Mark Twain | Bouquin |
+---------------------+--------------+
Dans la syntaxe des requêtes les paramètres sont toujours précédés et suivis soit d'un mot clé, d'une virgule ou d'un point virgule. Cela est utilisé pour générer un raccourci de la syntaxe ... parametre AS synonyme ... en ... parametre synonyme ... sans le mot clé AS, mais cette syntaxe qui peut entrainer des confusions est particulièrement dangereuse.
SELECT auteur,titre FROM classiques ORDER BY auteur;
seront classés par auteur ordre alphabétique croissant (A, B,...)
SELECT auteur,titre FROM classiques order by auteur DESC, annee;
+---------------------+-----------------------------+
| auteur | titre |
+---------------------+-----------------------------+
| William Shakespeare | Roméo et Juliette |
| Mark Twain | Les aventures de Tom Sawyer |
| Jane Austen | Orgueil et préjugés |
| Charles Dickens | Le magasin d'antiquités |
| Charles Dickens | La Petite Dorrit |
| Charles Darwin | De l'origine des espèces |
+---------------------+-----------------------------+
enregistrements classés auteur en ordre inverse et année en ordre direct (on aurait pu préciser annee ASC).
mysql> SELECT COUNT(auteur) FROM classiques;
+---------------+
| COUNT(auteur) |
+---------------+
| 6 |
+---------------+
On affiche seulement le nombre d'auteurs et non pas leur nom.
mysql> SELECT categorie, COUNT(auteur) FROM classiques;
+-----------+---------------+
| categorie | COUNT(auteur) |
+-----------+---------------+
| Bouquin | 6 |
+-----------+---------------+
On demande l'affichage de la catégorie, mais on n'obtient que celle du dernier (ou du premier ?) enregistrement.
mysql> SELECT categorie, COUNT(auteur) FROM classiques GROUP BY categorie;
+--------------+---------------+
| categorie | COUNT(auteur) |
+--------------+---------------+
| Bouquin | 4 |
| Scientifique | 1 |
| Tragédie | 1 |
+--------------+---------------+
GROUP BY permet d'avoir le décompte COUNT(auteur) par contenu du champ categorie.
On peut limiter l'inspection de la taille à une cetaine zone en postfixant la requête par :
.... LIMIT ideb, imax;
ou ideb est le numéro de la ligne de la table où commencera l'inspection (débute à 0) et imax est le nombre maximal de lignes à fournir.
On peut ajuster la sélection avec WHERE =. Exemple :
INSERT INTO classiques(auteur, titre, categorie, annee, isbn)
VALUES('Charles Dickens','La Petite Dorrit','Roman','1857','9780141439969');
permet d'avoir 2 enregistrements avec Charles Dickens que l'on pourra sélectionner par :
> SELECT * FROM classiques WHERE auteur="Charles Dickens";
La comparaison par signe = demandant trop de précision, on peut la simplifier en remplaçant le signe = par LIKE. Dans ce cas, dans la chaine de comparaison on peut utiliser le caractère % pour remplacer un début ou une fin de chaine quelconque. Exemple :
> SELECT * FROM classiques WHERE titre LIKE "%origine%";
+----------------+--------------------------+--------------+-------+---------------+
| auteur | titre | categorie | annee | isbn |
+----------------+--------------------------+--------------+-------+---------------+
| Charles Darwin | De l'origine des espèces | Scientifique | 1856 | 9780517123201 |
+----------------+--------------------------+--------------+-------+---------------+
Exemples :
> SELECT auteur,titre FROM classiques WHERE auteur LIKE "Charles%" AND auteur LIKE "%Darwin";
+----------------+--------------------------+
| auteur | titre |
+----------------+--------------------------+
| Charles Darwin | De l'origine des espèces |
+----------------+--------------------------+
> SELECT auteur,titre FROM classiques WHERE auteur LIKE "%Mark Twain%" OR auteur LIKE "%Samuel Langhorne Clemens%";
+------------+-----------------------------+
| auteur | titre |
+------------+-----------------------------+
| Mark Twain | Les aventures de Tom Sawyer |
+------------+-----------------------------+
> SELECT auteur,titre FROM classiques WHERE auteur LIKE "Charles%" AND auteur NOT LIKE "%Darwin";
+-----------------+-------------------------+
| auteur | titre |
+-----------------+-------------------------+
| Charles Dickens | Le magasin d'antiquités |
| Charles Dickens | La Petite Dorrit |
+-----------------+-------------------------+
Sur les champs dotés d'une clé d'indexation FULLTEXT on peut effectuer des recherches floues en précisant un ou quelques mots qui seront recherchés le texte, mais attention au fait que certains mots très fréquents comme le, la, et... sont ignorés.
> SELECT auteur,titre FROM classiques WHERE MATCH(titre) AGAINST('et');
Empty set (0.00 sec)
Réponse vide car bien que présent le mot et est ignoré. Pour qu'un mot vide de sens (stopword, des par exemple) soit pris en compte, il faut enserrer la chaine de caractère où il apparait entre guillemets comme ceci 'AGAINST('"origine des"');
Pour qu'un enregistrement soit sélectionné, il suffit qu'il y ait un certain niveau de concordance.
> SELECT auteur,titre FROM classiques WHERE MATCH(titre) AGAINST('antiquités truc magasin');
+-----------------+-------------------------+
| auteur | titre |
+-----------------+-------------------------+
| Charles Dickens | Le magasin d'antiquités |
+-----------------+-------------------------+
trouvé une réponse bien que la correspondance ne soit pas parfaite (mot truc absent et ordre des mots non conforme).
Les mots doivent être identiques, mais la case est indifférente.
mysql> SELECT auteur,titre FROM classiques WHERE MATCH(auteur) AGAINST('Dick');
Empty set (0.00 sec)
mysql> SELECT auteur,titre FROM classiques WHERE MATCH(auteur) AGAINST('dicKEns');
+-----------------+-------------------------+
| auteur | titre |
+-----------------+-------------------------+
| Charles Dickens | Le magasin d'antiquités |
| Charles Dickens | La Petite Dorrit |
+-----------------+-------------------------+
> SELECT auteur,titre FROM classiques WHERE MATCH(auteur) AGAINST('charles') AND NOT MATCH(titre) AGAINST('magasin');
+-----------------+--------------------------+
| auteur | titre |
+-----------------+--------------------------+
| Charles Dickens | La Petite Dorrit |
| Charles Darwin | De l'origine des espèces |
+-----------------+--------------------------+
Fonctionne comme SELECT * que l'on remplace par DELETE. Exemple :
DELETE FROM classiques WHERE titre='La Petite Dorrit';
mysql> UPDATE classiques SET auteur='Mark Twain (Samuel Langhorne Clemens)'
-> WHERE auteur='Mark Twain';
mysql> SELECT auteur, titre FROM classiques;
+---------------------------------------+-----------------------------+
| auteur | titre |
+---------------------------------------+-----------------------------+
| Charles Dickens | Le magasin d'antiquités |
| Charles Dickens | La Petite Dorrit |
| William Shakespeare | Roméo et Juliette |
| Charles Darwin | De l'origine des espèces |
| Jane Austen | Orgueil et préjugés |
| Mark Twain (Samuel Langhorne Clemens) | Les aventures de Tom Sawyer |
+---------------------------------------+-----------------------------+
> UPDATE classiques SET auteur="Mark Twain" where auteur='Mark Twain (Samuel Langhorne Clemens)';
> UPDATE classiques SET categorie='Bouquin' WHERE categorie='Roman';
mysql> SELECT auteur, titre, categorie FROM classiques;
+---------------------+-----------------------------+--------------+
| auteur | titre | categorie |
+---------------------+-----------------------------+--------------+
| Charles Dickens | Le magasin d'antiquités | Bouquin |
| Charles Dickens | La Petite Dorrit | Bouquin |
| William Shakespeare | Roméo et Juliette | Tragédie |
| Charles Darwin | De l'origine des espèces | Scientifique |
| Jane Austen | Orgueil et préjugés | Bouquin |
| Mark Twain | Les aventures de Tom Sawyer | Bouquin |
+---------------------+-----------------------------+--------------+
Créons une deuxième table :
> CREATE TABLE clients (
nom VARCHAR(128),
isbn VARCHAR(13),
PRIMARY KEY (isbn)) ENGINE InnoDB;
> INSERT INTO clients(nom,isbn)
VALUES('Jean Bergeron','9780099533474'), ('Marie Savard','9780582506206'), ('Jacques Vachon','9780517123201');
> SELECT * FROM clients;
+----------------+---------------+
| nom | isbn |
+----------------+---------------+
| Jean Bergeron | 9780099533474 |
| Jacques Vachon | 9780517123201 |
| Marie Savard | 9780582506206 |
+----------------+---------------+
Faisons une requête qui sélectionne des champs de ces 2 tables :
mysql> select nom, auteur, titre FROM clients, classiques;
+----------------+---------------------+-----------------------------+
| nom | auteur | titre |
+----------------+---------------------+-----------------------------+
| Jean Bergeron | Charles Dickens | Le magasin d'antiquités |
| Jacques Vachon | Charles Dickens | Le magasin d'antiquités |
| Marie Savard | Charles Dickens | Le magasin d'antiquités |
| Jean Bergeron | Charles Dickens | La Petite Dorrit |
| Jacques Vachon | Charles Dickens | La Petite Dorrit |
| Marie Savard | Charles Dickens | La Petite Dorrit |
| Jean Bergeron | William Shakespeare | Roméo et Juliette |
| Jacques Vachon | William Shakespeare | Roméo et Juliette |
| Marie Savard | William Shakespeare | Roméo et Juliette |
| Jean Bergeron | Charles Darwin | De l'origine des espèces |
| Jacques Vachon | Charles Darwin | De l'origine des espèces |
| Marie Savard | Charles Darwin | De l'origine des espèces |
| Jean Bergeron | Jane Austen | Orgueil et préjugés |
| Jacques Vachon | Jane Austen | Orgueil et préjugés |
| Marie Savard | Jane Austen | Orgueil et préjugés |
| Jean Bergeron | Mark Twain | Les aventures de Tom Sawyer |
| Jacques Vachon | Mark Twain | Les aventures de Tom Sawyer |
| Marie Savard | Mark Twain | Les aventures de Tom Sawyer |
+----------------+---------------------+-----------------------------+
Le résultat est une liste hétéroclite combinant les données des deux tables comprenant les livres " La Petite Dorrit" et " Orgueil et préjugés" bien qu'ils n'aient été acheté par aucun client.
On limite la liste aux livres effectivement achetés comme suit :
> select nom, auteur, titre FROM clients, classiques WHERE clients.isbn=classiques.isbn;
+----------------+-----------------+--------------------------+
| nom | auteur | titre |
+----------------+-----------------+--------------------------+
| Jean Bergeron | Charles Dickens | Le magasin d'antiquités |
| Jacques Vachon | Charles Darwin | De l'origine des espèces |
| Marie Savard | Jane Austen | Orgueil et préjugés |
+----------------+-----------------+--------------------------+
ou bien comme suit :
> select nom, auteur, titre FROM clients NATURAL JOIN classiques;
+----------------+-----------------+--------------------------+
| nom | auteur | titre |
+----------------+-----------------+--------------------------+
| Jean Bergeron | Charles Dickens | Le magasin d'antiquités |
| Jacques Vachon | Charles Darwin | De l'origine des espèces |
| Marie Savard | Jane Austen | Orgueil et préjugés |
+----------------+-----------------+--------------------------+
qui utilise la syntaxe NATURAL JOIN qui n'associe que les enregistrements possédant les même données dans les champs communs.
On peut aussi préciser les colonnes qui doivent correspondre comme ceci :
> select nom, auteur, titre FROM clients JOIN classiques ON clients.isbn=classiques.isbn;
+----------------+-----------------+--------------------------+
| nom | auteur | titre |
+----------------+-----------------+--------------------------+
| Jean Bergeron | Charles Dickens | Le magasin d'antiquités |
| Jacques Vachon | Charles Darwin | De l'origine des espèces |
| Marie Savard | Jane Austen | Orgueil et préjugés |
+----------------+-----------------+--------------------------+
On peut utiliser les alias pour raccourcir les noms des tables utilisés dans les recherches, ce qui facilite l'écriture des requêtes :
> SELECT nom, auteur, titre, categorie AS cat FROM clients AS x, classiques AS y WHERE x.isbn=y.isbn;
+----------------+-----------------+--------------------------+--------------+
| nom | auteur | titre | cat |
+----------------+-----------------+--------------------------+--------------+
| Jean Bergeron | Charles Dickens | Le magasin d'antiquités | Bouquin |
| Jacques Vachon | Charles Darwin | De l'origine des espèces | Scientifique |
| Marie Savard | Jane Austen | Orgueil et préjugés | Bouquin |
+----------------+-----------------+--------------------------+--------------+
à comparer avec :
> SELECT nom, auteur, titre, categorie AS cat FROM clients, classiques WHERE clients.isbn=classiques.isbn;
Le gain est faible, mais il y a un gain.
Les bases de données bien formées sont constituées de tables qui respectent trois règles de normalité énoncées par E.F. Codd. Si une table ne respecte pas une règle elle doit être éclatée pour que chaque sous table la respecte. Ce processus s'appelle la normalisation et il comporte 3 étapes :
1.La 1ère normalisation traite de la redondance des données entre les colonnes :
◦une colonne unique pour chaque type de donnée
◦chaque colonne ne contient qu'une seule valeur
◦une clé primaire (affectée à une colonne) doit permettre d'identifier de manière unique chaque enregistrement
Si ce n'est pas le cas la table est éclatée pour que chaque sous-table respecte ces règles.
2.La 2ème normalisation (qui suppose la 1ère déja effectuée) traite de la redondance parmi les lignes : Chaque ligne doit contenir des données différentes. Si ce n'est pas le cas la table est éclatée pour que chaque sous-table respecte les règles des 2 normalisations.
3.La 3ème règle, qui est faculative en particulier si la base est très sollicitée, exige qu'un champ qui est associé à un autre champ de la même table alors que ces deux champs ne sont pas liées, doit être placée dans une autre table (sauf si ce champ dépend d'une clé primaire). Cette troisième règle est généralement ignorée, car par exemple dans le cas d'adresses clients cette règle conduit à générer des tables séparées pour les noms des villes, les codes postaux, les noms et abbréviations des départements, les noms des régions... et pour reconstituer l'adresse, il faut parcourir plusieurs table pour, par exemple à partir du code postal retrouver le nom de la ville, du département, de la région...
Considérons la base suivante :
CREATE TABLE comptes (
numero INT, solde FLOAT, PRIMARY KEY(numero)
) ENGINE InnoDB;
INSERT INTO comptes(numero, solde) VALUES(12345, 1025.50);
INSERT INTO comptes(numero, solde) VALUES(67890, 140.00);
SELECT * FROM comptes;
+--------+--------+
| numero | solde |
+--------+--------+
| 12345 | 1025.5 |
| 67890 | 140 |
+--------+--------+
Comme plusieurs utilisateurs pouvent accéder en même temps à une base, cela peut amener à des incohérence. Par exemple un utilisateur peut consulter un compte, puis vouloir retirer la somme lue, et ne pas l'obtenir si quelqu'un d'autre la retirée entre temps. Pour éviter cela, on peut grouper les séquences de requêtes dans une transaction en les faisant débuter par START TRANSACTION ou plus simplement BEGIN.
BEGIN;
UPDATE comptes SET solde=solde+25.11 WHERE numero=12345;
COMMIT;
SELECT * FROM comptes;
+--------+---------+
| numero | solde |
+--------+---------+
| 12345 | 1050.61 |
| 67890 | 140 |
+--------+---------+
L'instruction COMMIT termine la transaction et demande la validation de ses requêtes. En son absence, l'instruction ROLLBACK permet de revenir à l'état précédent la transaction. Exemple :
>BEGIN;
> UPDATE comptes SET solde=solde-250 WHERE numero=12345;
> UPDATE comptes SET solde=solde+250 WHERE numero=67890;
> SELECT * FROM comptes;
+--------+--------+
| numero | solde |
+--------+--------+
| 12345 | 800.61 |
| 67890 | 390 |
+--------+--------+
> ROLLBACK;
> SELECT * FROM comptes;
+--------+---------+
| numero | solde |
+--------+---------+
| 12345 | 1050.61 |
| 67890 | 140 |
+--------+---------+
Avant d'appliquer une requête, on peut l'essayer précédée du mot clé EXPLAIN pour analyser con contenu et voir si elle est bien formée. Exemple :
> SELECT * FROM comptes WHERE numero='12345';
+--------+--------+
| numero | solde |
+--------+--------+
| 12345 | 800.61 |
+--------+--------+
> EXPLAIN SELECT * FROM comptes WHERE numero='12345';
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
| 1 | SIMPLE | comptes | NULL | const | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------+
select_type = SIMPLE : la requête ne concerner qu'une table.
table = comptes : la requête concerne la table comptes
type = const : la requête est du type const. Les différents types sont ALL, index, range, ref (réference), eq_ref, const, system et NULL.
possible_keys = PRIMARY : l'utilisation d'une clé primaire est possible avec cette requête (ce qui est fait)
key = PRIMARY : Effectivement la requête utilise une clé primaire.
key_len = 4 : la clé est codée sur 4 octets.
rows = 1 : le nombre de lignes concernées par la requête.
Dans MySql les variables sont précédées du caractère @ et définies à l'aide du mot clé SET :
SET @année = "2022";
On peut définir plusieurs variables à la suite en séparants les affectations par des virgules :
SET @auteur = "Emily Brontë",
@titre = "Les Hauts de Hurlevent",
@categorie = "Roman classique",
@annee = "1847",
@isbn = "9780553212587";
On peut également définir des patrons de commandes pour effectuer des requêtes avec les places des arguments réservées par des "?", comme ceci :
PREPARE my_cde FROM "INSERT INTO classiques VALUES(?,?,?,?,?)";
Ensuite cette commnde est exécutée de la manière suivante :
EXECUTE my_cde USING @auteur,@titre,@categorie,@annee,@isbn;
Lorsqu'elle n'est plus utile, on récupère ses ressources par la commande :
DEALLOCATE PREPARE my_cde;
Depuis PHP l'accès à une des bases de données présentes peut se faire à travers les méthodes de la classe mysqli. On crée une instance de connexion comme ceci :
$conn = new mysqli('localhost', 'michel', 'pistache', 'publications');
où le 1er argument est l'url de l'hote, puis l'identification de l'utilisateur de la base, son mot de passe et le nom de la base de données que l'on veut utiliser (c'est l'équivalent de la commande MySQL : use publications). Après cette instruction, si $conn->connect_error est true, la connexion a échouée, sinon elle a réussie.
Dès qu'on a terminé de travailler avec la base, il est bon de libérer la connexion :
$conn->close();
Les requêtes à la base de données sont envoyées en utilisant la chaine de caractère standard MySQL comme paramètre de la méthode $conx->query. Exemple :
$result = $conn->query("SELECT * FROM classiques");
La méthode renvoie l'objet $result de la classe mysqli_result qui permet de manipuler la réponse à la requête.
En particulier $result->num_rows est le nombre de lignes de la réponse.
Certaine sparties des chaines de caractères que l'on passe en requête proviennent souvent d'un dialogue avec l'usager et sont reçues via des $_GET[param] ou $_POST[$param]. Si ces chaînes contiennent des caractères spéciaux comme des guillemets simples ', ou doubles ", ou des barres obliques \, ceux-ci peuvent permettre d'altérer la construction du string requète en cours en la terminant et/ou en lui ajoutant des actions malveillantes. Ainsi pour que ces caractères soient conservés (car il se peut qu'ils soient désirés dans le texte) et ramenés à un rôle de caractère ordinaire, ils sont échappés afin de ne pas être interprétés comme de caractère de commande. Dans ce but la classe mysqli fournit la méthode real_escape_string($str) qui échappe les caractères qui doivent l'être. Ainsi au lieu de mettre :
$auteur = $_POST['auteur'];
on mettra :
$auteur = conn->real_escape_string($_POST['auteur']);
et si l'auteur s'appelle O'Connor, la fonction va transformer son nom en O\'Connor. Ainsi l'apostrophe sera toujours interprété comme un complément des lettres et non comme un caractère de commande de début ou fin de chaine de caractères.
Pour manipuler les lignes on dispose de plusieurs méthodes dont la méthode fetch_assoc() :
$tabli = $result->fetch_assoc();
qui renvoie le tableau associatif du contenu d'une ligne, l'une après l'autre et false après la dernière, ce qui permet de l'utiliser dans un while ($tabli = $result->fetch_assoc())...
La methode $tabli = $result->fetch_row() renvoie quand à elle un tableau indexé.
La méthode fetch_assoc() a pour synonyme la méthode fetch_array(MYSQLI_ASSOC).
La méthode fetch_row() a pour synonyme la méthode fetch_array(MYSQLI_NUM).
Enfin la méthode fetch_array() ou fetch_array(MYSQLI_BOTH) renvoie le contenu d'une ligne sous forme d'un tableau qui est à la fois accessible en numérique (par un index) et par association avec une clé.
La méthode $result->data_seek($li) permet de positionner le pointage sur la ligne numéro $li (avec 0 <= $li < $result->num_rows) avant l'accès par une des méthodes fetch_assoc, fetch_row, ou fetch_array.
Dès que le résultat renvoyé ne sert plus il est judicieux de libérer ses ressources par un close() :
$result->close();
Considérons des champs reçus par un $_POST. Voici comment on peut les enserrer dans la base :
// Fonction pour sécuriser les textes reçus par $_POST
function get_post($var)
{
global $conn;
return $conn->real_escape_string($_POST[$var]);
}
// TRAITEMENT RECEPTION <input type="submit" name="ajouter".....
if (isset($_POST["ajouter"]) &&
isset($_POST['auteur']) &&
isset($_POST['titre']) &&
isset($_POST['categorie']) &&
isset($_POST['annee']) &&
isset($_POST['isbn']))
{
$auteur = get_post('auteur');
$titre = get_post('titre');
$categorie = get_post('categorie');
$annee = get_post('annee');
$isbn = get_post('isbn');
$query = "INSERT INTO classiques VALUES" .
"('$auteur', '$titre', '$categorie', '$annee', '$isbn')";
$result = $conn->query($query);
if (!$result) echo "Échec de l'insertion<br><br>";
}
Si l'enregistrement possède un champ AUTO_INCREMENT, on lui donne la valeur NULL. La variable $conn->insert_id fournit la valeur de l'index attribué à cet enregistrement.
Il peut y avoir des problèmes sur un champ 'name' reçu de l'extérieur qui doit alimenter une requête. Examinons le cas suivant :
...
$sql = "SELECT * FROM addresses WHERE name='".$_GET['name']."'";
$result = mysql_query($sql);
...
un usager mal intentionné peut fournir dans name la chaine suivante :
name="; DELETE FROM addresses WHERE 1=1 OR name='
ce qui tranforme notre requête en :
$sql = "SQL SELECT * FROM addresses WHERE name="'; DELETE FROM addresses WHERE 1=1 OR name=' '' ; (Codage approximatif)
Détourne le travail en sélection des champs de nom vide, puis effacement de tous les champs. Pour éviter cela, on peut filtrer la chaine reçue par $conn->real_escape_string(chaine) qui enlève leur faculté aux caractères d'échappement en les rendant ordinaires, et en plus par htmlspecialchars ou htmlentities. On peut également filtrer les valeurs numériques par intval().
Exemple de fonction à utiliser pour filtrer les données provenant des $_GET ou $_POST :
function secure_string($string)
{
global $conn; // provenant de $conn = new mysqli(...);
return htmlspecialchars($conn->real_escape_string($string));
}
On peut également filtrer les valeurs numériques par intval().
Mais la meilleurs méthode consiste à utiliser les patrons de requêtes MySql préparées avec les zones réservées aux données spécifiées par des caractères "?" que sont présentés paragraphe 5.
Pour cela mysqli offre la méthode prepare qui fonctonne comme la requête PREPARE ... FROM de MySQL :
$mycde = $conn->prepare('INSERT INTO classiques VALUES(?,?,?,?,?)');
et l'objet généré offre la méthode bind_param pour lier les paramètres :
$mycde->bind_param('sssss', $auteur, $titre, $categorie, $annee, $isbn);
L'affectation des valeurs aux paramètres peut se faire ensuite (ou avant) et la méthode execute execute la requête EXECUTE .. USING .. :
$mycde->execute();
La propriété affected_rows contiendra le nombre d'enregistrements affectés.
L'instruction $mycde->close(); permet de libérer les ressources de cet objet.
Le premier argument de bind_param, ici 'sssss' est une chaine de caractères qui décrit le type des arguments qui suivent :
i correspond à une variable de type entier
d correspond à une variable de type nombre décimal
s correspond à une variable de type chaîne de caractères
b correspond à une variable de type BLOB, qui sera envoyé par paquets.
Exemple d'injection de données dans une base.
<?php
require_once 'login.php';
$conn = new mysqli($hn, $un, $pw, $db);
if ($conn->connect_error) die("Erreur fatale");
$query = "SET NAMES utf8"; // Force l'encodage en utf-8
$result = $conn->query($query);
if (!$result) die("Erreur fatale");
$mycde = $conn->prepare('INSERT INTO classiques VALUES(?,?,?,?,?)');
$mycde->bind_param('sssss', $auteur, $titre, $categorie, $annee, $isbn);
$auteur = "Emily Brontë";
$titre = "Les Hauts de Hurlevent";
$categorie = "Roman classique";
$annee = "1847";
$isbn = '9780553212587';
$mycde->execute();
printf("%d ligne insérée.\n", $mycde->affected_rows);
$mycde->close();
$conn->close();
?>
Création de la table :
<?php //definitionutilisateurs.php
require_once 'login.php';
// Rappel : $hn = nom d'hôte, $un = nom d'utilisateur,
// $pw = mdp, $db = base de données
$connexion = new mysqli($hn, $un, $pw, $db);
if ($connexion->connect_error) die("Erreur fatale");
$query = "CREATE TABLE utilisateurs (
prenom VARCHAR(32) NOT NULL,
nomfamille VARCHAR(32) NOT NULL,
nomutil VARCHAR(32) NOT NULL UNIQUE,
motdepasse VARCHAR(255) NOT NULL
)";
$result = $connexion->query($query);
if (!$result) die("Erreur fatale");
$prenom = 'Bertrand';
$nomfamille = 'Simard';
$nomutil = 'bsimard';
$motdepasse = 'monsecret';
$jeton = password_hash($motdepasse, PASSWORD_DEFAULT);
ajouter_utilisateur($connexion, $prenom, $nomfamille, $nomutil, $jeton);
$prenom = 'Pauline';
$nomfamille = 'Leduc';
$nomutil = 'pleduc';
$motdepasse = 'acrobate';
$jeton = password_hash($motdepasse, PASSWORD_DEFAULT);
ajouter_utilisateur($connexion, $prenom, $nomfamille, $nomutil, $jeton);
//$connexion->close();
function ajouter_utilisateur($connexion, $pn, $nf, $nu, $mp)
{
// Préparation de l'insertion sécurisée
$insertion = $connexion->prepare('INSERT INTO utilisateurs VALUES(?,?,?,?)');
// Association des valeurs aux paramètres
$insertion->bind_param('ssss', $pn, $nf, $nu, $mp);
// Exécution de l'insertion
$insertion->execute();
// Libération des ressources
$insertion->close;
}
?>
On remarquera que ce qui est stocké dans la table c'est le hash du mot passe et non pas le mot de passe.
Attention : Les variables d'authentification PHP $_SERVER['PHP_AUTH_USER']) et $_SERVER['PHP_AUTH_PW'] utilisées dans ce qui suit ne fonctionnent pas toujours. Voir la solution alternative présentée section Erreur : source de la référence non trouvée.
Le script suivant ne donne accès qu'aux utilisateurs présents dans la table du paragraphe précédent :
<?php // authentification2.php
// Connexion à la BD "publications" sur "localhost" accessible par "michel"
$hn = 'localhost'; $db = 'publications'; $un = 'michel'; $pw = 'pistache';
$connexion = new mysqli($hn, $un, $pw, $db);
if ($connexion->connect_error) die("Erreur fatale");
// On teste la réponse à la demande de nom d'usager - mot de passe faite plus bas
if (isset($_SERVER['PHP_AUTH_USER']) && isset($_SERVER['PHP_AUTH_PW']))
{ // On sécurise les réponses
$nu_temp = htmlentities($connexion->real_escape_string($_SERVER['PHP_AUTH_USER']));
$mp_temp = htmlentities($connexion->real_escape_string($_SERVER['PHP_AUTH_PW']));
// On demande la ligne avec l'id fournie
$query = "SELECT * FROM utilisateurs WHERE nomutil='$nu_temp'";
$result = $connexion->query($query);
if (!$result) die("Utilisateur introuvable");
elseif ($result->num_rows)
{
$row = $result->fetch_row();//conversion en tableau indexé standard
$result->close();
if (password_verify($mp_temp, $row[3]))//Le mot de passe est bon
{
session_start();
$_SESSION['prenom'] = $row[0];
$_SESSION['nomfamille'] = $row[1];
echo htmlspecialchars("$row[0] $row[1] :
Bonjour $row[0], vous êtes connecté en tant que '$row[2]'");
die ("<p><a href=continue.php>Cliquez ici pour continuer</a></p>");
}
else die("Nom d'utilisateur ou mot de passe non valide");
}
else die("Nom d'utilisateur ou mot de passe non valide");
}
else
{
header('WWW-Authenticate: Basic realm="Zone restreinte"');
header('HTTP/1.0 401 Unauthorized');
die ("Entrez votre nom d'utilisateur et votre mot de passe");
}
$connexion->close();
?>
Et voici le script continue.php appelé (en gras) en cas de succès.
<?php // continue.php - Exemple 12-6
session_start();
if (isset($_SESSION['prenom']))
{
$prenom = htmlspecialchars($_SESSION['prenom']);
$nomfamille = htmlspecialchars($_SESSION['nomfamille']);
echo "Rebonjour, $prenom.<br>
Votre nom complet est $prenom $nomfamille.<br>";
}
else echo "Cliquez <a href=authentification2.php>ici</a> " .
"pour vous identifier.";
?>
jQuery est une bibliothèque JavaScript libre et multiplateforme créée pour faciliter l'écriture de scripts côté client dans le code HTML des pages web.
On peut soit télécharger une version de la bibliothèque sur l'espace de son site et référencer cette version télécharger dans nos fichiers html, soit faire référence à une bibliothèque située sur un CDN (Content Delevery Network, cad un réeau de diffusion de contenu : RDC). On peut utiliser soit la version minimisée (non compressée pour la mise au point et le développement) soit la version compressée (en production, de plus elle est sans commentaires).
Pour l'utiliser on peut, par exemple, inclure la bibliothèque standard comme ceci :
<script src='https://code.jquery.com/jquery-3.6.0.js'></scrip>
ou la version compressée comme ceci.
<script src='https://code.jquery.com/jquery-3.6.0.min.js'></scrip>
Tout appel à une fonctionnalité de jQuery débute par le caractère $.
http://sdz.tdct.org/sdz/des-applications-ultra-rapides-avec-node-js.html
Node est un environnement qui permet d'exécuter du code javascript coté serveur. Sous windows, un environnement node est fourni avec Emscripten (qui est un environnement qui permet d'incorporer du code C/C++ dans une page HTML).
Les expressions régulières, également appelés motifs, regex ou expressions rationnelles ou expressions normales sont des chaines de caractères qui prennent une signification particulière.
En javascript les expressions rationnelles sont surtout utilisées par les méthodes split(), test() et replace().
Dans PHP elles sot surtout utilisées avec preg_match, preg_match_all et preg_replace.
Les expressions rationnelles sont encadrées par deux barres obliques /...../ mais dans les fichiers .htaccess, elles sont directement utilisées sans ces délimiteurs.
Rappel sur les expressions rationnelles :
Motif |
Signification |
Exemple |
/ |
Début et fin de l'expression rationnelle |
/hello/ |
. |
Correspond à tout caractère unique saufnne ligne |
c.t correspondra à cat, cot, cut, etc. |
+ |
Répète le caractère de correspondance précédent une ou plusieurs fois |
a+ correspond à a, aa, aaa, etc. |
* |
Répète le caractère de correspondance précédent zéro ou plusieurs fois |
a* correspond à tout ce à quoi correspond a+, mais correspond aussi à la chaîne vide. |
? |
Rend la correspondance optionnelle (0 ou 1 fois) |
colou?r correspondra à color et colour. |
\ |
Echappe le caractère suivant |
\. correspondra à . (le point) et non à tout caractère unique comme expliqué plus haut |
^ |
correspondance au début de la chaîne |
^a correspond à une chaîne qui commence par a |
$ |
correspondance à la fin de la chaîne. |
a$ correspond à une chaîne qui se termine par a. |
( ) |
Regroupe plusieurs caractères en une seule entité, et conserve une correspondance à des fins d'utilisation dans une référence arrière. |
(ab)+ correspond à ababab - à savoir, le + s'applique au groupe. Pour plus de détails sur les références arrières, voir ci-dessous. |
[ ] |
Un caractère parmi l'ensemble |
c[uoa]t correspond à cut, cot ou cat. |
[p-t] |
Un caractère parmi l'ensemble p q r s t |
|
[^ ] |
Un caractère qui n'est pas dans l'ensemble |
c[^/]t correspond à cat ou c=t mais pas à c/t |
ab|yz |
Identifie ab et/ou yz |
|
\b |
Identifie une limite de mot |
|
\B |
Identifie une absence de limite de mot |
|
\d |
Identifie un seul chiffre |
|
\D |
Identifie un caractère non chiffre |
|
\n |
Identifie un caractère nlle ligne |
|
\r |
Identifie un caractère retour début de ligne |
|
\s |
Identifie un caractère d'espacement |
|
\S |
Identifie un caractère non espacement |
|
\t |
Identifie un caractère tabulation |
|
\w |
Identifie un caractère de mot [a-zA-Z0-9_] |
|
\W |
Identifie un caractère pas de mot |
|
\x |
Identifie le caractère x |
|
{n} |
Identifie exactement n occurences |
|
{n,} |
Identifie n occurences ou plus |
|
{min,max} |
Identifie entre min et max occurences |
|
Attention : Les caractères accentués àéè... ne sont pas inclus dans \w. Il faut les ajouter et définir la RegExp /a-zA-Z0-9_àâçèéêëîôùûüÂÀÉÊÈÔÛÇ/.
Après la RegExp on peut ajouter un délimiteur final :
g |
global cad toutes les occurences trouvées et non pas uniquement la première |
i |
indépendant de la casse |
m |
recherche multiligne |
u |
utilisation d unicode |
y |
adhérence (sticky ???) |
Exemple dans javascript : Remplacer les espaces ordinaires par des espaces html insécables ' ':
String.replace(/ /g, ' ');
Effacer les espaces en fim de lignes (simuler trim) :
String.replace(/^\s+|\s+$/g,'');
Remplacer les retour ligne window par unix :
const lines = source.split(/\r\n|\n/);
destination = lines.join('\n');