Développement d'applications Web avec LAMP

Document d'accompagnement pour le cours 420-KB9-LG

Les fichiers

Dernière mise à jour le 8 octobre 2022

Ouverture d'un fichier

La plupart des opérations sur un fichier nécessite que celui soit préalablement ouvert.

On utilise la fonction fopen() dont la syntaxe est :

resource descripteur = fopen(string nom_fichier, string mode [,bool utiliser_include_path]);

Paramètres :

Qu'est-ce que le "include_path"?

Le "include_path" est une directive de configuration qui spécifie un chemin formé d'une liste de répertoires où les instructions require et include, de même que les fonctions fopen(), file(), readfile() et file_get_contents() chercheront les fichiers.

Le format est identique à la variable d'environnement système PATH, soit une liste de répertoires séparés par deux points (:) sous Linux ou par un point-virgule (;) sous Windows.

Ceci est un exemple de directive "include_path" :

.:/usr/share/php

Dans ce exemple, les fichiers seront d'abord recherchés dans le répertoire courant ("."), puis dans /usr/share/php.

Le "include_path" est défini dans le fichier php.ini.

Attention! Les chemins utilisés par les fonctions de gestion des fichiers sont les chemins dans le système de fichiers du serveur!

Le chemin du répertoire Web d'un utilisateur est contenu dans la variable $_SERVER['CONTEXT_DOCUMENT_ROOT'].

Pour l'utilisateur patoche, ce chemin correspond sur notre serveur d'hébergement à /home/patoche/public_html.

Mode d'ouverture

Le tableau suivant présente quelques-unes des valeurs possibles du deuxième paramètre de la fonction fopen() :

Mode Signification
r Lecture seulement.
r+ Lecture et écriture. À la différence de "w+", le fichier doit exister.
w Écriture seulement. Si le fichier existe déjà, son contenu est écrasé. Dans le cas contraire, le fichier est créé.
w+ Lecture et écriture. Si le fichier existe déjà, son contenu est écrasé. Dans le cas contraire, le fichier est créé. Rare.
a Écriture seulement avec ajout des données à la fin du fichier. Si le fichier n'existe pas encore, il est créé.
a+ Lecture et écriture avec ajout des données à la fin du fichier. Si le fichier n'existe pas encore, il est créé. Rare.

Exemples d'ouverture d'un fichier :

// ouverture en lecture seulement du fichier "proverbes.txt" dans le sous-répertoire
// "textes" du répertoire courant

$fich = fopen("textes/proverbes.txt", "r");
// ouverture en écriture du fichier "/home/utilisateur/public_html/donnees/clients.csv"

$chemin = $_SERVER['CONTEXT_DOCUMENT_ROOT'] . "/donnees/clients.csv";
$data = fopen($chemin, "w");
// ouverture du fichier "/home/utilisateur/commandes/commandes.txt"

$chemin = $_SERVER['CONTEXT_DOCUMENT_ROOT'] . "/../commandes/commandes.txt"
$fich_commandes = fopen($chemin, "a");

Ouverture via FTP et HTTP

Dans la fonction fopen(), le nom de fichier local peut parfois être remplacé par une URL de type FTP ou HTTP. Cette possibilité dépend du serveur où est stocké le fichier.

Lorque l'URL commence par ftp://, une connexion FTP (mode passif) est ouverte avec le serveur et la fonction retourne un pointeur sur le début du fichier.

Lorque l'URL commence par http://, une connexion HTTP est ouverte avec le serveur et la fonction retourne un pointeur sur la réponse fournie (texte normalement affiché dans le navigateur suite à une requête GET).

Exemple :

// ouverture d'un fichier distant via HTTP

$url = "http://198.245.55.77/~bob/stats/Baseball.csv";
$fich = fopen($url, "r");
...

Rappelons que la casse (lettres majuscules/minuscules) n'a pas d'importance dans les noms de domaines, mais peut en avoir dans les chemins d'accès.

Problèmes d'ouverture

Les deux erreurs les plus fréquentes sont :

  1. un fichier inexistant;
  2. des droits d'accès inadéquats.

Lorsque l'appel à la fonction fopen() échoue, celle-ci renvoie la valeur FALSE.

Exemple :

$fich = fopen("fichiers/blabla.txt", "r");

// si l'ouverture du fichier est possible
if ($fich !== false) {
  ...
}

L'échec peut entraîner (dépendant de la configuration de PHP sur le serveur) l'affichage d'un message déplaisant :

Warning: fopen(fichiers/blabla.txt): failed to open stream: No such file or directory in /var/www/html/test_fichiers.php on line 37

On peut traiter l'erreur avec plus de convivialité en supprimant le message d'erreur PHP (avec le caractère "@") et en générant notre propre message.

Exemple :

@ $fich = fopen("fichiers/blabla.txt", "r");

if ($fich !== false) {
  ...
} else {
  echo <<<FIN
    Votre commande ne peut être traitée<br>
    Veuillez S.V.P. essayer plus tard
FIN;
  exit(1); // termine immédiatement le programme
}

Droits d'accès

Puisque notre but est de créer des applications Web, toutes les opérations sur nos fichiers se feront à partir d'un script invoqué depuis un navigateur Web.

Vous devrez donc maintenant tenir compte du fait suivant : pour des raisons de sécurité, toutes les opérations sur les fichiers effectuées à partir du Web sont effectuées dans le système de fichiers du serveur par un même usager, soit www-data.

Supposez par exemple que l'usager bob a écrit un script qui doit ajouter des données au fichier donnees/data.txt. Pour le serveur, lorsqu'un internaute exécute le script, la ligne suivante :

  $fich = fopen("donnees/data.txt", "a");

n'est pas exécutée par l'usager bob, mais par l'usager www-data. L'usager virtuel dont le nom est www-data (ou le groupe du même nom selon le réglage des permissions) doit donc avoir accès en écriture au fichier donnees/data.txt.

Puisque plusieurs d'entre vous ne sont pas (pour l'instant) très confortables en Linux, voici une procédure simple à utiliser si votre application contient un script qui doit écrire dans un fichier :

Et voilà! Vous avez maintenant un fichier (donnees/data.txt) dans lequel vos scripts pourront écrire!

Fermeture d'un fichier

Il est nécessaire de fermer un fichier pour s'assurer que les données écrites y soient réellement enregistrées et qu'un autre usager puisse accéder au fichier en écriture.

On utilise la fonction fclose()

Exemple :

fclose($fich);

Cette fonction retourne TRUE en cas de réussite et FALSE en cas d'échec.

Les cas d'échec sont tellement rares que la valeur de retour est rarement testée.

Écriture

Attention! Tous nos exemples utiliseront des chaînes de caractères (usage le plus fréquent des fichiers plats (flat files) en PHP! Pour écrire et lire des données binaires, voir les fonctions pack() et unpack().

Pour écrire, on peut utiliser indistinctement fwrite() et fputs(), un alias de fwrite().

Syntaxe :

int fwrite(resource descripteur, string chaîne [, int longueur]);

Le paramètre longueur est optionnel et spécifie le nombre maximal d'octets à écrire.

L'écriture s'arrête lorsque toute la chaîne est écrite ou que le nombre d'octets spécifié est atteint.

La fonction fwrite() retourne le nombre d'octets écrits ou FALSE en cas d'erreur.

Excemple :

$nb = fwrite($fich, $chaine);

if ($nb === FALSE) {
  echo "Erreur d'écriture";
  exit(1);
} else {
  echo "$nb octets écrits";
}

Dans l'exemple précédent, il est important d'utiliser l'opérateur === et non ==, puisque le fait d'avoir écrit 0 octets n'est pas nécessairement une erreur.

Lecture

Il existe plusieurs façons de lire le contenu d'un fichier. Nous verrons tour à tour :

Détection de la fin du fichier

Mais avant de voir comment lire dans un fichier, il faut savoir comment on en détecte la fin lors de la lecture.

On utilise pour cela la fonction feof() dont la syntaxe est :

bool feof(resource descripteur);

Cette fonction retourne TRUE si le "pointeur" de lecture est positionné à la fin du fichier et FALSE autrement.

Exemple :

while (! feof($fich_clients)) {
  // lecture et traitement d'un enregistrement
  // ...
}

Lecture ligne par ligne

On utilise la fonction fgets(), dont la syntaxe est :

string fgets(resource descripteur [, int longueur]);

La fonction fgets() s'arrête lorsqu'elle rencontre une des conditions suivantes :

En cas d'absence du paramètre "longueur", la lecture se poursuit jusqu'à la fin de la ligne.

Exemple :

$fich = fopen("proverbes.txt", "r");

while (! feof($fich)) {
  $ligne = fgets($fich, 256); // lit au maximum 255 caractères par ligne
  echo $ligne . "<br>";
}

Lecture avec élimination des balises HTML

AVERTISSEMENT : La fonction fgetss() est maintenant considérée obsolète et ne devrait plus être utilisée. Vous pouvez passer immédiatement à la fonction strip_tags().

On utilise la fonction fgetss().

La syntaxe est  :

string fgetss(resource descripteur, int longueur [, string balises_autorisées] );

Fonctionne de la même façon que fgets(), mais supprime toutes les balises HTML et PHP rencontrées.

Le paramètre optionnel permet de spécifier les balises acceptées.

Soit un fichier HTML (incomplet) :

<h2>Un titre de niveau 2</h2>
<p>Un paragraphe</p>
Une ligne de texte

Le code suivant affiche son contenu sans aucune balise HTML :

$fich = fopen("texte.html", "r");

while (! feof($fich)) {
  $ligne = fgetss($fich, 256);
  echo $ligne . "<br>";
}

Sortie :

Un titre de niveau 2
Un paragraphe
Une ligne de texte

Permettons maintenant les titres de niveau 2 :

$fich = fopen("texte.html", "r");

while (! feof($fich)) {
  $ligne = fgetss($fich, 256, "<h2>");
  echo $ligne . "<br>";
}

Sortie :

Un titre de niveau 2

Un paragraphe
Une ligne de texte

La fonction strip_tags()

La fonction fgetss(), maintenant obsolète, utilise le même moteur de recherche que la fonction strip_tags().

Reprenons donc notre dernier exemple en utilisant fgets() et strip_tags().

Soit un fichier HTML :

<h2>Un titre de niveau 2</h2>
<p>Un paragraphe</p>
Une ligne de texte

Le code suivant lit ligne par ligne le contenu du fichier précédent en supprimant toutes les balises HTML et PHP, à l'exception de la balise <h2> :

$fich = fopen("texte.html", "r");

while (! feof($fich)) {
  $ligne = strip_tags(fgets($fich, 256), "<h2>");
  echo $ligne . "<br>";
}

Sortie :

Un titre de niveau 2

Un paragraphe
Une ligne de texte

La fonction strip_tags() est souvent utilisée pour filtrer les données entrées par formulaire.

Lecture de fichiers CSV

Le format CSV

Lorsqu'on utilise des fichiers plats (flat files) pour stocker des données, on doit s'assurer que ces dernières seront facilement...

La stratégie la plus courante consiste à stocker exactement un enregistrement par ligne, le caractère de nouvelle ligne ("\n") jouant alors le rôle de séparateur.

Dans chaque enregistrement (chaque ligne), les champs sont délimités par un caractère particulier, par exemple la virgule, qui joue ainsi le rôle de délimiteur.

Ce qui est décrit ici correspond exactement au format CSV.

Le format CSV pour "Comma Seperated Value" est presque devenu un standard (utilisé entre autres par Microsoft Excel et LibreOffice Calc).

La principale caractéristique de CSV est que les enregistrements sont séparés par une fin de ligne et que les champs sont délimités (par défaut) par des virgules (comma en anglais).

Exemple de fichier CSV :

Patoche,Alain,patoche@gmail.com,Ste-Thérèse
Gratton,Robert,elvis@hotmail.com,Pointe-Calumet
Monette,Linda,doudoune@hotmail.com,Laval
Narrache,Jean,jean.narrache@dentistes.ca,Deux-Montagnes
Labrosse,Adam,adam.labrosse@dentistes.ca,Ste-Thérèse

Un fichier CSV peut tout aussi bien être lu par un logiciel comme Microsoft Excel que par un simple éditeur de texte.

Il est important de choisir un séparateur et un délimiteur qui ne se retrouveront pas dans les données.

Il sera parfois nécessaire de filtrer les données à écrire (par exemple, celles provenant d'un formulaire) pour éviter que de tels caractères ne soient introduits, malicieusement ou non, parmi les données

Exemple de création d'un fichier CSV :

define("DELIMITEUR", ":");

$fich = fopen("fichiers/clients.csv", "w");

while (...) {
    ...
    $client = $nom . DELIMITEUR . $prenom . DELIMITEUR . $adresse . DELIMITEUR
        . $telephone . "\n";
    fwrite($fich, $client);
}
...

Il n'existe pas de fonction spécialisée pour écrire dans un fichier CSV.

Il en existe toutefois une pour lire.

Syntaxe de la fonction fgetcsv() :

array fgetcsv(resource descripteur, int longueur [, string délimiteur]);

La longueur spécifiée doit être plus grande que la plus grande ligne du fichier.

Le paramètre optionnel, le délimiteur, est par défaut la virgule.

Exemple d'affichage du contenu d'un fichier CSV :

define("DELIMITEUR", ":");
define("MAX_CAR", 128);

$fich = fopen("fichiers/clients.csv", "r");

while (!feof($fich)) {
  // lit une ligne
  $tab_client = fgetcsv($fich, MAX_CAR, DELIMITEUR);

  // affiche les données
  echo "Nom : " . $tab_client[0] . "<br>";
  echo "Prénom : " . $tab_client[1] . "<br>";
  echo "Adresse : " . $tab_client[2] . "<br>";
  echo "Téléphone : " . $tab_client[3] . "<br>";
  echo "<hr>";
}

Lecture de l'intégralité d'un fichier

Pour lire un fichier au complet en une seule opération, la façon la plus intuitive consiste à...

Il existe heureusement des fonctions plus performantes!

readfile()

Syntaxe :

int readfile(string nom_de_fichier [, bool utiliser_include_path]);

La fonction readfile()...

Le paramètre optionnel spécifie si l'interpréteur PHP doit rechercher le fichier dans le "include_path".

fpassthru()

Syntaxe :

int fpassthru(resource descripteur);

Fonctionne de manière semblable à readfile(), sauf que le fichier doit être préalablement ouvert.

La fonction fpassthru()...

file()

Syntaxe :

array file(string nom_fichier [, utiliser_include_path]);

Ici le contenu du fichier n'est plus envoyé vers la sortie standard, mais récupéré dans un tableau.

Chaque élément du tableau contiendra une ligne du fichier.

La fonction retourbe FALSE en cas de problème (ex : fichier introuvable).

file_get_contents()

Syntaxe :

string file_get_contents(string nom_fichier [, utiliser_include_path]);

Fonctionne de la même façon que file(), sauf que l'ensemble du fichier est retourné dans une seule chaîne.

En résumé

Les quatre fonctions suivantes permettent de lire un fichier au complet :

Fonction
readfile() Ouvre le fichier, lit et affiche son contenu, puis le ferme.
fpassthru() Lit un fichier ouvert à partir de la position courante, affiche son contenu, puis le ferme.
file() Retourne le contenu du fichier dans un tableau.
file_get_contents() Retourne le contenu du fichier dans une chaîne.

Lecture caractère par caractère

Il arrive, même si cela est plutôt rare, que l'on doive lire un fichier caractère par caractère.

Syntaxe :

string fgetc(resource descripteur);

Retourne (sous forme de chaîne) le caractère à la position courante dans le fichier.

Retourne FALSE à la fin du fichier.

Exemple :

// affiche le contenu du fichier en lisant un caractère à la fois

$fini = FALSE;
while (!fini) {
  $car = fgetc($fich);
  if ($car !== FALSE) {
    echo $car;
  else
    $fini = FALSE;
}

// en plus condensé

while (FALSE !== ($car = fgetc( $fich))) {
  echo $car; 
}

Lecture d'une longueur arbitraire

La fonction fread() lit le nombre d'octets spécifié (ou moins si la fin du fichier est rencontrée) et les retourne sous forme de chaîne.

Syntaxe :

string fread(resource descripteur, int longueur);

Exemples :

// lecture de 50 octets

$nom_fichier = "/usr/local/quelque_chose.txt";
$fich = fopen($nom_fichier, "r");
$contenu = fread($fich, 50);
fclose($fich);
// lecture de l'intégralité du fichier, soit la même chose que file_get_contents()

$nom_fichier = "/usr/local/quelque_chose.txt";
$fich = fopen($nom_fichier, "r");
$contenu = fread($fich, filesize($nom_fichier));
fclose($fich);

Détermination de la taille d'un fichier

On utilise la fonction filesize().

Syntaxe :

int filesize(string nom_fichier);

Retourne la taille en octets.

Vérification de l'existence d'un fichier

On utilise la fonction file_exists().

Syntaxe :

bool file_exists(string nom_fichier);

Permet de vérifier si un fichier existe, sans même l'ouvrir.

$nom_fichier = "/chemin/jusqu/a/fichier.txt";
echo "Le fichier $nom_fichier ";

if (file_exists($nom_fichier)) {
  echo "existe";
} else {
  echo "n'existe pas";
}

Suppression

On utilise la fonction unlink().

Syntaxe :

bool unlink(string nom_fichier);

Supprime le fichier dont le nom est passé en paramètre.

En cas d'échec (ex : droits d'accès insuffisants), retourne FALSE.

Remarque : le nom de la commande vient du monde Linux où un fichier existe tant qu'il y a un lien (link) dessus.

Exercices à faire en classe

Exercice 1

Vous trouverez à l'URL https://prog101.com/cours/kb9/anneaux.html un fichier contenant un extrait important du roman La Communauté de l'anneau.

Écrivez le script exercice1.php qui affiche ce texte en supprimant toutes les balises HTML s'y trouvant, à l'exception de la balise <strong>.

La sortie devrait ressembler à ceci :

Les anneaux de pouvoir Trois anneaux pour les Rois Elfes sous le ciel, Sept pour les Seigneurs Nains dans leurs demeures de pierre, Neuf pour les Hommes Mortels destinés au trépas, Un pour le Seigneur Ténébreux sur son sombre trône, Dans le Pays de Mordor où s'étendent les Ombres. Un anneau pour les gouverner tous. Un anneau pour les trouver, Un anneau pour les amener tous et dans les ténèbres les lier Au Pays de Mordor où s'étendent les Ombres. — J. R. R. Tolkien, La Communauté de l'anneau

Pour réaliser cet exercice, vous devrez d'abord récupérer le contenu du fichier distant avec file_get_contents(), puis l'afficher en supprimant certaines balises HTML avec strip_tags().

Assurez vous que la page affichée par votre script est bien valide.

[Solution]

Exercice 2

Dans cet exercice, vous allez réaliser une petite application (page HTML + script PHP) permettant de constituer une mini-base de données.

Commencez par écrire le fichier exercice2.html qui présente à l'internaute un formulaire permettant d'entrer un nom, un prénom et une adresse courriel, comme dans l'exemple suivant :

formulaire

Écrivez ensuite le script exercice2.php dont le rôle est de recevoir les données du formulaire et de les ajouter à chaque fois à la fin d'un fichier CSV.

Vous devez gérer les deux types d'erreur suivants :

Oubliez la validation côté HTML. Il doit être possible d'envoyer un formulaire avec des champs vides pour tester le deuxième type d'erreur.

[Solution]

Exercice 3

Écrivez le script exercice3.php qui affiche dans un tableau HTML le contenu du fichier CSV de l'exercice précédent.

Exemple de sortie :

tableau

[Solution]

Questions et exercices de révision

AVERTISSEMENT

Nous ne fournirons pas les réponses à ces questions pour deux bonnes raisons. La première est que celles-ci se trouvent dans les notes de cours. La seconde est que, dans certains cas, ce serait presque donner la réponse à des questions d'examen. :

Question 1

Donnez la différence entre r+ et w+ comme deuxième paramètre de la fonction fopen().

Question 2

Que contient, pour l'usager "patoche", la variable $_SERVER['CONTEXT_DOCUMENT_ROOT']?

Question 3

Vrai ou faux? Un script PHP peut ouvrir un fichier situé sur un autre serveur.

Question 4

À quoi sert le symbole @ placé devant une instruction en PHP?

Question 5

Que va écrire le code suivant dans le fichier?

fwrite($fich, "A", 5);

Question 6

Expliquez la différence entre les opérateurs == et ===.

Question 7

Supposez que le fichier dont le descripteur est $fich contient la chaîne "ABCDEFGH". Donnez la sortie du code suivant :

$ligne = fgets($fich, 8);
echo $ligne;

Question 8

Donnez le code HTML généré par la série d'instructions suivante :

$html = "<div>Un texte <strong>important</strong> à lire.</div>";
$balises = "<div>";
$sortie = strip_tags($html, $balises);
echo $sortie;

Question 9

Que veut dire l'acronyme CSV? Traduisez-le en français.

Question 10

Donnez la différence entre les fonctions readfile() et fpassthru().

Question 11

Donnez la différence entre les fonctions file() et file_get_contents().

Question 12

Donnez la différence entre les fonctions fgets() et fread().