PHP 今更ながらオブジェクト指向を自分自身に向けて解説してみた ~基本編~

# 概要

- かなり前に下記のような記事を記載したが、まとめただけでちゃんと理解できていなかった気がするのでもう一度記載する。
    - [PHP Laravel オブジェクト指向 用語集](https://qiita.com/miriwo/items/974adcee45f699553cd4)

# 前提

- 下記サービスを用いて検証を行った。
    - [https://paiza.io/](https://paiza.io/)

# 内容

### クラス

- オブジェクト指向の説明でよく「設計図」と言われるもの。
- 設計図と言われるけどそんなにたいしたものではなく、中に関数とか変数とかを入れておいて使い回せるようにしたものである。
- 基本的に一個の.phpファイルに一個のクラスを記載する。
- .phpファイルのファイル名はクラス名と一致させる。
- わかりにくくなるため推奨はされないが一個の.phpのファイルに複数のクラスを定義することもできる。
- 下記のように定義する。（クラス名はパスカルケースで記載する）

    ```php
    <?php

    class クラス名
    {
        
    }
    ```

- 上記のように記載すればもちろんクラスの定義はできている。しかし、中身がなにもない。文字通り白紙の設計図を用意しただけの状態である。
- 後々の説明のために下記の様なTestクラスを定義してみる。クラス内部に色々追記されているが今はあまり深く考えなくて良い。

    ```Test.php
    <?php

    class Test
    {
        public $firstStr;
        public $secondStr;
    
        public function echoStr(){
            echo 'hoge hoge';
        }
    
    }
    ```

- 非常に簡単な「設計図」が準備できたので早速、設計図をもとに物を作ってみよう。

### インスタンスとインスタンス化、インスタンス変数

- 設計図から作られる物をインスタンスと呼ぶ。
- 設計図から物を作る行為をインスタンス化と呼ぶ。
- インスタンス化して作ったインスタンスを入れた変数をインスタンス変数と呼ぶ。
- ↑プラモデルで言うなら取説（設計図）をもとに作り完成したプラモデルそのものを「インスタンス」、プラモデルを作る過程を「インスタンス化」という。
- 作ったプラモデルはそのままテーブルの上においておいたら壊れたりホコリが被ったりする。作ったプラモデルを入れておく箱をインスタンス変数と呼ぶ。
- インスタンス化は下記のようにnew演算子を使って行う。試しに先に定義したTestクラスをインスタンス化してインスタンスをインスタンス変数`$test`に格納してみる。

    ```Test.php
    <?php

    class Test
    {
        public $firstStr;
        public $secondStr;
    
        public function echoStr(){
            echo 'hoge hoge';
        }
    
    }

    // インスタンス化してインスタンス変数$testにインスタンスを格納している。
    $test = new Test();
    ```

- この辺から若干難しくなってくると思うので、わからなくても「ふーん」くらいに思っておけば大丈夫である。とりあえず「new演算子使ってクラスをインスタンス化する」だけ覚えておけばOK！
- ちなみに、上記の処理を実行しても特に何か出力されるとかは無い。「じゃあ、クラス内部で定義している変数とか関数はなんのために定義したの？」となるとおもうので次の項目で実際に触ってゆこうと思う。

### プロパティ

- Testクラスの内部で定義されている変数`$firstStr`とか`$secondStr`はただの変数ではない。クラスをインスタンス化して初めて使うことのできる「プロパティ」と呼ばれる特殊な変数だ。その証拠にPHPで変数定義するときには不要な「public」などのアクセス修飾子が記載されている。
- プロパティには下記のようにすることでアクセスする事ができる。

    ```php
    $インスタンスメソッド->プロパティ名;
    ```

- イメージが湧きにくいと思うので実際に触ってみよう。
- `$firstStr`プロパティには「hoge」を`$secondStr`には「fuga」をそれぞれ格納して出力してみよう。

    ```Test.php
    <?php

    class Test
    {
        public $firstStr;
        public $secondStr;
    
        public function echoStr(){
            echo 'hoge hoge';
        }
    
    }

    // インスタンス化してインスタンス変数$testにインスタンスを格納している。
    $test = new Test();

    // プロパティへの値格納
    $test->firstStr = 'hoge';
    $test->secondStr = 'fuga';

    // プロパティの呼び出し
    echo $test->firstStr;
    echo $test->secondStr;
    // hogefugaと出力される
    ```

- 「いやいや、変数に値格納して出力しているだけじゃんｗｗ」と思われた方もいらっしゃるかもしれない。実際そのとおりだ。
- ここで一つだけ覚えておかないと行けないことはクラスの中に記載した変数（プロパティ）は一部の例外を除きインスタンス化（new演算子を用いた処理）を行わないと使えないことである。（「一部の例外」についてはあとで記載する。）

### メソッド

- インスタンス化しないと使えないものは何もプロパティだけではない。
- Testクラス内で定義されているechoStr関数もインスタンス化しないと使うことのできない関数である。これをメソッドと呼ぶ。
- メソッドには下記のようにすることでアクセスする事ができる。

    ```php
    $インスタンスメソッド->メソッド名();
    ```

- 実際にechoStrメソッドをインスタンス化後に呼び出してみる。`//メソッドの呼び出し`から下のコードを追加した。（プロパティの呼び出し部分は一時的にコメントアウトした。）

    ```Test.php
    <?php
    
    class Test
    {
        public $firstStr;
        public $secondStr;
        
        public function echoStr(){
            echo 'hoge hoge';
        }
        
    }
    
    // インスタンス化してインスタンス変数$testにインスタンスを格納している。
    $test = new Test();
    
    // プロパティへの値格納
    $test->firstStr = 'hoge';
    $test->secondStr = 'fuga';
    
    // プロパティの呼び出し
    //echo $test->firstStr;
    //echo $test->secondStr;
    // hogefugaと出力される
    
    // メソッドの呼び出し
    $test->echoStr();
    // hoge hogeと出力される
    ```

- プロパティの例に漏れず、メソッドもまたインスタンス化後でないと呼び出すことができない。
- ここまでの説明を見て「このレベルの処理ならクラスを用意してインスタンスを作る必要は無いんじゃない？」と思った方もいらっしゃるはずである。こちらもその通りだ。今のコードだとオブジェクト指向の恩恵は無に近いと思う。次の項目でちょっとだけ「これならクラスを用意してもいいかも」と思える物を作ってゆこうと思う。

### どうしてオブジェクト指向が良いのか

- あなたが、とあるチームメンバーの紹介文をPHPで出力する事になったとする。（「Wordやメモ帳で入力したほうが早い」などは禁句だ。。！）
- 出力する紹介文は下記である。

    ```
    私の名前は「名字 名前です。
    ```

- これをそれぞれ「山田 一郎」「斎藤 桜子」「佐藤 二郎」「鈴木 梅子」の4人分出力するとする。
- その場合、下記のような至って簡単なechoを用いた処理が想像できる。

    ```php
    <?php

    echo '私の名前は山田 一郎です' . "\n";
    echo '私の名前は斎藤 桜子です' . "\n";
    echo '私の名前は佐藤 二郎です' . "\n";
    echo '私の名前は鈴木 梅子です' . "\n";
    ```

- しかし、「私の名前は」の前に「はじめまして、」という文字を入れてほしいとあとから要望があった。その場合、すべてのecho文の出力文字列の最初に「はじめまして、」という文字を入れる作業が必要になる。この作業は非常めんどくさい。まだ今回は4人だったからいいものの、これが10人20人となると更にめんどくさい。こんなときに冗長さを解決してくれるのがまさにオブジェクト指向である。
- 今回の要件を満たす紹介文を出力する処理をオブジェクト指向で記載してみた。まずは「私の名前は「名字 名前です。」と出力するものから。（初見のアクセス修飾子や__constructメソッドなどがあるが今は一旦スルーで良い）

    ```Test.php
    <?php
    
    class Test
    {
        // 名前
        private $firstName;
        // 名字
        private $secondName;
        
        // インスタンス化時に実行される関数
        public function __construct(string $secondName, string $firstName)
        {
            $this->firstName = $firstName;
            $this->secondName = $secondName;
        }
        
        // 個人の紹介文を出力する関数
        public function echoSelfIntroductionStr()
        {
            echo '私の名前は' . $this->secondName . ' ' . $this->firstName . 'です。' . "\n";
        }
        
    }
    
    // インスタンス化してインスタンス変数$testにインスタンスを格納している。
    $test_1 = new Test('山田', '一郎');
    $test_2 = new Test('斎藤', '桜子');
    $test_3 = new Test('佐藤', '二郎');
    $test_4 = new Test('鈴木', '梅子');
    
    // メソッドの呼び出し
    $test_1->echoSelfIntroductionStr();
    $test_2->echoSelfIntroductionStr();
    $test_3->echoSelfIntroductionStr();
    $test_4->echoSelfIntroductionStr();
    ```

- 上記の処理でもまだまだ冗長ではある。（各個人の名前ごとにインスタンス化が必要だったり）
- しかし、これなら`「私の名前は」の前に「はじめまして、」という文字を入れてほしいとあとから要望があった。`こんなときにはどうだろうか？
- なんと`echoSelfIntroductionStr()`メソッドの`echo '私の名前は' . $this->secondName . ' ' . $this->firstName . 'です。' . "\n";`の「私の名前は」の前に「はじめまして、」と入力するだけで要望を満たせるのだ。
- 例に上げたコードは説明用の物なのでまだまだそんなにオブジェクト指向の恩恵は受けられていないと思う。しかし、これがプロジェクトで使うソースコードならどうだろう？？
- 何百ステップもある処理を書き終えお客さんに確認を求めたら「ん〜ここの表示文言を変えてほしいんだよね〜」なんてことよくある話である。
- 当該箇所が繰り返し表示にも関わらずコピペで同じコードがたくさん並んでたりしたら最悪だ。（昨今のエディタはかなり優秀で一括で編集できる可能性もあるけど。）
- 「なるべく冗長な部分をまとめて、管理を楽にしましょう。ステップ数を減らしましょう」ということがオブジェクト指向の真の目的であると言えるのではないか。

### アクセス修飾子

- プロパティやメソッドの定義時に必ず最初に記載する「アクセス修飾子」は実は3種類存在し、文字通り「当該のプロパティやメソッドがどこからアクセスできるか」を定義している。（よく使うのはpublicとprivateである。「サブクラス」などは後々出てくる「クラスの継承」部分で説明される。）

    | アクセス修飾子 | アクセス制限 |  |
    | :-: | --- | --- |
    | public | どこからでもアクセス可能 | インスタンス化しても使える |
    | protected | 今のクラスとサブクラス（継承先）でアクセス可能 |  |
    | private | 今のクラスのみアクセス可能 | インスタンス化しちゃうと使えない |

- なので下記のような処理はエラーになる。（private修飾子がついている$firstStrにインスタンス化後に当該クラスの外からアクセスしているため。）


    ```Test.php
    <?php
    
    class Test
    {
        private $firstStr;
        private $secondStr;
        
        public function echoStr(){
            echo 'hoge hoge';
        }
        
    }
    
    // インスタンス化してインスタンス変数$testにインスタンスを格納している。
    $test = new Test();
    
    // プロパティへの値格納
    $test->firstStr = 'hoge';
    $test->secondStr = 'fuga';
    
    // プロパティの呼び出し
    echo $test->firstStr;
    echo $test->secondStr;
    ```

- エラー内容

    ```
    PHP Fatal error:  Uncaught Error: Cannot access private property 
    Test::$firstStr in Test.php:18
    Stack trace:
    #0 {main}
      thrown in Test.php on line 18
    ```

### 特殊な変数`$this`

- ※若干理解に苦しむかもしれないので「`$this`には自らのインスタンスが入っている」とだけわかっていれば良いかも。
- 先の説明に登場した`$this`は変数である。
- しかし、少し特殊な変数である。
- この変数`$this`には「自らのインスタンス」が格納されている。
- 下記のようなクラスが有り、インスタンス化やインスタンス化後のプロパティへの値格納、メソッドの呼び出しが定義されていたとする。

    ```Test.php
    <?php
    
    class Test
    {
        public $str;
        
        public function echoStr(){
            echo $this->str;
        }
        
    }
    
    $obj = new Test();
    
    // 変数の格納
    $obj->str = '出力される文字列です';
    
    $obj->echoStr();
    ```

- `$obj->str`はインスタンス化後に当該インスタンスの外からプロパティにアクセスしている。
    - $インスタンス変数->プロパティのルール
- `$this->str`はインスタンス化後に当該インスタンスの中からプロパティにアクセスしている。
    - $インスタンス変数->プロパティのルールに変更は無い。そうなると`$this`は自らのインスタンが格納されているインスタンス変数を表している。

    ![「PHP_オブジェクト指向の用語をまとめ」を編集_-_Qiita.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/306417/e086537d-55bf-a4c7-68e3-eed55c959689.png)


### 特殊なメソッド`__construct()`

- インスタンス化時に実行される特殊なメソッドである`__construct()`というものがある。
- インスタンス化時に引数を渡す場合などによく使われる。
- 先に説明した紹介文を出力する処理の`__construct()`を下記に記載する。（クラス定義部分は記載せず関係ありそうなところだけ抜粋）

    ```php
    // 名前
    private $firstName;
    // 名字
    private $secondName;

    // インスタンス化時に実行される関数
    public function __construct(string $secondName, string $firstName)
    {
        $this->firstName = $firstName;
        $this->secondName = $secondName;
    }
    ```

- 処理の流れは下記である。
    1. new演算子を用いて第一引数、第二引数がある状態でインスタンス化
    1. `__construct()`メソッドが実行され、型宣言により第一引数、第二引数がメソッドに渡されるときにstring型かどうかを判定
    1. 型に問題がなければ第一引数が変数`$secondName`に、第二引数が変数`$firstName`に格納される。（`string $secondName, string $firstName`の処理）
    1. 変数`$firstName`に格納された値を`__construct()`メソッド定義の少し前で定義しているプロパティ`$firstName`にインスタンスの中で格納（`$this->firstName = $firstName;`の処理）
    1. 変数`$secondName`に格納された値を`__construct()`メソッド定義の少し前で定義しているプロパティ`$secondName`にインスタンスの中で格納（`$this->secondName = $secondName;`の処理）
- 上記の処理がインスタンス化時に実行されるため、インスタンス内部では`$this->firstName`とすればインスタンス化時の第二引数の値、`$this->secondName`とすればインスタンス化時の第一引数の値にアクセスする事ができる。

### インスタンス化しなくても使える静的プロパティ

- 実はクラス内で定義する変数にはインスタンス化しないでも使える変数を定義する方法がある。
- 下記のようにアクセス修飾子と変数名の間に`static`キーワードを記載することで静的プロパティを定義できる。

    ```Test.php
    <?php
    
    class Test
    {
        public static $str = '文字列';
    }
    
    echo Test::$str;
    // 文字列と出力される。
    ```

- 上記の例にて静的プロパティの呼び出し部分がプロパティの呼び出しと違うことをご確認いただきたい。
    - プロパティ
        - $インスタンス変数->プロパティ
    - 静的プロパティ
        - クラス::$静的プロパティ

### インスタンス化しなくても使える静的メソッド

- インスタンス化しなくても使えるものはプロパティだけではなくメソッドも存在する。これを静的メソッドと呼ぶ。
- 定義の方法も静的プロパティと一緒でアクセス修飾子の後ろに`static`キーワードを記載するだけである。

    ```Test.php
    <?php
    
    class Test
    {
        public static $str = '文字列';
        
        public static function echoStr()
        {
            echo 'hoge hoge';
        }
    }
    
    Test::echoStr();
    //hoge hogeと出力される
    ```

- 静的メソッド内では変数`$this`を使うことはできない（`$this`は自らのインスタンスを格納している。静的メソッドはインスタンス化せずに使うためインスタンス化されていなければ`$this`には自らのインスタンスは格納されていない。）
- 逆に静的メソッド内部で静的プロパティを使うことはできる。`Self`キーワードを用いて下記のように静的プロパティを呼び出す。

    ```Test.php
    <?php
    
    class Test
    {
        public static $str = '文字列';
        
        public static function echoStr()
        {
            echo Self::$str;
        }
    }
    
    Test::echoStr();
    //文字列 と出力される。
    ```