include_path + conventions + autoload = chargement automatique des classes PHP
Vous avez des dizaines de require/include en début de chacun de vos script ?
Pire dans vos scripts PHP, un peu partout ?
Et si l'autoload vous changeait la vie !
Les fonctions d'inclusion en PHP
Les fonctions include et require permettent à tout endroit d'un script PHP d'inclure le contenu d'un autre fichier php à un endroit précis. Voici un exemple d'utilisation :
<?php require 'MonEntreprise/Xml/Service.php';
Plus que des fonctions, il s'agit de directives PHP, elles peuvents donc être appelées avec ou sans leur parenthèses :
<?php
require ('MonEntreprise/Xml/Service.php');
require 'MonEntreprise/Xml/Service.php' ;
Les 4 fonctions disponibles
Il existe 4 fonctions principales pour inclure un fichier PHP dans un autre :
- require $file : inclusion systématique du fichier PHP avec erreur fatale si $file n'existe pas
- include $file : inclusion systématique du fichier PHP avec warning si $file n'existe pas (mais le script continue)
- require_once $file : inclusion du fichier $file uniquement que si il n'a jamais été inclus (une seule inclusion même si plusieurs appels), même comportement d'erreur que require
- include_once $file : inclusion du fichier $file uniquement que si il n'a jamais été inclus (une seule inclusion même si plusieurs appels), même comportement d'erreur que include
Attention, dans le cas de l'utilisation des inclusions en *_once, le chemin transmis en tant que paramètre doit être strictement le même pour que l'inclusion ne se fasse qu'une seule fois :
<?php /** * les deux lignes suivantes sont exécutées et le fichier * est inclus 2 fois, bien que ce soit physiquement le même fichier : */ require_once 'contexts/http.php'; require_once 'c:/dev/workspace/depot-silo/contexts/http.php';
Chemin du fichier à inclure
Il existe plusieurs possibilités pour indiquer le nom du fichier :
- un chemin absolu, ex: c:/dev/workspace/mon-projet/library/MonEntreprise/Xml/service.php
- un chemin relatif, ex: ../../library/MonEntreprise/Xml/service.php
- un chemin basé sur l'include path, ex: MonEntreprise/Xml/Service.php
l'Include path
L'include path en PHP est une notion similaire au concept de class path en Java.
Il s'agit d'une liste de répertoire dans lesquels PHP tentera de chercher les ressources (fichiers) à inclure.
Les fonctions d'inclusions (require/include) sont donc massivement basées sur l'include path et tente de cherche les fichiers indiqués dans ces répertoires.
D'autres fonctions PHP (ex: file_get_contents) peuvent utiliser l'include_path.
L'include path est donc une " chaîne de caractère " listant un ensemble de répertoires, séparés par un séparateur dépendant de la plateforme (: pour *nix, ; pour windows) :
Exemple sous windows: .;c:/php/pear Exemple sous linux: .:/usr/share/pear
Paramétrer l'include path
Il existe plusieurs possibilités pour positionner/modifier l'include path PHP :
- soit pour tout les scripts / requêtes
- soit pour l'exécution précise / requête en cours
Paramétrage global pour tout les scripts
Il suffit de modifier la directive "include_path" du fichier php.ini :
include_path = .:/usr/share/pear:/opt/mon-repertoire-perso
Paramétrage local à l'exécution du script en cours
Il suffit d'utiliser la fonction "set_include_path()" n'importe où dans votre script :
<?php
set_include_path(get_include_path() . PATH_SEPARATOR
. '/opt/mon-repertoire-perso');
L'utilisation de get_include_path() permet de récupérer l'include_path actuel (pour éviter de perdre les répertoires déjà listés).
La constante PATH_SEPARATOR est automatiquement positionnée par PHP avec ":" sous linux et ";" sous windows.
Exemple d'utilisation peut importe le type de paramétrage
Si le répertoire /opt/mon-repertoire-perso a été ajouté à l'include_path, et si le fichier /opt/mon-repertoire-perso/mon-script.php existe, alors les lignes suivantes permettront d'inclure le fichier :
<?php // par rapport à /opt/mon-repertoire-perso/ include 'mon-script.php'; // par rapport à /opt/mon-repertoire-perso/ require 'mon-script.php'; // par rapport à /opt/mon-repertoire-perso/ include '../mon-repertoire-perso/mon-script.php'; // par rapport à /usr/share/pear/ include '../../../opt/mon-repertoire-perso/mon-script.php';
L'instanciation de classes
PHP est compatible programmation orientée objet. Il est possible de développer en utilisant classes, méthodes, interfaces...
Voici un exemple de classe php qui hérite de la classe standard Exception, avec un constructeur (attention pas de polymorphisme en php) :
<?php
// définie la classe d'exception
class MonException extends Exception
{
private $params;
public function __construct($message,$code,$params)
{
parent::__construct($message,$code);
$this->params = $params;
}
public function getParams()
{
return $this->params;
}
}
// instancie l'exception
$exception = new MonException();
// lève l'exception instanciée
throw $exception;
Il est aussi possible, et recommandé de déplacer la définition des classes dans des fichiers bien distinct portant le nom de la classe :
- Fichier library/MonException.php :
<?php
class MonException extends Exception
{
private $params;
public function __construct($message,$code,$params)
{
parent::__construct($message,$code);
$this->params = $params;
}
public function getParams()
{
return $this->params;
}
}
- Script principal mon-script.php :
<?php
set_include_path(get_include_path().PATH_SEPARATOR
.dirname(__FILE__).'/library');
require_once 'MonException.php';
throw new MonException();
L'utilisation de set_include_path() permet d'ajouter le répertoire library/ dans l'include path et permet ainsi aux fonctions d'inclusions de trouver le fichier contenant la classe.
L'utilisation de require_once permet de ne charger qu'une seule fois le fichier MonException.php, au cas où il aurait été déjà chargé si le script mon-script.php est lui même inclus par un autre script qui avait déjà chargé le fichier MonException.php, et donc permet de ne charger qu'une seule fois la définition de la classe MonException (sinon PHP lève une erreur fatale dès que 2 définitions de classes portent le même nom).
Autoload: où chargement automatique des classes en PHP
Lors de la première utilisation d'une classe dans un script, il est possible d'inclure automatiquement sa définition si elle placée dans un fichier respectant certaines conventions.
En effet, le script mon-script.php suivant :
<?php
class MaClass
{
public function maMethod()
{
return 'result';
}
}
$o = new MaClass();
echo $o->maMethod();
peut être remplacé par 2 fichier :
- library/MaClass.php :
<?php
class MaClass
{
public function maMethod()
{
return 'result';
}
}
- mon-script.php :
<?php
set_include_path(get_include_path().PATH_SEPARATOR.dirname(__FILE__).'/library');
function __autoload($className)
{
require_once $className.'.php';
}
$o = new MaClass();
echo $o->maMethod();
En effet, PHP propose le mécanisme d'autoload permettant de définir une fonction qui sera automatiquement appelée dès que PHP détectera l'utilisation d'une classe inconnue.
Dans notre cas, dans le fichier mon-script.php, la première utilisation de la classe MaClass est faite lors de l'utilisation de l'opérateur d'instanciation (new) : nous tentons d'instancier la classe sans avoir inclus le fichier de définition de la classe. PHP déclenche alors le mécanisme d'autoload CAR nous avons définit avant une fonction callback spéciale nommée __autoload($className), charge à notre fonction d'inclure le fichier qui porte le nom de la classe en espérant qu'il existe dans l'include path.
Autoload, classes et packages
Avec PHP < 5.3, le concept de package/namespace n'existe pas réellement et nous sommes obligés d'avoir recours à certains artifices pour simuler le concept de package (comme nous pourrions l'avoir dans d'autres langages objet) :
- par exemple la classe MaClass dans le package MonPackage situé dans le fichier library/MonPackage/MaClass.php :
<?php
class MonPackage_MaClass
{
public function maMethod()
{
return 'result';
}
}
- et le script mon-script.php qui utilise cette classe grâce à un autoload adapté :
<?php
set_include_path(get_include_path().PATH_SEPARATOR
.dirname(__FILE__).'/library');
function __autoload($className)
{
require_once str_replace('_','/',$className).'.php';
}
$o = new MonPackage_MaClass();
echo $o->maMethod();
Autoload à la mode SPL (Standard PHP Library)
la fonction magique autoload() est un mécanisme standard php. Cependant l'extension SPL, devenu standard depuis quelques temps en PHP 5 permet de fournir plusieurs fonctions autoload selon une syntaxe légèrement différente (le principe est le même) :
<?php
function maFonctionAutoload1($className)
{
if ('Pkg1_' !== substr($className,0,5)) {
return false;
}
require_once 'repertoire1/'
.str_replace('_','/',$className).'.php';
}
function maFonctionAutoload2($className)
{
if ('Pkg2_' !== substr($className,0,5)) {
return false;
}
require_once 'repertoire2/'
.str_replace('_','/',$className).'.php';
}
spl_autoload_register('maFonctionAutoload1');
spl_autoload_register('maFonctionAutoload2');
// ...
Classe d'autoload
Pour ceux qui développe 100% en objet, il est possible d'utiliser une méthode d'autoload plutôt qu'une fonction via SPL :
- Fichier library/Autoloader.php :
<?php
class Autoloader
{
public function __construct()
{
// ajout du répertoire parent (library/) à l'include path
set_include_path(get_include_path()
.PATH_SEPARATOR.realpath(dirname(__FILE__).'/..'));
spl_autoload_register(array('Autoloader','autoload'));
}
public function autoload($className)
{
require_once str_replace('_','/',$className).'.php';
}
}
- Script mon-script.php :
/** * inclusion de l'autoloader avec chemin absolu * car l'include path n'est pas encore positionné */ include dirname(__FILE__).'/library/Autoloader.php'; $o = new MaClass(); echo $o->maMethod();
Conclusion
Grâce à cette technique vous n'aurez plus besoin d'utiliser les require/include dans vos scripts, si vous développez objet et si vous nommez correctement vos classes/fichiers Les frameworks Zend Framework, Symfony, ... proposent des modules d'auto-chargement basés sur ces principes (dans les grandes lignes).
Attention cependant à certains points :
- plus il y a de répertoires listés dans l'autoload et plus vous aurez potentiellement un impact sur les performances
- les répertoires contenant les classes les plus "populaires" (i.e. les plus utilisées dans votre code) doivent se situer au début de l'include path, pour optimiser les performances, notamment les répertoires des frameworks
- si vous développez un framework ou une librairie, privilégiez l'utilisation explicite des require_once plutôt que l'autoload, car votre librairie sera incluse dans une application qui aura peut être son propre mécanisme d'include path potentiellement incompatible avec le votre
- vous devez avoir une rigueur à toute épreuve pour le nommage et le placement de vos classes/fichiers : 1 fichier par classe, 1 classe par fichier, les fichiers portent le même nom que les classes.
Et vous quelles sont vos pratiques pour le nommage / rangement et auto-chargement de vos classes ?
Commentaires récents