パターンを網羅してstaticキーワードの挙動を理解する_クラスプロパティ/クラスメソッド編

# はじめに

PHP Manualの `static` キーワードに関するページ（[日本語版](https://www.php.net/manual/ja/language.oop5.static.php), [英語版](https://www.php.net/manual/en/language.oop5.static.php)）を読んで、どのようなクラスプロパティ/クラスメソッド定義がOKでどのようなのがNGなのか気になりました。

PHPにはクラスの他にもインターフェースやトレイトというクラスっぽいものもあるので、 **どこの中で** **どう定義するか** のパターンを考えて実験してみることにしました。

# どこの中で

## クラス

PHPには **ただのクラス** の他にも **abstractクラス** と **finalクラス** があります。 **abstract かつ final なクラス** はありません。

```php:OKなクラス定義
// ただのクラス
class SomeClass {}

// abstractクラス
abstract class SomeAbstractClass {}

// finalクラス
final class SomeFinalClass {}
```

**abstract かつ final なクラス** を作ろうとするとどうなるかも実験してみました。

```php:NGなクラス定義
// abstractなfinalクラスを作りたい
abstract final class SomeAbstractFinalClass {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class

// finalなabstractクラスを作りたい
final abstract class SomeFinalAbstractClass {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class
```

脱線します。 **abstract かつ final なクラス** を作れないことに今までは納得していたのですが、今、納得できなくなりました。

今までは次のように考えて納得していました。

> **abstractクラス** のインスタンスを生成するためには **abstractメソッド** を全て実装した継承クラスを作らなければならない。一方で **finalクラス** の継承クラスを作ることはできない。 **abstract かつ final なクラス** があったとしても、そのクラスのインスタンスを生成することはできないことになるから、言語仕様で **abstract かつ final なクラス** が許可されていないのは納得できる。

しかし今、次のように考えて納得できなくなりました。

> **abstract かつ final なクラス** はインスタンスを生成して使うことができず、また継承クラスを作って使うこともできない。だが、そのクラスのクラスメソッドを使うことはできる。もし **インスタンスを生成して使いたくはないし、継承クラスを作って使いたくもないが、クラスメソッドだけは使いたい** ようなクラスを作りたくなったとすると **abstract かつ final なクラス** が必要になるのではないか。使い道があるのに、理由がなく言語仕様で許可されていないとしたら納得できない。

脱線が長くなりましたが、PHPの仕様や実装上の理由、もしくはPHPの思想、オブジェクト指向プログラミングといった観点から理由がわかるよ、という方がいらっしゃいましたらご教示いただけると喜びます。

## インターフェース

インターフェースはただのインターフェースしかありません。

```php:OKなインターフェース定義
// ただのインターフェース
interface SomeInterface {}
```

**abstractなインターフェース** や **finalなインターフェース** はありません。

```php:NGなインターフェース定義
// abstractなインターフェースを作りたい
abstract interface SomeAbstractInterface {}
// => PHP Parse error:  syntax error, unexpected 'interface' (T_INTERFACE), expecting abstract (T_ABSTRACT) or final (T_FINAL) or class (T_CLASS)

// finalなインターフェースを作りたい
final interface SomeFinalInterface {}
// => PHP Parse error:  syntax error, unexpected 'interface' (T_INTERFACE), expecting abstract (T_ABSTRACT) or final (T_FINAL) or class (T_CLASS)

// abstractでfinalなインターフェースを作りたい
abstract final interface SomeAbstractFinalInterface {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class

// finalでabstractなインターフェースを作りたい
final abstract interface SomeFinalAbstractInterface {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class
```

インターフェースは何もしなくても **abstract** のようなものなので、**abstractなインターフェース** が許可されていないのは納得できます。一方、インターフェースを `extend` してほしくない場合に **finalなインターフェース** のような需要があるのではないかと思いました。

## トレイト

トレイトもただのトレイトしかありません

```php:OKなトレイト定義
// ただのトレイト
trait SomeTrait {}
```

**abstractなトレイト** や **finalなトレイト** はありません。

```php:NGなトレイト定義
// abstractなトレイトを作りたい
abstract trait SomeAbstractTrait {}
// => PHP Parse error:  syntax error, unexpected 'trait' (T_TRAIT), expecting abstract (T_ABSTRACT) or final (T_FINAL) or class (T_CLASS)

// finalなトレイトを作りたい
final trait SomeFinalTrait {}
// => PHP Parse error:  syntax error, unexpected 'trait' (T_TRAIT), expecting abstract (T_ABSTRACT) or final (T_FINAL) or class (T_CLASS)

// abstractでfinalなトレイトを作りたい
abstract final trait SomeAbstractFinalTrait {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class

// finalでabstractトレイトを作りたい
final abstract trait SomeFinalAbstractTrait {}
// => PHP Fatal error:  Cannot use the final modifier on an abstract class
```

トレイトは実装を定義するためのものだと思っているので、 **abstractなトレイト** が許可されていないのは納得できます。一方、他のクラスやトレイトで `use` したときに、 `use` されたトレイトの実装を上書いてほしくない場合に **finalなトレイト** のような需要があるのではないかと思いました。

# どう定義するか

## クラスプロパティ

アクセス権を3パターン試します。アクセス権を宣言しないパターンと `var` を使うパターンは現在は使うべきではないと考えているので試しません。

また、クラスプロパティの宣言時に初期化するかどうかで2パターン試します。

アクセス権で3パターン x 初期化するかで2パターン = 6パターン の宣言方法を試すことになりました。

```php:クラスプロパティの宣言パターン
public static $public_static_property_without_initialization;
public static $public_static_property_with_initialization = null;

protected static $protected_static_property_without_initialization;
protected static $protected_static_property_with_initialization = null;

private static $private_static_property_without_initialization;
private static $private_static_property_with_initialization = null;
```

## クラスメソッド

アクセス権を3パターン試します。アクセス権を宣言しないパターンは現在は使うべきではないと考えているので試しません。

また、本体があるかどうかで2パターン試します。

さらに、 `abstract` キーワードと `final` キーワードのくっつき方の組み合わせで5パターン試します。

アクセス権で3パターン x 本体があるかどうかで2パターン x 5パターン = 30パターン の宣言方法を試すことになりました。

```php:クラスメソッドの宣言パターン
// public x 10パターン
public static function public_static_method_without_body();
public static function public_static_method_with_body() {}

abstract public static function abstract_public_static_method_without_body();
abstract public static function abstract_public_static_method_with_body() {}

final public static function final_public_static_method_without_body();
final public static function final_public_static_method_with_body() {}

abstract final public static function abstract_final_public_static_method_without_body();
abstract final public static function abstract_final_public_static_method_with_body() {}

final abstract public static function final_abstract_public_static_method_without_body();
final abstract public static function final_abstract_public_static_method_with_body() {}

// protected x 10パターン
protected static function protected_static_method_without_body();
protected static function protected_static_method_with_body() {}

abstract protected static function abstract_protected_static_method_without_body();
abstract protected static function abstract_protected_static_method_with_body() {}

final protected static function final_protected_static_method_without_body();
final protected static function final_protected_static_method_with_body() {}

abstract final protected static function abstract_final_protected_static_method_without_body();
abstract final protected static function abstract_final_protected_static_method_with_body() {}

final abstract protected static function final_abstract_protected_static_method_without_body();
final abstract protected static function final_abstract_protected_static_method_with_body() {}

// private x 10パターン
private static function private_static_method_without_body();
private static function private_static_method_with_body() {}

abstract private static function abstract_private_static_method_without_body();
abstract private static function abstract_private_static_method_with_body() {}

final private static function final_private_static_method_without_body();
final private static function final_private_static_method_with_body() {}

abstract final private static function abstract_final_private_static_method_without_body();
abstract final private static function abstract_final_private_static_method_with_body() {}

final abstract private static function final_abstract_private_static_method_without_body();
final abstract private static function final_abstract_private_static_method_with_body() {}
```

# 実験

エラーになるものをコメントアウトして示します。

## ただのクラス

```php:ただのクラス
class SomeClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;
    
    // public x 10パターン
    // public static function public_static_method_without_body();
    public static function public_static_method_with_body() {}
    
    // abstract public static function abstract_public_static_method_without_body();
    // abstract public static function abstract_public_static_method_with_body() {}
    
    // final public static function final_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}
    
    // abstract final public static function abstract_final_public_static_method_without_body();
    // abstract final public static function abstract_final_public_static_method_with_body() {}
    
    // final abstract public static function final_abstract_public_static_method_without_body();
    // final abstract public static function final_abstract_public_static_method_with_body() {}
    
    // protected x 10パターン
    // protected static function protected_static_method_without_body();
    protected static function protected_static_method_with_body() {}
    
    // abstract protected static function abstract_protected_static_method_without_body();
    // abstract protected static function abstract_protected_static_method_with_body() {}
    
    // final protected static function final_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}
    
    // abstract final protected static function abstract_final_protected_static_method_without_body();
    // abstract final protected static function abstract_final_protected_static_method_with_body() {}
    
    // final abstract protected static function final_abstract_protected_static_method_without_body();
    // final abstract protected static function final_abstract_protected_static_method_with_body() {}
    
    // private x 10パターン
    // private static function private_static_method_without_body();
    private static function private_static_method_with_body() {}
    
    // abstract private static function abstract_private_static_method_without_body();
    // abstract private static function abstract_private_static_method_with_body() {}
    
    // final private static function final_private_static_method_without_body();
    final private static function final_private_static_method_with_body() {}
    
    // abstract final private static function abstract_final_private_static_method_without_body();
    // abstract final private static function abstract_final_private_static_method_with_body() {}
    
    // final abstract private static function final_abstract_private_static_method_without_body();
    // final abstract private static function final_abstract_private_static_method_with_body() {}
}
```

コメントを消したものを再掲します。

```php:ただのクラス
class SomeClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;

    public static function public_static_method_with_body() {}
    final public static function final_public_static_method_with_body() {}

    protected static function protected_static_method_with_body() {}
    final protected static function final_protected_static_method_with_body() {}

    private static function private_static_method_with_body() {}
    final private static function final_private_static_method_with_body() {}
}
```

見慣れた光景になりましたね。

## abstractクラス

```php:abstractクラス
abstract class SomeAbstractClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;
    
    // public x 10パターン
    // public static function public_static_method_without_body();
    public static function public_static_method_with_body() {}
    
    abstract public static function abstract_public_static_method_without_body();
    // abstract public static function abstract_public_static_method_with_body() {}
    
    // final public static function final_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}
    
    // abstract final public static function abstract_final_public_static_method_without_body();
    // abstract final public static function abstract_final_public_static_method_with_body() {}
    
    // final abstract public static function final_abstract_public_static_method_without_body();
    // final abstract public static function final_abstract_public_static_method_with_body() {}
    
    // protected x 10パターン
    // protected static function protected_static_method_without_body();
    protected static function protected_static_method_with_body() {}
    
    abstract protected static function abstract_protected_static_method_without_body();
    // abstract protected static function abstract_protected_static_method_with_body() {}
    
    // final protected static function final_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}
    
    // abstract final protected static function abstract_final_protected_static_method_without_body();
    // abstract final protected static function abstract_final_protected_static_method_with_body() {}
    
    // final abstract protected static function final_abstract_protected_static_method_without_body();
    // final abstract protected static function final_abstract_protected_static_method_with_body() {}
    
    // private x 10パターン
    // private static function private_static_method_without_body();
    private static function private_static_method_with_body() {}
    
    // abstract private static function abstract_private_static_method_without_body();
    // abstract private static function abstract_private_static_method_with_body() {}
    
    // final private static function final_private_static_method_without_body();
    final private static function final_private_static_method_with_body() {}
    
    // abstract final private static function abstract_final_private_static_method_without_body();
    // abstract final private static function abstract_final_private_static_method_with_body() {}
    
    // final abstract private static function final_abstract_private_static_method_without_body();
    // final abstract private static function final_abstract_private_static_method_with_body() {}
}
```

コメントを消したものを再掲します。

```php:abstractクラス
abstract class SomeAbstractClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;

    public static function public_static_method_with_body() {}    
    abstract public static function abstract_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}

    protected static function protected_static_method_with_body() {}    
    abstract protected static function abstract_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}

    private static function private_static_method_with_body() {}
    final private static function final_private_static_method_with_body() {}
}
```

クラスがabstractになったので `abstract public static function` と `abstract protected static function` が許可されるようになりました。 `abstract private static function` は意味わかんないので相変わらず許可されません。

## finalクラス

```php:finalクラス
final class SomeFinalClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;
    
    // public x 10パターン
    // public static function public_static_method_without_body();
    public static function public_static_method_with_body() {}
    
    // abstract public static function abstract_public_static_method_without_body();
    // abstract public static function abstract_public_static_method_with_body() {}
    
    // final public static function final_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}
    
    // abstract final public static function abstract_final_public_static_method_without_body();
    // abstract final public static function abstract_final_public_static_method_with_body() {}
    
    // final abstract public static function final_abstract_public_static_method_without_body();
    // final abstract public static function final_abstract_public_static_method_with_body() {}
    
    // protected x 10パターン
    // protected static function protected_static_method_without_body();
    protected static function protected_static_method_with_body() {}
    
    // abstract protected static function abstract_protected_static_method_without_body();
    // abstract protected static function abstract_protected_static_method_with_body() {}
    
    // final protected static function final_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}
    
    // abstract final protected static function abstract_final_protected_static_method_without_body();
    // abstract final protected static function abstract_final_protected_static_method_with_body() {}
    
    // final abstract protected static function final_abstract_protected_static_method_without_body();
    // final abstract protected static function final_abstract_protected_static_method_with_body() {}
    
    // private x 10パターン
    // private static function private_static_method_without_body();
    private static function private_static_method_with_body() {}
    
    // abstract private static function abstract_private_static_method_without_body();
    // abstract private static function abstract_private_static_method_with_body() {}
    
    // final private static function final_private_static_method_without_body();
    final private static function final_private_static_method_with_body() {}
    
    // abstract final private static function abstract_final_private_static_method_without_body();
    // abstract final private static function abstract_final_private_static_method_with_body() {}
    
    // final abstract private static function final_abstract_private_static_method_without_body();
    // final abstract private static function final_abstract_private_static_method_with_body() {}
}
```

コメントを消したものを再掲します。

```php:finalクラス
final class SomeFinalClass
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;

    public static function public_static_method_with_body() {}
    final public static function final_public_static_method_with_body() {}

    protected static function protected_static_method_with_body() {}
    final protected static function final_protected_static_method_with_body() {}

    private static function private_static_method_with_body() {}
    final private static function final_private_static_method_with_body() {}
}
```

ただのクラスの場合と同じですね。

## ただのインターフェース

```php:ただのインターフェース
interface SomeInterface
{
    // public static $public_static_property_without_initialization;
    // public static $public_static_property_with_initialization = null;
    
    // protected static $protected_static_property_without_initialization;
    // protected static $protected_static_property_with_initialization = null;
    
    // private static $private_static_property_without_initialization;
    // private static $private_static_property_with_initialization = null;
    
    // public x 10パターン
    public static function public_static_method_without_body();
    // public static function public_static_method_with_body() {}
    
    // abstract public static function abstract_public_static_method_without_body();
    // abstract public static function abstract_public_static_method_with_body() {}
    
    // final public static function final_public_static_method_without_body();
    // final public static function final_public_static_method_with_body() {}
    
    // abstract final public static function abstract_final_public_static_method_without_body();
    // abstract final public static function abstract_final_public_static_method_with_body() {}
    
    // final abstract public static function final_abstract_public_static_method_without_body();
    // final abstract public static function final_abstract_public_static_method_with_body() {}
    
    // protected x 10パターン
    // protected static function protected_static_method_without_body();
    // protected static function protected_static_method_with_body() {}
    
    // abstract protected static function abstract_protected_static_method_without_body();
    // abstract protected static function abstract_protected_static_method_with_body() {}
    
    // final protected static function final_protected_static_method_without_body();
    // final protected static function final_protected_static_method_with_body() {}
    
    // abstract final protected static function abstract_final_protected_static_method_without_body();
    // abstract final protected static function abstract_final_protected_static_method_with_body() {}
    
    // final abstract protected static function final_abstract_protected_static_method_without_body();
    // final abstract protected static function final_abstract_protected_static_method_with_body() {}
    
    // private x 10パターン
    // private static function private_static_method_without_body();
    // private static function private_static_method_with_body() {}
    
    // abstract private static function abstract_private_static_method_without_body();
    // abstract private static function abstract_private_static_method_with_body() {}
    
    // final private static function final_private_static_method_without_body();
    // final private static function final_private_static_method_with_body() {}
    
    // abstract final private static function abstract_final_private_static_method_without_body();
    // abstract final private static function abstract_final_private_static_method_with_body() {}
    
    // final abstract private static function final_abstract_private_static_method_without_body();
    // final abstract private static function final_abstract_private_static_method_with_body() {}
}
```

コメントを消したものを再掲します。

```php:ただのインターフェース
interface SomeInterface
{
    public static function public_static_method_without_body();
}
```

1つしか残りませんでした。

## ただのトレイト

```php:ただのトレイト
trait SomeTrait
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;
    
    // public x 10パターン
    // public static function public_static_method_without_body();
    public static function public_static_method_with_body() {}
    
    abstract public static function abstract_public_static_method_without_body();
    // abstract public static function abstract_public_static_method_with_body() {}
    
    // final public static function final_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}
    
    // abstract final public static function abstract_final_public_static_method_without_body();
    // abstract final public static function abstract_final_public_static_method_with_body() {}
    
    // final abstract public static function final_abstract_public_static_method_without_body();
    // final abstract public static function final_abstract_public_static_method_with_body() {}
    
    // protected x 10パターン
    // protected static function protected_static_method_without_body();
    protected static function protected_static_method_with_body() {}
    
    abstract protected static function abstract_protected_static_method_without_body();
    // abstract protected static function abstract_protected_static_method_with_body() {}
    
    // final protected static function final_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}
    
    // abstract final protected static function abstract_final_protected_static_method_without_body();
    // abstract final protected static function abstract_final_protected_static_method_with_body() {}
    
    // final abstract protected static function final_abstract_protected_static_method_without_body();
    // final abstract protected static function final_abstract_protected_static_method_with_body() {}
    
    // private x 10パターン
    // private static function private_static_method_without_body();
    private static function private_static_method_with_body() {}
    
    // abstract private static function abstract_private_static_method_without_body();
    // abstract private static function abstract_private_static_method_with_body() {}
    
    // final private static function final_private_static_method_without_body();
    final private static function final_private_static_method_with_body() {}
    
    // abstract final private static function abstract_final_private_static_method_without_body();
    // abstract final private static function abstract_final_private_static_method_with_body() {}
    
    // final abstract private static function final_abstract_private_static_method_without_body();
    // final abstract private static function final_abstract_private_static_method_with_body() {}
}
```

コメントを消したものを再掲します。

```php:ただのトレイト
trait SomeTrait
{
    public static $public_static_property_without_initialization;
    public static $public_static_property_with_initialization = null;
    
    protected static $protected_static_property_without_initialization;
    protected static $protected_static_property_with_initialization = null;
    
    private static $private_static_property_without_initialization;
    private static $private_static_property_with_initialization = null;

    public static function public_static_method_with_body() {}
    abstract public static function abstract_public_static_method_without_body();
    final public static function final_public_static_method_with_body() {}

    protected static function protected_static_method_with_body() {}    
    abstract protected static function abstract_protected_static_method_without_body();
    final protected static function final_protected_static_method_with_body() {}

    private static function private_static_method_with_body() {}
    final private static function final_private_static_method_with_body() {}
}
```

abstractクラスと同じになるんですか！？