Laravelとマジックメソッド

そんなにLaravel要素はありません

この記事は`マジックメソッド`ってなに？どこでどのタイミングで使えばいいの？というのを実例を用いてつらつらまとめたものです。
ミス等ありましたらご指摘お願い致します。

#マジックメソッドとは

>以下の関数名 `__construct()`, `__destruct()`, `__call()`, `__callStatic()`, `__get()`, `__set()`, `__isset()`, `__unset()`, `__sleep()`, `__wakeup()`, `__serialize()`, `__unserialize()`, `__toString()`, `__invoke()`, `__set_state()`, `__clone()` および `__debugInfo()` は、PHP クラスにおける特殊関数の名前です。 これらの関数に関連する特別な機能を使用する場合を除き、 クラス内にこれらの名前を有する関数を作成してはいけません。
全てのマジックメソッドは public として宣言されていなければ なりません
PHP は、__ で始まる関数名を特殊関数として取り置きしてあります。 特殊な機能を必要としないのであれば、 関数名を __ で始めないほうが良い。
参考：https://www.php.net/manual/ja/language.oop5.magic.php

上記記載の通り、特殊な関数のことです。
**クラスに対して、それぞれあるタイミングで呼ばれる関数のことです。**
どのタイミングで呼ばれるかは、公式を確認してください

全部は多いので、一部を実例出しつつ紹介していきます

#1.__construct()

>コンストラクタメソッドを有するクラスは、新たにオブジェクトが 生成される度にこのメソッドをコールします。これにより、 そのオブジェクトを使用する前に必要な初期化を行うことができます。

上記の通り、`new`した際に実行されます。

例

```php:sample_construct.php
<?php

class Baby
{
    public $birthDay;
    public function __construct()
    {
        $this->birthDay = date('Y-m-d H:i:s');
    }
}

$baby = new Baby();
echo $baby->birthDay; // 現在の日時が出力される
```

Laravelでの例

ルーティングでUserController内のメソッドが指定されていれば必ず、オブジェクトの生成がされるので
複数のメソッドで使う、DIだったりミドルウェアの適応等に適しています


```php:UserController
class UserController extends Controller
{
    public $service;
    public function __construct(\App\Services\UserService $service)
    {
        $this->middleware('auth');

        $this->middleware('log')->only('index');

        $this->service = $service;
    }
}
```

#2. __destruct()
>デストラクタメソッドは、 特定のオブジェクトを参照するリファレンスがひとつもなくなったときにコールされます。 あるいは、スクリプトの終了時にも順不同でコールされます。

上記の通り、オブジェクトが破棄される際や、スクリプトが終わったら実行されます
exit() でスクリプトの実行を止めた場合にもデストラクタはコールされます。

ファイルのポインタ開いたのをを閉じたり、GuzzleとかCurlHttpClient等ではつかってるぽいです

```php:sample_destruct.php
<?php

class SampleFileOpen
{
    public $handle;
    public function __construct($user,$pass)
    {
        // ファイル開く
        $this->handle = fopen('somefile.txt', 'r');
    }

    public function __destruct()
    {
        fclose($handle);
    }
}

$file = new SampleFileOpen();
echo fgets($file->handle); //1行目が表示

// スクリプト終了したので、ファイルが自動で閉じられる

```

#3. __call(), __callStatic

__call()
>アクセス不能メソッドをオブジェクトのコンテキストで実行したときに起動します
引数 $name は、 コールしようとしたメソッドの名前です。 引数 $arguments は配列で、メソッド $name に渡そうとしたパラメータが格納されます。  

__callStatic
> アクセス不能メソッドを静的コンテキストで実行したときに起動します。
引数 $name は、 コールしようとしたメソッドの名前です。 引数 $arguments は配列で、メソッド $name に渡そうとしたパラメータが格納されます。  

上記の通り、生成したオブジェクトが呼び出すメソッドを持っていない場合に起動します

```php:sample_call.php
<?php

class User
{
    public function __call($method,$arguments)
    {
        echo "$methodはありません";
    }

}

$user = new User();
$user->sampleFunction(); // sampleFunctionはありません；

```



使い道としては例えば
Laravelのモデルでは、下記のような`__callStatic`が定義されています。


```php:Model.php
   
    /**
     * Handle dynamic static method calls into the method.
     *
     * @param  string  $method
     * @param  array  $parameters
     * @return mixed
     */
    public static function __callStatic($method, $parameters)
    {
        return (new static)->$method(...$parameters);
    }

```

これは、例えば`find`というメソッドがありますが、これを静的に呼んだときに
自オブジェクトをインスタンス化(詳しくは遅延的束縛参照)し、そこから対象メソッドを呼ぶことで、静的コールしているように振る舞っています

```php
User::find(1);

//実際は__callStatic内で下記のように変換される
(new User())->find(1);
```

FWとかパッケージで使われているのは観測するが自身で使ったことはないです

#4. __get(), __set()

__get() 
>__get() は、 アクセス不能(protected または private)または存在しないプロパティからデータを読み込む際に使用します。

__set() 
>__set() は、 アクセス不能(protected または private)または存在しないプロパティへデータを書き込む際に実行されます。

上記の通り、オブジェクトのプロパティへの読みor書き時に起動します
__get、__setはLaravelのModelの属性へのアクセスに利用されています


下記が使用例です
本来、存在しないプロパティにアクセスすると落ちますが、
配列`$attributes`を用意しておき、そこにセットしたり、なかったらnullを返すのでエラーで落ちません

LaravelのModelで行われているgetとsetを超簡略化した形です

```php:sample_get_set.php
<?php

class User
{
    protected $attributes = [];
    
    public function __get($key)
    {
        if (array_key_exists($key, $this->attributes)){
            return $this->attributes[$key];
        }
        return null;
    }

    public function __set($key, $value)
    {
        $this->attributes[$key] = $value;
        return $this;
    }

}

$user = new User();

// __getが呼ばれ、$attributesの中にキーがnameのがないので nullを返し、Undefined propertyにならない
echo $user->name; // null

// __setが呼ばれ、$attributesの中にキーがnameで、値HogeHogeで配列に格納される
$user->name = 'HogeHoge';
echo $user->name; //HogeHoge

```

#5. __invoke() 

>__invoke() メソッドは、 スクリプトがオブジェクトを関数としてコールしようとした際にコールされます。

上記の通り、オブジェクトを関数のように呼び出したときに起動します



```php:sample_invoke.php
<?php

class User
{
    protected $name = 'hoge';

    public function __invoke()
    {
        echo $this->name;
    }

    public function showName()
    {
        echo $this->name;
    }
}

$user = new User();
$user->showName(); // hoge
$user(); //hoge

```

Laravelでは、invokeを利用した
シングルアクションControllerを使用できます

```php:web.php
Route::get('user/{id}', 'ShowProfile');
```

```php:ShowProfile.php
<?php

namespace App\Http\Controllers;

use App\User;
use App\Http\Controllers\Controller;

class ShowProfile extends Controller
{
    /**
     * 指定ユーザーのプロフィール表示
     *
     * @param  int  $id
     * @return View
     */
    public function __invoke($id)
    {
        return view('user.profile', ['user' => User::findOrFail($id)]);
    }
}
```

#おわりに
もっと詳しく解説してる記事があったので貼っておきます
https://www.atmarkit.co.jp/ait/articles/1804/05/news008.html

基本的にFWを使った開発しかしたことないので、コンストラクタぐらいしか使った記憶がありません
頭の隅に入れておくぐらいでよいのではと思っています
こんな使い方したとかありましたら、ご教授いただけますと幸いです