RitoLabo

【PHP】PSR-1 Basic Coding Standard(基本コーディング標準)

  • 公開:
  • 更新:
  • カテゴリ: PHP PSR
  • タグ: PHP,PSR,PSR-1

PSR-1は、PHPの基本的なコーディングの標準についてを示しているセクションです。

アジェンダ
  1. PHP Tags
  2. Character Encoding
  3. Side Effects
    1. 副作用について
  4. Namespace and Class Names
    1. 名前空間とクラス名
    2. 各クラスが単独ファイルとなっている事。
    3. 最上位のベンダー名である少なくとも1つのレベルの名前空間にある事
    4. クラス名はStudlyCapsで宣言する
  5. Class Constants, Properties, and Methods
    1. 定数
    2. プロパティ
    3. メソッド

PHP Tags

PHPタグは <?php<?= のみ使用する。

Character Encoding

ファイル文字コードはUTF-8(BOMなし)のみを使用する。

Side Effects

ファイルへは、シンボル(クラス・関数など)の宣言、もしくは副作用を発生させる処理のどちらか一方を記述する。

副作用の一例
  • ini設定の変更
  • requireやincludeなどの、外部サービスへの接続
  • echoやprintなどの、出力の生成
  • エラーまたは例外の発行
  • グローバルまたは静的変数の変更
  • ファイルの読み書き

以下は推奨されない、宣言と副作用が混在してしまっている例です。

<?php

// [副作用] ini設定の変更
ini_set('upload_max_filesize', '1M');

// [副作用] ファイルの読み込み
include "component.php";

// [副作用] 出力生成
echo "<p>";

// 宣言
function example()
{

}

PSR-1に準拠する為には、以下のように宣言と副作用を切り離します。

<?php

// 宣言
function example()
{

}

// 条件付き宣言(=副作用ではない)
if (! function_exists('hoge')) {
function hoge()
{

}
}

副作用の一切をファイルから排除し、宣言のみのファイルとします。

副作用を持つものに関しては、

  • ini設定を行う必要があるならどこかで専用のファイルにまとめる。
  • requireやincludeなどはオートローダー使って名前空間で設定する。
  • echoやprintなどの出力もメソッドで切り出す。

などでそれぞれ宣言とは別のファイルに切り出していきます。

副作用について

Side Effects=副作用
ですが、ここで少し副作用について踏み込んでみます。

副作用とは、ある機能が1つの処理である値の状態を変化させる事で、それ以降の処理で得られる結果に影響を与えることを言います。

例えば以下のような関数があったとします。

/**
* 引数を2倍にして返却する
* @param int $num
* @return float|int
*/
public function twice(int $num)
{
return $num = $num * 2;
}

引数を2倍して返す単純な関数ですが、ここに渡す引数を「2」として、for文で10回ループさせたとします。

public function sideEffects()
{
$result = [];
for ($i=0; $i<10; $i++) {
$result['twice'][] = $this->twice(2);
}
}

すると当然ながら結果は以下のようになります。

Array
(
[0] => 4
[1] => 4
[2] => 4
[3] => 4
[4] => 4
[5] => 4
[6] => 4
[7] => 4
[8] => 4
[9] => 4
)

このように、この関数は同じ条件(例では引数「2」)を渡せば必ず同じ結果になり、かつその処理自体は他のどんな機能の結果にも影響を与えません。この性質を「参照透過性」と呼び、参照透過な機能はそれ自体は状態を持たないことで副作用と独立するという事がわかります。

一方、こんな場合はどうでしょうか。

/**
* グローバルな値に引数分を追加し返却する
* @param int $num
* @return int
*/
public function add(int $num)
{
return $this->number = $this->number + $num;
}

あるグローバルな値があるとして、それに任意の引数を加算して返す関数ですが、これにも、渡す引数を「1」としてfor文で10回ループさせたとします。

// あるグローバルな値 変数number
public $number = 1;

public function sideEffects()
{
for ($i=0; $i<10; $i++) {
$result['add'][] = $this->add(1);
}
}

すると、結果は以下のようになります。

Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 5
[4] => 6
[5] => 7
[6] => 8
[7] => 9
[8] => 10
[9] => 11
)

グローバルな値が直接書き換えられている(破壊的代入)ので参照透過性が保たれず、他の機能でこのグローバルな値を利用・処理を行った場合にその結果も変化します。これを「副作用を持つ」機能であると言います。

破壊的代入がよくわかるように、これらの処理をまとめて実行してみると以下のような結果になります。

// あるグローバルな値 変数number
public $number = 1;

public function sideEffects()
{
$result = [];

// グローバルな値 number 1 に設定されている。
$result['number']['before'] = $this->number;

for ($i=0; $i<10; $i++) {
$result['twice'][] = $this->twice(2);
$result['add'][] = $this->add(1);
}

// 破壊的代入が行われた為に、グローバルな値が変更されてしまっている
$result['number']['after'] = $this->number;

}

/**
* 引数を2倍にして返却する
* @param int $num
* @return float|int
*/
public function twice(int $num)
{
return $num = $num * 2;
}

/**
* グローバルな値に引数分を追加し返却する
* @param int $num
* @return int
*/
public function add(int $num)
{
return $this->number = $this->number + $num;
}

// 結果
Array
(
[number] => Array
(
[before] => 1
[after] => 11
)

[twice] => Array
(
[0] => 4
[1] => 4
[2] => 4
[3] => 4
[4] => 4
[5] => 4
[6] => 4
[7] => 4
[8] => 4
[9] => 4
)

[add] => Array
(
[0] => 2
[1] => 3
[2] => 4
[3] => 5
[4] => 6
[5] => 7
[6] => 8
[7] => 9
[8] => 10
[9] => 11
)
)

参照透過なtwice()メソッドは同じ結果を返していますが、一方でadd()メソッドは実行毎にグローバル値が変更されているので、結果が違うものになっています。

そして、処理前と処理後で、グローバルな値が変わっている事が確認できます。

この事から、グローバルな値に対してそれを複数の関数で用いるとした例を考えても、この時に副作用を持つ機能があれば他の関数での処理結果が変わってしまう場合があります。

これらの事から、副作用を持たないという事は、いつでも同一の結果が得られるため、バグの発生が抑制されるという利点があります。

ただしそれだけでは色々と不利な状況もあるので、副作用を許容する設計も厳密に悪しとはされていません。なので単純に「副作用」とだけ突き詰めて考えると、それも状況によりけり。という事にはなります。

あいまいにならないように結論付けておくと、PSR-1ではあくまでも、未知なる弊害を発生させないように宣言を定義するファイルはシンプルに書きましょう。という事です。

Namespace and Class Names

名前空間とクラス名について。

名前空間とクラス名

名前空間とクラス名はPSR-4(オートローディング標準)に従わなければならない。

各クラスが単独ファイルとなっている事。

1ファイルにつき1クラスを定義していく。

最上位のベンダー名である少なくとも1つのレベルの名前空間にある事

つまり好き放題に宣言すると名前空間が汚れるので、大本のベンダー名は最低限含めてね。という事。

/**
* ダメな例
*/
// vendor/sample/file_a.php
namespace file_a_sample;

// vendor/sample/file_b.php
namespace file_b_sample;

// vendor/hoge/file_a.php
namespace file_a_hoge;

/**
* 適切な例
*/
// vendor/sample/file_a.php
namespace Vendor\Sample;

// vendor/sample/file_b.php
namespace Vendor\Sample;

// vendor/hoge/file_a.php
namespace Vendor\Hoge;

クラス名はStudlyCapsで宣言する

単語の先頭大文字&空白は詰めるの書き方。とりあえずローワーキャメルケースやスネークケースなんかはやめて、スタッドリーキャプスで書こうか。という事。

各単語の先頭文字を大文字とする記法なので、アッパーキャメルケースと同じ。

語源が違うだけで以下は同じ記法。
StudlyCaps = UpperCamelCase = PascalCase

Class Constants, Properties, and Methods

クラス定数、プロパティ、およびメソッドについて。

定数

クラス定数はすべて大文字で、必要ならアンダースコアセパレータを用いて宣言する。

const VERSION = '3.0';
const DATE_APPROVED = '2018-03-18';

プロパティ

プロパティに関しては、StudlyCaps・camelCase・under_scoreなど、記法に制限はないが、ベンダーレベル・パッケージレベル・クラスレベル・メソッドレベルの範囲内で統一して使っていくべきである。

個人的にはプロパティ名はメソッドレベルではスネークケースで統一派。クラス名やメソッド名がキャメっているので、メソッド内のプロパティ名までコブ付きだと一瞬視覚のピントが合わなくなる。わかりやすいのが一番かなと。

メソッド

メソッド名は、camelCase() で宣言されなければならない。

キャメルケース。ひとコブらくださんでお願いします。