Compléments sur les bases de PHP

Alexandre Niveau
GREYC — Université de Caen
En partie adapté du cours de Jean-Marc Lecarpentier
  • type checking
  • dates
  • namespaces
  • system()
  • serialisation
  • input filters ⇒ plutôt du web

Bizarreries des tableaux

Le fait que les tableaux, même numériques, soient en fait des ordered map peut causer des surprises !

<?php
$tab 
= array();
$tab[0] = "zéro";
$tab[1] = "un";
$tab[100] = "cent";
$tab[] = "je suis le dernier";
$tab[2] = "deux";
$tab[] = "où suis-je ?";
foreach (
$tab as $key => $val) {
  echo 
"$key --> $val\n";
}
0 --> zéro
1 --> un
100 --> cent
101 --> je suis le dernier
2 --> deux
102 --> où suis-je ?

  • Lors du parcours avec un foreach, seul l'ordre d'insertion dans le tableau compte.
  • La construction $tab[] = ... ajoute la valeur à une clef numérique qui est la plus grande clef numérique + 1
Attention, on ne peut pas utiliser comme indice une chaîne de caractères « contenant » un entier, car elle est convertie en entier. Une façon de l'éviter est de faire commencer la chaîne par un +. ce qui empêche d'utiliser === comme on voudrait
<?php
$tab 
= array();
$tab['0'] = 'chaîne 0';
$tab[0] = 'entier 0';
$tab['1.0'] = 'chaîne 1.0';
$tab[1.0] = 'flottant 1.0';
$tab[1.1] = 'flottant 1.1';
$tab['+1'] = 'chaîne +1';
foreach (
$tab as $key => $val) {
  echo 
"$key --> $val\n";
}
0 --> entier 0
1.0 --> chaîne 1.0
1 --> flottant 1.1
+1 --> chaîne +1

L'indice '0' est converti en 0, l'indice '100' est converti en 100, l'indice '-1' est converti en -1, mais les indices '0.0', '+1', '03', '00', '0x0', '2e2' restent des chaînes, car ce ne sont pas des « entiers décimaux valides ne commençant pas par + » (0.0 est flottant, +1 commence par +, 03 et 00 seraient en octal, 0x0 en hexa, et 2e2 un float).
détails sur la conversion des indices dans le manuel

Les tableaux sont manipulés « par copie » : dans le code suivant, la 2e ligne crée une copie du tableau !

<?php
$x 
= array('toto' => 1'tata' => 2);
$y $x;
$x['titi'] = 3;
echo 
count($x) . "\n";
echo 
count($y) . "\n";
3
2

Ce n'est pas une erreur d'utiliser un nombre ou null comme un tableau. Ça renvoie simplement toujours null.
$tab null;
var_export($tab['toto']);  // NULL
$tab 1234;
var_export($tab['toto']);  // NULL

Fonctions utiles avec les tableaux

Principales fonctions utiles avec les tableaux :
  • key_exists($key, $array) : renvoie true si le tableau $array contient la clef $key, et false sinon (il existe l'alias plus long array_key_exists)
  • in_array($val, $array, true) : renvoie true si le tableau $array contient la valeur $val, et false sinon.
    Le troisième paramètre sert à utiliser la comparaison stricte (celle de ===) plutôt que la comparaison faible (celle de ==), qui est le défaut. À ne pas oublier ! Si on l'enlève ou si on le met à false, les résultats peuvent être très surprenants…
    l'explication est que le tableau contient true et false. Toute valeur est égale soit à l'un soit à l'autre, donc in_array renverra toujours true.
  • array_search pour récupérer l'indice d'une valeur dans un tableau. Attention à la comparaison faible :
    • il faut passer true comme 3e paramètre pour que la comparaison lors de la recherche soit stricte, comme pour in_array
    • mais il faut aussi faire attention à la valeur de retour : la fonction peut renvoyer 0 ou false, ce n'est pas du tout pareil !
    • unset pour supprimer un élément ; attention, il n'y a pas de redécalage des indices, utiliser array_values derrière si besoin
    • Voir aussi array_keys, array_values, array_slice, et éventuellement array_flip

toutes les fonctions sur les tableaux

tuto sur la manipulation de tableaux

Fonctions anonymes

PHP permet d'utiliser des fonctions anonymes, comme en JavaScript.

On peut ainsi mettre une fonction dans une variable :
<?php
$toto 
= function () {
    echo 
"je suis anonyme\n";
};
$toto();

$titi = function ($x) {
    echo 
"\$x = $x\n";
};
$titi("salut");

$titi $toto;
$titi();
je suis anonyme
$x = salut
je suis anonyme

Cela évite de déclarer une fonction qui n'est utilisée qu'une seule fois

Particulièrement utile comme paramètre de certaines fonctions (voir la suite)

Map/Filter/Reduce sur des tableaux

Exemple d'utilisation de fonctions anonymes : filtrage ou modification des valeurs d'un tableau à l'aide d'une fonction

  • Modifier les valeurs d'un tableau : array_map($fonction, $tab) applique la fonction $fonction à chaque élément du tableau $tab et retourne le résultat (sans modifier $tab)
  • Filtrer les valeurs d'un tableau : array_filter($tab, $fonction) renvoie un tableau ne contenant que les valeurs du tableau $tab pour lesquelles la fonction $fonction a renvoyé true
<?php
$tab 
= [ 112371520, -24 ];
echo 
"Tableau initial :\n";
var_export($tab);
echo 
"\n";

$tab2 array_map(function ($value) {
  return 
$value 3;
}, 
$tab);
echo 
"Tableau modifié :\n";
var_export($tab2);
echo 
"\n";

$tab3 array_filter($tab2, function ($value) {
  return 
$value 25;
});
echo 
"Tableau résultant filtré :\n";
var_export($tab3);
echo 
"\n";

echo 
"NB: le tableau initial\n n'a pas bougé :\n";
var_export($tab);
echo 
"\n";
Tableau initial :
array (
  0 => 1,
  1 => 12,
  2 => 3,
  3 => 7,
  4 => 15,
  5 => 20,
  6 => -24,
)
Tableau modifié :
array (
  0 => 3,
  1 => 36,
  2 => 9,
  3 => 21,
  4 => 45,
  5 => 60,
  6 => -72,
)
Tableau résultant filtré :
array (
  0 => 3,
  2 => 9,
  3 => 21,
  6 => -72,
)
NB: le tableau initial
 n'a pas bougé :
array (
  0 => 1,
  1 => 12,
  2 => 3,
  3 => 7,
  4 => 15,
  5 => 20,
  6 => -24,
)

Attention, attention, l'ordre des paramètres n'est pas le même dans les deux fonctions

Dans la même famille, mais plus subtil, il existe aussi array_reduce, pour effectuer une opération sur toutes les valeurs à la fois (par exemple pour récupérer le produit de toutes les valeurs)

Tableaux de tableaux

On peut bien sûr mettre des tableaux dans des tableaux, et récursivement, sans difficulté :

<?php
$couleurs 
= array(
    
"aqua" => array(0255255),
    
"black" => array(000),
    
"blue" => array(00255),
);

$couleurs["white"] = array(255255255);

// On parcourt le tableau $couleurs
foreach ($couleurs as $c => $rgb) { 
    
// Ici $rgb est lui-même un tableau
    
echo "$c = rgb($rgb[0]$rgb[1]$rgb[2])\n";
}
aqua = rgb(0, 255, 255)
black = rgb(0, 0, 0)
blue = rgb(0, 0, 255)
white = rgb(255, 255, 255)

Tableaux en paramètres des fonctions

Passer un gros tableau à une fonction peut sembler une mauvaise idée, car la sémantique des paramètres est celle du « passage par copie »

Cependant, le moteur de PHP fait cette copie paresseusement, c'est-à-dire uniquement au moment où ça devient nécessaire

si le tableau n'est jamais modifié, il n'est jamais copié !

On a parfois besoin de faire des fonctions qui modifient un tableau donné en place ; dans ce cas on peut passer en paramètre une référence vers le tableau, en ajoutant & devant le paramètre formel

<?php
function no_modif($tab) {
  
$tab[0] = 'youpi';
}
function 
yes_modif(&$tab) {
  
$tab[0] = 'youpi';
}

$x = [ 'toto''tutu' ];
echo 
"no_modif ne modifie pas le tableau :\n";
no_modif($x);
var_export($x);

echo 
"\n--------\n";
echo 
"yes_modif modifie le tableau :\n";
yes_modif($x);
var_export($x);
no_modif ne modifie pas le tableau :
array (
  0 => 'toto',
  1 => 'tutu',
)
--------
yes_modif modifie le tableau :
array (
  0 => 'youpi',
  1 => 'tutu',
)

NB : le & est à mettre seulement lors de la définition de la fonction, pas lorsqu'on l'appelle. On ne passe pas explicitement un « pointeur ».

Manipulation simple de fichiers

Les fonctions suivantes sont simples à utiliser, mais pas forcément pertinentes sur de gros fichiers (on charge tout le fichier en mémoire).
  • Lire le contenu d'un fichier : file_get_contents("nomdufichier"), qui renvoie le contenu du fichier sous forme d'une chaîne de caractères.
  • Pour récupérer les lignes du fichier, on peut utiliser file("nomdufichier"), qui renvoie un tableau contenant toutes les lignes du fichier (avec les sauts de ligne).
  • Écrire un fichier : file_put_contents("nomdufichier", "contenu")
  • Ajouter du contenu à un fichier : file_put_contents("nomdufichier", "contenu", FILE_APPEND) (on utilise un paramètre optionnel de la fonction)
<?php
$file 
"demo/fichier.txt";

$contents file_get_contents($file);
echo 
"Contenu du fichier, V1 :\n$contents";

// get contents of a file line by line
$lines file($file);
foreach (
$lines as $num => $line) {
  echo 
"Ligne $num : « $line »\n";
}

Contenu du fichier, V1 :
Coucou,
je suis un fichier !

Au revoir

Ligne 0 : « Coucou,
 »
Ligne 1 : « je suis un fichier !
 »
Ligne 2 : « 
 »
Ligne 3 : « Au revoir
 »
Ligne 4 : « 
 »

Manipulation subtile de fichiers

Pour des besoins plus précis, ou pour des fichiers lourds, il existe des fonctions bas niveau c'est en fait une surcouche très fine de la bibliothèque standard de C :
  • La fonction fopen prend en 2e paramètre le mode d'ouverture du fichier : r r+ w w+ a a+
  • La fonction fread lit le contenu du fichier jusqu'à une limite donnée
  • La fonction fgets envoie la ligne courante sur laquelle se trouve le pointeur du fichier
  • La fonction fwrite permet d'écrire dans un fichier
  • La fonction fclose permet de fermer la ressource (elle est cependant fermée automatiquement à la fin du script)
<?php
// à l'ancienne
$fd fopen$file"r" );
$contents fread($fdfilesize($file));
echo 
"Contenu du fichier, V2 :\n$contents";
fclose($fd);

// get contents of a file line by line
$file "demo/fichier.txt";
$fd fopen$file"r" );
while (
$line fgets($fd4096)) {
  echo 
"<p>une ligne : « $line »</p>";
}
fclose($fd);
Contenu du fichier, V2 :

une ligne : « Coucou, »

une ligne : « je suis un fichier ! »

une ligne : « »

une ligne : « Au revoir »

une ligne : « »

Expressions régulières

Plusieurs fonctions existent pour faire des recherches et remplacements dans des chaînes ou des tableaux en utilisant des expressions régulières (regex)

Le format des regex est celui de Perl («PCRE»), comme la plupart des langages modernes (notamment Python ou Java)

Voir la doc pour tous les détails sur le format des regex ou les options des fonctions

Compléments à trier

PHP en ligne de commande

function usage($msg) {
    echo 
'Error: '.$msg.PHP_EOL;
    echo 
'Usage: blabla…'.PHP_EOL;
    die(
1);
}
/* PHP_SAPI indicates the access mode */
if (PHP_SAPI !== 'cli') {
    
usage('command line only');
}
/* $argv contains the argv array from C (there is no $argc) */
if (count($argv) != 2) {
    
usage('takes one argument');
}
$mode $argv[1];

Namespaces

Si on utilise des namespaces, on a envie d'utiliser use

Mais du coup sans autoloader c'est pas très DRY :
    require_once('views/View.php');
    use views\View;

Définir une fonction d'autoload C'est pas très DRY. Mais ça m'embête de définir une fonction d'autoload à la main qu'il faut mettre dans tous les projets…

J'ai regardé un peu, et a priori la solution la plus simple c'est d'utiliser l'autoloader par défaut. Il suffit de mettre spl_autoload_register(); quelque part, et il va chercher le fichier de classe dans le répertoire correspondant au namespace.

Seul bémol : le nom du fichier de classe doit être tout en minuscules, sinon ça ne marche pas. (NB: Les noms de classes et de fonctions en PHP ne sont pas sensibles à la casse.) C'est pas génial, mais c'est pas dramatique non plus.

Alternative : ne pas utiliser les namespaces comme des packages, mais juste pour protéger notre espace de noms. On peut continuer à utiliser des répertoires pour grouper nos fichiers, même s'ils sont dans le même namespace.