Configuration Apache, namespace et autoloader

.htaccess

Le fichier .htaccess permet de paramétrer Apache dossier par dossier. Au TD5, nous l’utilisons pour interdire l’accès par Internet à tout le site, sauf au dossier web (et ressources).

Pourquoi est-ce que les ACL ne permettent pas d’obtenir ce comportement ?

Les ACL règlent les droits d’accès aux fichiers sur le disque. Or, quand web/controleurFrontal.php s’exécute, PHP (qui tourne sous l’utilisateur www-data d’Apache) doit pouvoir lire tous les fichiers de src pour les charger avec require. On ne peut donc pas retirer à www-data le droit de lecture sur src.

Ce que l’on veut interdire, c’est seulement que ces fichiers soient servis en réponse à une requête HTTP. C’est le rôle du fichier .htaccess, qui règle le comportement d’Apache face aux requêtes, et non l’accès au disque.

Chemin de fichier ou URL ?

Depuis que le contrôleur frontal est dans web et que les vues sont dans src/vue, beaucoup d’erreurs viennent d’une confusion entre deux sortes d’adresses qui se ressemblent beaucoup : les chemins de fichiers et les URL.

Deux sortes d’adresses

Le chemin d’une URL ressemble à un chemin de fichier, mais ce n’est pas le même : c’est Apache qui fait la correspondance entre les deux. Par exemple, le chemin d’URL /tds-php/TD5/web/controleurFrontal.php correspond au fichier /var/www/html/tds-php/TD5/web/controleurFrontal.php.

Qui lit l’adresse ?

Pour savoir quelle sorte d’adresse écrire, il faut se demander qui va l’utiliser :

Emplacement dans le code Utilisé par Type d’adresse
require, require_once (déclaration de classe, vue) PHP chemin de fichier
parse_ini_file, move_uploaded_file, … PHP chemin de fichier
<a href="…"> navigateur URL
<form action="…"> navigateur URL
<link rel="stylesheet" href="…"> (CSS), <img src="…">, <script src="…"> navigateur URL
header("Location: …") (TD9) navigateur URL

En résumé : ce qui est argument d’une fonction PHP est un chemin de fichier ; ce qui est écrit dans du HTML (ou dans un en-tête HTTP) envoyé au navigateur est une URL.

Le complément sur l’upload de fichiers illustre bien la différence : la même image est désignée par le chemin de fichier __DIR__ . "/../../ressources/img/profils/$nomFichier" dans move_uploaded_file, et par l’URL relative ../ressources/img/profils/$nomFichier dans <img src="…">.

Relatif par rapport à quoi ?

Les deux sortes d’adresses peuvent être relatives, c.-à-d. ne pas commencer par / (ni par http://). Mais elles ne sont pas relatives à la même chose !

Voici comment le navigateur complète quelques URL, si la page affichée est http://localhost/tds-php/TD5/web/controleurFrontal.php :

URL écrite dans le HTML URL complétée par le navigateur
controleurFrontal.php?action=afficherListe http://localhost/tds-php/TD5/web/controleurFrontal.php?action=afficherListe
./css/styles.css http://localhost/tds-php/TD5/web/css/styles.css
../ressources/img/logo.png http://localhost/tds-php/TD5/ressources/img/logo.png
../../index.html http://localhost/tds-php/index.html
/projet/ http://localhost/projet/

Remarques :

Exemple : le formulaire de création d’un utilisateur

Le formulaire de src/vue/utilisateur/formulaireCreation.php doit envoyer ses données à l’action creerDepuisFormulaire du contrôleur frontal.

<!-- Correct -->
<form method="get" action="controleurFrontal.php">
<!-- Incorrect -->
<form method="get" action="../../../web/controleurFrontal.php">

La deuxième version a été écrite en partant du fichier src/vue/utilisateur/formulaireCreation.php sur le disque. Mais le navigateur la complète à partir de l’URL de la page affichée et obtient http://localhost/web/controleurFrontal.php, qui n’existe pas.

C’est aussi pour cela que PhpStorm affiche parfois un warning sur les URL de vos vues : de son point de vue, vous êtes dans src/vue/…, alors que le navigateur chargera la page depuis web.

Erreurs classiques

namespace

Les espaces de noms permettent d’encapsuler des classes, fonctions pour éviter les conflits. Ils fonctionnent de manière similaire aux fichiers qui sont répartis dans des dossiers.

Source : Documentation sur PHP.net

Noms non qualifiés, qualifiés et absolus

<?php
namespace EspaceBase\SousEspace;

class Foo
{
    static function methodeStatique() {
      echo "Méthode statique de Foo dans file1.php\n";
    }
    function methode() {
      echo "Méthode dynamique de Foo dans file1.php\n";
    }
}
<?php
namespace EspaceBase;
require_once 'file1.php';

class Foo
{
    static function methodeStatique() {
      echo "Méthode statique de Foo dans file2.php\n";
    }
    function methode() {
      echo "Méthode dynamique de Foo dans file2.php\n";
    }
}

/* nom non qualifié */
$f = new Foo(); // Classe \EspaceBase\Foo
$f->methode(); // Affiche "Méthode dynamique de Foo dans file2.php"
Foo::methodeStatique(); // Affiche "Méthode statique de Foo dans file2.php"

/* nom qualifié */
$f = new SousEspace\Foo(); // Classe \EspaceBase\SousEspace\Foo
$f->methode(); // Affiche "Méthode dynamique de Foo dans file1.php"
SousEspace\Foo::methodeStatique(); // Affiche "Méthode statique de Foo dans file1.php"

/* nom absolu */
$f = new \EspaceBase\SousEspace\Foo(); // Classe \EspaceBase\SousEspace\Foo
$f->methode(); // Affiche "Méthode dynamique de Foo dans file1.php"
\EspaceBase\SousEspace\Foo::methodeStatique(); // Affiche "Méthode statique de Foo dans file1.php"

Source : Documentation sur PHP.net

Accès aux classes, fonctions et constantes globales depuis un espace de noms

Les classes, fonctions et constantes fournies par PHP (PDO, strlen, PHP_EOL, …) sont déclarées dans l’espace de noms global \. Depuis un espace de noms, PHP ne les traite pas toutes de la même façon :

<?php
namespace App\Covoiturage\Modele;

$a = strlen('hi');          // OK : fonction globale strlen trouvée par repli
$b = PHP_EOL;               // OK : constante globale PHP_EOL trouvée par repli
$c = new \DateTime();       // OK : classe globale DateTime
$d = new DateTime();        // Erreur : classe App\Covoiturage\Modele\DateTime introuvable

Si l’espace de noms courant déclare lui-même une fonction, une constante ou une classe de même nom, on peut toujours accéder à celle de l’espace global avec un \ :

<?php
namespace Foo;

function strlen() {}
const INI_ALL = 3;
class Exception {}

$a = \strlen('hi'); // appelle la fonction globale strlen
$b = \INI_ALL; // accède à la constante globale INI_ALL
$c = new \Exception('error'); // instancie la classe globale Exception

Source : Documentation sur PHP.net

Explication de l’implémentation de l’autoloader

spl_autoload_register

La fonction spl_autoload_register est le cœur du mécanisme de chargement automatique de classes de PHP. On lui donne en argument une fonction qui sera appelée si PHP rencontre une classe qui n’a pas encore été déclarée.

La méthode register() de Psr4AutoloaderClass ne fait qu’enregistrer la méthode loadClass() de l’objet chargeur avec un appel à spl_autoload_register.

Le reste de la classe transforme un nom de classe qualifié en un nom de fichier (méthode loadMappedFile), puis charge le fichier avec requireFile.

Exemple plus simple

Vous pouvez voir un exemple plus court d’autoloader à cette adresse. Attention, cet exemple n’est pas recommandé car :

Pas d’autoloader pour les vues ?

Pourquoi n’utilise-t-on pas l’autoloader pour charger les vues ? Parce que l’autoloader charge automatiquement des classes. Or les vues ne sont pas des classes PHP.