コンテンツにスキップ

制御構文

はじめに

本サイトにつきまして、【IT技術の知見】はじめに - 俺の技術ノート をご認識のほど宜しくお願いいたします。


01. 条件式

if-elseif-else 、switch-case-break

曜日をバリデーションし、文字列を出力する。

▼ if-elseif-else

<?php
// 変数に Tue を格納
$weeks = "Tue";

// if文でTueに該当したら"火曜日"と表示する。
if ($weeks == "Mon") {
    echo "月曜日";
} elseif ($weeks == "Tue") {
    echo "火曜日";
} elseif ($weeks == "Wed") {
    echo "水曜日";
} elseif ($weeks == "Thu") {
    echo "木曜日";
} elseif ($weeks == "Fri") {
    echo "金曜日";
} elseif ($weeks == "Sat") {
    echo "土曜日";
} else {
    echo "日曜日";
}

// 実行結果
// 火曜日

▼ switch-case-break

定数ごとに処理が変わるときは、こちらのほうが読みやすい。

<?php

// 変数に Tue を格納
$weeks = "Tue";

// 条件分岐でTueに該当したら"火曜日"と表示する。breakでif文を抜けなければ、全て実行されてしまう。
switch ($weeks) {
    case "Mon":
        echo "月曜日";
        break;
    case "Tue":
        echo "火曜日";
        break;
    case "Wed":
        echo "水曜日";
        break;
    case "Thu":
        echo "木曜日";
        break;
    case "Fri":
        echo "金曜日";
        break;
    case "Sat":
        echo "土曜日";
        break;
    case "Sun":
        echo "日曜日";
        break;
    default:
        echo "曜日がありません";
}

// 実行結果
// 火曜日


if-else の回避方法

▼ if-else を使用した場合

可読性が悪いため、避けるべき。

<?php

class Example
{
    /**
     * マジックナンバー
     */
    const noOptionItem = 0;

    private Entity $routeEntity;

    public function example(array $result): array
    {

        // RouteEntityからoptionsオブジェクトに格納されるoptionオブジェクト配列を取り出す。
        if (!empty($this->routeEntity->options)) {
            foreach ($this->routeEntity->options as $option) {

                // if文を通過した場合、関数の返却値が格納される。
                // 通過しない場合、定数が格納される。
                if ($option->isOptionItemA()) {
                    $result["optionItemA"] = $option->optionItemA();
                } else {
                    $result["optionItemA"] = self::noOptionItem;
                }

                if ($option->isOptionItemB()) {
                    $result["optionItemB"] = $option->optionItemB();
                } else {
                    $result["optionItemB"] = self::noOptionItem;
                }

                if ($option->isOptionItemC()) {
                    $result["optionItemC"] = $option->optionItemC();
                } else {
                    $result["optionItemC"] = self::noOptionItem;
                }
            }
        }

        return $result;
    }
}

▼ 三項演算子を使用した場合

よりすっきりした書き方になる。

<?php

class Example
{
    /**
     * マジックナンバー
     */
    const noOptionItem = 0;

    private Entity $routeEntity;

    public function example(array $result): array
    {
        // RouteEntityからoptionsオブジェクトに格納されるoptionオブジェクト配列を取り出す。
        if (!empty($this->routeEntity->options)) {
            foreach ($this->routeEntity->options as $option) {

                // if文を通過した場合、関数の返却値が格納される。
                // 通過しない場合、定数が格納される。
                $result["optionItemA"] = ($option->isOptionItemA())
                  ? $option->optionItemA()
                  : self::noOptionItem;

                $result["optionItemB"] = ($option->isOptionItemB())
                  ? $option->optionItemB()
                  : self::noOptionItem;

                $result["optionItemC"] = ($option->isOptionItemC())
                  ? $option->optionItemC()
                  : self::noOptionItem;
            };
        }

        return $result;
    }
}

▼ 初期値と上書きのロジックを使用した場合

よりすっきりした書き方になる。

<?php

class Example
{
    /**
     * マジックナンバー
     */
    const noOptionItem = 0;

    private Entity $routeEntity;

    public function example(array $result): array
    {
        // 初期値0を設定
        $result["optionItemA"] = self::noOptionItem;
        $result["optionItemB"] = self::noOptionItem;
        $result["optionItemC"] = self::noOptionItem;

        // RouteEntityからoptionsオブジェクトに格納されるoptionオブジェクト配列を取り出す。
        if(!empty($this->routeEntity->options)) {
            foreach ($this->routeEntity->options as $option) {

                // if文を通過した場合、関数の返却値によって初期値0が上書きされる。
                // 通過しない場合、初期値0が使用される。
                if ($option->isOptionItemA()) {
                    $result["optionItemA"] = $option->optionItemA();
                }

                if ($option->isOptionItemB()) {
                    $result["optionItemB"] = $option->optionItemB();
                }

                if ($option->isOptionItemC()) {
                    $result["optionItemC"] = $option->optionItemC();
                }
            };
        }

        return $result;
    }
}


if-elseif-else の回避方法

▼ 決定表を使用した条件分岐の整理

うるう年であるかをバリデーションし、文字列を出力する。

以下の手順で設計と実装をする。

  1. 条件分岐の処理順序の概要を日本で記述する。

  2. 記述内容を、条件部と動作部に分解し、決定表で表す。

  3. 決定表を、流れ図で表す。

    決定表

▼ if-elseif-else は使用しない

可読性が悪いため、避けるべき。

<?php
// 西暦を格納する。
$year = N;
<?php

function leapYear(int $year): string
{
    // (5)
    if ($year <= 0) {
        throw new Exception("負の数はバリデーションできません。");

    // (4)
    } elseif ($year % 4 != 0) {
        return "平年";

    // (3)
    } elseif ($year % 100 != 0) {
        return "うるう年";

    // (2)
    } elseif ($year % 400 != 0) {
        return "平年";

    // (1)
    } else {
        return "うるう年";
    }
}

▼ if と return を使用した早期リターン

各 if 文で return を使用することで、if が入れ子状になることを防げる。

これを、早期リターンともいう。

<?php

// 西暦を格納する。
$year = N;

function leapYear(int $year): string
{
    // (5)
    if($year <= 0){
        throw new Exception("負の数はバリデーションできません。");
    }

    // (4)
    if($year % 4 != 0 ){
        return "平年";
    }

    // (3)
    if($year % 100 != 0){
        return "うるう年";
    }

    // (2)
    if($year % 400 != 0){
        return "平年";
    }

    // (1)
    return "うるう年";

}

▼ switch-case-break を使用した早期リターン

if 文の代わりに、switch-case-break によって、実装に、『◯◯の場合に切り換える』という意味合いを持たせられる。

ここでは、関数に実装することを想定して、break ではなく return を使用している。

<?php

function leapYear(int $year): string
{
    switch(true) {

    // (5)
    case($year <= 0):
        throw new Exception("負の数はバリデーションできません。");

    // (4)
        case($year % 4 != 0 ):
        return "平年";

    // (3)
    case($year % 100 != 0):
        return "うるう年";

    // (2)
    case($year % 400 != 0):
        return "平年";

    // (1)
    default:
        return "うるう年";
    }

}

▼ ガード節を使用した早期リターン

早期リターンの if 文の波括弧を省略した記法を、ガード節という。

<?php

function leapYear(int $year): string
{
    // (5)
    if($year <= 0) throw new Exception("負の数はバリデーションできません。");

    // (4)
    if($year % 4 != 0 ) return "平年";

    // (3)
    if($year % 100 != 0) return "うるう年";

    // (2)
    if($year % 400 != 0) return "平年";

    // (1)
    return "うるう年";

}


02. 反復対象のデータ

配列

▼ 走査 (スキャン)

配列内の要素を順に調べていくことを『走査 (スキャン) 』という。

例えば、foreach は、配列内のすべての要素を走査する処理である。

下図では、連想配列が表現されている。

配列の走査

▼ 内部ポインタと配列の関係

『内部ポインタ』とは、PHP の配列で、参照したい要素を位置で指定するためのカーソルのこと。

<?php

$array = ['あ', 'い', 'う'];

// 内部ポインタが現在指定している要素を出力。
echo current($array); // あ

// 内部ポインタを1つ進め、要素を出力。
echo next($array); // い

// 内部ポインタを1つ戻し、要素を出力。
echo prev($array); // あ

// 内部ポインタを最後まで進め、要素を出力。
echo end($array); // う

// 内部ポインタを最初まで戻し、要素を出力
echo reset($array); // あ


03. foreach

文法

▼ 基本

配列を走査する。

<?php

$array = [1, 2, 3, 4];
foreach ($array as &$value) {
    $value = $value * 2;
    echo $value;
}

▼ 制御文

流れ図_foreach文

<?php

$a = [1, -1, 2];
$sum = 0;

foreach ($a as $foo) {
    if ($foo > 0) {
        $sum += $foo;
    }
}


一次元配列の走査

▼ 配列関数

複雑な走査をするために、ビルトイン関数が用意されている。

▼ 配列の値へのアクセス

単に配列を作るのみでなく、要素にアクセスするためにも使われる。

<?php

class Foo
{
     /**
     * オプションを調べて、文字列を返却します。
     */
    public function printOption(array $options): string
    {
        $result = '何も設定されていません。';

        // $options配列には、OptionA,B,Cエンティティのいずれかが格納されていると想定
        foreach ($options as $option) {

            if ($option->name() == 'オプションA') {
                $result = 'オプションAが設定されています。';
            }

            if ($option->name() == 'オプションB') {
                $result = 'オプションBが設定されています。';
            }

            if ($option->name() == 'オプションC') {
                $result = 'オプションCが設定されています。';
            }

            return $result;
        }

        return $result;
    }
}

▼ 配列の各値を加算代入

<?php

// 以下の引数が渡されると想定
// ($K, $A) = (4, [1, 2, 3, 4, 1, 1, 3])

function iteration(int $K, array $A): void
{
    $topesNumber = 0;

    $currentLength = 0;

    for ($i = 0; $i < count($A); $i++) {

        // 前回の走査に今回のものを加算する。
        $currentLength += $A[$i];

        if ($currentLength >= $K) {
            // 加算代入
            $topesNumber++;

            // 長さを元に戻す。
            $currentLength = 0;
        }
    }
}

▼ 配列の値を固定

<?php

// 以下の引数が渡されると想定
// ($M, $A) = (6, [3, 4, 5, 5, 2])

function iteration(int $M, array $A): int {

    $count = 0;

    foreach ($A as $key => $value) {

        // vを固定して、以降のvと比較する。
        for ($i = $key; $i < count($A); $i++) {
            if($value <= $A[$i]){
                // 加算代入
                $count++;
            }
        }
    }

    return $count;
}


多次元配列の走査

▼ 二次元配列を一次元配列に変換

コールバック関数の使用が必要になる。

call_user_func_array() 関数の第一引数に、コールバック関数の array_merge() 関数の文字列を渡し、第二引数に 2 次元配列を渡す。

その結果、平坦になって 1 次元配列になる。

例えば、不要なインデックス (0) で入れ子になっている場合に役に立つ。

<?php

$twoDimension = [
  [
    'date'  => '2015/11/1',
    'score' => 100,
    'color' => 'red',
  ],
  [
    'date'  => '2015/11/2',
    'score' => 75,
    'color' => 'blue',
  ]
];

$oneDimension = call_user_func_array(
  'array_merge',
  // 二次元配列
  $twoDimension
);

▼ 多次元配列でキー名から値を取得

例えば、以下のような多次元配列があったとする。

配列の score キーから値を取り出し、1 次元配列を作成する。

<?php

$twoDimension = [
  [
    'date'  => '2015/11/1',
    'score' => 100,
    'color' => 'red',
  ],
  [
    'date'  => '2015/11/2',
    'score' => 75,
    'color' => 'blue',
  ]
];

// この配列のscoreキーから値を取り出し、一次元配列を作成する。
$oneDimension = array_column($twoDimension, 'score');

// Array
// (
//     [0] => 100
//     [1] => 75
// )


04. while

文法

▼ 基本

配列の走査を含む反復処理を実行する。

ただし、配列の走査は、while ではなく foreach を使用する。

また、for とは異なり、反復回数が決まっていない場合に使用する。

<?php

$count = 0;
// 反復回数が決まっていないため、満たせたらbreakで停止する。
while(true) {

    if($count == 10){
        break;
    }

    $count++;
}

echo $count;

▼ 制御文

流れ図_while文

<?php

$a = [1, -1, 2];
$sum = 0;

$i = 0;
while (true) {

    // 全て走査したら停止
    if($i == count($a)) {
        break;
    }

    $foo = $a[$i];

    if($foo > 0) {
        $sum += $foo;
    }

    // 代入加算
    $i ++;
}


無限ループ

▼ 無限ループとは

反復処理では、何らかの状態になったときは反復処理を終えなければならない。

しかし、終えられないと、無限ループが発生してしまう。


05. for

文法

▼ 基本

配列の走査を含む反復処理を実行する。

ただし、配列の走査は、for ではなく foreach を使用する。

また、while とは異なり、反復回数が決まっている場合に使用する。

<?php

// 10回反復することが決まっている。
for($i = 1; $i <= 10; $i++){
    $count++;
}

echo $count;

▼ 制御文

流れ図_for文

<?php

$a = [1, -1, 2];
$sum = 0;

for ($i = 0; $i < 2; $i++) {
    $foo = $a[$i];
    if ($foo > 0) {
        $sum += $foo;
    }
}

echo $sum;

// 1


06. 反復回数の変更

continue

▼ continue とは

反復処理の現在のループをスキップし、次のループを開始する。

<?php

$array = [1, 2, 3, 4, 5];

foreach ($array as $key => $value) {

    // キーが偶数の組をスキップする。
    if (!($key % 2 == 0)) {
        continue;
    }

    echo $value . 'は奇数です' . "\n";
}

// 1は奇数です
// 3は奇数です
// 5は奇数です

▼ array_walk を使用した代替法

反復処理のループを continue でスキップするときと同じ動作を、配列を処理する関数のコールバック関数の早期リターンで実現できる。

continue を使用するより、こちらのほうがよい。

<?php

$array = [1, 2, 3, 4, 5];

array_walk($array, function (int $value, int $key): void {

    // キーが偶数の組をスキップする。
    if (!($key % 2 == 0)) {
        return; // continueと同じ動作を早期リターンで実現する。
    }

    echo $value . 'は奇数です' . "\n";
});

// 1は奇数です
// 3は奇数です
// 5は奇数です


break

▼ break とは

反復処理の現在のループを停止し、以降のループも実行しない。

<?php

$array = [1, 2, 3, 4, '', 5];

foreach ($array as $value) {

    // 空の値で繰り返しを停止する。
    if (empty($value)) {
        break;
    }

    echo $value;
}