PHP初心者によるマジックメソッドの理解


#マジックメソッドとは

コンストラクタ「__construct()」等のアンダースコア2つから始まるメソッドで、
基本的には、ある状況において実行されるメソッド。

#主なマジックメソッド
##constructとdestruct
####__construct()
　**インスタンスの生成時**に実行される

####__destruct()
　**プロセスの終了時や、unset()で明示的にインスタンスを破棄した場合に**実行される

##getとset
####__get()
　**アクセスできないプロパティにアクセスした場合に**実行される

####__set()
　**アクセスできないプロパティに対して値をセットした場合に**実行される

##issetとunset
####__isset()
　**アクセスできないプロパティに対してisset()を行った場合に**実行される

####__unset()
　**アクセスできないプロパティに対してunset()を行った場合に**実行される

####isset()
　変数が宣言されていて、NULLでなければ、true
　NULLが代入されているか、変数が存在しない場合は、falseを返す。

```php
  isset(/*$変数名*/);
```
####unset()
　指定した変数を破棄する。
　※あるグローバル変数を、ある関数の中でunset()した場合は、ローカル変数のみが破棄される。

```php
  unset(/*$変数名*/);
```

##callとcallStatic
####__call()
　**未定義のインスタンスメソッドを呼び出した場合に**実行される

####__callStatic()
　**未定義のクラスメソッドを呼び出した場合に**実行される
　
##sleep()とwakeup()
####__sleep()
　**インスタンスをserializeする場合に**実行される

####__wakeup()
　**インスタンスをunserializeする場合に**実行される

####serialize()　unserialize()とは
　インスタンスをデータベースやファイルに保存したい場合、プロパティをそれぞれ保存して行くと、データが膨大になるため、
　シリアライズ化することで、クラス内部の情報を文字列に変換して保存することが可能となる。
　シリアライズ化した情報を再度生成したい場合は、**アンシリアライズ**することで再構築できる。

```php
<?php

class Test
{
    public $firstProperty = "プロパティ１";
    public $secondProperty = "プロパティ２";

    public function __sleep()  //serialize()されたときに実行される
    {
        echo "シリアライズしました。\n";
        return array('firstProperty', 'secondProperty');
    }

    public function __wakeup()  //unserialize()されたときに実行される
    {
        echo "アンシリアライズしました。\n";
    }
}


$test = new Test();

$serialized = serialize($test);
$unserialized = unserialize($serialized);
echo $unserialized->firstProperty . "\n";
echo $unserialized->secondProperty . "\n";

// 実行結果↓
// シリアライズしました。
// アンシリアライズしました。
// プロパティ１
// プロパティ２
```

##toString
####__toString()
　**インスタンスを文字列として扱いたい場合に**`利用できる`

```php
<?php
class Test
{
  private $property = 'プロパティ';
  public function __toString() {
    return $this->property;
  }
}

  $test = new Test();
  echo $test;

// 実行結果↓
// プロパティ
```


##invoke
####__invoke()
　__invokeを**実装したクラスを関数のように**`扱うことが可能になる`

```php
<?php
class Test
{
  public function __invoke($params) {
    print_r($params);
    echo 'を受け取りました。';
  }
}

  $test = new Test();
  //関数として利用(__invoke()が実行される)
  $test('hoge');

// 実行結果↓
// hogeを受け取りました。
```

##clone
####__clone()
　**インスタンスがcloneされた場合に**実行される

####clone
　インスタンスを複製することができる。

```php:代入するだけでは複製できない
class Test
{
    public $property = "オリジナル";
}

$test = new Test();
$test1 = $test;  //インスタンスはポインタであり、代入では複製されない
$test1->property = "コピー";  //上記の理由から、$testも$test1も同じプロパティということになる

echo "original:" . $test->property;
echo "\ncopy:" . $test1->property . "\n";

// 実行結果↓
//original:コピー
//copy:コピー
```

```php:複製するにはcloneを使う
<?php

class Test
{
    public $property = "オリジナル";
}

$test = new Test();
$test1 = clone $test;  //cloneでインスタンスを複製
$test1->property = "コピー";

echo "original:" . $test->property;
echo "\ncopy:" . $test1->property . "\n";

// 実行結果↓
//original:オリジナル
//copy:コピー
```

####__clone()を使うケース

```php:cloneした$testの内部のBarインスタンスはcloneされていないため、変更されてしまう。
<?php

class Test
{
    public $bar;
}

class Bar
{
    public $val;
}

$test = new Test();
$test->bar = new Bar();
$test->bar->val = 'hoge';

$test1 = clone $test;

echo "\n before fuga";
echo "\n original:" . $test->bar->val;
echo "\n copy:" . $test1->bar->val . "\n";

$test1->bar->val = 'fuga';
echo "\n after fuga";
echo "\n original:" . $test->bar->val;
echo "\n copy:" . $test1->bar->val . "\n\n";


// 実行結果↓
// before fuga
// original:hoge
// copy:hoge

// after fuga
// original:fuga　オリジナルにもfugaが代入されてしまう！
// copy:fuga
```

　上記のプログラムでは、
　①＄testにTestインスタンスを代入
　②＄test(Testインスタンス)の＄barにBarインスタンスを代入
　③＄test(Testインスタンス)の＄bar(Barインスタンス)のvalに'hoge'を代入
　④＄test１に＄testをcloneして代入
　
　この時点での＄testと＄test1を見てみると...

```console:＄testと＄test1のvar_dump()結果
#$test
object(Test)#1 (1) {
  ["bar"]=>
  object(Bar)#2 (1) {
    ["val"]=>
    string(4) "hoge"
  }
}
#$test1
object(Test)#3 (1) {  #cloneしたので、Testインスタンスは新しく生成(#3)されている。
  ["bar"]=>
  object(Bar)#2 (1) { #$Barインスタンスは$testと同じもの(#2)を指している
    ["val"]=>
    string(4) "hoge"
  }
}
```

　⑤＄test1の＄var->valに'fuga'を代入

　＄test1の＄var->valは＄testの＄var->valと同じものを指すので、オリジナルもコピーも値が変更されてしまう。

　上記は、__clone()を使うと解決できる

```php:Testクラスを以下のように変える
<?php

class Test
{
    public $bar;
    public function __clone()      //cloneされた時に実行されるメソッドを追加
    {
        $this->bar = clone $this->bar;
    }
}

//...省略...

// 実行結果↓
// before fuga
// original:hoge
// copy:hoge

// after fuga
// original:hoge オリジナルはそのまま！
// copy:fuga
```

```console:＄testと＄test1のvar_dump()結果
#$test
object(Test)#1 (1) {
  ["bar"]=>
  object(Bar)#2 (1) {
    ["val"]=>
    string(4) "hoge"
  }
}
#$test1
object(Test)#3 (1) {
  ["bar"]=>
  object(Bar)#4 (1) { #新たなBarインスタンスがcloneされている！
    ["val"]=>
    string(4) "fuga"
  }
}
```