Manuel:Conventions de codage/PHP
Cette page documente un guide de développement de MediaWiki, construit au fil du temps par consensus des développeurs (ou parfois simplement par un des développeurs principaux) |
Cette page décrit les conventions de codage utilisées pour les fichiers de la base de code de MediaWiki écrits en PHP. Voir aussi les conventions générales qui s'appliquent à tous les langages de programmation, y compris PHP. Si vous désirez un résumé des éléments à vérifier lorsque vous faites vos validations (submit), voyez la Liste des vérifications avant validation .
La plupart des règles de style du code peuvent être corrigées automatiquement, ou du moins détectées par PHP_CodeSniffer (avec PHPCS), en utilisant un ensemble de règles adaptées pour MediaWiki. Pour plus d'informations, voir Intégration continue/CodeSniffer PHP .
Structure du code
Espaces
MediaWiki encourage un style bien structuré au niveau des espaces pour une lisibilité optimale.
Indenter avec des caractères de tabulation et non pas en mettant des espaces. Limiter les lignes à 120 caractères (en portant la largeur de tabulation à 4 caractères).
Placez les espaces de chaque côté des opérateurs binaires, par exemple :
// Non :
$a=$b+$c;
// Oui :
$a = $b + $c;
Placez les espaces à côté de l'intérieur des parenthèses, sauf si elles sont vides. Ne mettez pas d'espace après un nom de fonction.
$a = getFoo( $b );
$c = getBar();
Mettre un espace après le :
dans l'indication du type de retour de la fonction, et non pas avant :
function square( int $x ): int {
return $x * $x;
}
Placez les espaces à l'intérieur des crochets lorsque vous déclarez un tableau, sauf si le tableau est vide. Ne mettez pas d'espace dans les crochets lorsque vous accédez aux éléments d'un tableau.
// Oui
$a = [ 'foo', 'bar' ];
$c = $a[0];
$x = [];
// Non
$a = ['foo', 'bar'];
$c = $a[ 0 ];
$x = [ ];
Les structures de contrôle telles que if
, while
, for
, foreach
, switch
, ainsi que le mot clé catch
, doivent être suivies d'un espace :
// Oui
if ( isFoo() ) {
$a = 'foo';
}
// Non
if( isFoo() ) {
$a = 'foo';
}
Lorsque le 'cast' est appliqué sur un type, ne mettez pas d'espace à l'intérieur ni après l'opérateur cast :
// Oui
(int)$foo;
// Non
(int) $bar;
( int )$bar;
( int ) $bar;
Dans les commentaires, il doit y avoir un espace entre le caractère #
(ou //
) et le commentaire lui-même.
// Oui : commentaire en ligne plus propre
//Non : il manque un espace
/***** Ne pas commenter de la sorte ***/
Opérateur ternaire
L'opérateur ternaire peut être utilisé à profit si les expressions sont très courtes et triviales :
$title = $page ? $page->getTitle() : Title::newMainPage();
Toutefois, si vous envisagez une expression multiligne avec un opérateur ternaire, utilisez plutôt un bloc if ()
.
N'oubliez pas que l'espace disque n'est pas cher, que la lisibilité du code est primordiale, if est en anglais mais ?:
ne l'est pas.
Si vous utilisez une expression ternaire sur plusieurs lignes, le point d'interrogation et les deux points doivent figurer au début des deuxième et troisième lignes et non à la fin des première et deuxième lignes (contrairement à la convention JavaScript de MediaWiki).
Depuis que MediaWiki nécessite PHP 7.4.3 ou ultérieur, l'utilisation du raccourci de l'opérateur ternaire (?:
) connu aussi sous le nom d'opérateur elvis et introduit en PHP 5.3, est autorisée.
Depuis PHP 7.0, l'opérateur de fusion nul est également disponible et peut remplacer l'opérateur ternaire dans certains cas d'utilisation.
Par exemple, au lieu de :
$wiki = isset( $this->mParams['wiki'] ) ? $this->mParams['wiki'] : false;
vous pouvez écrire ceci :
$wiki = $this->mParams['wiki'] ?? false;
Chaînes de caractères
Il est recommandé d'utiliser les apostrophes simples dans tous les cas où elles sont équivalentes aux doubles.
Le code utilisant des guillemets simples est moins sujet aux erreurs et plus facile à relire, car il ne peut pas contenir accidentellement de séquences d'échappement ni de variables.
Par exemple, l'expression régulière "/\\n+/"
nécessite une barre oblique inverse supplémentaire, ce qui la rend légèrement plus confuse et source d'erreurs que '/\n+/'
.
De même, pour les personnes utilisant les claviers qwerty US/UK, ils sont plus faciles à taper car ils évitent d’appuyer sur la touche Maj.
Néanmoins, n'ayez pas peur d'utiliser la fonctionalité PHP d'interpolation des chaînes à double apostrophes :
$elementId = "myextension-$index";
Ceci a des caractéristiques de performance quelque peu meilleures que l'équivalent utilisant l'opérateur de concatenation '.' (dot) , et c'est aussi plus joli.
Les chaînes de style Heredoc ('Here document' ou document en ligne) sont parfois utiles :
$s = <<<EOT
<div class="mw-some-class">
$boxContents
</div>
EOT;
Certains auteurs aiment utiliser END comme balise de fin, qui est également le nom d'une fonction PHP.
Fonctions et paramètres
Evitez de passer un trop grand nombre de paramètres aux fonctions at aux constructeurs :
// Constructeur pour Block.php de 1.17 à 1.26. NE PAS faire ceci !
function __construct( $address = '', $user = 0, $by = 0, $reason = '',
$timestamp = 0, $auto = 0, $expiry = '', $anonOnly = 0, $createAccount = 0, $enableAutoblock = 0,
$hideName = 0, $blockEmail = 0, $allowUsertalk = 0
) {
...
}
Il devient rapidement impossible de se souvenir de l'ordre des paramètres et vous finirez inévitablement par coder toutes les valeurs par défaut dans les appelants simplement pour personnaliser un paramètre à la fin de la liste. Si vous êtes tenté de coder une fonction comme celle-ci, envisagez plutôt de passer un tableau associatif de paramètres nommés.
En général, l'utilisation de paramètres booléens est déconseillée dans les fonctions.
Dans $object->getSomething( $input, true, true, false )
, sans consulter la documentation de MyClass::getSomething()
, il est impossible de savoir ce que ces paramètres sont censés indiquer.
Il vaut bien mieux utiliser les constantes de classe et créer un paramètre drapeau générique :
$myResult = MyClass::getSomething( $input, MyClass::FROM_DB | MyClass::PUBLIC_ONLY );
Ou pour que votre fonction accepte un tableau de paramètres nommés :
$myResult = MyClass::getSomething( $input, [ 'fromDB', 'publicOnly' ] );
Essayez de ne pas modifier les variables au cours d'une fonction et évitez de modifier les paramètres transmis à une fonction (à moins qu'ils ne soient passés par référence et que c'est évidemment le but de la fonction).
Expressions d'assignement
Utiliser une assignation comme une expression est surprenant pour le lecteur et ressemble à une erreur. N'écrivez pas ce type de code :
if ( $a = foo() ) {
bar();
}
L'espace n'est pas cher et vous écrivez rapidement, utilisez donc à la place :
$a = foo();
if ( $a ) {
bar();
}
Utiliser l'assignation dans une boucle while()
est légitime pour l'itération :
$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
while ( $row = $dbr->fetchObject( $res ) ) {
showRow( $row );
}
Ceci est inutile dans le nouveau code; remplacez par :
$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
foreach ( $res as $row ) {
showRow( $row );
}
Emprunts au langage C
Le langage PHP a été conçu par des personnes qui aiment le langage C et qui ont voulu en ramener le souvenir dans PHP. Mais PHP a quelques différences importantes par rapport à C.
En C, les constantes sont implémentées en tant que macros de préprocesseur et sont rapides. En PHP, elles sont implémentées en effectuant une recherche dans une table de hachage à l'exécution avec le nom de la constante et sont plus lents que si on utilisait simplement un littéral de chaîne. Dans la plupart des endroits où vous utiliseriez un ensemble de macros en C ou similaires à enum, vous pouvez utiliser des littéraux de chaîne en PHP.
PHP a trois littéraux spéciaux pour lesquels la combinaison des majuscules/minuscules n'est pas significative dans le langage (depuis PHP 5.1.3), mais pour lesquels notre convention utilise toujours les minuscules: true
, false
et null
.
Utilisez elseif
et non pas else if
.
Ils ont des significations différentes et subtiles :
// Ce :
if ( $foo === 'bar' ) {
echo 'Hello world';
} else if ( $foo === 'Bar' ) {
echo 'Hello world';
} else if ( $baz === $foo ) {
echo 'Hello baz';
} else {
echo 'Eh?';
}
// Est actuellement équivalent à :
if ( $foo === 'bar' ) {
echo 'Hello world';
} else {
if ( $foo == 'Bar' ) {
echo 'Hello world';
} else {
if ( $baz == $foo ) {
echo 'Hello baz';
} else {
echo 'Eh?';
}
}
}
Et le dernier a de piètres performances.
Syntaxe alternative pour les structures de contrôle
PHP offre une syntaxe alternative pour les structures de contrôle en utilisant des points-virgule ';' et des mots-clés tels que endif
, endwhile
, etc. :
if ( $foo == $bar ):
echo "<div>Hello world</div>";
endif;
Cette syntaxe doit être évitée car elle empêche beaucoup d'éditeurs de texte de faire correspondre et de refermer automatiquement les paires d'accolades. Les accolades doivent être utilisées à la place :
if ( $foo == $bar ) {
echo "<div>Hello world</div>";
}
Placement des parenthèses
Voir Indentation et alignement dans les conventions de codage.
Pour les fonctions anonymes, préférez l'utilisations des fonctions fléchées quand la fonction anonyme tient sur une seule ligne. Le fonctions fléchées sont plus concises et plus lisibles que les fonctions régulières anonymes et évitent soigneusement les problèmes de formatage qui surviennent avec les fonctions anonymes d'une seule ligne.[1]
Déclarations de type dans les paramètres de fonction
Utilisez les déclarations de type et les déclarations du type renvoyé (type hinting) si nécessaire. (But see #Don't add type declarations for "big" legacy classes below.)
Notez qu'avant 7.4 PHP ne pouvait pas gérer la restriction/relaxation du type renvoyé dans les sous-classes.
Les indications de type scalaire sont autorisées à partir de MediaWiki 1.35, suite au passage en PHP 7.2 (T231710).
Utilisez la syntaxe PHP 7.1 pour les paramètres nuls : choisissez
public function foo ( ?MyClass $mc ) {}
au lieu de
public function foo ( MyClass $mc = null ) {}
Le premier transmet précisément la nullabilité d'un paramètre, sans risquer l'ambiguïté avec les paramètres optionnels. Les IDE et les outils d'analyse statique le reconnaîtront également comme tel et ne se plaindront pas si un paramètre non nullable suit un paramètre nullable.
Nommage
Files | UpperCamelCase | PHP files should be named after the class they contain, which is UpperCamelCase. For instance, WebRequest.php contains the WebRequest class. See also Conventions de codage .
|
---|---|---|
Namespaces | UpperCamelCase | |
Classes | UpperCamelCase | Utilisez des majuscules alternées (UpperCamelCase) pour les noms des classes : $1. For example: class ImportantClass
|
Constants | Uppercase with underscores | Utilisez les majuscules avec des caractères souligné '_' pour les constantes de classe et les constantes globales : DB_PRIMARY , IDBAccessObject::READ_LATEST .
|
Functions | lowerCamelCase | Use lowerCamelCase when naming functions. For example:
private function doSomething( $userPrefs, $editSummary )
|
Function variables | lowerCamelCase | Use lowerCamelCase when naming function variables. Les autres variables sont habituellement en minuscules ou en lowerCamelCase; évitez d'utiliser les soulignés dans les noms de variables. |
Prefixes
Il existe aussi des préfixes utilisés à différents endroits :
Préfixes de fonctions
wf
(fonctions wiki) – fonctions de niveau général, par exemplefunction wfFuncname() { ... }
ef
(fonctions d'extensions) = les fonctions globales des extensions, bien que dans la plupart des cas, le style moderne déclare les fonctions d'accroche comme des méthodes statiques de classe, laissant peu ou pas de fonctions brutes de premier niveau ainsi nommées. (-- brion dans Manual_talk:Coding_conventions#ef_prefix_9510)
Préférez les phrases verbales : utilisez getReturnText()
au lieu de returnText()
.
Lorsque vous exposez des fonctions utilisées pour les tests, marquez-les comme @internal
conformément à la Politique des interfaces stables.
Une mauvaise utilisation ou une dépendance non officielle à celles-ci est plus problématique que la plupart des méthodes internes, et en tant que tel, nous avons tendance au throw si elles s'exécutent en dehors d'un environnement de test.
/**
* Reset example data cache.
*
* @internal For testing only
*/
public static function clearCacheForTest(): void {
if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
throw new RuntimeException( 'Not allowed outside tests' );
}
self::$exampleDataCache = [];
}
Préfixes de variables
$wg
– variables globales, par exemple$wgTitle
. Utilisez toujours ceci pour les nouvelles variables globales, de sorte qu'il soit facile de repérer les déclarations "global $wgFoo
" manquantes. Dans les extensions, le nom de l'extension doit être utilisé comme séparateur d'espace de noms. Par exemple,$wgAbuseFilterConditionLimit
, et non$wgConditionLimit
.- Les déclarations globales doivent être au début d'une fonction pour que les dépendances puissent être déterminées sans avoir à lire l'intégralité de la fonction.
Il est courant de travailler avec une instance de la classe Database
; nous avons pour cela une convention de nommage qui permet de garder une trace de la nature du serveur auquel nous sommes connectés.
Ceci est particulièrement important dans les environnements répliqués, tels que Wikimedia et d'autres gros wikis; dans les environnement de développement, il n'y a généralement pas de différence entre les deux types, ce qui peut cacher des erreurs subtiles.
$dbw
– un objetDatabase
pour l'écriture (une connexion maître)$dbr
– un objetDatabase
pour la lecture indépendante des accès concurrents (cela peut être un esclave en mode lecture seule, discrètement derrière l'état maître, donc n'essayez jamais d'écrire dans la base de données avec, ou essayez d'obtenir une réponse d'« autorisation » pour les requêtes importantes comme les droits d'accès et les états bloqués)
Vous encore pouvez trouver ce qui suit dans du code ancien, mais pour du nouveau code, cela n'est pas recommandé :
$ws
– Variables de session, par exemple$_SESSION['wsSessionName']
$wc
– Variables de cookies, par exemple$_COOKIE['wcCookieName']
$wp
– Variables postées (envoyées par les champs de formulaire), par exemple$wgRequest->getText( 'wpLoginName' )
$m
– variables membres objets :$this->mPage
. Ceci n'est pas du tout recommandé dans le nouveau code, mais essayez de rester cohérent à l'intérieur d'une même classe.
Pièges
empty()
La fonction empty() ne doit être utilisée que pour ignorer les erreurs.
Sinon utilisez simplement !
(conversion booléenne).
empty( $var )
essentially does!isset( $var ) || !$var
.
Cas d'utilisation courant : clés de configuration booléennes optionnelles valantfalse
par défaut.$this->enableFoo = !empty( $options['foo'] );
- Méfiez-vous des pièges des conversions booléennes.
- Cela supprime les erreurs concernant les propriétés et les variables non définies. Si vous souhaitez uniquement tester les non définis, utilisez
!isset()
. Si vous souhaitez uniquement tester les valeurs « vides » (par exemple,false
, 0,[]
, etc.), utilisez!
.
isset()
N'utilisez pas isset() pour tester null
.
L'utilisation de isset
dans cette situation pourrait entraîner des erreurs en masquant les noms de variables mal orthographiées.
Au lieu de cela, utilisez $var === null
.
Conversion booléenne
if ( !$var ) {
…
}
- N'utilisez pas
!
niempty
pour tester si une chaîne ou un tableau est vide, car PHP considère'0'
commefalse
– mais'0'
est un titre valide et un nom d'utilisateur valide dans MediaWiki. A la place, utilisez=== ''
ou=== []
. - Etudiez les règles à propos de la conversion en booléen. Faites bien attention quand vous convertissez des chaînes de caractères en boléens.
Autre
- L'opérateur '+' des tableaux ne renumérote pas les clés des tableaux indexés numériquement, donc
[ 'a' ] + [ 'b' ] === [ 'a' ]
. Si vous voulez renuméroter les clés, utilisez array_merge() :array_merge( [ 'a' ], [ 'b' ] ) === [ 'a', 'b' ]
- Assurez-vous que error_reporting() vaut
-1
. Ceci vous notifiera s'il existe des variables non définies et des autres pièges subtils que PHP ignore. Voir aussi Comment déboguer . - Lorsque vous travaillez avec un fichier PHP pur (c'est à dire pas un modèle HTML), omettez les balises
?>
fermantes. Ces balises causent souvent des problèmes avec les espaces de fin et les messages d'erreur du type en-tête déjà envoyés (cf. bugzilla:17642 et http://news.php.net/php.general/280796). Il est conventionnel dans le contrôle de version pour les fichiers, qu'ils se terminent par un carractère de passage à la ligne (que les éditeurs peuvent ajouter automatiquement), ce qui peut alors déclencher cette erreur. - N'utilisez pas la syntaxe goto() introduite en 5.3. PHP a peut-être introduit la fonctionnalité, mais cela ne signifie pas que nous devons l'utiliser.
- Ne passez pas par référence lorsque vous parcourez un tableau avec
foreach
, à moins que vous n'y soyez obligé. Même dans ce cas, soyez conscient des conséquences. (Voir https://web.archive.org/web/20220924191559/https://www.intracto.com/en-be/blog/php-quirks-passing-an-array-by-reference/ et le Manuel PHP) - PHP vous permet de déclarer des variables statiques même au sein d'une méthode non statique d'une classe. Cela a conduit à des bogues subtiles dans certains cas, car les variables sont partagées entre les instances. Là où vous n'utiliseriez pas une propriété de
private static
, n'utilisez pas non plus de variable statique.
Opérateurs d'égalité
Faites attention avec les opérateurs de comparaison à double signe égal.
Le triple-égal (===
) est généralement plus intuitif et doit être préféré, à moins que vous ayez une raison particulière d'utiliser le double-égal (==
).
'000' == '0'
esttrue
(!)'000' === '0'
estfalse
- Pour vérifier que deux scalaires que vous supposez numériques sont égaux, utilisez
==
, par exemple5 == "5"
est vrai. - Pour vérifier que deux variables sont de type 'string' et représentent la même séquence de charactères, utilisez
===
, par exemple"1.e6" === "1.0e6"
est faux.
Repérez les fonctions internes et les structures qui utilisent de mauvaises comparaisons; par exemple fournir un troisième paramètre à in_array
, et ne pas mélanger les types scalaires dans les structures switch
.
N'utilisez pas les conditions Yoda.
Précision des nombres JSON
JSON utilise le système des types JavaScript, donc tous les chiffres sont représentés comme des nombres IEEE 64bits à virgule flottante. Cela signifie que plus les nombres deviennent grands, plus ils perdent en precision, jusqu'au point de où vous ne pouvez plus les différencier : les nombres au delà de 2^52 ont une précision inférieure à ±0,5 et donc un entier très grand peut se transformer en un nombre différent. Pour éviter ce problème, représentez les nombres entiers potentiellement très grands, par des chaînes en JSON.
A faire et à ne pas faire
Ne pas utiliser la construction lors de la sérialisation
Le mécanisme de sérialisation intégré de PHP (les fonctions serialize()
et unserialize()
) ne doivent pas être utilisées pour l'enregistrement des données (ou leur lecture) en dehors du processus actuel.
A la place, utilisez la sérialisation basée sur JSON (néanmoins, faites attention aux pièges).
Ceci est la règle établie par la RFC T161647.
La raison est double : (1) les données sérialisées avec ce mécanisme ne peuvent pas être désérialisées de manière fiable avec une version ultérieure de la même classe. Et (2), des données sérialisées spécialement construites peuvent être utilisées pour exécuter du code malveillant, ce qui pose un risque grave pour la sécurité.
Quelques fois, votre code ne va pas contôler le mécanisme de sérialization, mais utilisera une bibliothèque ou un pilote qui lui l'utilisera de manière interne. Dans de tels cas, il faut procéder par étapes pour réduire les risques. Le premier problème mentionné ci-dessus peut être évité en convertissant toute donnée en des tableaux ou des objets simples anonymes avant la sérialisation. Le deuxième problème peut éventuellement être résolu en utilisant la fonctionnalité de liste blanche introduite par PHP 7 pour la désérialisation.
Don't add type declarations for "big" legacy classes
MediaWiki contains some big classes that are going to be split up or replaced sooner or later.
This will be done in a way that keeps code compatible for a transition period,
but it can break extension code that expects the legacy classes in parameter types, return types, property types, or similar.
For instance, a hook handler's $title
parameter may be passed some kind of MockTitleCompat
class instead of a real Title
.
Such big legacy classes should therefore not be used in type hints, only in PHPDoc.[2][3] The classes include:
Title
Article
WikiPage
User
MediaWiki
OutputPage
WebRequest
EditPage
Commentaires et documentation
Il est essentiel que votre code soit bien documenté afin que les autres développeurs et les correcteurs de bogues puissent facilement naviguer dans la logique de votre code. Les nouvelles classes, méthodes et variables membres doivent inclure des commentaires fournissant une brève description de leurs fonctionnalité (sauf si cela est évident), même si elles sont privées. En outre, toutes les nouvelles méthodes doivent documenter leurs paramètres et renvoyer des valeurs.
Nous utilisons le style de documentation Doxygen (il est très similaire à PHPDoc pour le sous-ensemble que nous utilisons) pour produire une documentation générée automatiquement à partir des commentaires du code (voir Manuel:Mwdocgen.php ).
Commencez un bloc de commentaires Doxygen avec /**
, au lieu du formatage de style Qt /*!
.
Les commandes structurelles de Doxygen commencent par @nom de balise
.
(utilisez pour le caractère d'échappement plutôt @
que \
– les deux styles sont acceptés dans Doxygen, mais pour raison de compatibilité arrière et avant, MediaWiki a choisi le style @param
).
Ils organisent la documentation générée (en utilisant @ingroup
) et en identifiant les auteurs (en utilisant les balises @author
).
Ils décrivent une fonction ou une méthode, leurs paramètres (en utilisant @param
), et ce qu'elles retournent (en utilisant @return
). Le format pour les paramètres est :
Si un paramètre peut avoir plusieurs types, séparez-les avec le caractère pipe '|', par exemple :
@param string|Language|bool $lang Language for the ToC title, defaults to user language
Continuez les phrases appartenant à une annotation sur la ligne d'après, indentée avec un espace supplémentaire.
Pour chaque interface publique (méthode, classe, variable, peu importe) que vous ajoutez ou modifiez, fournissez une balise @since VERSION
, ainsi les personnes qui reprendront le code via cet interface sauront qu'ils rompent la compatibilité avec les anciennes versions du code.
class Foo {
/**
* @var array Description here
* @example [ 'foo' => Bar, 'quux' => Bar, .. ]
*/
protected $bar;
/**
* Description here, following by documentation of the parameters.
*
* Some example:
* @code
* ...
* @endcode
*
* @since 1.24
* @param FooContext $context context for decoding Foos
* @param array|string $options Optionally pass extra options. Either a string
* or an array of strings.
* @return Foo|null New instance of Foo or null if quuxification failed.
*/
public function makeQuuxificatedFoo( FooContext $context = null, $options = [] ) {
/* .. */
}
}
FIXME signifie généralement que quelque chose est mauvais ou cassé. TODO signifie que des améliorations sont nécessaires. cela ne signifie pas nécessairement que la personne qui ajoute le commentaire va le faire. HACK signifie qu'une solution rapide, mais inélégante, maladroite ou autrement sous-optimale, à un problème immédiat a été apportée, et qu'une réécriture plus complète du code devrait éventuellement être effectuée.
Entêtes des fichiers source
Afin d'être compatible avec la plupart des licences, vous devez avoir quelque chose de similaire à ce qui suit (spécifique aux applications GPLv2) au début de chaque fichier source.
<?php
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
* @file
*/
Balises Doxygen
Nous utilisons les annotations suivantes reconnues par Doxygen. Utilisez-les dans cet ordre, par mesure de cohérence :
Niveau du fichier :
- @file
Classe, membre de classe, ou membre global :
- @todo
- @var
- @stable, @newable, @deprecated, @internal, @unstable, @private
- @see
- @since
- @ingroup
- @param
- @return
- @throws
- @author
- @copyright
Annotations de test
Dans les tests, nous utilisons entre autres les annotations suivantes. Ce n'est pas simplement de la documentation, elles ont un sens pour PHPUnit et ont un impact sur l'exécution du test.
- @depends
- @group
- @covers
- @dataProvider
Intégration
Il existe dans le code MediaWiki quelques éléments de code destinés à être autonomes et facilement portables vers d'autres applications.
Bien que certains d’entre eux existent maintenant sous la forme de bibliothèques séparées, d'autres restent dans l’arborescence source de MediaWiki (par exemple les fichiers de /includes/libs
).
En dehors de ceux-ci, le code doit être intégré au reste de l'environnement MediaWiki et doit permettre à d'autres domaines de la base de code de s'y intégrer en retour.
Visibilité
Marquez le code comme private
sauf s'il y a une raison de le rendre plus visible.
Ne protégez pas tout (comme rendre les sous-classes public), ni avec public.
Objets globaux
N'accédez pas directement aux superglobales PHP $_GET
, $_POST
, etc. utilisez $request->get*( 'param' )
à la place; il existe diverses fonctions selon le type de valeur que vous souhaitez. Vous pouvez obtenir une requête WebRequest
à partir du RequestContext
le plus proche, ou de RequestContext::getMain()
si cela est absolument nécessaire.
De même, n’accédez pas directement à $_SERVER
; utilisez $request->getIP()
si vous voulez obtenir l'adresse IP de l'utilisateur actuel.
Méthodes statiques et propriétés
Code using static methods should be written so that all method calls inside a class use Late Static Bindings, which basically means that calls to overridable static methods are resolved in the same way as calls to overridable instance methods. Specifically:
- When calling static methods that may be overridden by subclasses from inside the class, use
static::func()
. This will call the override methods defined in subclasses if they exist, just like$this->func()
does for instance methods. - When calling static methods that may not be overridden (especially private methods), use
self::func()
. This will only call the methods of the class where it is used and its parent classes. - When calling a parent method from an override of a static method, use
parent::func()
. - If you ever think you need to call a grandparent class's version of a static method, or a child class's, think about it again, and use
forward_static_call()
if you don't come up with any better ideas.
Do not write out the class name like ClassName::func()
in the above cases, as that will cause all method calls inside that method to ignore overrides of that class's members in subclasses.[4] This is only a problem for static methods, it works like you'd expect in instance methods, but avoid that syntax in instance methods too to avoid confusion about what the call will do.[5]
These complications are annoying. Best avoid static methods so that you don't have to think about this.
Calling methods
For clarity, the method call syntax should match the method type:
- Calls to static methods from should always use
::
, even though PHP lets you use->
sometimes.[6] - Calls to instance methods should always use
->
, even though PHP lets you use::
sometimes.[7] (self::
andparent::
may be used when needed.)
Classes
Encapsulez votre code dans une classe orientée objet ou ajoutez des fonctionnalités aux classes existantes; n'ajoutez pas de nouvelles fonctions ni variables globales.
Essayez de faire attention à la distinction entre les classes du serveur (backend), qui représentent des entités dans la base de données (par exemple, User
, Block
,RevisionRecord
, etc.) et les classes de l'IHM (frontend), qui représentent les pages ou les interfaces visibles à l'utilisateur (SpecialPage
, Article
, ChangesList
, etc..
Même si votre code n'est pas clairement orienté objet, vous pouvez le déclarer dans une classe statique (par exemple, IP
ou Html
).
En raison de l'absence de méthodes et de membres de classe privés dans PHP 4, le code anciens sera marqué avec des commentaires tels que /** @private */
pour indiquer l'intention; respecter cela comme si c'était imposé par l'interpréteur.
Marquez le nouveau code avec des modificateurs de visibilité adaptés, y compris public
si c'est le cas, mais n'ajoutez jamais de visibilité à du code existant sans vérifier, tester et refactoriser si nécessaire.
Il est bon d'éviter les modifications de visibilité sauf si vos modications cassent de toutes manières le fonctionnement précédent de la fonction.
Gestion des erreurs
D'une manière générale, il ne faut pas masquer les erreurs PHP. La méthode propre pour gérer les erreurs est de gérer les erreurs quand elles se produisent.
Par exemple, si vous souhaitez utiliser un opérateur de suppression d'erreur pour masquer l'avertissement concernant un index non valide de tableau, il est préférable de coder à la place un contrôle isset
sur l'index du tableau avant d'essayer d'y accéder.
Lorsque c'est possible, vous devez toujours lever ou empêcher naturellement les erreurs PHP.
Seulement s'il existe une situation dans laquelle vous attendez un avertissement PHP inévitable, vous pouvez utiliser l'opérateur @
de PHP. Il s'agit des cas suivants :
- Il est impossible d'anticiper l'erreur qui va arriver, et
- Vous envisagez de gérer l'erreur de manière appropriée une fois qu'elle est arrivée.
Nous utilisons PHPCS pour avertir de l'utilisation de l'opérateur at. Si vous en avez besoin à tout prix, vous devrez aussi avertir PHPCS pour qu'il l'ignore et ce de la manière suivante :
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$content = @file_get_contents( $path );
Un cas d'utilisation d'exemple est l'ouverture d'un fichier avec fopen()
. Vous pouvez essayer de prédire l'erreur en appelant file_exists()
et is_readable()
, mais contrairement à isset()
, de telles opérations sur les fichiers ajoutent un travail supplémentaire significatif et rendent le code instable. Par exemple, le fichier peut avoir été supprimé ou modifié entre la vérification et l'appel du fopen()
(voir TOC/TOU Time-of-check to time-of-use).
Dans ce cas, écrivez le code juste pour essayer l'opération principale que vous voulez réaliser. Puis gérez le cas où le fichier ne peut pas être ouvert, en utilisant l'opérateur @
pour éviter d'être perturbé par PHP, puis vérifiez le résultat. Pour fopen()
et filemtime()
il s'agit de tester que le booléen renvoyé est à false
, puis de réaliser la séquence de repli, ou de générer une exception.
Bibliothèque PHP AtEase
Pour PHP 5 et plus ancien, les développeurs MediaWiki ne doivent plus utiliser l'opérateur @
car il cause des erreurs fatales non expliquées et ne sont pas tracées dans les journaux (r39789).
Il est remplacé par l'utilisation des méthodes adaptées AtEase::suppressWarnings()
et AtEase::restoreWarnings()
de la bibliothèque at-ease .
La raison est que l'opérateur at fait que PHP ne fournit pas les messages d'erreur ni le contenu de la pile lors des erreurs fatales.
Alors que l'opérateur at sert principalement pour les erreurs non fatales (pas pour les exceptions ni pour les erreurs fatales), si une erreur fatale survient, cela accroitra peu l'expérience du développeur.
use Wikimedia\AtEase\AtEase;
AtEase::suppressWarnings();
$content = file_get_contents( $path );
AtEase::restoreWarnings();
En PHP 7, le gestionnaire d'exceptions a été corrigé (exemple) pour fournir toujours ces erreurs, avec le contenu de la pile, indépendamment du fait que la suppression des erreurs a été demandée. En 2020, l'utilisation de AtEase a amorcé une phase de suppression progressive en rétablissant l'opérateur at. (T253461)
Gestion des exceptions
Les exceptions peuvent être vérifiées (les appelants sont censés les récupérer) ou non vérifiées (les appelants doivent les ignorer).
Les exceptions non vérifiées sont couramment utilisées pour les erreurs de programmation, telles que les arguments invalides transmis à une fonction.
Ces exceptions doivent généralement utiliser (soit directement, soit par sous-classement) les classes d'exception de la SPL et ne doivent pas être documentées avec les annotations @throws
.
Les exceptions vérifiées, en revanche, doivent toujours être documentées avec des annotations @throws
.
Lorsqu'on appelle une méthode qui peut générer une exception vérifiée, cette exception doit être capturée ou documentée dans le commentaire du document de l'appelant.
Les exceptions vérifiées doivent généralement utiliser des classes d'exception dédiées qui étendent Exception
.
Il est recommandé de ne pas utiliser les exceptions SPL comme classes de base pour les exceptions vérifiées, afin que l'utilisation correcte des classes d'exception puisse être appliquée avec des analyseurs de code statique.
La classe de base Exception
ne doit jamais être utilisée directement : servez-vous plutôt de classes d'exception plus spécifiques.
Peut être utilisé dans une clause catch
si l'intention est de capturer toutes les exceptions possibles, mais Throwable
est généralement plus correct pour cela.
Dans l'ancien code, il est relativement courant d'utiliser ou de sous-classer la classe MWException
.
Cette classe doit être évitée dans le nouveau code, car elle ne présente aucun avantage et pourrait amener des perturbations (T86704).
Lors de la création d'une nouvelle classe d'exception, envisagez d'implémenter INormalizedException
si le message de l'exception contient des parties variables, et ILocalizedException
s'il doit être affiché aux utilisateurs.
Voir aussi
Notes
- ↑ T154789 Le formatage de la fermeture est horrible
- ↑ https://phabricator.wikimedia.org/T240307#6191788
- ↑ https://phabricator.wikimedia.org/T354697
- ↑
class X { // Print the name of the class on which the method was called protected static function f() { echo static::class; } // Test what happens when calling the method public static function test() { A::f(); B::f(); C::f(); } } class A extends X { // With no override for f(), this prints "A" } class B extends X { // With an override that calls the parent method using `parent`, this prints "B" protected static function f() { parent::f(); } } class C extends X { // With an override that calls the parent method using `X`, this prints "X" instead of "C" protected static function f() { X::f(); } } X::test();
- ↑
class X { // Print the name of the class on which the method was called protected function f() { echo static::class; } // Test what happens when calling the method public static function test() { ( new A )->f(); ( new B )->f(); ( new C )->f(); } } class A extends X { // With no override for f(), this prints "A" } class B extends X { // With an override that calls the parent method using `parent`, this prints "B" protected function f() { parent::f(); } } class C extends X { // With an override that calls the parent method using `X`, this prints "C" // (when the method is not static). protected function f() { X::f(); // discouraged } } X::test();
- ↑
class A { static function f() { echo 'f'; } } $a = new A; A::f(); $a::f(); // works, but discouraged $a->f(); // works, but discouraged
- ↑
class A { function f() { echo 'f'; } function __construct() { $this->f(); $this::f(); // works, but discouraged static::f(); // works, but discouraged A::f(); // works, but discouraged } } $a = new A; A::f(); // works in PHP 7, doesn't work in PHP 8