【初心者向け】PHPで知っておきたい実装方法３選

# はじめに
こんにちは！
株式会社BTMの畑です。

今回は、これからPHPでプログラミングを始める方や、いざ実務でコードを書いていくぞーという方向けに、知っていると役に立つ実装方法をご紹介します。

#### 軽く自己紹介
私自身、他業種からプログラミング未経験での入社だったので、初めは分からないことばかりでした。
特に、プログラミングの教本や学習サイトでは紹介されない実装方法は、自分で調べて情報にたどり着けないと、そもそも存在にすら気付けませんでした。
実際に実務のソースコードを見たり、先輩エンジニアからレビューやフィードバックを受けて初めて「えっ、そんな実装方法あったんだ！」と知るものも多かったです。

未経験で入社したばかりの自分に宛てて、当時こういうこと知っていたら助かったなー、と思う内容を紹介していきますので、新たなチャレンジを選んだ皆様の一助となれば幸いです。

:::note info
本記事で紹介する実装方法は以下のバージョン以降で対応しています
・PHP 8.0.11 
:::


# PHPで知っておきたい実装方法３選
それでは早速見ていきましょう！


## 1. 三項演算子
まず最初は「三項演算子」です。

私が未経験で入社して最初に「えっ、そんな方法あったんだ！」となったのが「三項演算子」でした。

if文といえば以下のような形式しか学習していなかったので、1行で表現できると知ったときは驚きでした。

```
if () {
  
} else {
  
}
```

例えば、ユーザ名の入力があれば変数に代入し、なければ"ゲスト"を代入したい場合、通常のif文なら以下になりますが、

```
if (isset($input_name)) {
  $username = $input_name;
} else {
  $username = "ゲスト";
}
```

三項演算子を使用すれば、以下の1行にできます。

```
$username = isset($input_name) ? $input_name : "ゲスト";
```

とてもスッキリした見た目になりましたね！

### 三項演算子の解説

三項演算子は3つの引数を持ちます。
左から右に評価され、左端（a）の命令の真偽をチェックし、trueなら次の値（b）、falseなら最後の値（c）を返します。

```
a ? b : c
```

以下のようにイメージすると分かりやすいですね。

```
(評価式) ? (評価式がtrueの場合) : (評価式がfalseの場合);
```

三項演算子はよく使う実装方法なので、既存のソースコードを読むと出くわす確率が高いです。
「なんだこの書き方？」とならないように覚えておきましょう。

:::note info
三項演算子の詳細は公式ドキュメントもご参照ください
:::

https://www.php.net/manual/ja/language.operators.comparison.php#language.operators.comparison.ternary

### プルス・ウルトラ

ここからは三項演算子によく似ていて、よく使われる演算子を紹介します。

#### エルビス演算子

エルビス演算子とは、三項演算子で使用していた「?:」を使用します。
```
$username = $input_name ?: "ゲスト";
```

三項演算子の真ん中の式が省略されたような見た目ですね。
左辺(```$input_name```)がtrueと同等であれば、$input_nameの値を返し、そうでないなら右辺（"ゲスト"）を返します。

```
(評価式かつ評価式がtrueと同等だった場合に返す値) ?: (評価式がfalseの場合);
```

#### Null合体演算子

Null合体演算子とは、三項演算子で使用していた「?」を2つ並べて使用します。
```
$username = $input_name ?? "ゲスト";
```

左辺(```$input_name```)がnullで無ければ、$input_nameの値を返し、nullなら右辺（"ゲスト"）を返します。

```
(評価式かつ評価式がnullでない場合に返す値) ?? (評価式がnullの場合);
```

:::note info
Null合体演算子の詳細は公式ドキュメントもご参照ください
:::

https://www.php.net/manual/ja/migration70.new-features.php#migration70.new-features.null-coalesce-op

　
では、最後にそれぞれの演算子に様々な値を入れて、どういう結果になるか見てみましょう。

| $input_nameの値 | エルビス演算子 ```$input_name ?: "ゲスト"``` | Null合体演算子 ```$input_name ?? "ゲスト"``` |
|:-----------:|:------------:|:------------:|
| 文字列("Test")   | "Test"         | "Test"       |
| 空文字列("")     | "ゲスト"        | ""           |
| null            | "ゲスト"        | "ゲスト"      |
| true            | true           | true          |
| false           | "ゲスト"        | false        |
| 1               | 1               | 1            |
| 0                | "ゲスト"       | 0            |
| -1              | -1             | -1            |
| 空の配列(array())| "ゲスト"       | array()       |
| 配列(array(0))   | array(0) | array(0) |

どちらも便利な演算子ですが、結果が異なる部分もあります。
実装したい内容に沿った演算子を選択できるように注意していきましょう。


## 2. オブジェクトの複製を作成する

次はオブジェクトの複製作成です。

本題に入る前に、「値渡し」と「参照渡し」について少し触れておきます。

まずは変数の「値渡し」の挙動について
以下のように、aをbに代入する場合を考えてみます。

```
$b = $a;
```

以下の場合、それぞれの値はどうなっているでしょうか。

```
$a = 1;
$b = $a;
$a = $a + 1;
```

```
echo $a;
-> 2

echo $b;
-> 1
```

aの値は最初の初期値から変更されています。
bの値はaを代入した時点の値から変わっていません。

続いて「参照渡し」の場合

```
$a = 1;
$b =& $a;
$a = $a + 1;
```

```
echo $a;
-> 2

echo $b;
-> 2
```

aの値は「値渡し」のときの同様に、最初の初期値から変更されています。
bの値は「値渡し」のときとは異なり、aを代入した時点の値ではなく、変更後のaの値と同じになっています。

つまり、「値渡し」は値のみを代入するので、代入した時点でのaの値をbは受け取りますが、「参照渡し」の場合はaの参照先（メモリ領域）をbは受け取るので、実質的にaとbは同じ変数となってしまい、上記の結果となります。

この辺りは混乱しやすいので気をつけていきましょう。

:::note info
参照渡し（リファレンス）の詳細は公式ドキュメントもご参照ください
:::

https://www.php.net/manual/ja/language.references.whatdo.php


　
では、本題に戻りましてオブジェクトの複製についてです。

イメージしやすいように、ゲームのキャラクターが持つパラメータで考えてみます。
以下のような、HPやMPのパラメータを管理するオブジェクトがあるとします。

```
class statusClass {
    public $hp = 10;
    public $mp = 10;
}  
```

aのステータスを作成し、bのステータスに代入します。
そして、aのステータスだけ値を変更します。

```
$a_status = new statusClass();
$b_status = $a_status;

$a_status->hp = 5;
```

変数と同じように「値渡し」であれば、bのステータスは代入されたタイミングの値のまま変わっていないはずですが、

```
echo $a_status->hp;
-> 5

echo $b_status->hp;
-> 5
```

bのステータスも変わっていますね。
まるで「参照渡し」のような挙動になっています。
しかし、オブジェクトは「参照渡し」なんだ！、というのは正確ではありません。

### オブジェクトと参照の解説

- 参照渡しの誤解
  - 一般に「オブジェクトは参照渡しされる」と言われますが、正確にはそうではありません
- PHPの参照とは
  - PHPの参照はエイリアス（別名）です
  - 2つの異なる変数が同じ値を指すようになります
- オブジェクトの内部構造
  - PHPのオブジェクト変数の値には、オブジェクト自体ではなく、そのオブジェクトのIDが含まれます
  - このIDを使って、実際のオブジェクトにアクセスします
- IDのコピー
  - オブジェクトが他の変数に代入される際には、オブジェクトのIDのコピーが行われます
  - これにより、同じオブジェクトを指すようになります


つまり、bのステータスにaのステータスを代入する際、実際にはオブジェクトそのものではなく、オブジェクトのIDがコピーされます。
これにより、aのステータスとbのステータスは同じオブジェクトを指しています。したがって、aのステータスの値を変更すると、その変更はbのステータスにも反映されます。

```
class statusClass {
    public $hp = 10;
    public $mp = 10;
}  

$a_status = new statusClass();
$b_status = $a_status; // $b_statusは$a_statusと同じオブジェクトIDを持つ

$a_status->hp = 5;

echo $a_status->hp;
-> 5

echo $b_status->hp;
-> 5
```

:::note info
オブジェクトと参照の詳細は公式ドキュメントもご参照ください
:::

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

　
では、別のオブジェクトとして扱いたいときはどうすればよいでしょうか。
aとbは別のキャラクターなのに、aのHPを減らすと、bのHPも減ってしまうのは困りますよね。

こういう時はクローンを使用します。

```
class statusClass {
    public $hp = 10;
    public $mp = 10;
}  

$a_status = new statusClass();
$b_status = clone $a_status; 

$a_status->hp = 5;
```

aのステータスをクローンし、新しいオブジェクトとしてbのステータスを作成します。

```
echo $a_status->hp;
-> 5

echo $b_status->hp;
-> 10
```

bのステータスは変わっていませんね！

### オブジェクトのクローンの解説

クローンを使った場合、PHPではオブジェクトのシャローコピー（浅いコピー）が作成されます。
つまり、新しいオブジェクトが生成され、元のオブジェクトのプロパティの値が新しいオブジェクトにコピーされますが、元のオブジェクトと新しいオブジェクトは別々のインスタンスとして存在します。

:::note info
クローンの詳細は公式ドキュメントもご参照ください
:::

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

　
ちなみにクローンは、クローンしたタイミングの値でコピーされます。
なので以下の場合、

```
class statusClass {
    public $hp = 10;
    public $mp = 10;
}  

$a_status = new statusClass();
$a_status->hp = 8;

$b_status = clone $a_status; 

$a_status->hp = 6;
```

aのHPが8の段階でクローンが作成されるので、bのHPは8として作成され、その後にaのHPを6としているので、以下の結果になります。

```
echo $a_status->hp;
-> 6

echo $b_status->hp;
-> 8
```

クローンのタイミングによって値が変わってくるので気をつけましょう。


#### クローンの注意点

:::note warn
オブジェクト型のプロパティは「参照渡し」でコピーされるのでご注意ください。
:::

例えば、以下のような別のオブジェクトがあり、スタミナの値を管理しているとします。

```
class conditionClass {
    public $stamina = 100;
}

class statusClass {
    public $hp = 10;
    public $mp = 10;
    public $condition;
}  

$a_status = new statusClass;
$a_status->condition = new conditionClass; // オブジェクト型でプロパティを追加

$b_status = clone $a_status; // クローン作成

$a_status->hp = 6;
$a_status->condition->stamina = 80; // aのスタミナを減少させる
```

hpはaだけ変更されており、bの値はクローンしたタイミングの値のままです。
しかし、スタミナはaと同じ値に変わっていますね。

```
echo $a_status->hp;
-> 6
echo $a_status->condition->stamina;
-> 80

echo $b_status->hp;
-> 10
echo $b_status->condition->stamina;
-> 80
```

このようにオブジェクト型のプロパティは、クローンしても元のオブジェクトを参照しているので気をつけましょう。

### まとめ

「値渡し」、「参照渡し」、そして「オブジェクトの参照」は、混乱する概念かと思いますが、しっかり意識していきましょう。
特にオブジェクトを初めて触るときは、「なぜか思った値にならない…」ということがよくあると思いますが、慌てず確認していきましょう。




## 3. 戻り値がvoidの関数

続いては戻り値がvoidの関数です。

関数は基本的に何かをreturnすることが多いですが、何も返さない関数を必要とするときもあります。
そういったとき、明示的な型宣言により、戻り値の型にvoidを宣言します。

```
function sample_function(): void
{
  
}
```

こうすることで、関数が「何か」を返した場合、致命的なエラーを発生させます。

エラーは以下のように表示されます。

```
PHP Fatal error:  A void function must not return a value in /Main.php on line 1
```

それでは、エラーになる・ならないのパターンを見てみましょう。

まず、スカラー型（文字列、整数、論理等）をリターンする場合、
```
function sample_function(): void
{
  return 1;  
}
```
```
PHP Fatal error:  A void function must not return a value in /Main.php on line 1
```
エラーになります。
値を返しているのでエラーになるのは分かりやすいですね。

では、nullではどうでしょうか。

```
function sample_function(): void
{
  return null;  
}
```

```
PHP Fatal error:  A void function must not return a value (did you mean "return;" instead of "return null;"?) in /Main.php on line 1
```

エラーになります。
nullであっても、returnしているのでエラーになります。

では、何もreturnしなければどうでしょうか。

```
function sample_function(): void
{
  return;  
}
```

これはエラーになりません。
明示的にデータをreturnしなければエラーにならないのです。

そのため、以下のようにreturn自体が無い場合もエラーになりません。

```
function sample_function(): void
{
  
}
```
　
ここまで見てきたvoid型ですが、どういったときに使うのでしょうか。
void型が追加された際のRFCを確認すると、以下のメリットがありそうです。

- void型を使用することで、その関数が値を返さないことが明示され、関数の目的が明確になる
- 関数が値を返さないことを強制するため、誤って値を返してしまうことを防げる
- 型システムを活用することで、静的解析ツールや統合開発環境（IDE）がコードの検査をより正確に行えるようになる

:::note info
void型はPHP 7.1.0 で追加されました。
追加された背景等は以下のRFCをご参照ください。
:::

https://wiki.php.net/rfc/void_return_type

### まとめ
新規で関数を作成する際はあまり気にする必要はありませんが、既存の関数を改修する際は気をつけていきましょう。
void型の関数にreturnを追加してしまったとしても、エラー文にしっかりと記載されますので、冷静にエラー文を読むのも大事です。


# おわりに

お疲れ様でした！

駆け出しだった当時知っておきたかった実装方法３選をご紹介しました。

この記事が皆さんの役に立ち、これからエンジニアとして経験を積んでいく手助けとなることを願っています。

最後までご覧いただきありがとうございました。


:::note info
株式会社BTMではエンジニアの採用をしております。
ご興味がある方はぜひ[コチラ](https://www.b-tm.co.jp/recruit/mid-career.html)をご覧ください。
:::