PHPの基礎③制御構造 (独学エンジニア レッスン11より)

# 目標
<strong>今回やること</strong>
* PHPの基礎を学ぶ③

<strong>タスクばらし</strong>
* 制御構造
* 条件分岐の基礎
* 繰り返し処理の基礎
* クイズ
* 発展

<strong>予備知識</strong>
テスト駆動開発
①テストコード書いてから、アプリケーションコードを書く
* アプリケーションコード用のファイルを作成し、処理を実行する関数を定義する
* テストコード用のファイルを作成し、上の関数の動作をテストする
* アプリケーションコードをテストが通るように書く
* テストが通ったらコードをリファクタリングする

②アプリケーションコードにcalculateメソッドを定義し、テストコードでcalculateメソッドを呼び出そう


# 制御構造
* 式は値があるものすべて

* 文は末尾に ; がついたもの、もしくは中括弧でグループ化されたもの

* 構文は文法・書式のこと(if文など)

* 言語構造は、言語を構成する要素のこと(ifなど)

* 制御構造は、プログラムの流れを制御するもの(言語構造の一種)
プログラムは上から順に実行されるが、制御構造を用いてプログラムの流れを条件に応じて変えることができる

# 条件分岐の基礎
<strong>PHPの条件分岐はif文、switch文、match式</strong>

* if文
基本的な条件分岐

```
// 比較演算子で真偽値で判定
if ($num === 10)

// 論理型変数で直接判定($completedがtrueだったら)
if ($completed)

// 暗黙的型変換し判定(整数型の変数$numが0以外だったら)
if ($num)

// 複数条件で判定
if ($height > 10 && $height < 20)
```

* switch文
同じ変数を複数の値で条件分岐したいときに使う
※switch文は緩やかな比較が行われる

```
$pref = '福岡';

switch ($pref) {
	case '東京':
		echo '関東';
		break;
	case '京都':
		echo '関西';
		break;
	case '福岡:
		echo '九州';
		break;
	default:
		echo 'それ以外のエリア';
} // 九州
```

* match式
同じ変数を複数の値で条件分岐し、値を代入したいときに使う
※match式は厳密な比較を行う

```
$pref = '大阪';

$area = match ($pref) {
	'東京' => '関東',
	'京都', '大阪' => '関西',
	default => 'その他エリア',
};

echo $area; // 関西
```

# 繰り返し処理の基礎
* foreachは配列の全要素に繰り返し処理を行う時に使う
```
$numbers = [1, 2, 3, 4];
foreach ($numbers as $number) {
	echo $number * 2 . PHP_EOL;
} // 2 4 6 8

$scores = [
	'Tsuchida' => 50,
	'Kawasaki' => 90,
	'Kohama' => 70,
];

foreach ($scores as $name => $score) {
	echo $name . ':' . $score . PHP_EOL;
} // Tsuchida:50 Kawasaki:90 Kohama:70
```

* forは決められた回数分の繰り返し処理を行う時に使う
```
for ($i = 1; $i <= 10; $i++) {
	echo $i;
} // 1 2 3 ... 10

$people = [
	['name' => 'Hamada', 'height' => 170],
	['name' => 'Matsumoto', 'height' => 180],
];

for ($i = 0; $i < count($people); $i++) {
	echo $people[$i]['height'] . PHP_EOL;
} // 170 180
```

* whileループはある条件を満たす間だけ繰り返し処理を行いたい時に使う
```
$i = 0;
while ($i <= 100) {
	echo $i . PHP_EOL;
	$i += 10;
} // 0 10 20
```

* break, continue(for, foreach, whileで使える)
breakはループを抜けたい時に使う
continueはループをスキップしたい時に使う

```
$numbers = [1, 4, 8, 'abc', 10];
$even = 0;
foreach ($numbers as $number) {
	if (!is_numeric($number)) {
		echo '数値ではないので処理を中断' . PHP_EOL;
		break;
	}
	if ($number % 2) { // 奇数をスキップ
		continue;
	}
	$even += $number;
}

echo '偶数の値の合計' . $even . PHP_EOL;
```

* HTMLと混ぜて使う時
 if, switch, foreach, for, whileには波括弧を使わない記法がある
 
 before
 ```
 <?php if ($num === 10) { ?>
	<p>$numは10</p>
<?php } ?>
 ```
 
 after
 ```
 <?php if ($num === 10): ?>
	<p>$numは10</p>
 <?php endif; ?>
 ```
 
# 発展
<strong>コードの順序を意識しよう</strong>
* コードの順序に依存性がある時は依存性が明確になるようにしよう
* コードの順序に依存性がない時は関連するコードをまとめよう

<strong>条件分岐を使いこなそう</strong>
* 正常なケースを先に書き、エラーケースはあとに書こう

エラーケースが色んな箇所に記載
```
$link = mysqli_connect();
if (!$link) {
	$status = 'fetchError'; // エラーケース
} else {
	$result = mysqli_query($link, $query); // 正常ケース
	if ($result === false) {
		$status = 'queryError'; // エラーケース
} else {
	$row = mysqli_fetch_array($result); // 正常ケース
}
}
```

正常ケースから記載
```
$mysqli = mysqli_connect();
if ($link) {
	$result = mysqli_query($link, $query); // 正常ケース
	if ($result) {
		$row = mysqli_fetch_array($result); // 正常ケース
	} else {
		$status = 'queryError'; // エラーケース
	}
} else {
	$status = 'fetchError'; // エラーケース
}
```

* else句が必要ないかは要チェック(8割のif文にはelse句が必要)
※if句とelse句の条件が逆になっていないかも要チェック
 
```
#mysqli = mysqli_connect();
if ($link) {
	$result = mysqli_query($link, $query); // 正常ケース 
	if ($result) {
		$row = mysqli_fetch_array($result); // 正常ケース
	}
}

// 上記のif文ではエラーケースが考えられていない
// MySQLに接続できなかったらどうする！？
// クエリエラーだったらどうする！？
```

* 複雑な条件式は関数に置き換える

条件式が複雑
```
if (
	$inputStatus === 'completed' && $paidStatus === 'paid' && $end_at > date("Y-m-d H:i:s") {
	// 処理
} elseif (
	$inputStaus === 'completed' && $paidStatus === 'free') {
	// 処理
	}
)
```

条件式を関数化
```
if (hasSubscribed()) {
	// 処理
} elseif (isFree()) {
	// 処理
}
```

* 一般的なケースを最初に書こう
※条件式の順番は気をつける、全てのケースがカバーされていることも大事

```
if (isLetter($inputName)) {
	// 処理(よくあるケース)
} elseif (isAlphabet) {
	// 処理
} elseif (shortLength($inputName)) {
	// 処理
} elseif (longLength($inputName)) {
	// 処理(レアケース)
}
```

* switch文のポイント

<dl>
  <dt>switch文でcaseがたくさん登場する時はcaseの順番に気を付けよう</dt>
  <dt>順番</dt>
  <dd>正常ケースを先頭にする</dd>
  <dd>よく登場するものから書く</dd>
  <dd>アルファベット順や数値順に書く</dd>
  <dt>その他</dt>
  <dd>全てのケースを網羅しよう</dd>
  <dd>default句はその他扱いの時に指定しよう</dd>
  <dd>各ケースの処理は関数を使ったりして短くしよう</dd>
</dl>

<strong>繰り返し処理を使いこなそう</strong>
* 繰り返し処理は複数種類あるので、適切な繰り返し処理を選択しよう

<dl>
  <dt>foreach</dt>
  <dd>配列の全要素に繰り返し処理をしたい時</dd>
  <dt>for</dt>
  <dd>決まった回数だけ繰り返し処理をしたい時</dd>
  <dt>while</dt>
  <dd>繰り返し処理を何回したいかわからない時</dd>
<dl>

<dl>
   <dt>開始</dt>
   <dd>初期化コードはループの直前に置こう</dd>
   <dd>無限ループにはwhilw(true)を使おう</dd>
   <dt>本文</dt>
   <dd>ループのネストを少なくしよう(3段階まで)</dd>
   <dd>ループの前処理・後処理は先頭か末尾にまとめよう</dd>
   <dd>ループ1回に付き1つの機能を実行しよう</dd>
   <dd>ループは50行以内にしよう(できれば20行以内)</dd>
   <dt>終了</dt>
   <dd>全てのケースでループが終了することを確認しよう</dd>
   <dd>ループの開始時と終了時をシュミレーションしよう(紙に書くなど)</dd>
<dl>

* ループは内から外へ書くと混乱しにくい(考えるスコープが限定されるため)
1. 日本語で本文の処理を書く
2. コードに置き換える
3. ループで囲む
4. 変数の初期化等を追記する


<strong>制御構造のよくある問題を回避しよう</strong>
* ブール変数やブール関数を使ってシンプルにしよう

式が複雑
```
if ($user['status'] === 'completed' && $user['payment'] === 'paid']) {
	// 処理
} 
```

ブール変数で置き換え
```
$completed = $user['status'] === 'completed';
$paid = $user['payment'] === 'paid';
if ($completed && $paid)
```

```
ブール関数で置き換え
function hadPaid() {}
if (hasPaid()) {}
```

* 否定文は理解に時間がかかるので、肯定的な論理式にしよう

否定式
```
if (!valid()) {
	// 異常時の処理
} else {
	// 正常時の処理
}
```

肯定式
```
if (valid()) {
	// 正常時の処理
} else {
	// 異常時の処理
}
```
※二つの条件の否定の時はドモルガンの法則を使うと肯定で書ける

* 複雑な論理式は括弧を使うと意味が明確になる

複雑な論理式
```
if ($price > 50 === $price < 100) {}
```

括弧を使った論理式
```
if (($price > 50) === ($price < 100)) {}
```

* 数値を含む式は数直線上に並べると分かりやすくなる

```
MIN_NUM < $i && $i < MAX_NUM
```

```
$i < <MIN_NUM || MAX_NUM < $i
```

* 深いネストはコードが読みにくいので、2~3段階までにしよう

深いネスト
```
if (mb_strlen($inputName) > 3) {
	// 処理
	if (mb_strlen($inputAddress) > 3) {
		// 処理
		if (is_int($inputPhone)) {
			//処理
		}
	} 
}
```

ネストを浅く
```
if (mb_strlen($inputName) > 3) {
	// 処理
}

if (mb_strlen($inputName) > 3 && mb_strlen($inputAddress) > 3) {  // &&を用いてる
	// 処理
	if (is_int($inputPhone)) {
		// 処理
}
```
※&&で結ぶ、elsefiを用いるなど

* 制御構造が複雑だとプログラム全体が複雑になる

<dl>
  <dt>複雑さの判定</dt>
  <dd>初期値は1</dd>
  <dd>it, do, while, &&, || が登場するたびに1を足す</dd>
  <dd>caseの選択肢ごとに1を足す</dd>
  
  <dd>1~5個：処理は適切</dd>
  <dd>6~10個：単純にできないか検討</dd>
  <dd>11~個：処理の一部を関数に分割する</dd>
<dl>

# 参考サイト

[独学エンジニア](https://dokugaku-engineer.com/course/serverside "独学エンジニア")