PHPの「static」は「4種類」？？

> [JavaScriptの「this」は「4種類」？？](https://qiita.com/takeharu/items/9935ce476a17d6258e27)
> 
> 負けた

<font color="Red">追記：静的無名関数を追加してJavaScriptに並びました！</font>

## 1.静的メンバ

静的プロパティ、静的メソッドを定義するために使うパターン

```php
<?php

class Example
{
    /**
     * @var string
     */
    protected static $staticProperty = '静的プロパティ';

    /**
     * @return string
     */
    final public static function getStaticProperty(): string
    {
        return self::$staticProperty;
    }
}


echo Example::getStaticProperty();
// 静的プロパティ
```

一番よく使うパターン

クラス自体に持たせるメンバ
インスタンス化しないで使う

静的メンバには `::` (スコープ演算子)を使ってアクセスする
スコープ演算子の左辺は `クラス` 、右辺は `静的メンバ`

## 2.静的変数

 - コンパイルされる時に **1度だけ** 初期化される
     - 同スコープ内で複数回初期化される場合 **最後に初期化される値** になる
     - **変数** や、**インスタンス** を使って初期化 **できない**
 - スコープが外れてもガベコレされない
     - 再度初期化された時、前回の値で初期化される
 - メソッドの中で使うと静的プロパティと同じ挙動をする

最初の一度のみ `0` で初期化され
スコープが外れてもガベコレされず
前回の値が入っていることがわかる例
↓

```php
<?php

/**
 * カウントアップする
 */
function countUp(): int
{
    static $cnt = 0;
    return ++$cnt;
}

echo countUp();
// 1
echo countUp();
// 2
echo countUp();
// 3
```

参照を切ってもガベコレされない
同スコープ内で複数回初期化される場合 最後に初期化された値になる
再度宣言された時、前回の値で初期化される例
↓

```php
<?php

/**
 * 第1引数で指定した名前の変数が存在する場合、その値をechoします
 * 存在しない場合は「参照ないよ」とechoします
 *
 * @param string $valName
 * @return void
 */
function echoIfExists(string $valName): void
{
    echo $GLOBALS[$valName] ?? '参照ないよ', PHP_EOL;
}

// 静的変数を初期化する(1回目)
static $staticVal = '静的変数_1';
echoIfExists('staticVal');
// 静的変数_2
// ↑1じゃなく2で初期化されている

$staticVal = '値を書き換える';

// 参照消す(普通の変数ならガベコレされる)
unset($staticVal);
echoIfExists('staticVal');
// 参照無いよ

// 静的変数を初期化する(2回目)
static $staticVal = '静的変数_2';
echoIfExists('staticVal');
// 値を書き換える
// ↑前回の値で初期化されている
```


変数を使って初期化できない例
↓

```php
<?php

$val = '適当な変数';

// 変数で初期化を試みる
static $staticVal = $val;
// PHP Fatal error:  Constant expression contains invalid operations in ...
```

**変数を使っても定義できる** 例
↓

```php
<?php

$val = '変数';
define('MY_CONST', $val); // 変数を定数にする
static $staticVal = MY_CONST;
echo $staticVal;
// 変数
```

インスタンスを使って初期化できない例
↓

```php
<?php

// インスタンスで初期化を試みる
static $staticVal = new class {};
// PHP Fatal error:  Constant expression contains invalid operations in ...
```

もちろんクロージャもインスタンスなのでだめ

```php
<?php

// クロージャで初期化を試みる
static $staticVal = function () {};
// PHP Fatal error:  Constant expression contains invalid operations in ...
```



ユーザー定義定数を使って初期化できない例
↓

```php
<?php

static $staticVal = '普通に初期化';
// PHP Warning:  Use of undefined constant MY_CONST - assumed 'MY_CONST'

$val = '変数';
define('MY_CONST', $val); // 変数を定数にする
static $staticVal = MY_CONST;
```

同スコープ内で複数回初期化される場合 **最後に初期化された値** になる ため
`MY_CONST` で初期化しようとするけど
最初の初期化のタイミングには `MY_CONST` は存在しないのでエラーになる


メソッドの中で使うと静的プロパティと大体同じ挙動をする例
↓
[PHPの静的変数 (static変数) の挙動まとめ](https://qiita.com/trashtoy/items/f4e2a97765e620bb2828)

静的プロパティとメソッド内の静的変数の違いは
**参照を切れるか** と **外からアクセスできるか**

プロパティは `unset()` できないけど
静的変数は `unset()` できる

静的プロパティはメンバなので `Reflection` を使えば
`private` であってもアクセスすることができる
静的変数はローカルスコープなので絶対にアクセスできない

## 3.遅延静的束縛

静的メンバにアクセスする際に使う特別なクラスに
`self` と `static` があります

 - `self` はそのメソッドが定義してあるクラス自身
     - コンパイルされるときに決定(束縛)される
 - `static` はそのメソッドが呼び出されたクラス自身
     - ランタイムで決定(束縛)される

名前の通り遅延して束縛されるわけですね

具体的にどういう違いがあるかというと、継承したときに違いが現れます

```php
<?php

class Base
{
    /**
     * selfが指すクラスをechoする
     *
     * @return void
     */
    public static function echoSelfClass(): void
    {
        echo self::class;
    }

    /**
     * staticが指すクラスをechoする
     *
     * @return void
     */
    public static function echoStaticClass(): void
    {
        echo static::class;
    }
}

class Sample extends Base {}

Sample::echoSelfClass();
// Base
Sample::echoStaticClass();
// Sample
```

## 4.静的無名関数

インスタンスをバインドできない無名関数
静的無名関数の中で `$this` を使おうとするとエラーになる

```php
<?php

class Hoge
{
    public function __toString(): string
    {
        return 'hoge';
    }

    public function closure()
    {
        $closure = function () {
            echo $this, PHP_EOL;
        };
        $closure();
    }

    public function staticClosure()
    {
        $staticClosure = static function () {
            echo $this, PHP_EOL;        
        };
        $staticClosure();
    }
}

$h = new Hoge();
$h->closure();
// hoge
$h->staticClosure();
// Fatal error: Uncaught Error: Using $this when not in object context in
```

また静的無名関数にインスタンスをバインドしようとしてもエラーになる

```php
$closure = static function () {};
$closure->bindTo(new class {});
// Warning: Cannot bind an instance to a static closure in
```

ただし `self` や `static` はバインドできる

```php
<?php

class Hoge
{
    protected static $hoge = 'hoge';

    public function staticClosure()
    {
        $staticClosure = static function () {
            echo self::$hoge, PHP_EOL, static::$hoge, PHP_EOL;        
        };
        $staticClosure();
    }
}

class Fuga extends Hoge
{
    protected static $hoge = 'fuga';
}


$h = new Fuga();
$h->staticClosure();
// hoge
// fuga
```

## まとめ

PHPの `static` は難しい

> まとまってない