PHP シリアライズを使って配列をDBに保存可能な文字列に変換する

## 概要

- PHPのシリアライズ（保存可能な文字列型への変換）について簡単にまとめてみる。

## シリアライズの方法

### 文字列をシリアライズする

- シリアライズにはPHPの組み込み関数である`serialize()`関数を用いる。
- 手始めに文字列型の「A」という文字をシリアライズしてみる。

    ```php
    <?php

    $str = 'A';
    $s = serialize($str);

    // s:1:"A";と出力される
    echo $s;
    ```

- 上記の処理では文字列型の「A」をシリアライズしている。
    - `s:1:"A"`は「ｓ」が型名（stringの頭文字）、「1」がバイト数、「A」がシリアライズ元の内容を表している。
- ただ、これは「文字列型のAを保存可能な文字列型への変化」なのでそこまで意味はない。
- ちなみに`unserialize()`関数を用いることでシリアライズされたものを元に戻す事ができる。

    ```php
    <?php

    $str = 'A';
    $s = serialize($str);

    // s:1:"A";と出力される
    echo $s;

    $u = unserialize($s);

    // Aと出力される
    echo $u;
    ```

### 配列をシリアライズする

- 方法は同じだが配列をシリアライズしてみる。
- 本来シリアライズは「配列や一部のオブジェクトを文字列化してDBなどに保存する」ことが使われ方の目的のようだ。

    ```php
    <?php

    $array = [
        'id' => 1,
        'name' => 'miriwo'
    ];
    $s = serialize($array);

    // a:2:{s:2:"id";i:1;s:4:"name";s:6:"miriwo";}と出力される
    echo $s . "\n";
    ```

- 配列をシリアライズした文字列を見てみる。（見やすいようにインデントを付けてみた。）

    ```
    a:2:
        {
            s:2:"id";i:1;
            s:4:"name";s:6:"miriwo";
        }
    ```

- 確かに型、容量、実際の文字がそれぞれ文字列で表現されている。
- シリアライズを使えば配列をそのままDBに格納する事ができる。
- もちろん`unserialize()`関数を用いることで元の配列に戻す事ができる。

### オブジェクトをシリアライズする

- 次はオブジェクトのシリアライズを試してみる。
- 下記のように記載することでオブジェクトをシリアライズすることはもちろん可能である。

    ```php
    <?php

    class A{
        // シリアライズされる
        public string $str_a = 'AAA';
    
        // シリアライズされない
        public function foo () {
            echo 'foo';
        }
    }

    $a = new A;

    $s = serialize($a);

    // O:1:"A":1:{s:5:"str_a";s:3:"AAA";}と出力される
    echo $s;
    ```

- シリアライズされた内容がこちら。

    ```
    O:1:"A":1:
        {
            s:5:"str_a";s:3:"AAA";
        }
    ```

- プロパティー（メンバ変数）だけがシリアライズされている。fooメソッドも有るはずなのにシリアライズされていない。これはどういうことなのか。
- どうやらオブジェクトはシリアライズするとプロパティーとクラス名のみがシリアライズされ、メソッドはシリアライズされないようだ。
    - `オブジェクトをシリアライズすると、オブジェクト内の変数もすべて保存されます。 オブジェクト内のメソッドは保存されません。 クラス名のみが保存されます。` [オブジェクトのシリアライズ](https://www.php.net/manual/ja/language.oop5.serialization.php)
- 更にオブジェクトの場合特殊で`unserialize()`関数を使うときはシリアライズ元クラス定義が同ファイル内に存在している必要がある模様。
- 下記のコードはクラスの定義もシリアライズもシリアライズからの戻しも同じファイル内に記載したため何もしなくても`unserialize()`関数を使う事ができている。

    ```php
    <?php
    
    class A{
        // シリアライズされる
        public string $str_a = 'AAA';
        
        // シリアライズされない
        public function foo () {
            echo 'foo';
        }
    }
    
    $a = new A;
    
    $s = serialize($a);
    
    // O:1:"A":1:{s:5:"str_a";s:3:"AAA";}と出力される
    echo $s . "\n";
    
    $u = unserialize($s);
    
    var_dump($u);
    $u->foo();
    ```

- 下記の様に出力される。

    ```
    O:1:"A":1:{s:5:"str_a";s:3:"AAA";}
    object(A)#2 (1) {
      ["str_a"]=>
      string(3) "AAA"
    }
    foo
    ```

- 実際はクラスとシリアライズ、シリアライズからの戻しは別のファイルに記載する事がほとんどなので`include`や`require`でクラスの実装部分を持ってきてあげる必要があるっぽい。（useでもいいのかな・・？勉強不足だ。include、require、useをしっかり学ぼう。）

## シリアライズされた文字列を格納するDBのカラム

- シリアライズされた文字列を格納するDBのカラムはCHAR型やTEXT型ではダメ。
- BLOB型（MySQLならTINYBLOB、BLOB、MEDIUMBLOB、LONGBLOBのいずれか）を使う必要がある。
- シリアライズされた文字列はバイナリ文字列であり、nullバイトを含む可能性があるらしい。
- BLOB型はデータサイズ指定無しの**バイナリデータ**を格納する為のカラム型である。（TEXTはデータサイズ指定無しの**文字列データ**を格納する為のカラム型）

## 参考文献

https://www.php.net/manual/ja/function.serialize.php

https://www.php.net/manual/ja/function.unserialize.php

https://www.php.net/manual/ja/language.oop5.serialization.php

https://dev.mysql.com/doc/refman/8.0/ja/blob.html