PHPのfinalキーワードについての考察

media thumbnail

こんにちは、株式会社Gizumoでエンジニアをしている日野です。

今回は弊社のバックエンド開発時のメイン言語としているPHPで使えるfinalキーワードについて使い所を見直してみました。

前提・対象とする読者

この記事は以下を前提として読み進めてください。

  • サンプルコードはPHPで記述
  • 筆者が動作確認を行った環境はPHP 8.1.11

また、対象とする読者は以下のような方を想定しています。

  • PHPを使ったことがある方
  • finalキーワードの使い所に悩んでいる方

finalキーワードとは

finalキーワードは、拡張(クラスの継承やメソッドのオーバーライド)をさせないよう制限するための修飾子です。
finalキーワードを付与できる対象は、クラス / メソッド / 定数となっています。※定数についてはPHP 8.1.0以降

実際のコードを例に挙動を確認してみたいと思います。

クラスに対してfinalキーワード付与

クラスに対してfinalキーワードを付与した場合、クラスの継承を禁止することができます。

<?php

declare(strict_types=1);

final class Sample
{
    public function hello(): void
    {
      echo 'hello! from Sample';
    }
}

class AnotherSample extends Sample
{
    public function hello(): void
    {
      echo 'hello! from AnotherSample';
    }
}

$anotherSample = new AnotherSample();
$anotherSample->hello();

このコードを実行すると以下のようなエラーになり、AnotherSampleクラスはfinalクラスであるSampleクラスを継承できないことが確認できます。

PHP Fatal error:  Class AnotherSample cannot extend final class Sample in /Path/to/sample.php on line 12

メソッドに対してfinalキーワード付与

続いてメソッドに対して個別にfinalキーワードを付与してみます。
そうすることで、finalキーワードを付与したメソッドのオーバーライドを禁止することができます。

<?php

declare(strict_types=1);

class Sample
{
    final public function hello(): void
    {
      echo 'hello! from Sample';
    }
}

class AnotherSample extends Sample
{
    public function hello(): void
    {
      echo 'hello! from AnotherSample';
    }
}

$anotherSample = new AnotherSample();
$anotherSample->hello();

コードの実行結果は以下のようになります。finalメソッドであるSampleクラスのhelloメソッドをオーバーライドできないことが確認できます。

PHP Fatal error:  Cannot override final method Sample::hello() in /Path/to/sample.php on line 14

定数に対してfinalキーワード付与

最後に定数に対して個別にfinalキーワードを付与してみます。
この指定により、finalメソッドを付与した定数のオーバーライドを禁止することができます。
注意点として、先述の通りPHP 8.1.0以降でのみ使用可能になります。

<?php

declare(strict_types=1);

class Sample
{
    final public const CITY = '渋谷';

    public function hello()
    {
      echo 'hello! from ' . self::CITY;
    }
}

class AnotherSample extends Sample
{
    public const CITY = '新宿';

    public function hello()
    {
      echo 'hello! from ' . self::CITY;
    }
}

$anotherSample = new AnotherSample();
$anotherSample->hello();

コードの実行結果は下記の通りです。AnotherSampleクラスの定数CITYは、Sampleクラスのfinal定数であるCITYをオーバーライドできないことを確認できます。

PHP Fatal error:  AnotherSample::CITY cannot override final constant Sample::CITY in /Path/to/sample.php on line 14

なにがいいのか

  • コードの理解が容易になる
    先の説明の通り、継承やオーバーライドを禁止することができるため、うまく使えばコードの複雑さを生みがちなクラスの多重継承を防ぐことができます。そうすると、継承元の深くまでコードを追う必要がなくコードの見通しがよくなり、コードを理解しやすくなります。
  • バグの発生を防げる
    コードの見通しがよくなることに関連しますが、見通しがよくなるとコードの影響範囲等も捉えやすくなるため、バグの発生を防げるというメリットも得ることができます。
  • 責務を明確にすることができコードをシンプルに保つことができる
    クラスの継承ができると、ベースとなるクラスを継承した新クラスに新たにpublicなメソッドを追加して、様々なことができる一見便利そうなクラスが出来上がったりしますが、役割を持ちすぎて責務が曖昧になると後の負債に繋がることは多々あります。
    final宣言で継承の禁止を強制することで、そういったリスクの発生を防ぎ責務が明確なクラスやメソッドを維持することができます。
  • final宣言されている限りは、そのクラスやメソッド、定数は継承やオーバーライドされることを意識しなくて済むため修正等を行いやすい

まとめ

最後までお読み頂きありがとうございます。
PHPのfinalキーワードについて改めて見直してみた個人の所感ですが、新規でクラスを作成する際はクラス単位でfinal宣言、既存クラスにメソッドや定数を追加するようなケースは新たに追加するメソッド・定数にfinal宣言を積極的に使っていいように感じました。
※会社やチーム方針がある場合はまずはメンバーと話し合ってみてください
finalキーワードについて調べると、手当たり次第にfinal宣言するものではないといった意見もあるようなので、実装を行う中で新たに見えてくる点があったら改めて記事にできればと思います。

参考

少しでも開発にお困りの方は
相談しやすいスペシャリストにお問い合わせください

お問い合わせ
  1. breadcrumb-logo
  2. メディア
  3. PHPのfinalキーワードについての考察