define(), constant() et cie : Le guide complet

20. août 2018 Guides
define(), constant() et cie : Le guide complet

Il devient rare qu’une extension, un thème, un projet PHP ne contienne pas de constantes PHP. Vous en utilisez surement aussi. Voici quelques subtilités qui devraient vous laisser perplexe, je vais non seulement jouer avec le code mais aussi avec les versions PHP pour nous rappeler à tous que la rétrocompatibilité est toujours au rendez-vous.

Rappel

Une constante, à l’opposé d’une variable, est constante est ne peut pas varier. J’ai l’impression de me répéter. La constante ne pourra donc jamais être modifiée en cours de script alors qu’une variable a pour but de pourvoir varier, et même exister ou non. Une constante ne peut pas non plus être supprimée.

Dans cet article je ne vais retourner plus loin que PHP 5.0, c’est déjà pas mal, et trop à la fois.

define()

La façon la plus courante et la plus connue pour déclarer une constante.

// Déclaration de la constante "FOO"
define( 'FOO', 'bar' );

On écrit habituellement les constantes en majuscules, ce qui permet à la lecture du code de les repérer facilement, leur lecture est relativement simple :

var_dump( FOO );
// string(3) "bar"

Sachez qu’il est possible de définir une constante dans une fonction, cela permet par exemple de créer une fonction qui déclare toutes les constantes de votre développement comme le fait WordPress avec wp_initial_constants() :

function default_constants() {
    define( 'BAR', 'qux' );
}
default_constants();
var_dump( BAR );
string(3) "qux"

On peut même concaténer des constantes, ça c’est bien :

define( 'MY_PATH', __DIR__ );
define( 'MY_FILE', MY_PATH . '/file.php' );
var_dump( MY_FILE );
string(17) "home/www/file.php"

Et si on s’amusait un peu. Il existe bien évidemment des noms de constantes interdits et impossibles. La documentation dit :

Un nom de constante valide commence par une lettre ou un souligné, suivi d’un nombre quelconque de lettres, chiffres ou soulignés. Sous forme d’expression régulière, cela peut s’exprimer comme ceci : [a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*

http://php.net/manual/fr/language.constants.php

En vérifiant cette expression sur PHPLiveRegex on peut voir que seule la première ligne qui est FOO permet de valider correctement le nom. J’y ai même inséré une ligne vide, incorrect !

Allez, j’essaie quand même :

define( '2FOO', '2bar' );
var_dump( 2FOO );
Parse error: syntax error, unexpected 'FOO' (T_STRING), expecting ')' on line 2

He bien non, il n’aime pas ma tentative de var_dump() sur une constante qui commence par un chiffre. Par contre, la déclaration s’est bien passée …

Allons plus loin :

define( '2FOO', '2bar' );
define( '', 'empty' );
define( ' FOO', 'space' );
var_dump( '' );
var_dump(  FOO );
var_dump( " FOO" );
string(0) ""
Warning: Use of undefined constant FOO - assumed 'FOO' on line 6
string(3) "FOO"
string(4) " FOO"

Aie, pas du tout. La constante vide affiche en fait le contenu de la chaine que je passe en paramètre et non pas ma constante, et l’espace en plus devant ne sert à rien et si j’ajoute des double quotes, encore une fois cela ne fait qu’écrire le contenu de ma chaine et non la valeur de ma constante, tout faux !

Donc c’est bien ça, on ne peut pas définir une constante avec ces noms. Essayons de voir ce que ça donne si on redéfinit la même constante 2 fois :

define( 'FOO', 'bar' );
define( 'FOO', 'qux' );
var_dump( FOO );

Notice: Constant FOO already defined on line 2
string(3) "bar"

PHP déclenche une notice et la constante ne change pas, c’est bien comme cela que c’est prévu.

MAIS la fonction define() prend un paramètre supplémentaire qui est bool $case_insensitive = false. Jouons avec :

define( 'FOO', 'bar', true );
define( 'FOO', 'qux' );
var_dump( FOO );

// PHP < 7.3
string(3) "qux"

// PHP >=7.3
Deprecated: define(): Declaration of case-insensitive constants is deprecated on line 1
string(3) "qux"

Quoi ? On peut donc redéfinir une constante ? Oui et non.
Non, en fait la première déclaration a été traitée en minuscule par PHP en réalité, si on fait :

var_dump( foo );
string(3) "bar"

On a bien la valeur attendue, le 3è paramètre force le traitement en minuscule, c’est traitre car on a justement l’habitude d’écrire les constantes en majuscules !

Remarquez qu’en PHP 7.3 cela déclenchera une notice de dépréciation afin d’éviter ce genre de désagrément.

À retenir

Ne définissez pas vos constantes avec le 3è paramètre !

Au fait, j’ai vu une notice ? Voyons de plus près :

var_dump( FOO );
// PHP < 7.2
Notice: Use of undefined constant FOO - assumed 'FOO' on line 1
// PHP < 7.3
Warning: Use of undefined constant FOO - assumed 'FOO' on line 1
// PHP >=7.3
Fatal Error: Use of undefined constant FOO - assumed 'FOO' on line 1

On passe d’une notice pour les versions PHP jusqu’à la 7.0, puis ils ont monté le niveau avec un warning pour finalement dès la 7.3 de faire péter une Fatal Error !

À retenir

PHP 7.3 déclenche une Fatal Error lors de la tentative d’utilisation d’une constante non définie.

Je vous laisse imaginer le nombre de développements, extensions ou thèmes WordPress qui font encore ceci :

if ( WPLANG == 'en_US' ) …

Vous aurez droit à un site en page blanche pour ceux qui auront désactivé l’affichage des erreurs, et un beau message d’erreur divulguant le chemin complet vers le script (faille FPD) pour les autres. Tenez vos développements à jour !

“PHP 7.3 déclenche une Fatal Error lors de la tentative d’utilisation d’une constante non définie.”
Lu chez https://boiteaweb.fr/define-constant-et-cie-le-guide-complet-110236.html‎ via @BoiteAWeb #WordPress #PHP #Dev

Tiens, et si on déclarait la constante TRUE pour s’amuser encore ?

define( 'TRUE', FALSE );
var_dump( TRUE );
// PHP < 5.1.3
bool(false)
// PHP >= 5.1.3
bool(true)

Ho ! Ça a fonctionné un jour de pouvoir redéfinir ces constantes ? Oui on pouvait. TRUE est une constante un peu comme les autres, mais insensible à la casse, voyons ça pour comprendre :

define( 'FOOBAR', 'baz' );
var_dump( fooBAR );
var_dump( trUE );
string(3) "baz"
bool(true)

On déclare FOOBAR,  on affiche alors la valeur de cette constante mais avec une graphie de casse différente. Souvenez-vous que nous n’utilisons pas le 3è paramètre, le nom de la constante est alors sensible à la casse.
Même chose avec TRUE, je peux donc l’afficher avec une graphie de casse différente à nouveau.

Depuis PHP 5.1.3 on ne peut plus. Euh, spoil, depuis PHP 5.3 on peut de nouveau, comme ça :

// PHP >=5.3 & < 7.0
namespace NS;
define( 'TRUE', FALSE );
var_dump( TRUE );
bool(false)

Merci les namespaces ! Cela revient à lire NS\TRUE, cette constante existe mais pas lisible avec un var_dump() par exemple, le préfixe du namespace s’ajoutant automatiquement et de façon invisible.

La véritable constante affichée est donc NS\TRUE et non TRUE, mais le raccourci magique nous empêche maintenant de lire TRUE

À éviter

Il est possible de redéfinir une constante grâce aux namespace.

Bon ok, c’est marrant, mais on peut définir quoi d’autres de marrant ? die ! die n’est pas une fonction, ni une constante mais un constructeur de langage qui peut être appelé sans les parenthèses, et bien, testons ça !

define( 'die', 'foo');
var_dump( die );
echo 'EOF.';
//

Rien ! Le die a pris le relai et a coupé le script. J’ai donc défini une constante die qui vaut bel et bien "foo", mais impossible de lui faire faire un var_dump() car le constructeur de langage a la priorité.

À éviter

Il est possible de définir une constante avec des noms interdits.

On a bien joué avec la déclaration d’un nom de constante, et sa valeur alors, on peut mettre quoi  à part "bar" et TRUE ? Un tableau je peux ?

define( 'FOO', array( 'bar' ) );
var_dump( FOO );
// PHP < 7.0
Warning: Constants may only evaluate to scalar values on line 1
Notice: Use of undefined constant FOO - assumed 'FOO' on line 2
string(3) "FOO"
// PHP >= 7.0
array(1) {
  [0]=>
  string(3) "bar"
}

Donc c’est devenu possible depuis PHP 7.0 de déclarer une constante ayant pour valeur un tableau, mais pas avant, je note je note.

Une dernière pour la route et on passe à autre chose :

define( 'NOMBRE_PI', 3,1415926535898 );
var_dump( NOMBRE_PI );
int(3)

Vous vous attendiez à float(3.1415926535898) ? Et bien non, je vous ai juste piégé·e·s, j’ai mis une virgule collée au 3, ce qui équivaut à :

define( 'NOMBRE_PI', 3, 1415926535898 );

Qui équivaut lui même à :

define( 'NOMBRE_PI', 3, true );

J’ai juste défini une constante avec casse sensible en jouant sur le fait que PHP prendra mon chiffre 1415926535898 pour TRUE. GOTCHA !
Pour info la véritable constante de π est M_PI.

const

L’autre moyen de déclarer une constante est l’utilisation du constructeur de langage const.

Petite différence, pas de quotes autour du nom de la future constante, cela ne génère pas d’erreur contrairement à si vous en mettez, c’est la Fatal Error directe !.

const FOO = 'bar';
var_dump( FOO );
// PHP < 5.3
Parse error: syntax error, unexpected T_CONST on line 1
// PHP >= 5.3
string(3) "bar"

Ok, c’est donc disponible depuis 5.3, quand on sait que WordPress est compatible 5.2.4, il n’y a donc pas de const dans le core (hors classe, on en parle après).

Revoyons si tout ce qu’on a vu précédemment qui fonctionnait avec define() fonctionne avec const, et inversement.

function default_constants() {
    const BAR = 'qux';
}
default_constants();
var_dump( BAR );
Parse error: syntax error, unexpected 'const' (T_CONST) on line 2

Ha en fait non, on ne peut pas déclarer une constante dans une fonction avec const. J’ai direct une Fatal Error de syntaxe. Par contre on peut faire ça dans une classe :

class defaultConstants {
    const BAR = 'qux';
}
var_dump( BAR );
Warning: Use of undefined constant BAR - assumed 'BAR' on line 2
string(3) "BAR"

Et bien non, la constante n’est finalement pas globale, il faut faire :

class defaultConstants {
    const BAR = 'qux';
}
var_dump( defaultConstants::BAR );
string(3) "qux"

Et bien alors, définissons aussi avec define() dans la classe !

class defaultConstants {
    define( 'BAR', 'qux' );
}
var_dump( BAR );
Parse error: syntax error, unexpected 'define' (T_STRING), expecting function (T_FUNCTION) or const (T_CONST) on line 2

Ha non en fait on ne peut pas, on a donc 2 comportements différents selon le cas d’une fonction ou d’une classe, pourquoi faire simple quand on peut faire compliqué !

Testons la déclaration sous condition, notre exemple simple de tout à l’heure :

if ( TRUE ) {
    const FOO = 'bar';
}
Parse error: syntax error, unexpected 'const' (T_CONST) on line 2

Ho ! On ne peut pas déclarer de constantes avec const dans une condition !? Ok …

À retenir

Il n’est pas possible d’utiliser define() dans une classe, ni const dans une condition.

Voyons maintenant la concaténation :

const MY_PATH = __DIR__;
const MY_FILE = MY_PATH . '/file.php';
var_dump( MY_FILE );
// PHP < 5.6
Parse error: syntax error, unexpected '.', expecting ',' or ';' on line 2
// PHP >= 5.6
string(13) "home/file.php"

Ça fonctionne que si on a PHP 5.6 alors qu’avec define() aucune restriction.  Et dans une classe ça donne quoi ?

class defaultConstants {
    const FOO = 'baz';
    const BAR = FOO . 'qux';
}
var_dump( defaultConstants::BAR );
Warning: Use of undefined constant FOO - assumed 'FOO' on line 3
string(6) "FOOqux"

Haaa non ça ne marche pas bien … ou alors …

class defaultConstants {
    const FOO = 'baz';
    const BAR = self::FOO . 'qux';
}
var_dump( defaultConstants::BAR );
string(6) "bazqux"

Ha voilà, il faut faire appel à self::.

Au tour de la double déclaration :

const FOO = 'bar';
const FOO = 'qux';
var_dump( FOO );
Notice: Constant FOO already defined on line 3
string(3) "bar"

Même comportement, et ici, pas possible de jouer sur la casse sans que se voit. ok la suite !

Et la redéclaration de TRUE ?

const TRUE = FALSE;
var_dump( TRUE );
Fatal error: Cannot redeclare constant 'TRUE' on line 1

Contrairement à define() qui ne dit rien et nous affiche même un TRUE, avec const on a droit à une Fatal Error !

Et peut-on ajouter un tableau alors ?

const FOO = array( 'bar' );
var_dump( FOO );
// PHP < 5.6
Fatal error: Arrays are not allowed as constants on line 1
// PHP >= 5.6
array(1) {
  [0]=>
  string(3) "bar"
}

Ho la bonne surprise ! En fait il était possible depuis PHP 5.6 de déclarer des constantes contenant des tableaux en passant par const, alors que pour define() nous avons vu précédemment qu’il faut être en version 7.0.

À retenir

Il est possible de définir une constante contenant des données scalaires depuis PHP 5.6 avec const et avec define() depuis PHP 7.0.

defined()

La fonction defined() sert à savoir si une constante est définie, tout simplement comme ceci :

if ( defined( 'FOO' ) ) {
    echo 'Hello World!';
} else {
    echo 'Nope!';
}

Astuce bien sympathique et propre pour définir une constante si elle ne l’est pas :

defined( 'FOO' ) || define( 'FOO', 'bar' );
var_dump( FOO );
string(3) "bar"

On repart sur autre chose, un exemple plutôt simple :

define( 'DEBUG', true );
if ( defined( 'DEBUG' ) ) {
    define( 'DEBUG_DISPLAY', true );
    var_dump( 'debug' );
}
// string(5) "debug"

Rien à redire n’est-ce pas ? On déclare une seconde constante si la première est définie. Alors, regardez de plus près, car si je fais ça :

define( 'DEBUG', false );
if ( defined( 'DEBUG' ) ) {
   var_dump( 'debug' );
}
// string(5) "debug"

He oui, même sur FALSE, je suis en mode debug ! Il ne suffit pas de vérifier si la constante est définie mais si elle vaut TRUE en plus !

define( 'DEBUG', false );
if ( defined( 'DEBUG' ) && DEBUG ) {
   var_dump( 'debug' );
}
//

Pourquoi on ne peut pas direct faire ça ? :

if ( DEBUG ) {
   var_dump( 'debug' );
}
// Notice: Use of undefined constant FOO - assumed 'FOO'
// string(5) "debug"

Car si la constante n’est pas définie pour une raison X ou Y, PHP va la transtyper (caster) en une chaîne, et une chaîne ça vaut TRUE en PHP ! Donc on passe dans le if.

Imaginez ça dans un contexte plus sécuritaire :

if ( true == IS_ADMIN ) {
    echo 'some private data. #GDPR';
}
// string(24) "some private data. #GDPR"

Oui, ça passe, code vulnérable, ouch.

Attention

La vérification d’une constante se fait à la fois sur son existence et sur sa valeur. Utilisez === pour cette vérification.

constant()

Cette fonction permet la lecture d’une constante. Son intérêt réside dans le fait que le nom est une chaine, on peut donc avoir des noms dynamiques et tout de même les lire :

define( 'FOO', 'bar' );

var_dump( constant( 'FOO' ) );
string(3) "bar"

$var = 'FOO';
var_dump( constant( $var ) );
string(3) "bar"

Plutôt sympa de pouvoir lire aussi de cette façon, tiens ça me donne une idée … Allez, reprenons les noms de constantes interdits avec PHPLiveRegex :

define( '2FAST', '2furious' ); // Je suis drôle.
define( '', 'empty' );
define( ' FOO', 'space' );
var_dump( constant( '2FAST' ) );
var_dump( constant( '' ) );
var_dump( constant( ' FOO' ) );
// Toutes versions de PHP
string(4) "2furious"
string(5) "empty"
string(5) "space"

Hahaha et bien il semblerait qu’il est donc tout à fait possible à la fois de déclarer des constantes avec des noms interdits et même de les lire !

Et donc avec le die aussi je peux le faire ?

define( 'die', 'foo' );
var_dump( constant( 'die' ) );
var_dump( 'EOF.' );
string(6) "foo"
string(4) "OEF."

Hahaha ! Oui on peut le lire, c’est quand même ultra permissif non ?

À éviter

Il est possible de définir une constante avec des noms interdits.

Allez, l’exemple de la classe pour lire une constante avec … constant() !

class defaultConstants {
    const BAR = 'qux';
}
var_dump( constant( 'defaultConstants::BAR' ) );
string(3) "qux"

Il faut utiliser la même chose MAIS sous forme de chaine, rien à concaténer, PHP se débrouille.

Je me sers beaucoup de constant() dans SecuPress, notamment dans le scanner du fichier wp-config.php, cela me permet de définir une liste de noms de constantes à vérifier et d’aller les lire via une boucle foreach au lieu d’écrire autant de lignes dupliquées si j’avais à utiliser le nom de la constante.

Lecture de toutes les constantes

Il est possible de lire toutes les constantes du core de PHP et utilisateur grâce à la fonction get_defined_constants(). Je conseille une utilisation avec le paramètre bool $categorize = FALSEà TRUE !

print_r( get_defined_constants( true ) );
array(20) {
  ["Core"]=>
  array(83) {
    ["E_ERROR"]=>
    int(1)
    ["E_RECOVERABLE_ERROR"]=>
    int(4096)
    ["E_WARNING"]=>
    int(2)
    …

Vous pouvez donc facilement récupérer celles qui ont été définies par les scripts PHP chargés comme WordPress, les extensions, les thèmes etc avec :

<?php
define( 'FOO', 'bar' );
print_r( get_defined_constants( true )['user'] );
array(1) {
  ["FOO"]=>
  string(3) "bar"
}

Constantes prédéfinies et réservées

PHP inclus bien évidemment ses propres constantes, la liste est disponible. Pour ne citer qu’elles, il y a PHP_VERSION, PHP_INT_MAX, PHP_EOL, E_ALL, TRUE, FALSE, NULL

Aussi chaque extension PHP peut ajouter les siennes comme par exemple les flags de la fonction filter_var() avec FILTER_VALIDATE_INT, FILTER_VALIDATE_EMAIL, FILTER_SANITIZE_NUMBER_INT, FILTER_FLAG_IPV4, DIRECTORY_SEPARATOR, CRYPT_SHA512, GLOB_BRACE etc

Les extensions et thèmes de WordPress ajouteront aussi les leur, comptez sur eux !

Comme nous l’avons vu, évitez de les redéclarer en trichant avec un namespace !

Il y a aussi les constantes magiques qui changent selon leur contexte, elles ne changent pas pendant mais pour l’execution, avant donc. La liste est aussi disponible, il y a __FILE__, __DIR__, __LINE__, __FUNCTION__, __CLASS__, __METHOD__, __TRAIT__, et __NAMESPACE__.

PHP recommande d’éviter de créer une constante sur ce modèle __FOO__ car si un jour PHP crée le même nom que vous, vous passez second, c’est la Fatal Error au rendez-vous !

Différences entre define() et const

Finalement, on s’aperçoit que ce n’est pas si identique. Je fais de l’ironie en disant que j’aime la logique et la consistance, mais en fait c’est normal que cela ne se comporte pas de la même façon, const n’est pas un alias de define() et inversement.

La différence fondamentale entre ces 2 façons de déclarer est que const définit les constantes lors de la compilation, alors que define() le fait lors de l’exécution.

Aussi, une constante déclarée dans une classe, avec const donc, prends moins de place en mémoire. Si vous avez une tonne de constantes à définir ou des constantes qui contiendront des données plus lourdes qu’à l’habituelle, mieux vaut le faire dans une classe et l’instancier au moment venu.

À retenir

Une constante déclarée dans une classe, avec const donc, prends moins de place en mémoire.

Les constantes de WordPress

WordPress contient plus de 160 constantes. En avril 2013 j’avais créé et vendu un ebook 6,80€ sur lesquels je gagnait 5,50€, au final j’ai gagné moins d’une journée de travail sur 5 ans de vente (bon ok, j’en fais pas la pub, pas grave).

J’ai décidé de le mettre à jour et de le rendre public et gratuit en licence CC-BY pour que tout le monde puisse en profiter, certes cela représente toujours un sacré travail alors j’ai décidé de le sortir au format papier.

Télécharger cet ebook gratuitement
Tous les secrets du wp-config.php
et partagez le avec vos contacts !

J’ai téléchargé le ebook gratuit “Tous les secrets du wp-config.php” sous #WordPress. À vous !
#PHP #Dev #ebooks

Vous pouvez (devez !) exporter ce document en PDF via le menu « File > Download as > PDF ». Les commentaires sont ouvert si jamais j’ai fait une erreur dans le texte, dans le code, un oubli etc, merci d’avance !

Le format papier vous intéresse aussi ? Dites le moi sur twitter @boiteaweb.

Je vous laisse, vous avez surement des constantes à mettre à jour et un ebook à lire !

Lire la suite

Vous aimez ? Partagez !


Réagir à cet article

120 caractères maximum