Jump to content

Příručka:Konvence pro psaní kódu/PHP

shortcut: CC/PHP
From mediawiki.org
This page is a translated version of the page Manual:Coding conventions/PHP and the translation is 93% complete.
Outdated translations are marked like this.

Tato stránka popisuje konvence které se používají při psaní u PHP skriptů v MediaWiki . Viz také obecné konvence , které platí pro všechny programovací jazyky včetně PHP. Pokud byste chtěli krátký kontrolní seznam, který vám pomůže zkontrolovat vaši tvorbu, zkuste použít Kontrolní seznam před potvrzením .

Většinu pravidel stylu kódu lze automaticky opravit nebo alespoň detekovat pomocí PHP_CodeSniffer (aka PHPCS) pomocí vlastní sady pravidel pro MediaWiki. Další informace viz Nepřetržitá integrace/PHP CodeSniffer .

Struktura kódu

Mezery

Pro MediaWiki je prvořadým úkolem srozumitelnost a přehlednost kódu. Proto se při psaní hojně využívají mezery.

Odsazení provádějte tabulátory, nikoli mezerami. Omezte řádky na 120 znaků (při šířce tabulátoru 4 znaky).

U binárních operátorů, přidejte z každé strany jednu mezeru, tak aby byly naprosto jasně odděleny. Viz příklad:

// Ne:
$a=$b+$c;

// Ano:
$a = $b + $c;

Uvnitř umístěte mezery vedle závorek, kromě případů, kdy jsou závorky prázdné. Za názvem funkce nevkládejte mezeru.

$a = getFoo( $b );
$c = getBar();

Vložte mezeru za : v nápovědě k návratovému typu funkce, ale ne před:

function square( int $x ): int {
    return $x * $x;
}

Mezeru do závorek vložte jen pokud chcete deklarovat pole, které nemá být prázdné. Mezery také nepoužívejte při adresování jednotlivých prvků pole.

// Ano
$a = [ 'foo', 'bar' ];
$c = $a[0];
$x = [];

// Ne
$a = ['foo', 'bar'];
$c = $a[ 0 ];
$x = [ ];

Za řídící prvky, jako je if, while, for, foreach, switch nebo klíčové slovo catch naopak mezeru vložte vždy:

// Ano
if ( isFoo() ) {
	$a = 'foo';
}

// Ne
if( isFoo() ) {
	$a = 'foo';
}

U deklarování typu proměnné nepoužívejte nikdy mezery. Ani v závorkách, ani za nimi:

// Ano
(int)$foo;

// Ne
(int) $bar;
( int )$bar;
( int ) $bar;

U komentářů přidejte mezeru za # nebo // a teprve pak napište svůj komentář.

// Ano: Správně vložený komentář
//Ne: Chybí mezera
/***** Takto nekomentujte ***/

Ternární operátor

ternární operátor lze výhodně použít, pokud jsou výrazy velmi krátké a zřejmé:

$title = $page ? $page->getTitle() : Title::newMainPage();

Pokud však uvažujete o víceřádkovém výrazu s ternárním operátorem, zvažte místo toho použití bloku if (). Pamatujte, že místo na disku je levné, čitelnost kódu je všechno, "if" je angličtina a "?:" ne. Pokud používáte víceřádkový ternární výraz, otazník a dvojtečka by měly být na začátku druhého a třetího řádku a nikoli na konci prvního a druhého (na rozdíl od konvence JavaScriptu MediaWiki).

Protože MediaWiki vyžaduje PHP 8.1.0 (MW stabilní php požadavek) nebo novější, je použití zkratky ternárního operátora (?:) známého také jako elvis operator, zavedeného v PHP 5.3, povoleno.

Od PHP 7.0 je k dispozici null coalescing operator (nulový koalescenční operátor) a v některých případech může nahradit ternárního operátora.

Například místo

$wiki = isset( $this->mParams['wiki'] ) ? $this->mParams['wiki'] : false;

byste mohli napsat:

$wiki = $this->mParams['wiki'] ?? false;

Literální řetězce

Jednotlivé uvozovky jsou preferovány ve všech případech, kdy jsou rovnocenné dvojitým uvozovkám. Kód používající jednoduché uvozovky je méně náchylný k chybám a snadněji se kontroluje, protože nemůže náhodně obsahovat únikové sekvence (escape sequences) nebo proměnné (variables). Například regulární výraz "/\\n+/" vyžaduje dodatečné zpětné lomítko, takže je o něco víc složitější a náchylnější k chybám než '/\n+/'. Také pro lidi, kteří používají US/UK qwerty klávesnice, je snazší jej psát, protože není nutné mačkat Shift.

Nebojte se však použít funkci PHP interpolace do dvojitých uvozovek: $elementId = "myextension-$index"; To má o něco lepší výkonové vlastnosti než ekvivalent pomocí operátoru zřetězení (tečka) a také to lépe vypadá.

Řetězce ve stylu heredoc jsou také někdy užitečné:

$s = <<<EOT
<div class="mw-some-class">
$boxContents
</div>
EOT;

Někteří autoři rádi používají END jako koncový token, který je také názvem funkce PHP.

Funkce a jejich parametry

Vyhněte se předávání velkého počtu parametrů funkcím nebo konstrukcím:

// Konstruktor pro Block.php od 1.17 do 1.26. TOHLE NEDĚLEJTE!
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
) {
	...
}

Je nemožné rychle si zapamatovat pořadí parametrů a nevyhnutelně skončíte s nutností zakódovat všechna výchozí nastavení u volajících, abyste si přizpůsobili parametr na konci seznamu. Pokud vás láká kódovat takovou funkci, zvažte místo toho předání asociativního pole pojmenovaných parametrů.

Obecně se nedoporučuje ve funkcích používání booleovských parametrů. V případě $object->getSomething( $input, true, true, false ), bez vyhledání dokumentace pro MyClass::getSomething(), je nemožné vědět, co mají tyto parametry označovat. Mnohem lepší je buď použít konstanty třídy a vytvořit obecný parametr příznaku:

$myResult = MyClass::getSomething( $input, MyClass::FROM_DB | MyClass::PUBLIC_ONLY );

Nebo, aby vaše funkce akceptovala řadu pojmenovaných parametrů:

$myResult = MyClass::getSomething( $input, [ 'fromDB', 'publicOnly' ] );

Zkuste nepřepočítávat proměnné v průběhu funkce a vyhýbejte se úpravám parametrů předávaných funkci (pokud nejsou předány odkazem a to je samozřejmě smyslem celé funkce).

Výrazy přiřazení

Použití přiřazení jako výrazu je pro čtenáře překvapivé a vypadá to jako chyba. Nepište kód takto:

if ( $a = foo() ) {
    bar();
}

Prostor je levný a vy jste rychlý písař, takže místo toho použijte:

$a = foo();
if ( $a ) {
    bar();
}

Použití přiřazení v klauzuli while() bývalo pro iteraci legitimní:

$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
while ( $row = $dbr->fetchObject( $res ) ) {
    showRow( $row );
}

V novém kódu to není nutné. Místo toho použijte:

$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
foreach ( $res as $row ) {
    showRow( $row );
}


Výpůjčky z jazyka C

PHP je programovací jazyk, který byl navržen lidmi, kteří milovali programování v jazyce C a proto chtěli do PHP implementovat takovou syntaxi, na jakou byli zvyklí. Ovšem PHP se v několika významných detailech od jazyka C liší.

U jazyka C se konstanty implementují jako makra pro preprocesor a tím pádem se zpracovávají velmi rychle. U PHP, se ale vyhledává jméno konstanty v indexované tabulce což je mnohem pomalejší, než když se použije řetězcový literál. Ve většině případů, tam, kde byste použili v jazyce C množinu maker, je u PHP výhodnější použít řetězcové literály. U PHP, se ale vyhledává jméno konstanty v indexované tabulce což je mnohem pomalejší, než když se použije řetězcový literál. Ve většině případů, tam, kde byste použili v jazyce C množinu maker, je u PHP výhodnější použít řetězcové literály.

PHP má tři speciální literály, pro které je velká, či malá nebo smíšená velikost písma v jazyce nevýznamná (od PHP 5.1.3), ale pro kterou je naše konvence vždy malá: true, false a null.

Použijte elseif, nikoli else if. Mají jemně odlišný význam:

// Tento kód:
if ( $foo === 'bar' ) {
	echo 'Hello world';
} else if ( $foo === 'Bar' ) {
	echo 'Hello world';
} else if ( $baz === $foo ) {
	echo 'Hello baz';
} else {
	echo 'Eh?';
}

// Bude mít v zásadě stejný efekt, jako tento kód:
if ( $foo === 'bar' ) {
	echo 'Hello world';
} else {
	if ( $foo == 'Bar' ) {
		echo 'Hello world';
	} else {
		if ( $baz == $foo ) {
			echo 'Hello baz';
		} else {
			echo 'Eh?';
		}
	}
}

Ovšem z hlediska výkonu na tom bude druhá varianta hůř.

Alternativní syntaxe pro řídící struktury

PHP umožňuje pro řídicí struktury, jako endif, endwhile, atp., používat alternativní syntaxi, při které se místo složených závorek použije dvojtečka:

if ( $foo == $bar ):
    echo "<div>Hello world</div>";
endif;

Takovéto syntaxi byste se měli vyhnout velikým obloukem, protože řada textových editorů by se zobrazením takového kódu mohla mít problém. Takže raději použijte složené závorky:

if ( $foo == $bar ) {
    echo "<div>Hello world</div>";
}

Závorky

Více o závorkách najdete v části o odsazování a zarovnávání kódu na stránce věnované všeobecné konvenci pro psaní zdrojového kódu.

Pro anonymní funkce upřednostňujte šipkové funkce, pokud se anonymní funkce skládá pouze z jednoho řádku. Funkce šipek jsou stručnější a čitelnější než běžné anonymní funkce a úhledně odstraňují problémy s formátováním, které vznikají u jednořádkových anonymních funkcí.[1]

Deklarování typů v parametrech funkce

Pokud je to možné, použijte deklarace typu a deklarace typu návratu (tipování typu). (But see #Don't add type declarations for "big" legacy classes below.)

Skalární typové rady jsou povoleny od MediaWiki 1.35 po přechodu na PHP 7.2 (T231710).

Pro parametry s možnou hodnotou Null použijte syntaxi PHP 7.1: Zvolte

public function foo ( ?MyClass $mc ) {}

namísto

public function foo ( MyClass $mc = null ) {}

První z nich přesně vyjadřuje nulovatelnost parametru, aniž by riskoval jakoukoli nejednoznačnost s volitelnými parametry. IDE a nástroje pro statickou analýzu jej jako takový také rozpoznají a nebudou si stěžovat, pokud parametr bez možnosti null následuje za parametrem s možností null.


Konvence pro jména

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 Příručka:Pravidla pro psaní kódu .
Namespaces UpperCamelCase
Classes UpperCamelCase Pro názvy tříd používejte naopak Upper-CamelCase: $1. For example: class ImportantClass
Constants Uppercase with underscores Použijte velká písmena s podtržítky pro globální konstanty a konstanty tříd: 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. Jiné proměnné jsou obvykle malými písmeny (lowercase) nebo lowerCamelCase. Nepoužívejte podtržítka v názvech proměnných.

Prefixes

Používejte také zavedené prefixy. I ty napovídají oč v kódu jde:

Funkce

  • wf (funkce wiki) – funkce, dostupné na té nejvyšší úrovni, např.
    function wfFuncname() { ... }
    
  • ef (funkce rozšíření) = jsou globální funkce, napsané v rámci rozšíření. I když "většina skriptů napsaných v moderním stylu vkládá do svých tříd funkce, které se registrují jako háčky, jako statické metody třídy, aby na té nejvyšší úrovni do budoucna zbylo jen minimum, takto pojmenovaného kódu." (-- brion in Manual_talk:Coding_conventions#ef_prefix_9510)

V tomto případě se preferují slovesa, která jasně naznačí co má funkce udělat: Takže místo getReturnText() použijte returnText(). Při vystavování funkcí pro použití při testování je označte jako @internal podle Zásady stabilního rozhraní. Zneužití nebo neoficiální spoléhání se na ně je problematičtější než většina interních metod, a proto máme tendenci tyto metody míjet, pokud běží mimo testovací prostředí.

/**
 * 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 = [];
}

Proměnné

  • $wg – globální proměnné, např. $wgTitle . Vždy to používejte pro nové globální, aby bylo snadné najít chybějící deklarace "global $wgFoo". V rozšířeních by měl být název přípony použit jako oddělovač oboru názvů. Například $wgAbuseFilterConditionLimit a ne $wgConditionLimit.
  • Globální prohlášení by měla být na začátku funkce, takže závislosti lze určit bez nutnosti číst celou funkci.

Je běžné pracovat s instancí třídy databáze Database. Máme pro ně konvenci pojmenování, která pomáhá sledovat povahu serveru, ke kterému jsme připojeni. Toto je obzvláště důležité v replikovaných prostředích, jako jsou Wikimedie a další velké wiki. Ve vývojových prostředích obvykle neexistuje žádný rozdíl mezi těmito dvěma typy, které mohou skrývat jemné odlišnosti.

  • $dbw – objekt Database pro zápis (primární připojení)
  • $dbr – objekt Database pro čtení, které není citlivé na souběžnost (může to být replika pouze pro čtení, mírně za primárním stavem, takže se nikdy nesnažte s ním zapisovat do databáze nebo získat "autoritativní" odpověď na důležité dotazy, jako jsou oprávnění a stav bloku)

Ve starém kódu lze vidět následující, ale v novém kódu se to nedoporučuje:

  • $ws – proměnné relace, např. $_SESSION['wsSessionName']
  • $wc – proměnné souborů cookie, např. $_COOKIE['wcCookieName']
  • $wp – proměnné příspěvku (odeslané prostřednictvím polí formuláře), např. $wgRequest->getText( 'wpLoginName' )
  • $m – proměnné členů objektu: $this->mPage To je v novém kódu odrazováno, ale zkuste zůstat v rámci třídy konzistentní.


Úskalí

empty()

Funkce empty() by měla být použita, pouze pokud chcete potlačit chyby. Jinak stačí použít ! (booleovský převod).

  • empty( $var ) essentially does !isset( $var ) || !$var.
    Obecný případ použití: Volitelné booleovské konfigurační klíče, které mají výchozí hodnotu false. $this->enableFoo = !empty( $options['foo'] );
  • Dejte si pozor na booleovské konverze.
  • Potlačuje chyby ohledně nedefinovaných vlastností a proměnných. Pokud chcete testovat pouze nedefinované, použijte !isset(). Pokud máte v úmyslu pouze otestovat "prázdné" hodnoty (např. false, 0, [], atd.), použijte !.

isset()

Nepoužívejte isset() k testování null. Použití isset v této situaci může způsobit chyby skrytím chybně napsaných názvů proměnných. Místo toho použijte $var === null.

Booleovská konverze

if ( !$var ) {
    
}
  • Nepoužívejte ! nebo empty k testování, zda je řetězec nebo pole prázdný, protože PHP považuje '0' za falešné - ale '0' je platný název a platné uživatelské jméno na MediaWiki. Místo toho použijte === '' nebo === [].
  • Prostudujte si pravidla pro převod na booleovský výraz. Při přeměně řetězců na boolean buďte opatrní.

Ostatní

  • Array plus nečísluje klíče numericky indexovaných polí, takže [ 'a' ] + [ 'b' ] === [ 'a' ]. Pokud chcete, aby klíče byly přečíslovány, použijte array_merge(): array_merge( [ 'a' ], [ 'b' ] ) === [ 'a', 'b' ]
  • Ujistěte se, že jste error_reporting() nastavili na -1. Toto vás upozorní na nedefinované proměnné a další drobné problémy, které bude PHP ignorovat. Viz též Příručka:Jak řešit chyby .
  • Při práci v čistě PHP souboru (např. ne v HTML šabloně) vynechejte koncové tagy ?>. Tyto značky často způsobují problémy s koncovými mezerami a chybovými zprávami "záhlaví již odeslány" (headers already sent) (srovnání viz. bugzilla:17642 a http://news.php.net/php.general/280796). V řízení verzí je běžné, že soubory mají na konci souboru nový řádek (který editoři mohou přidat automaticky), což by tuto chybu vyvolalo.
  • Nepoužívejte syntaxi goto() zavedenou v 5.3. PHP možná zavedlo tuto funkci, ale to neznamená, že bychom ji měli používat.
  • Nepředávejte odkaz při procházení pole s foreach, pokud musíte. I tehdy si uvědomte důsledky. (Podívejte se na stránku https://web.archive.org/web/20220924191559/https://www.intracto.com/en-be/blog/php-quirks-passing-an-array-by-reference/ příručka PHP)
  • PHP umožňuje deklarovat statické proměnné i v rámci nestatické metody třídy. To v některých případech vedlo k drobným chybám, protože proměnné jsou sdíleny mezi instancemi. Pokud byste nepoužili vlastnost private static, nepoužívejte ani statickou proměnnou.

Operátory rovnosti

Buďte opatrní s operátory porovnávání dvojitého rovná se (double-equals comparison operators). Trojité rovná se (===) je obecně intuitivnější a mělo by být upřednostňováno, pokud nemáte důvod k použití dvojitých rovná se (==).

  • '000' == '0' je true (!)
  • '000' === '0' je false
  • Chcete-li zkontrolovat, zda jsou dva skaláry, které mají být číselné, stejné, použijte ==, např. 5 == "5" je pravda.
  • Chcete-li zkontrolovat, zda jsou dvě proměnné typu 'řetězec' a mají stejnou posloupnost znaků, použijte ===, například "1.e6" === "1.0e6" je nepravda.

Dejte si pozor na vnitřní funkce a konstrukce, které používají slabá srovnání. Například poskytněte třetí parametr in_array a nemíchejte skalární typy v konstrukcích switch.

Nepoužívejte Yodovy podmínky.

Přesnost čísla JSON

JSON používá systém typu JavaScriptu, takže všechna čísla jsou reprezentována jako 64bitová čísla IEEE s pohyblivou řádovou čárkou. To znamená, že čísla ztrácejí přesnost, když se zvětšují, a to do té míry, že některá celá čísla jsou nerozeznatelná: Čísla nad 2^52 budou mít přesnost horší než ±0,5. Takže velké celé číslo se může nakonec změnit na jiné celé číslo. Chcete-li se tomuto problému vyhnout, řešte potenciálně velká celá čísla jako řetězce v JSON.

Pro a proti

Nepoužívejte vestavěnou serializaci

Integrovaný mechanismus serializace PHP (funkce serialize() a unserialize()) by neměl být použit pro data uložená (nebo načtená) mimo aktuální proces. Místo toho použijte serializaci založenou na JSON (pozor na úskalí). Toto jsou pravidla zavedená RFC T161647.

Důvod je dvojí: (1) data serializovaná tímto mechanismem nemohou být spolehlivě unserializována s novější verzí stejné třídy. A (2) vytvořená sériová data lze použít ke spuštění škodlivého kódu, což představuje vážné bezpečnostní riziko.

Někdy váš kód nebude řídit mechanismus serializace, ale bude používat nějakou knihovnu nebo ovladač, který je používán interně. V takových případech je třeba podniknout kroky ke zmírnění rizika. První výše uvedený problém lze zmírnit převedením jakýchkoli dat na pole nebo prosté anonymní objekty před serializací. Za druhé lze snad riziko zmírnit pomocí funkce whitelisting, kterou PHP 7 zavádí pro unserializaci.

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

Komentáře a dokumentace

Je nezbytné, aby váš kód byl dobře zdokumentován, aby ostatní vývojáři a opraváři chyb mohli snadno pochopit logiku vašeho kódu. Nové třídy, metody a proměnné členy by měly zahrnovat komentáře poskytující stručné popisy jejich funkčnosti (pokud to není zřejmé), i když soukromé. Kromě toho by všechny nové metody měly dokumentovat jejich parametry a návratové hodnoty.

Při dokumentaci využíváme značky a styl, podobný jako když se používá Doxygen (ostatně PHPDoc z něj vychází, takže se jejich styl navzájem moc neliší). Díky tomu lze dokumentaci automaticky generovat rovnou z komentářů v kódu (viz Manual:mwdocgen.php ). Začněte blok Doxygenových komentářů pomocí /**, namísto formátování ve formátu Qt /*!. Strukturální příkazy doxygen začínají @tagname. (Jako únikový znak použijte spíše @ než \ - oba styly fungují v Doxygenu, ale pro zpětnou a budoucí kompatibilitu si MediaWiki zvolila @param styl.) Organizují vygenerovanou dokumentaci (pomocí @ingroup) a identifikují autory (pomocí značek @author).

Popisují funkci nebo metodu, parametry, které potřebuje (pomocí @param) a to, co funkce vrací (pomocí @return). Formát parametrů je:

@param type $paramName Description of parameter

Pokud parametr akceptuje různé typy, použijte k jejich oddělení znak svislé čáry, neboli svislítka '|' (pipe), podobně jako u následujícího příkladu:

@param string|Language|bool $lang Language for the ToC title, defaults to user language

V popisu pokračujte na dalším řádku, odsazeném o jednu mezeru navíc.

Pro každé veřejné rozhraní (metoda, třída, proměnná, cokoli), které přidáte nebo změníte, zadejte značku @since VERSION, aby lidé rozšiřující kód prostřednictvím tohoto rozhraní věděli, že porušují kompatibilitu se starší verzí kódu.

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 obvykle znamená, že je něco špatného nebo zničeného. TODO znamená, že je zapotřebí vylepšení. Neznamená to nutně, že to bude osoba, která přidává komentář. HACK znamená, že bylo provedeno rychlé, ale nevhodné, nepříjemné nebo jinak suboptimální řešení okamžitého problému a nakonec by mělo být provedeno důkladnější přepsání kódu.

Záhlaví zdrojových souborů

Chcete-li vyhovět většině licencí, měli byste mít v horní části každého zdrojového souboru něco podobného následujícímu (specifické pro aplikace GPLv2).

<?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
 */

Značky pro Doxygen

V našich zdrojových kódech používáme při vkládání poznámek následující značky, s nimiž pracuje Doxygen. Aby byla zajištěna konzistence vašeho souboru, zachovejte prosím při jejich použití i vy stejné pořadí, v jakém jsou uvedeny zde:

Poznámky k souboru:

  • @file

Třídy a jejich vlastnosti:

  • @todo
  • @var
  • @stable, @newable, @deprecated, @internal, @unstable, @private
  • @see
  • @since
  • @ingroup
  • @param
  • @return
  • @throws
  • @author
  • @copyright


Poznámky k testům

U testů používáme, mimo jiné, i následující poznámky. V tomto případě se nejedná o dokumentaci, protože mají význam pouze pro PHPUnit a svůj význam mají pouze při testování.

  • @depends
  • @group
  • @covers
  • @dataProvider

Integrace kódu

V kódu MediaWiki najdete i kódy, které jsou na ní zcela nezávislé a tím pádem je lze snadno implementovat do jiných aplikací. Zatímco některé z nich nyní existují jako oddělené knihovny, jiné zůstávají ve zdrojovém stromu MediaWiki (např. soubory v /includes/libs). Kromě toho by měl být kód integrován do zbytku prostředí MediaWiki a měl by umožňovat, aby se s ním na oplátku integrovaly další oblasti codebase.

Viditelnost

Označte kód jako private, pokud neexistuje důvod, proč jej zviditelnit. Nejen, aby vše chránilo (= veřejné podtřídy) nebo zveřejňovalo.

Globální objekty

Hlavní stránka: Manual:RequestContext.php

Nepřistupujte k superglobálním proměnným PHP jako $_GET, $_POST, atp. přímo. Získávejte jejich hodnotu raději přes volání metody $request->get*( 'param' ). Většinou jsou k dispozici různé metody, které vám umožní získat požadovanou hodnotu ve formě typu, jaký zrovna potřebujete. Takže hodnotu pro aktuální WebRequest můžete zjistit z nejbližšího objektu RequestContext, takže není vůbec nutné kvůli tomu volat RequestContext::getMain(). Stejně tak nepřistupujte přímo k $_SERVER. Použijte $request->getIP(), pokud chcete získat IP adresu aktuálního uživatele.

Statické metody a jejich vlastnosti

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:: and parent:: may be used when needed.)

Třídy

Zapouzdřete svůj kód do objektově orientované třídy, nebo ho přidejte jako novou funkci do třídy již existující. Nepřidávejte nové globální funkce či proměnné, pokud to není nezbytné. Snažte se mít neustále na paměti rozdíl mezi třídami, které patří k 'backendu', jakou jsou například entity které pracují s databází, jako User, Block, RevisionRecord, aj. A třídami co obsluhují 'frontend', jako SpecialPage, Article, ChangesList a jiné. Pokud není váš kód zrovna objektově orientovaný, je lepší jej umístit alespoň do statické třídy, jako jsou např. třídy IP nebo Html.

Jako ochrana před nedostatkem soukromých členů třídy a metod v PHP 4 bude starší kód označen komentáři, jako je /** @private */, které označují záměr; respektujte to, jako by to vynutil tlumočník.

Označte nový kód správnými modifikátory viditelnosti, včetně public, pokud je to vhodné, ale nepřidávejte viditelnost existujícímu kódu bez předchozí kontroly, testování a refaktoringu podle potřeby. Obecně je dobré vyhnout se změnám viditelnosti, pokud neprovádíte změny ve funkci, které by stejně narušily její stará použití.


Jak ošetřit chyby

Obecně byste neměli chyby PHP potlačovat. Správná metoda řešení chyb je skutečně chyby řešit.

Pokud například uvažujete o použití operátoru potlačení chyb k potlačení upozornění na neplatný index pole, měli byste místo toho provést kontrolu isset indexu pole, než se k němu pokusíte přistupovat. Pokud je to možné, vždy zachyťte nebo přirozeně zabraňte chybám PHP.

Pouze pokud nastane situace, kdy očekáváte nevyhnutelné varování PHP, můžete použít operátor PHP @. To platí pro případy, kdy:

  1. Je nemožné předvídat chybu, která se chystá.
  2. Chystáte se na řešení chyby vhodným způsobem poté, co k ní dojde.

K varování před použitím operátoru at-operator používáme PHPCS. Pokud jej opravdu potřebujete použít, budete také muset instruovat PHPCS, aby udělal výjimku, například:

// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$content = @file_get_contents( $path );

Příkladem použití je otevření souboru pomocí fopen(). Chybu se můžete pokusit předpovědět voláním file_exists() a is_readable(), ale na rozdíl od isset() takové operace se soubory zvyšují značně režii a vytváří nestabilní kód. Soubor může být například smazán nebo změněn mezi kontrolou a skutečným voláním fopen() (viz TOC/TOU ).

V tomto případě zapište kód a vyzkoušejte pouze hlavní operaci, kterou musíte provést. Potom vyřešte případ, kdy se soubor neotevře, pomocí operátoru @, abyste zabránili reakci PHP, a poté zkontrolujte výsledek. Pro fopen() a filemtime() to znamená zkontrolovat booleovský falešný návrat a poté provést nouzový návrat nebo vyvolat výjimku.

Volné

Pro PHP 5 a starší vývojáři MediaWiki nedoporučovali používat operátor @, protože způsoboval nepřihlášené a nevysvětlené fatální chyby (r39789). Místo toho se používali vlastní AtEase::suppressWarnings() a AtEase::restoreWarnings() metody z knihovny at-ease . Důvodem je, že operátor at způsobil, že PHP neposkytovalo chybové zprávy nebo trasování zásobníku při fatálních chybách. Zatímco at-operator je určen hlavně pro nezávažné chyby (nikoli výjimky nebo fatální chyby), pokud by k fatální události došlo, znamenalo by to velmi špatný vývojářský zážitek.

use Wikimedia\AtEase\AtEase;

AtEase::suppressWarnings();
$content = file_get_contents( $path );
AtEase::restoreWarnings();

V PHP 7 byla opravena obsluha výjimek (příklad), aby vždy poskytovala takové chyby, včetně trasování zásobníku, bez ohledu na potlačení chyb. V roce 2020 bylo používání AtEase zahájeno postupným ukončováním a obnovením provozovatele at-operátora. (T253461)

Zpracování výjimek

Výjimky lze zaškrtnout (to znamená, že se očekává, že je volající zachytí) nebo odškrtnout (to znamená, že je volající nemusí zachytit).

Nekontrolované výjimky se běžně používají pro chyby programování, jako jsou neplatné argumenty předané funkci. Tyto výjimky by obecně měly používat (buď přímo nebo podtřídou) třídy výjimek SPL a nemusí být dokumentovány poznámkami @throws.

Na druhou stranu zaškrtnuté výjimky by měly být vždy dokumentovány anotacemi @throws. Při volání metody, která může vyvolat kontrolovanou výjimku, by uvedená výjimka musí být buď zachycena, nebo zdokumentována v komentáři volajícího k dokumentu. Kontrolované výjimky by obecně měly používat vyhrazené třídy výjimek rozšiřující Exception. Doporučuje se nepoužívat výjimky SPL jako základní třídy pro kontrolované výjimky, aby bylo možné správné použití tříd výjimek vynutit pomocí analyzátorů statického kódu.

Základní třída Exception by nikdy neměla být vyvolána přímo: Místo toho použijte specifičtější třídy výjimek. Může být použit v klauzuli catch, pokud je záměrem zachytit všechny možné výjimky, ale Throwable je obvykle pro tento účel správnější.

Ve starším kódu je relativně běžné otevřít nebo podtřídu MWException třídy. Této třídě je třeba se v novém kódu vyhnout, protože neposkytuje žádnou výhodu a ve skutečnosti by mohla být matoucí (T86704).

Při vytváření nové třídy výjimky zvažte implementaci INormalizedException, pokud zpráva o výjimce obsahuje proměnné části, a ILocalizedException, pokud se zpráva o výjimce zobrazuje uživatelům.

Související odkazy

Poznámky pod čarou

  1. T154789 Formátování uzávěru je ošklivé
  2. https://phabricator.wikimedia.org/T240307#6191788
  3. https://phabricator.wikimedia.org/T354697
  4. 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();
    
  5. 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();
    
  6. class A {
    	static function f() {
    		echo 'f';
    	}
    }
    
    $a = new A;
    A::f();
    $a::f(); // works, but discouraged
    $a->f(); // works, but discouraged
    
  7. 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