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.phps’exécute, PHP (qui tourne sous l’utilisateurwww-datad’Apache) doit pouvoir lire tous les fichiers desrcpour les charger avecrequire. On ne peut donc pas retirer àwww-datale droit de lecture sursrc.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
- Un chemin de fichier désigne un fichier sur le disque dur du serveur. Il
est utilisé par PHP, qui s’exécute sur le serveur. Exemples :
/home/lebreton/public_html/ILovePHP/bonjour.php/var/www/html/tds-php/TD5/web/controleurFrontal.php
-
Une URL désigne une ressource sur Internet. Elle est utilisée par le navigateur (le client), qui envoie une requête HTTP au serveur. Exemple :
http://localhost/tds-php/TD5/web/controleurFrontal.phpse décompose en le protocole
http://, le nom de domainelocalhost, et le chemin/tds-php/TD5/web/controleurFrontal.php(voir aussi la note sur les URL du TD1).
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 !
-
Un chemin de fichier relatif est interprété par PHP par rapport au dossier du script exécuté, c.-à-d. en pratique
web, le dossier decontroleurFrontal.php, et pas par rapport au fichier qui contient lerequire. C’est pour cela que nous n’utilisons que des chemins de fichiers absolus construits avec__DIR__(voir le TD5).À l’inverse,
__DIR__contient le dossier du fichier dans lequel il est écrit, et pas celui du script exécuté. Par exemple, siweb/controleurFrontal.phpchargesrc/Modele/ConnexionBaseDeDonnees.php, qui contientparse_ini_file(__DIR__ . '/../Configuration/ConfigurationBaseDeDonnees.ini');alors
__DIR__vaut/var/www/html/tds-php/TD5/src/Modele(et non/var/www/html/tds-php/TD5/web), même si c’estcontroleurFrontal.phpqui est exécuté. Le chemin obtenu ne dépend donc pas du script qui a fait lerequire, ce qui le rend fiable. -
Une URL relative est complétée par le navigateur par rapport à l’URL de la page affichée, c.-à-d. celle de la barre d’adresse. Le navigateur ne sait rien de vos fichiers PHP : il ne voit que le HTML qu’il a reçu. Or toutes nos pages sont produites par
http://localhost/tds-php/TD5/web/controleurFrontal.php. Les URL relatives sont donc toutes complétées à partir du dossier d’URLhttp://localhost/tds-php/TD5/web/, même si le code HTML est écrit dans une vue desrc/vue/utilisateur/.
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 :
- Une URL qui commence par
/est relative au nom de domaine : elle repart de la racine du sitehttp://localhost/. Ce n’est donc pas la même chose que le/d’un chemin de fichier, qui désigne la racine du disque dur. - Quand une URL désigne un dossier, comme
http://localhost/projet/, Apache renvoie le fichierindex.htmlouindex.phpde ce dossier s’il existe, ou sinon liste les fichiers du dossier (simod_autoindexest activé). - On privilégie les URL relatives car elles continuent de fonctionner si le site
est déplacé, par exemple de
http://localhost/tds-php/TD5/vershttp://webinfo.iutmontp.univ-montp2.fr/~votre_login/TD5/.
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
- Calculer une URL relative depuis l’emplacement du fichier de vue : voir l’exemple ci-dessus.
- Mettre un chemin de fichier dans une URL, par exemple
<link rel="stylesheet" href="<?= __DIR__ ?>/../../ressources/css/navstyle.css">Le navigateur reçoit
href="/var/www/html/tds-php/TD5/src/vue/../../ressources/css/navstyle.css", qu’il interprète comme l’URLhttp://localhost/var/www/html/tds-php/TD5/src/vue/../../ressources/css/navstyle.css, d’où une erreur 404.__DIR__(ou toute constante contenant un chemin de fichier) n’a rien à faire dans un attributhref,srcouaction. - Utiliser une URL en
file://: elle désigne un fichier de votre disque dur et ne passe pas par le serveur HTTP. Elle est donc à proscrire (voir le TD1). - Mettre une URL dans un
require, par exemplerequire "controleurFrontal.php?action=afficherListe";. Unrequireattend un chemin de fichier : il n’y a pas de query string. Pour passer des données à une vue, on définit des variables avant lerequire.
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
file1.php
<?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";
}
}
file2.php
<?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 :
- pour une fonction ou une constante non qualifiée, PHP la cherche
d’abord dans l’espace de noms courant, puis, s’il ne la trouve pas, dans
l’espace de noms global. C’est pour cela que
strlen("abc")fonctionne sans\; - pour une classe, PHP ne cherche que dans l’espace de noms courant.
C’est pour cela qu’il faut écrire
\PDOouuse PDO;au TD5, sinon on obtient l’erreurClass "App\Covoiturage\Modele\PDO" not found.
<?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 :
- il n’utilise pas de programmation orientée-objet,
- il ne permet d’associer qu’un seul préfixe de nom de classe qualifié à un chemin de fichier.
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.