Manual:コーディング規約/PHP
このページでは、開発者の合意によって (または主任開発者 (lead developer) からの宣言によって) 長い時間をかけて作成されてきた、MediaWiki 開発の指針を文書化しています。 |
このページでは、MediaWiki コードベース の PHP で書かれたファイル内でのコーディング規約を説明します。 PHP を含むすべてのプログラミング言語に適用される全般的な規約 も参照してください。 コミットの確認に役立つ短いチェックリストが必要な場合は、コミット前のチェックリスト を使用してみてください。
PHP_CodeSniffer (別名 PHPCS) は、MediaWiki 用のカスタム ルールセットを使用して、ほとんどのコード スタイル規則を自動的に修正、または少なくとも検出できます。 詳細情報は Continuous integration/PHP CodeSniffer を参照してください。
コードの構造
空白
MediaWiki は、読みやすさを最適化するために、間隔が広いスタイルを優先します。
空白ではなくタブでインデントをします。 行の長さを 120 文字までに制限します (タブ幅は4文字として数えます)。
二項演算子の前後に空白を入れます。例:
// 不可:
$a=$b+$c;
// 可:
$a = $b + $c;
括弧内が空の場合を除いて、括弧の隣 (内側) に空白を入れます。関数名の後に空白を入れないでください。
$a = getFoo( $b );
$c = getBar();
関数の戻り値の型ヒントでは、:
の後ろに空白を入れますが、前には入れません:
function square( int $x ): int {
return $x * $x;
}
配列を宣言する際は、配列が空の場合を除いて、角括弧内に空白を入れます。配列要素にアクセスする際は、角括弧内に空白を入れないでください。
// 可
$a = [ 'foo', 'bar' ];
$c = $a[0];
$x = [];
// 不可
$a = ['foo', 'bar'];
$c = $a[ 0 ];
$x = [ ];
if
、while
、for
、foreach
、switch
や、catch
キーワードなどの制御構造の直後に、空白を入れるべきです:
// 可
if ( isFoo() ) {
$a = 'foo';
}
// 不可
if( isFoo() ) {
$a = 'foo';
}
型キャストの場合は、キャスト演算子の中または後に空白を使用しないでください:
// 可
(int)$foo;
// 不可
(int) $bar;
( int )$bar;
( int ) $bar;
コメントでは、#
文字または //
文字とコメントの間に1つの空白を入れるべきです。
// 可: 適切なインライン コメント
//不可: 空白が不足
/***** このようなコメントは書かないでください ***/
三項演算子
三項演算子は、式が非常に短くて明白な場合に有益に利用できます:
$title = $page ? $page->getTitle() : Title::newMainPage();
ただし、三項演算子を含む複数行の式を検討している場合は、代わりに if ()
ブロックの使用を検討してください。
ディスク領域は安価で、コードの可読性が最優先であり、「if」が英語で「?:
」はそうではないことを忘れないでください。
複数行の三項式を使用している場合、疑問符とコロンは、MediaWiki の JavaScript 規則とは対照的に、1 行目と 2 行目の末尾ではなく、2 行目と 3 行目の先頭に置く必要があります。
MediaWiki は PHP 8.1.0 以降を必要とするため、PHP 5.3 で導入されたエルビス演算子 (elvis operator) としても知られる 短縮形の三項演算子 (?:
) の使用が許容されます。
PHP 7.0 以降、null 合体演算子も利用可能であり、一部のユース ケースでは三項演算子をこれに置換できます。
例えば、
$wiki = isset( $this->mParams['wiki'] ) ? $this->mParams['wiki'] : false;
の代わりに、以下のように書けます:
$wiki = $this->mParams['wiki'] ?? false;
文字列リテラル
一重引用符が二重引用符と同等であるすべての場合について、一重引用符の使用が推奨されます。
一重引用符を使用したコードは、誤ってエスケープ シーケンスや変数を含めることができないため、誤りが発生しにくく、レビューが容易です。
例えば、正規表現 "/\\n+/"
には追加のバックスラッシュが必要であるため、'/\n+/'
よりも少し混乱を生じて誤りが発生しやすくなります。また、米国/英国の QWERTY キーボードを使用している場合は、Shift キーを押す必要がないため、入力が簡単です。
ただし、PHP の二重引用符で囲まれた文字列補間機能を使用することを恐れないでください:
$elementId = "myextension-$index";
これは、連結 (ドット) 演算子を使用した同等の機能よりもわずかに優れたパフォーマンス特性を備えており、見た目もよくなっています。
ヒア ドキュメント スタイルの文字列が役立つ場合があります:
$s = <<<EOT
<div class="mw-some-class">
$boxContents
</div>
EOT;
一部の作者は、終了トークンとして END を使用することを好みますが、これは PHP 関数の名前でもあります。
関数とパラメーター
膨大な数のパラメーターを関数やコンストラクターに渡さないでください:
// Block.php のコンストラクター (1.17 ~ 1.26)。 これは*しないでください*!
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
) {
...
}
パラメーターの順序を素早く覚えられなくなります。必然的に、リストの最後にあるパラメーターをカスタマイズするためだけに、呼出し元のすべての既定値をハード コーディングする必要があります。 このような関数をコーディングしたい場合は、代わりに名前付きパラメーターの連想配列を渡すことを検討してください。
一般に、関数では真偽値パラメーターの使用は推奨されていません。
$object->getSomething( $input, true, true, false )
では、MyClass::getSomething()
の説明文書を調べないと、これらのパラメーターが何を示しているのかを理解できません。
クラス定数を使用し、汎用フラグ パラメーターを作成する方が、はるかに優れています。
$myResult = MyClass::getSomething( $input, MyClass::FROM_DB | MyClass::PUBLIC_ONLY );
または、関数に名前付きパラメーターの配列を渡すには、以下のようにします:
$myResult = MyClass::getSomething( $input, [ 'fromDB', 'publicOnly' ] );
関数の過程で変数を再利用しないように心がけ、関数に渡されるパラメーターの変更を避けてください (パラメーターが参照渡しされ、値の変更がその関数の本質である場合を除きます)。
代入式
代入文を式として使用することは、読者にとって意外なことであり、誤りのように見えます。 以下のようなコードを記述しないでください:
if ( $a = foo() ) {
bar();
}
空白は安価であり、あなたは高速タイピングできるでしょうから、代わりに以下を使用してください:
$a = foo();
if ( $a ) {
bar();
}
反復について、以前は while()
句での代入は合理的でした:
$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
while ( $row = $dbr->fetchObject( $res ) ) {
showRow( $row );
}
上記は新しいコードでは不要です。代わりに以下を使用してください:
$res = $dbr->query( 'SELECT foo, bar FROM some_table' );
foreach ( $res as $row ) {
showRow( $row );
}
C からの拝借
PHP 言語は、大好きな C 言語の機能を PHP に持ち込みたいと考えている人々によって設計されました。 しかし、PHP には C と比較していくつかの重要な違いがあります。
C では、定数はプリプロセッサー マクロとして実装され、高速です。 PHP では、定数名の実行時ハッシュ テーブルの検索で実装されており、文字列リテラルを使用するよりも低速です。 C で列挙型または列挙型風のマクロの集合を使用するほとんどの場所で、PHP では文字列リテラルを使用できます。
PHP には 3 つの特殊なリテラルがあり、大文字/小文字/大文字小文字の混在は PHP では重要ではありませんが (PHP 5.1.3 以降)、我々の規則では常に以下のような小文字です: true
、false
、null
。
elseif
を使用し、else if
は使用しないでください。
これらには微妙に異なる意味があります:
// これは:
if ( $foo === 'bar' ) {
echo 'Hello world';
} else if ( $foo === 'Bar' ) {
echo 'Hello world';
} else if ( $baz === $foo ) {
echo 'Hello baz';
} else {
echo 'Eh?';
}
// 実際にはこれと同等です:
if ( $foo === 'bar' ) {
echo 'Hello world';
} else {
if ( $foo == 'Bar' ) {
echo 'Hello world';
} else {
if ( $baz == $foo ) {
echo 'Hello baz';
} else {
echo 'Eh?';
}
}
}
そして後者はパフォーマンスが劣ります。
制御構造の代替構文
PHP は、コロンや endif
、endwhile
、などのキーワードを使用した制御構造の代替構文を提供します:
if ( $foo == $bar ):
echo "<div>Hello world</div>";
endif;
この構文は、多くのテキスト エディターで中括弧を自動的に照合して折りたたむ機能を妨げるため、避ける必要があります。 代わりに中括弧を使用する必要があります:
if ( $foo == $bar ) {
echo "<div>Hello world</div>";
}
括弧の配置
Manual:コーディング規約#インデントと配置を参照してください。
無名関数の場合、無名関数が 1 行で構成される場合はアロー関数を使用することを推奨します。 アロー関数は通常の無名関数よりも簡潔で読みやすく、また 1 行の無名関数に起因する書式の問題をうまく回避します。[1]
関数パラメーターでの型宣言
該当する場合は、型宣言および戻り値型宣言 (型ヒント) を使用します。 (But see #Don't add type declarations for "big" legacy classes below.)
スカラー型ヒントは、PHP 7.2 への切り替えに従い、MediaWiki 1.35 以降で使用できます。 (T231710).
nullable なパラメーターには PHP 7.1 の構文を使用し、以下を選択します:
public function foo ( ?MyClass $mc ) {}
以下は使用しません:
public function foo ( MyClass $mc = null ) {}
前者は、省略可能なパラメーターとの曖昧さを回避しつつ、パラメーターの null 可能性を正確に伝えます。 IDE や静的解析ツールも、それを認識し、nullable なパラメーターの後に nullable ではないパラメーターが続く場合、エラーを報告しません。
命名
ファイル | UpperCamelCase | PHP ファイルにはそれが含むクラスにちなんだ名前を付ける必要があり、そのクラス名はアッパーキャメルケース (UpperCamelCase) です。 For instance, WebRequest.php contains the WebRequest class. See also Manual:コーディング規約 .
|
---|---|---|
名前空間 | UpperCamelCase | |
クラス | UpperCamelCase | クラスに名前を付けるときはアッパーキャメルケース (UpperCamelCase) を使用します。 For example: class ImportantClass
|
定数 | Uppercase with underscores | グローバル定数とクラス定数には大文字とアンダースコアを使用します: DB_PRIMARY , IDBAccessObject::READ_LATEST 。
|
関数 | lowerCamelCase | 関数に名前を付けるときはローワーキャメルケース (lowerCamelCase) を使用します。 For example:
private function doSomething( $userPrefs, $editSummary )
|
関数の変数 | lowerCamelCase | 関数の変数に名前を付けるときはローワーキャメルケース (lowerCamelCase) を使用します。 変数名にアンダースコアは使用しないようにします。 |
接頭辞
さまざまな場所で使用される接頭辞もいくつかあります:
関数名
wf
(wiki functions: ウィキ関数) – トップ レベルの関数。例:function wfFuncname() { ... }
ef
(extension functions: 拡張機能の関数) = 拡張機能のグローバル関数。ただし、「ほとんどの場合、最新のスタイルでは、フック関数を静的メソッドとしてクラスに配置するため、そのように名前を付ける生のトップレベル関数をほとんどまたはまったく残しません。」 (-- brion が Manual_talk:Coding_conventions#ef_prefix_9510 で)
動詞句が推奨されます。returnText()
ではなく getReturnText()
を使用してください。
テストで使用するために関数を公開する場合は、安定したインターフェイス方針に従い、これらに @internal
をつけてください。
これらを誤用したり非公式に依存したりすることは、ほとんどの内部メソッドよりも問題が深刻であり、そのため、これらがテスト環境外で実行された場合には例外をスローするようにする傾向があります。
/**
* 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 = [];
}
変数名
$wg
– グローバル変数。例:$wgTitle
。 新しいグローバル変数を宣言する場合は常に「global $wgFoo
」を使用してください。これにより、不足している宣言を見つけやすくなります。 拡張機能では、拡張機能の名前を名前空間の区切りとして使用する必要があります。(訳注: $wg と変数名を拡張機能名で区切る) 例えば、$wgConditionLimit
ではなく$wgAbuseFilterConditionLimit
を使用します。- 関数内でグローバル宣言をする場合は、依存関係が関数全体を読まなくてもわかるように、関数の先頭に配置する必要があります。
Database
クラスのインスタンスを使うことは一般的です。私たちはこれらのために命名規則を持っており、接続されているサーバーの性質を把握するのに役立ちます。
この点は、ウィキメディアや他の大規模なウィキなどのレプリケーション環境で特に重要です。開発環境では、通常、これら 2 つのタイプの違いはありませんが、これは微妙なエラーを隠すことがあります。
$dbw
– 書き込み用のDatabase
オブジェクト (プライマリ接続)$dbr
– 並列セーフではない読み取り用のDatabase
オブジェクト (これは読み取り専用レプリカであり、主要な接続よりも若干遅れているため、これを使用してデータベースに書き込みを試みたり、権限やブロック状態のような重要なクエリに「正式な」回答を得ようとしてはなりません)
古いコードに以下のようなものが見られますが、新しいコードでは推奨されていません:
$ws
– セッション変数。例:$_SESSION['wsSessionName']
$wc
– Cookie 変数。例:$_COOKIE['wcCookieName']
$wp
– POST 変数 (フォームフィールドを介して送信される)。 例:$wgRequest->getText( 'wpLoginName' )
$m
– オブジェクト メンバー変数:$this->mPage
。 この方法は新しいコードでは推奨されていませんが、クラス内では一貫性を保つようにしてください。
落とし穴
empty()
empty() 関数はエラーを抑制したい場合にのみ使用するべきです。
それ以外の場合は、!
(ブール変換) を使用してください。
empty( $var )
essentially does!isset( $var ) || !$var
.
よくある使用例: 既定でfalse
に設定された省略可能な真偽値の構成キー。$this->enableFoo = !empty( $options['foo'] );
- 真偽値変換の落とし穴に注意してください。
- 未定義のプロパティや変数に関するエラーを抑制します。 未定義かどうかをテストする場合は、
!isset()
を使用してください。 「空」の値をテストする意図しかない場合 (例:false
、0、[]
など)、!
を使用してください。
isset()
null
をテストするために isset() を使用しないでください。
このような場合に isset
を使用すると、変数名の綴りが誤っている場合にエラーを隠す可能性があるため、エラーが発生する可能性があります。代わりに、$var === null
を使用してください。
真偽値の変換
if ( !$var ) {
…
}
- 文字列や配列が空かどうかをテストするために
!
または'0'
を使用しないでください。PHP では、empty
が false と見なされますが、empty
は MediaWiki の有効なページ名や利用者名であるためです。 代わりに=== ''
または=== []
を使用してください。 - 真偽値への変換の規則を学びましょう。文字列を真偽値に変換する場合には注意してください。
その他
- 配列の加算は、数字添字の配列のキーを再度番号付けしないため
[ 'a' ] + [ 'b' ] === [ 'a' ]
となります。 配列のキーを再度番号付けしたい場合は array_merge() を使用してください:array_merge( [ 'a' ], [ 'b' ] ) === [ 'a', 'b' ]
- error_reporting() に
-1
が設定されていることを確認してください。 これにより、ストック PHP が無視する未定義の変数やその他の微妙なトラップについて通知されます。 Manual:デバッグの方法 も参照してください。 - PHP ファイルが純粋な場合 (例: HTML テンプレートではない場合)、末尾の
?>
タグを省略してください。 これらのタグは、末尾の空白と「ヘッダーが送信済み」というエラー メッセージの問題を引き起こすことがあります (bugzilla:17642 と http://news.php.net/php.general/280796 を参照)。 バージョン管理では、ファイルの末尾に改行文字があることが慣例となっており (エディターによって自動的に追加される場合があります)、それがこのエラーを引き起こす原因となることがあります。 - 5.3 で導入された goto() 構文を使用しないでください。 機能が PHP に導入されたとしても、それを使用するべきというわけではありません。
- 必要がある場合を除いて、
foreach
で配列をトラバースする際には参照渡しを使わないでください。それでも使用する場合は、その結果に注意してください。 (https://web.archive.org/web/20220924191559/https://www.intracto.com/en-be/blog/php-quirks-passing-an-array-by-reference/ と PHP マニュアルを参照してください) - PHP では、非静的メソッドの中でも静的変数を宣言できます。 この機能は、いくつかのケースで変数がインスタンス間で共有されるため、微妙なバグを引き起こすことがあります。
private static
というプロパティを使わない場合は、同様に静的変数も使用しないでください。
比較演算子
== 比較演算子を使用する際は注意が必要です。
通常、より直感的な ===
演算子を使用することが望ましく、==
を使用する理由がない限り、===
を優先すべきです。
'000' == '0'
はtrue
(注意!)'000' === '0'
はfalse
- To check if two scalars that are supposed to be numeric are equal, use
==
, e.g.5 == "5"
is true.
- To check if two variables are both of type 'string' and are the same sequence of characters, use
===
, e.g."1.e6" === "1.0e6"
is false.
Watch out for internal functions and constructs that use weak comparisons; for instance, provide the third parameter to in_array
, and don't mix scalar types in switch
constructs.
Do not use Yoda conditionals.
JSON number precision
JSON uses JavaScript's type system, so all numbers are represented as 64bit IEEE floating point numbers. This means that numbers lose precision when getting bigger, to the point where some whole numbers become indistinguishable: Numbers beyond 2^52 will have a precision worse than ±0.5, so a large integer may end up changing to a different integer. To avoid this issue, represent potentially large integers as strings in JSON.
Dos and don'ts
Don't use built in serialization
PHP's built in serialization mechanism (the serialize()
and unserialize()
functions) should not be used for data stored (or read from) outside of the current process.
Use JSON based serialization instead (however, beware the pitfalls).
This is policy established by RFC T161647.
The reason is twofold: (1) data serialized with this mechanism cannot reliably be unserialized with a later version of the same class. And (2) crafted serialized data can be used to execute malicious code, posing a serious security risk.
Sometimes, your code will not control the serialization mechanism, but will be using some library or driver that uses it internally. In such cases, steps should be taken to mitigate risk. The first issue mentioned above can be mitigated by converting any data to arrays or plain anonymous objects before serialization. The second issue can perhaps be mitigated using the whitelisting feature PHP 7 introduces for unserialization.
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
コメントおよび説明文書
It is essential that your code be well documented so that other developers and bug fixers can easily navigate the logic of your code. New classes, methods, and member variables should include comments providing brief descriptions of their functionality (unless it is obvious), even if private. In addition, all new methods should document their parameters and return values.
We use the Doxygen documentation style (it is very similar to PHPDoc for the subset that we use) to produce auto-generated documentation from code comments (see Manual:mwdocgen.php ).
Begin a block of Doxygen comments with /**
, instead of the Qt-style formatting /*!
.
Doxygen structural commands start with @tagname
.
(Use @
rather than \
as the escape character – both styles work in Doxygen, but for backwards and future compatibility MediaWiki has chosen the @param
style.)
They organize the generated documentation (using @ingroup
) and identify authors (using @author
tags).
They describe a function or method, the parameters it takes (using @param
), and what the function returns (using @return
). The format for parameters is:
If a parameter can be of multiple types, separate them with the pipe '|' character, for example:
@param string|Language|bool $lang Language for the ToC title, defaults to user language
Continue sentences belonging to an annotation on the next line, indented with one additional space.
For every public interface (method, class, variable, whatever) you add or change, provide a @since VERSION
tag, so people extending the code via this interface know they are breaking compatibility with older versions of the 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 usually means something is bad or broken. TODO means that improvements are needed; it does not necessarily mean that the person adding the comment is going to do it. HACK means that a quick but inelegant, awkward or otherwise suboptimal solution to an immediate problem was made, and that eventually a more thorough rewrite of the code should be done.
ソース ファイルのヘッダー
In order to be compliant with most licenses you should have something similar to the following (specific to GPLv2 applications) at the top of every source file.
<?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
*/
Doxygen タグ
We use the following annotations which Doxygen recognizes. Use them in this order, for consistency:
ファイルのレベル:
- @file
Class, class member, or global member:
- @todo
- @var
- @stable, @newable, @deprecated, @internal, @unstable, @private
- @see
- @since
- @ingroup
- @param
- @return
- @throws
- @author
- @copyright
テストの注釈
In tests, we use the following annotations among others. These aren't merely documentation, they mean something to PHPUnit and affect test execution.
- @depends
- @group
- @covers
- @dataProvider
統合
There are a few pieces of code in the MediaWiki codebase which are intended to be standalone and easily portable to other applications.
While some of these now exist as separate libraries, others remain within the MediaWiki source tree (e.g. the files in /includes/libs
).
Apart from these, code should be integrated into the rest of the MediaWiki environment, and should allow other areas of the codebase to integrate with it in return.
可視性
Mark code as private
unless there is a reason to make it more visible.
Don't just make everything protected (= public to subclasses) or public.
グローバル オブジェクト
Do not access the PHP superglobals $_GET
, $_POST
, etc, directly; use $request->get*( 'param' )
instead; there are various functions depending on what type of value you want.You can get a WebRequest
from the nearest RequestContext
, or if absolutely necessary RequestContext::getMain()
.
Equally, do not access $_SERVER
directly; use $request->getIP()
if you want to get the IP address of the current user.
static メソッド
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.)
クラス
Encapsulate your code in an object-oriented class, or add functionality to existing classes; do not add new global functions or variables.
Try to be mindful of the distinction between 'backend' classes, which represent entities in the database (e.g. User
, Block
, RevisionRecord
, etc.), and 'frontend' classes, which represent pages or interfaces visible to the user (SpecialPage
, Article
, ChangesList
, etc.).
Even if your code is not obviously object-oriented, you can put it in a static class (e.g. IP
or Html
).
As a holdover from PHP 4's lack of private class members and methods, older code will be marked with comments such as /** @private */
to indicate the intention; respect this as if it were enforced by the interpreter.
Mark new code with proper visibility modifiers, including public
if appropriate, but do not add visibility to existing code without first checking, testing and refactoring as required.
It's generally a good idea to avoid visibility changes unless you're making changes to the function which would break old uses of it anyway.
エラー処理
In general, you should not suppress PHP errors. The proper method of handling errors is to actually handle the errors.
For example, if you are thinking of using an error suppression operator to suppress an invalid array index warning, you should instead perform an isset
check on the array index before trying to access it. When possible, always catch or naturally prevent PHP errors.
Only if there is a situation where you are expecting an unavoidable PHP warning, you may use PHP's @
operator. This is for cases where:
- It is impossible to anticipate the error that is about to occur; and
- You are planning on handling the error in an appropriate manner after it occurs.
We use PHPCS to warn against use of the at-operator. If you really need to use it, you'll also need to instruct PHPCS to make an exemption, like so:
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$content = @file_get_contents( $path );
An example use case is opening a file with fopen()
. You can try to predict the error by calling file_exists()
and is_readable()
, but unlike isset()
, such file operations add significant overhead and make for unstable code. For example, the file may be deleted or changed between the check and the actual fopen()
call (see TOC/TOU).
In this case, write the code to just try the main operation you need to do. Then handle the case of the file failing to open, by using the @
operator to prevent PHP from being noisy, and then check the result afterwards. For fopen()
and filemtime()
, that means checking for a boolean false return, and then performing a fallback, or throw an exception.
AtEase
For PHP 5 and earlier, MediaWiki developers discouraged use of the @
operator due to it causing unlogged and unexplained fatal errors (r39789).
Instead, we used custom AtEase::suppressWarnings()
and AtEase::restoreWarnings()
methods from the at-ease library.
The reason is that the at-operator caused PHP to not provide error messages or stack traces upon fatal errors.
While the at-operator is mainly intended for non-fatal errors (not exceptions or fatals), if a fatal were to happen it would make for a very poor developer experience.
use Wikimedia\AtEase\AtEase;
AtEase::suppressWarnings();
$content = file_get_contents( $path );
AtEase::restoreWarnings();
In PHP 7, the exception handler was fixed (example) to always provide such errors, including a stack trace, regardless of error suppression. In 2020, use of AtEase started a phase out, reinstating the at-operator. (T253461)
Exception handling
Exceptions can be checked (meaning callers are expected to catch them) or unchecked (meaning callers must not catch them).
Unchecked exceptions are commonly used for programming errors, such as invalid arguments passed to a function.
These exceptions should generally use (either directly or by subclassing) the SPL exception classes, and must not be documented with @throws
annotations. Nonetheless, the conditions that lead to these exceptions being thrown should be documented in prose in the doc comment when they're part of a method's contract (for example, a string argument that must not be empty, or an integer argument that must be non-negative).
Checked exceptions, on the other hand, must always be documented with @throws
annotations.
When calling a method that can throw a checked exception, said exception must either be caught, or documented in the caller's doc comment.
Checked exceptions should generally use dedicated exception classes extending Exception
.
It's recommended not to use SPL exceptions as base classes for checked exceptions, so that correct usage of exception classes can be enforced with static code analyzers.
The base Exception
class must never be thrown directly: use more specific exception classes instead.
It can be used in a catch
clause if the intention is to catch all possible exceptions, but Throwable
is usually more correct for that purpose.
In legacy code it is relatively common to throw or subclass the MWException
class.
This class must be avoided in new code, as it does not provide any advantage, and could actually be confusing (T86704).
When creating a new exception class, consider implementing INormalizedException
if the exception message contains variable parts, and ILocalizedException
if the exception message is shown to users.
If you're not sure what exception class to use, you can throw a LogicException
for problems that indicate bugs in the code (e.g. function called with wrong arguments, or an unreachable branch being reached), and RuntimeException
for anything else (e.g. an external server being down).
関連項目
注記
- ↑ T154789 クロージャの書式は醜い
- ↑ 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