ショートカット: CC/PHP

Manual:コーディング規約/PHP

From mediawiki.org
Jump to navigation Jump to search
This page is a translated version of the page Manual:Coding conventions/PHP and the translation is 53% complete.
Outdated translations are marked like this.
Other languages:
English • ‎dansk • ‎español • ‎français • ‎čeština • ‎русский • ‎中文 • ‎日本語

このページでは、MediaWiki コードベース PHP で書かれたファイル内でのコーディング規約を説明します。 PHP を含むすべてのプログラミング言語に適用される全般的な規約 も参照してください。 コミットの確認に役立つ短いチェックリストが必要な場合は、コミット前のチェックリスト を使用してみてください。

不適切な空白スタイルのコードを開発者が修正するのを支援するために、構文をレビューして適切な場所に空白を追加する stylize と呼ばれるツールが存在します。このツールは Web インターフェイスでも利用できます。 Git にコミットされたファイルに対してこれを実行することをお勧めします。

PHP_CodeSniffer 向けの規則も利用できます。 詳細情報は Continuous integration/PHP CodeSniffer を参照してください。 git リポジトリ mediawiki/tools/codesniffer には、MediaWiki コードの PHP_CodeSniffer 設定が含まれています。

コードの構造

空白

MediaWiki は、読みやすさを最適化するために、間隔が広いスタイルを優先します。

空白ではなくタブでインデントをします。 行の長さを 120 文字までに制限します (タブ幅は4文字として数えます)。

二項演算子の前後に空白を入れます。例:

// 不可:
$a=$b+$c;

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

括弧内が空の場合を除いて、括弧の隣 (内側) に空白を入れます。関数名の後に空白を入れないでください。

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

配列を宣言する際は、配列が空の場合を除いて、角括弧内に空白を入れます。配列要素にアクセスする際は、角括弧内に空白を入れないでください。

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

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

ifwhileforforeachswitch や、catch キーワードなどの制御構造の直後に、空白を入れるべきです:

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

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

型キャストの場合は、キャスト演算子の中または後に空白を使用しないでください:

// 可
(int)$foo;

// 不可
(int) $bar;
( int )$bar;
( int ) $bar;

コメントでは、# 文字または // 文字とコメントの間に1つの空白を入れるべきです。

// 可: 適切なインライン コメント
//不可: 空白が不足
/***** このようなコメントは書かないでください ***/

不適切な空白スタイルのコードを開発者が修正するのを支援するために、stylize と呼ばれるツールが存在し、PHP の規則によって空白が自動的に追加されます。このツールは Web インターフェイスで利用できます。

三項演算子

ternary operator は、式が非常に短く明白な場合に有効に使用できます:

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

ただし、三項演算子を含む複数行の式を検討している場合は、代わりに if () ブロックの使用を検討してください。 ディスク領域は安価で、コードの可読性が最優先であり、「if」が英語で「?:」はそうではないことを忘れないでください。 複数行の三項式を使用している場合、疑問符とコロンは、MediaWiki の JavaScript 規則とは対照的に、1 行目と 2 行目の末尾ではなく、2 行目と 3 行目の先頭に置く必要があります。

MediaWiki は PHP 7.3.19 以降を必要とするため、PHP 5.3 で導入されたエルビス演算子 (elvis operator) としても知られる shorthand 三項演算子 (?:) の使用が許容されます。

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 * FROM some_table' );
while ( $row = $dbr->fetchObject( $res ) ) {
    showRow( $row );
}

上記は新しいコードでは不要です。代わりに以下を使用してください:

$res = $dbr->query( 'SELECT * 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 以降)、我々の規則では常に以下のような小文字です: truefalsenull

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 は、コロンや endifendwhileなどのキーワードを使用した制御構造の代替構文を提供します:

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

この構文は、多くのテキスト エディターで中括弧を自動的に照合して折りたたむ機能を妨げるため、避ける必要があります。 代わりに中括弧を使用する必要があります:

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

括弧の配置

Manual:コーディング規約#インデントと配置を参照してください。

関数パラメーターでの型宣言

該当する場合は、型宣言および戻り値型宣言 (型ヒント) を使用します。

7.4 未満の PHP は、サブクラスでの型ヒントの制限/緩和を処理できないことにご注意ください。

Scalar typehints are allowed as of MediaWiki 1.35, following the switch to PHP 7.2 (T231710).

Use PHP 7.1 syntax for nullable parameters: choose

public function foo ( ?MyClass $mc ) {}

instead of

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

The former conveys precisely the nullability of a parameter, without risking any ambiguity with optional parameters. IDEs and static analysis tools will also recognize it as such, and will not complain if a non-nullable parameter follows a nullable one.

命名

関数または変数に名前を付けるときは、ローワー キャメル ケース (lowerCamelCase) を使用します。例:

private function doSomething( $userPrefs, $editSummary )

クラスに名前を付けるときはアッパー キャメル ケース (UpperCamelCase) を使用します: class ImportantClass。 グローバル定数とクラス定数には大文字とアンダースコアを使用します: DB_MASTER, Revision::REV_DELETED_TEXT。 その他の変数は小文字またはローワー キャメル ケース (lowerCamelCase) にし、変数名にアンダースコアは使用しないようにします。

さまざまな場所で使用される接頭辞もいくつかあります:

関数

  • wf (wiki functions: ウィキ関数) – トップ レベルの関数。例:
    function wfFuncname() { ... }
    
  • ef (extension functions: 拡張機能の関数) = 拡張機能のグローバル関数。ただし、「ほとんどの場合、最新のスタイルでは、フック関数を静的メソッドとしてクラスに配置するため、そのように名前を付ける生のトップレベル関数をほとんどまたはまったく残しません。」 (-- brion が Manual_talk:Coding_conventions#ef_prefix_9510 で)

動詞句が推奨されます。returnText() ではなく getReturnText() を使用してください。

変数

  • $wg – グローバル変数。例: $wgTitle Always use this for new globals, so that it's easy to spot missing "global $wgFoo" declarations. In extensions, the extension name should be used as a namespace delimiter. For example, $wgAbuseFilterConditionLimit, not $wgConditionLimit.
  • Global declarations should be at the beginning of a function so dependencies can be determined without having to read the whole function.

It is common to work with an instance of the Database class; we have a naming convention for these which helps keep track of the nature of the server to which we are connected. This is of particular importance in replicated environments, such as Wikimedia and other large wikis; in development environments, there is usually no difference between the two types, which can conceal subtle errors.

  • $dbwa Database object for writing (a master connection)
  • $dbra Database object for non-concurrency-sensitive reading (this may be a read-only replica, slightly behind master state, so don't ever try to write to the database with it, or get an "authoritative" answer to important queries like permissions and block status)

The following may be seen in old code but are discouraged in new code:

  • $wsSession variables, e.g. $_SESSION['wsSessionName']
  • $wcCookie variables, e.g. $_COOKIE['wcCookieName']
  • $wpPost variables (submitted via form fields), e.g. $wgRequest->getText( 'wpLoginName' )
  • $mobject member variables: $this->mPage. This is discouraged in new code, but try to stay consistent within a class.

落とし穴

empty()

The empty() function should only be used when you want to suppress errors. Otherwise just use ! (boolean conversion).

  • empty( $var ) essentially does !isset( $var ) || !$var.
    Common use case: Optional boolean configuration keys that default to false. $this->enableFoo = !empty( $options['foo'] );
  • It suppresses errors about undefined properties and variables.

If only intending to test for undefined, use !isset(). If only intending to test for "empty" values (e.g. false, zero, empty array, etc.), use !.

isset()

Do not use isset() to test for null. Using isset in this situation could introduce errors by hiding misspelled variable names. Instead, use $var === null.

真偽値の変換

if ( !$var ) {
    
}
  • Do not use ! or empty to test if a string or array is empty, because PHP considers '0' to be falsy – but '0' is a valid title and valid user name in MediaWiki. Use === '' or === [] instead.
  • Study the rules for conversion to boolean. Be careful when converting strings to boolean.

その他

  • Array plus does not renumber the keys of numerically-indexed arrays, so [ 'a' ] + [ 'b' ] === [ 'a' ].

If you want keys to be renumbered, use array_merge(): array_merge( [ 'a' ], [ 'b' ] ) == [ 'a', 'b' ]

This will notify you of undefined variables and other subtle gotchas that stock PHP will ignore. Manual:デバッグの方法 も参照してください。

  • When working in a pure PHP file (e.g. not an HTML template), omit any trailing ?> tags. These tags often cause issues with trailing white-space and "headers already sent" error messages (cf. bugzilla:17642 and http://news.php.net/php.general/280796). It is conventional in version control for files to have a new line at end-of-file (which editors may add automatically), which would then trigger this error.
  • Do not use the goto() syntax introduced in 5.3.

PHP may have introduced the feature, but that does not mean we should use it.

  • PHP lets you declare static variables even within a non-static method of a class. This has led to subtle bugs in some cases, as the variables are shared between instances. Where you would not use a private static property, do not use a static variable either.

Equality operators

Be careful with double-equals comparison operators. Triple-equals (===) is generally more intuitive and should be preferred unless you have a reason to use double-equals (==).

  • 'foo' == 0 is true (!)
  • '000' == '0' is true (!)
  • '000' === '0' is 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.

コメントおよび説明文書

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:

@param type $paramName Description of parameter

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 of 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
  • @ingroup
  • @author

Class, class member, or global member:

  • @todo
  • @var
  • @deprecated
  • @class
  • @see
  • @since
  • @param
  • @return
  • @throws
  • @private

テストの注釈

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.

可視性

Make methods public/protected/private (think what makes sense). Don't just make everything 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 メソッドおよびプロパティ

Static methods and properties can be used in PHP, but care should be taken when inheriting to distinguish between the self and static keywords. self will always refer to the class in which it was defined, whereas static will refer to the particular sub-class invoking it. See the PHP documentation on Late Static Bindings for more details.

クラス

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:

  1. It is impossible to anticipate the error that is about to occur; and
  2. 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.

Exceptions that indicate programming errors should be one of the exceptions that ship with PHP or a more specific subclass, while exceptions that indicate errors that are relevant to the end user should be an ErrorPageError or one of its subclasses.

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)

関連項目