基礎

PHPのfinalメソッド・finalクラス|継承先での再定義を禁止する方法

PHPのfinalキーワードは、クラスやメソッドの継承・オーバーライドを禁止するための修飾子です。メソッドに final を付けると子クラスでの再定義が不可能になり、クラスに付けるとそのクラス自体を継承できなくなります。

セキュリティ上重要な処理や、変更されると不具合が生じるメソッドに final を付けることで、意図しないオーバーライドを防ぎ、クラスの安全性を確保できます。

基本的な使い方

メソッド定義の前に final を付けると、子クラスでオーバーライドできなくなります。

PHP
<?php
class PaymentProcessor {
    public int $amount;

    public function __construct(int $amount) {
        $this->amount = $amount;
    }

    // finalメソッド:子クラスでオーバーライドできない
    final public function processPayment(): string {
        $validated = $this->validate();
        if (!$validated) {
            return "バリデーションエラー";
        }
        return "決済完了: {$this->amount}円";
    }

    // こちらはオーバーライド可能
    protected function validate(): bool {
        return $this->amount > 0;
    }
}

class CreditCardPayment extends PaymentProcessor {
    // validate()はオーバーライドOK
    protected function validate(): bool {
        return parent::validate() && $this->amount <= 1000000;
    }

    // processPayment()はオーバーライドNG
    // final public function processPayment(): string { ... }  // Fatal Error!
}

$payment = new CreditCardPayment(5000);
echo $payment->processPayment() . "\n";

$large = new CreditCardPayment(2000000);
echo $large->processPayment() . "\n";
実行結果
決済完了: 5000円
バリデーションエラー

processPayment()final なのでオーバーライドできませんが、その中で呼ばれる validate() はオーバーライド可能です。これにより、処理の流れは固定しつつ、部分的なカスタマイズを許可する設計ができます。

finalクラス

クラス自体に final を付けると、そのクラスを継承すること自体が禁止されます。

PHP
<?php
final class Singleton {
    private static ?self $instance = null;

    private function __construct(
        private string $name
    ) {}

    public static function getInstance(string $name = "default"): self {
        if (self::$instance === null) {
            self::$instance = new self($name);
        }
        return self::$instance;
    }

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

// class ExtendedSingleton extends Singleton {}  // Fatal Error!

$s1 = Singleton::getInstance("アプリ");
$s2 = Singleton::getInstance("別名");

echo $s1->getName() . "\n";
echo $s2->getName() . "\n";
echo ($s1 === $s2 ? "同一インスタンス" : "別インスタンス") . "\n";
実行結果
アプリ
アプリ
同一インスタンス

テンプレートメソッドパターン

final はテンプレートメソッドパターンで特に活躍します。全体の流れを固定し、個別の処理だけを子クラスに委譲します。

PHP
<?php
abstract class ReportGenerator {
    // テンプレートメソッド(処理の流れを固定)
    final public function generate(): string {
        $header = $this->createHeader();
        $body = $this->createBody();
        $footer = $this->createFooter();
        return $header . "\n" . $body . "\n" . $footer;
    }

    abstract protected function createHeader(): string;
    abstract protected function createBody(): string;

    protected function createFooter(): string {
        return "--- レポート終了 ---";
    }
}

class SalesReport extends ReportGenerator {
    protected function createHeader(): string {
        return "=== 売上レポート ===";
    }
    protected function createBody(): string {
        return "月間売上: 1,500,000円\n前月比: +15%";
    }
}

class InventoryReport extends ReportGenerator {
    protected function createHeader(): string {
        return "=== 在庫レポート ===";
    }
    protected function createBody(): string {
        return "在庫数: 2,450個\n要発注: 3品目";
    }
    protected function createFooter(): string {
        return "※ 在庫は毎日更新されます";
    }
}

echo (new SalesReport())->generate() . "\n\n";
echo (new InventoryReport())->generate() . "\n";
実行結果
=== 売上レポート ===
月間売上: 1,500,000円
前月比: +15%
--- レポート終了 ---

=== 在庫レポート ===
在庫数: 2,450個
要発注: 3品目
※ 在庫は毎日更新されます
finalを使うべき場面

セキュリティに関わる処理(認証・決済など)、テンプレートメソッドの骨格、シングルトンパターンのクラスなど、意図しない変更が深刻な問題を引き起こす場面で final を使います。フレームワークの内部クラスでも頻繁に使われています。

注意

final は継承によるオーバーライドのみを禁止します。リフレクション等による直接操作は防げないため、完全なセキュリティ対策にはなりません。また、final を付けすぎるとクラスの拡張性が失われるため、本当に必要な場面に限定しましょう。

まとめ

  • final をメソッドに付けると子クラスでのオーバーライドを禁止できる
  • final をクラスに付けるとそのクラスの継承自体を禁止できる
  • テンプレートメソッドパターンで処理の流れを固定するのに最適
  • セキュリティに関わる処理やシングルトンに適している
  • 過度な使用は拡張性を損なうため、必要な場面に限定する