PHP 入門

PHP コーディング規約:PSR-1 と PSR-12

PHPの標準推奨規約(PHP Standard Recommendations、略して PSRs)は、PHP Framework Interop Group(PHP-FIG)によって策定された一連のガイドラインおよび規約です。これらの推奨事項は、PHPコーディングのあらゆる側面を標準化することを目的としており、異なるプロジェクトや開発者の間でもコードの一貫性、互換性、そして理解しやすさを確保するために作られました。

PSR規約に従うことで、プロジェクトを切り替える際の認知負荷を軽減し、コードの品質を高め、チーム開発におけるコラボレーションを劇的に円滑にすることができます。

1. PSR-1 を理解する:基本コーディング標準

PSR-1 はPHPコードの基礎的な要件を規定しており、ファイル構造、名前空間(Namespace)の宣言、クラス名、メソッド名、および可視性(Visibility)などの基本事項に焦点を当てています。これは、すべてのPHPコードのための共通の土台となります。

1.1 PHP タグ

PSR-1 では、PHPコードを記述する際に <?php<?= タグの使用を強制しています。完全な <?php タグはロジックや複数行のコードブロックに使用され、<?= は変数の値を直接出力するための短縮出力タグとして特化しています。これにより一貫性が保たれ、XML処理命令や、特定のサーバー環境で無効化されている可能性のある他の短縮タグ設定との衝突を避けることができます。

<?php
// これは完全な PHP タグです。ロジックや複数行のコードを記述するために使用します。
$name = "Alice";
echo "こんにちは、" . $name . "さん!"; 

// これは短縮出力タグで、値を出力することに特化しています。
?>
<p>ようこそ、<?= $name ?>さん!</p>

1.2 BOM なしの UTF-8 エンコーディング

すべてのPHPファイルは、BOM(バイト順マーク)なしの UTF-8 エンコーディングを使用しなければなりません。BOMはファイルの先頭にある不可視の文字ですが、特にファイルのインクルードやHTTPヘッダーの送信時に、予期しない出力やエラーを引き起こす原因となります。BOMなしのUTF-8を使用することで、最大の互換性を確保し、エンコーディングに関連する問題を回避できます。

1.3 副作用 (Side Effects)

一つのファイルは、「シンボル(クラス、関数、定数など)の宣言」を行うか、「副作用(出力の生成、グローバルなINI設定の変更、ファイルの読み書き実行など)を生成する」かのどちらか一方のみを行うべきであり、両方を同時に含めてはなりません

この分離により、ファイルの主な用途が明確になり、コードのテスト、理解、デバッグが容易になります。ここでいう「副作用」とは、宣言以外の、プログラムの状態に影響を与えるあらゆる操作を指します。

<?php
// 良い例 (GOOD):このファイルはクラスの宣言のみを行っています。副作用はありません。
namespace App\Model;

class User
{
    public function __construct(private string $name) {}

    public function getName(): string
    {
        return $this->name;
    }
}
<?php
// 悪い例 (BAD):このファイルは関数を宣言しつつ、副作用(テキストの出力)も発生させています。
namespace App\Helper;

function greet(string $name): string
{
    return "こんにちは、" . $name . "さん!";
}

echo greet("Bob"); // 副作用:直接的な出力

1.4 名前空間とクラス

PSR-1 では、クラスは独立したファイルに定義し、完全修飾名(Fully Qualified Name)を持つ名前空間を使用することを要求しています。名前空間の宣言は、<?php タグの直後の最初のステートメントである必要があります。

クラス名は StudlyCaps(別名 PascalCase、パスカルケース)で宣言しなければなりません。つまり、クラス名の中の各単語の先頭を大文字にし、アンダースコアやハイフンを含めてはいけません。

<?php
// ファイルパス: src/App/Model/Product.php
namespace App\Model;

class Product
{
    private string $name;
    private float $price;

    public function __construct(string $name, float $price)
    {
        $this->name = $name;
        $this->price = $price;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getPrice(): float
    {
        return $this->price;
    }
}

ここでは App\Model が名前空間であり、Product がクラス名で、StudlyCaps 規約に従っています。

1.5 クラスの定数、プロパティ、メソッド

  • クラス定数: すべて大文字を使用し、単語間はアンダースコアで区切ります(UPPER_SNAKE_CASE)。
  • プロパティ(クラス内の変数): 通常は camelCase(キャメルケース)を使用することが推奨されます。
  • メソッド(クラス内の関数): 必ず camelCase(キャメルケース)で宣言しなければなりません。つまり、最初の単語の先頭は小文字にし、それ以降の単語の先頭は大文字にします。
<?php
// ファイルパス: src/App/Utility/Calculator.php
namespace App\Utility;

class Calculator
{
    public const DEFAULT_PRECISION = 2; // 定数:すべて大文字とアンダースコア
    private float $result; // プロパティ:キャメルケース
    
    public function __construct(float $initialValue = 0.0) // メソッド:キャメルケース
    {
        $this->result = $initialValue;
    }
    
    public function add(float $number): self // メソッド:キャメルケース
    {
        $this->result += $number;
        return $this;
    }
    
    public function getCurrentResult(): float // メソッド:キャメルケース
    {
        return round($this->result, self::DEFAULT_PRECISION);
    }
}

2. PSR-12 を理解する:拡張コーディングスタイル標準

PSR-12 は、PSR-2(それ自体がPSR-1の拡張)をベースに構築され、それに代わるものとして策定された包括的なコードスタイルルールです。インデント、行の長さ、制御構造の構文、関数やメソッドの宣言など、多くの側面をカバーしており、これらが組み合わさることで一貫性があり読みやすいコードベースが形成されます。

2.1 基礎要素

  • ファイル: すべてのPHPファイルは一つの空行で終わらなければなりません。これはファイルを連結した際に行が不完全になるのを防ぐためで、多くのプログラミング言語で共通の慣習です。
  • 行: 非空行の末尾にスペースを置いてはいけません。これはバージョン管理システムにおいて検知しにくい差異を防ぎ、可読性を高めるためです。1行の長さは最大120文字以内とし、理想的な「ソフトリミット」は80文字です。80文字を超える場合は、可読性のために改行することが推奨されます。
<?php
// 1行のコード
$veryLongVariableName = new SomeVeryLongClassName(
    $parameterOne,
    $parameterTwo,
    $parameterThree
); // 120文字以内に収めるため、可読性を考慮して複数行に分割されています。

// ... (ファイル末尾には空行が必要)

2.2 インデント

PSR-12 では、インデントに 4つのスペース を使用することを規定しており、タブ(Tab)を使用してはいけません。一貫したインデントは可読性に不可欠です。スペースが推奨される理由は、異なるテキストエディタや環境でも描画される幅が完全に同じであるのに対し、タブは環境によって幅が異なる可能性があるためです。

<?php
namespace App\Service;

class AuthService
{
    public function authenticate(string $username, string $password): bool
    {
        if (empty($username) || empty($password)) {
            return false;
        }
        
        // ... 認証ロジック
        return true;
    }
}

上記のコードでは、各階層のインデントに正確に4つのスペースが使用されています。

2.3 クラス、メソッド、プロパティの定義

クラスとインターフェース
クラスやインターフェースの開始波括弧 { は独立した行に配置し、終了波括弧 } も本体コンテンツの後の独立した行に配置しなければなりません。extendsimplements キーワードはクラス名と同じ行に記述します。もし長すぎる場合は、implements を改行して1段階インデント(4スペース)することも可能です。改行する場合、実装される各インターフェースはそれぞれ独立した行に配置する必要があります。

<?php
namespace App\Service;

use App\Model\User;
use App\Repository\UserRepositoryInterface;

class UserManagementService implements
    UserRepositoryInterface,
    OtherServiceInterface
{
    public function __construct(private UserRepositoryInterface $userRepository)
    {
    }

    public function findUserById(int $id): ?User
    {
        // ...
        return $this->userRepository->findById($id);
    }
}

プロパティ

すべてのプロパティは、可視性(publicprotectedprivate)を宣言しなければなりません。var キーワードの使用は固く禁じられています。1つのステートメントで宣言できるプロパティは1つだけです。プロパティの型宣言はプロパティ名の前に配置します。

<?php
class Product
{
    public string $name; // 型宣言付きのパブリックプロパティ
    protected float $price; // 型宣言付きのプロテクトプロパティ
    private int $stockQuantity; // 型宣言付きのプライベートプロパティ
    // ...
}

メソッド

すべてのメソッドは可視性を宣言しなければなりません。メソッドの開始波括弧 { は独立した行に配置する必要があります。デフォルト値を持つ引数は、引数リストの最後に配置します。戻り値の型宣言は、引数リストの閉じ括弧の後にコロンとスペース(: )を置いて記述します。

<?php
class Logger
{
    public function log(string $message, string $level = 'info'): void
    {
        // 指定されたレベルのログを記録
        echo "[" . strtoupper($level) . "] " . $message . "\n";
    }

    private function formatMessage(string $rawMessage): string
    {
        return date('Y-m-d H:i:s') . " - " . $rawMessage;
    }
}

2.4 制御構造

if, elseif, else

if キーワードの後、開始括弧 ( の前にはスペースを1つ入れなければなりません。開始波括弧 {if 条件文と同じ行に、スペースを1つ置いて配置します。終了波括弧 } は独立した行に配置します。elseifelse も同じルールに従います。else キーワードは前のブロックの終了波括弧と同じ行に、スペースを置いて配置します。

<?php
if ($conditionA) {
    // コード A
} elseif ($conditionB) {
    // コード B
} else {
    // コード C
}

switch, case

switch キーワードと開始括弧 ( の間にスペースを入れます。開始波括弧 { は同じ行に置きます。case ステートメントは switch に対して1段階(4スペース)インデントします。case ブロック内のコードはさらに1段階(計8スペース)インデントします。break はその上のコードと同じインデントを維持します。default のインデントルールも case と同様です。

<?php
switch ($status) {
    case 'pending':
        echo "注文は保留中です。";
        break;
    case 'completed':
        echo "注文は完了しました。";
        // 意図的なフォールスルー (Fall-through) の場合は、必ずコメントを添える
        // no break
    case 'shipped':
        echo "注文は発送済み、または完了しています。";
        break;
    default:
        echo "未知のステータスです。";
        break;
}

while, do-while, for, foreach

これらのループ構造におけるスペースや括弧の配置ルールは、if 文と全く同じです。

<?php
$i = 0;
while ($i < 5) {
    echo $i++ . "\n";
}

$items = ['apple', 'banana', 'cherry'];
foreach ($items as $item) {
    echo $item . "\n";
}

try-catch-finally

trycatchfinally ブロックの開始波括弧 { も対応するキーワードと同じ行にスペースを置いて配置します。catchfinally キーワードは前のブロックの終了波括弧と同じ行に配置します。

<?php
try {
    // 例外を投げる可能性のあるコード
    throw new \Exception("エラーが発生しました!");
} catch (\Exception $e) {
    echo "例外をキャッチしました:" . $e->getMessage() . "\n";
} finally {
    echo "ここは常に実行されます。\n";
}

2.5 演算子

  • 二項演算子: +-======&&|| などの演算子の両側には、必ずスペースを1つずつ入れます。これによりオペランドが視覚的に区別され、可読性が向上します。
  • 単項演算子: !++-- などの演算子とオペランドの間にスペースを入れてはいけません。
<?php
// 二項演算子
$sum = $a + $b;
$isEqual = ($x === $y);
$canProceed = $isValid && $hasPermissions;

// 単項演算子
$notTrue = !$isTrue;
$count++;

3. PSR 規約の実践的応用

一貫して PSR 規約を適用することは、大規模プロジェクトの管理を劇的に簡素化します。もし一つの企業が複数のPHPアプリケーションやライブラリを開発している場合、統一された標準がなければ、開発者はプロジェクトを切り替えるたびに異なるインデントスタイル、命名規則、コード構造に直面し、適応するために時間を浪費し、ミスを犯す確率も高まります。

PSR-1 と PSR-12 を採用することで、企業はコードベースの統一性を保証し、認知負荷を著しく軽減でき、新しいチームメンバーのオンボーディング(研修)も非常にスムーズになります。例えば、規約に準拠したプロジェクトで camelCase(キャメルケース)を見れば、それがメソッドやプロパティであることを即座に判断でき、推測する必要がなくなるのです。