PHPのgoto文について

採用はこちら

目次

PHPのgoto文とは

PHPのgoto文は、プログラム内の指定したラベルへ処理をジャンプさせるための制御構文です。

通常、PHPの処理はコードの上から下へ順番に実行されます。

しかし、gotoを使うと、途中の処理を飛ばして、指定した位置から処理を続けることができます。

PHPでは、goto文はPHP 5.3.0以降で使用できます。

基本構文

goto文は、次のように書きます。

goto ラベル名;

ラベル名:
    // ここから処理が続く

ラベル名の後ろにはコロン:を付けます。

たとえば、次のコードを見てください。

<?php

goto a;

echo 'Foo';

a:
echo 'Bar';

このコードでは、goto a;によって処理がa:の位置までジャンプします。

そのため、echo 'Foo';は実行されず、出力結果は次のようになります。

Bar

ラベルとは

gotoでジャンプする先を示す目印がラベルです。

end:

このように、ラベル名の後ろにコロンを付けて定義します。

次の例では、goto end;によってend:まで処理が飛びます。

<?php

goto end;

echo 'この行は実行されません';

end:
echo '終了';

出力結果は次のとおりです。

終了

ラベル名は大文字・小文字を区別する

PHPのgotoで使うラベル名は、大文字・小文字を区別します。

たとえば、end:End:は別のラベルとして扱われます。

<?php

goto end;

End:
echo 'End';

end:
echo 'end';

この場合、goto end;は小文字のend:へジャンプします。

動的なラベル指定はできない

gotoのジャンプ先は、ソースコード上に固定で書かれたラベル名でなければなりません。

そのため、次のように変数を使ってジャンプ先を指定することはできません。

<?php

$label = 'end';

goto $label; // 不可

end:
echo '終了';

PHPのgotoは、いわゆる「動的goto」や「computed goto」のような使い方には対応していません。

正しくは、次のようにラベル名を直接指定します。

<?php

goto end;

echo 'この行は実行されません';

end:
echo '終了';

PHPのgoto文でできること

PHPのgotoは、同じファイル内の指定したラベルへ処理を移動させる構文です。

ただし、どこへでも自由にジャンプできるわけではありません。

PHPのgotoには、コードの構造を壊さないための制限があります。

同じファイル内のラベルへジャンプできる

gotoでジャンプできるのは、基本的に同じファイル内にあるラベルです。

<?php

goto result;

echo '途中の処理';

result:
echo '結果表示';

このように、同じファイル内にあるラベルであれば、処理をジャンプできます。

同じ実行コンテキスト内で使う必要がある

PHPのgotoは、同じファイル内であれば何でも自由に飛べるわけではありません。

ジャンプ先は、同じ関数・メソッドなどの実行コンテキスト内にある必要があります。

たとえば、関数の外から関数の中へジャンプすることはできません。

<?php

goto inside;

function test()
{
    inside:
    echo '関数の中';
}

このコードは不可です。

反対に、関数の中から関数の外へジャンプすることもできません。

<?php

function test()
{
    goto outside;
}

outside:
echo '関数の外';

これも不可です。

gotoは、同じファイル内かつ同じ関数・メソッドなどの実行コンテキスト内で使う必要があります。

ループやswitchの外へ抜けることはできる

PHPのgotoは、ループやswitchの中から外へ抜ける用途には使えます。

たとえば、次のようにループの途中で外側のラベルへジャンプできます。

<?php

for ($i = 0; $i < 10; $i++) {
    if ($i === 5) {
        goto end;
    }

    echo $i;
}

end:
echo '終了';

このコードでは、$i5になった時点でend:へジャンプします。

つまり、ループの中から外へ抜ける動きになります。

多重ループから一気に抜けることもできる

gotoは、多重ループから一気に抜ける場合にも使えます。

<?php

for ($i = 0; $i < 10; $i++) {
    for ($j = 0; $j < 10; $j++) {
        if ($i === 3 && $j === 5) {
            goto end_loop;
        }

        echo "i={$i}, j={$j}\n";
    }
}

end_loop:
echo 'ループを抜けました';

このコードでは、$i === 3 && $j === 5になった時点で、内側のループだけでなく外側のループもまとめて抜けます。

ただし、この用途であれば、多くの場合はgotoよりもbreak 2;の方が自然です。

<?php

for ($i = 0; $i < 10; $i++) {
    for ($j = 0; $j < 10; $j++) {
        if ($i === 3 && $j === 5) {
            break 2;
        }

        echo "i={$i}, j={$j}\n";
    }
}

echo 'ループを抜けました';

break 2;は、2階層分のループやswitchから抜けるという意味です。

このように、多重ループを抜けるだけであれば、gotoを使わずにbreakで表現できます。

PHPのgoto文でできないこと

PHPのgotoは便利そうに見えますが、使い方には明確な制限があります。

特に重要なのは、関数・メソッドの境界をまたげないことと、ループやswitchの中へ飛び込めないことです。

関数やメソッドの外から中へ飛べない

次のように、関数の外から関数内のラベルへジャンプすることはできません。

<?php

goto inside;

function sample()
{
    inside:
    echo 'sample';
}

このコードは、関数の外から関数の中に入ろうとしているため不可です。

関数やメソッドの中から外へ飛べない

次のように、関数内から関数外のラベルへジャンプすることもできません。

<?php

function sample()
{
    goto outside;
}

outside:
echo 'outside';

これも不可です。

gotoは、関数やメソッドの境界をまたいでジャンプするための構文ではありません。

ループの外から中へ飛び込めない

PHPでは、ループの外からループの中にあるラベルへジャンプすることはできません。

<?php

goto loop;

for ($i = 0; $i < 10; $i++) {
    loop:
    echo $i;
}

これは不可です。

forwhileforeachなどのループ構造の外から、ループ内部へgotoで入ることはできません。

switchの外から中へ飛び込めない

switchについても同じです。

switchの外から、switchの内部にあるラベルへジャンプすることはできません。

<?php

goto case_label;

switch ($value) {
    case 1:
        case_label:
        echo 'case 1';
        break;
}

このような書き方は避けるべきです。

PHPのgotoは、ループやswitchの中へ飛び込む用途には使えません。

goto文と他の制御構文の違い

goto文は処理をジャンプさせる構文ですが、PHPには他にも処理の流れを制御する構文があります。

実務では、gotoよりもifbreakcontinuereturn、例外処理などを使う方が自然です。

gotoとbreakの違い

breakは、ループやswitchから抜けるための構文です。

<?php

for ($i = 0; $i < 10; $i++) {
    if ($i === 5) {
        break;
    }

    echo $i;
}

このコードでは、$i5になった時点でループを終了します。

一方、gotoは指定したラベルへジャンプします。

<?php

for ($i = 0; $i < 10; $i++) {
    if ($i === 5) {
        goto end;
    }

    echo $i;
}

end:
echo '終了';

どちらもループを抜けるような動きに見えますが、単純にループを終了したいだけであれば、breakを使う方が適切です。

gotoとcontinueの違い

continueは、現在のループ処理を途中でスキップし、次の繰り返しへ進む構文です。

<?php

for ($i = 0; $i < 5; $i++) {
    if ($i === 2) {
        continue;
    }

    echo $i;
}

出力結果は次のようになります。

0134

$i2のときだけ、echo $i;がスキップされます。

gotoでも似たような処理を作ることはできます。

<?php

for ($i = 0; $i < 5; $i++) {
    if ($i === 2) {
        goto skip;
    }

    echo $i;

    skip:
}

しかし、このような書き方は読みづらくなります。

ループ内で特定の処理をスキップしたい場合は、gotoではなくcontinueを使うべきです。

gotoとreturnの違い

returnは、関数やメソッドの処理を終了し、呼び出し元へ戻る構文です。

<?php

function checkAge(int $age): void
{
    if ($age < 20) {
        echo '未成年';
        return;
    }

    echo '成人';
}

このコードでは、$age < 20の場合、echo '未成年';を実行したあと、関数の処理を終了します。

一方、gotoは関数を終了する構文ではありません。

同じコンテキスト内にあるラベルへ処理を移動させるだけです。

<?php

function checkAge(int $age): void
{
    if ($age < 20) {
        goto minor;
    }

    echo '成人';
    return;

    minor:
    echo '未成年';
}

このようなコードも書けますが、実務ではreturnを使った方が読みやすくなります。

gotoと例外処理の違い

エラー処理のためにgotoを使うこともできます。

<?php

if (!file_exists($path)) {
    goto error;
}

$content = file_get_contents($path);

if ($content === false) {
    goto error;
}

echo $content;
goto end;

error:
echo 'ファイルを読み込めませんでした';

end:

しかし、エラーの種類や原因を扱うなら、throwtry-catchを使う方が適切です。

<?php

try {
    if (!file_exists($path)) {
        throw new RuntimeException('ファイルが存在しません');
    }

    $content = file_get_contents($path);

    if ($content === false) {
        throw new RuntimeException('ファイルを読み込めませんでした');
    }

    echo $content;
} catch (RuntimeException $e) {
    echo $e->getMessage();
}

例外処理を使えば、エラーの原因を分けて扱いやすくなり、処理の見通しもよくなります。

goto文の実務での扱い

PHPにはgoto文がありますが、実務では基本的に積極的には使いません。

理由は、処理の流れが追いにくくなり、コードの可読性や保守性が下がりやすいからです。

goto文が避けられる理由

gotoを使うと、処理がコード上の別の場所へ突然ジャンプします。

小さなコードであれば問題に見えないこともありますが、コード量が増えると、次のような問題が起きやすくなります。

<?php

goto start;

error:
echo 'エラー';
goto end;

start:
if (!isset($_GET['id'])) {
    goto error;
}

echo '処理中';

end:
echo '終了';

この程度であればまだ追えますが、実際のアプリケーションで処理が長くなると、「どの条件でどこへ飛ぶのか」「この処理は本当に実行されるのか」が分かりにくくなります。

その結果、次のような問題につながります。

  • 処理順を上から下へ読みづらくなる
  • デバッグしづらくなる
  • 条件分岐の流れを追いにくくなる
  • 修正時に副作用が出やすくなる
  • チーム開発で他の人が理解しづらくなる
  • テストすべき処理経路が分かりにくくなる

goto文よりも優先したい書き方

多くの場合、gotoを使わなくても、PHPの標準的な制御構文で十分に表現できます。

条件によって処理を分けたい場合は、ifelseifelseを使います。

<?php

if ($user === null) {
    echo 'ユーザーが存在しません';
} elseif (!$user->isActive()) {
    echo 'ユーザーが有効ではありません';
} else {
    echo 'ログインできます';
}

複数の値に応じて分岐したい場合は、switchmatchを使います。

<?php

$status = 'paid';

$message = match ($status) {
    'pending' => '支払い待ちです',
    'paid' => '支払い済みです',
    'cancelled' => 'キャンセル済みです',
    default => '不明なステータスです',
};

echo $message;

ループを抜けたい場合はbreakを使います。

<?php

foreach ($items as $item) {
    if ($item === null) {
        break;
    }

    echo $item;
}

ループの次の回へ進みたい場合はcontinueを使います。

<?php

foreach ($items as $item) {
    if ($item === null) {
        continue;
    }

    echo $item;
}

関数の処理を途中で終えたい場合はreturnを使います。

<?php

function showUser(?User $user): void
{
    if ($user === null) {
        echo 'ユーザーが存在しません';
        return;
    }

    echo $user->name;
}

エラー処理にはthrowtry-catchtry-finallyを使います。

<?php

try {
    if (!file_exists($path)) {
        throw new RuntimeException('ファイルが存在しません');
    }

    echo file_get_contents($path);
} catch (RuntimeException $e) {
    echo $e->getMessage();
}

共通の後処理にはtry-finallyを検討する

gotoは、共通の後処理へジャンプする用途で使われることがあります。

<?php

$fp = fopen('data.txt', 'r');

if (!$fp) {
    goto cleanup;
}

$data = fread($fp, 1024);

if ($data === false) {
    goto cleanup;
}

echo $data;

cleanup:
if (isset($fp) && $fp) {
    fclose($fp);
}

しかし、このような後処理にはtry-finallyを使う方が自然な場合が多いです。

<?php

$fp = fopen('data.txt', 'r');

try {
    if (!$fp) {
        throw new RuntimeException('ファイルを開けません');
    }

    $data = fread($fp, 1024);

    if ($data === false) {
        throw new RuntimeException('読み込みに失敗しました');
    }

    echo $data;
} finally {
    if ($fp) {
        fclose($fp);
    }
}

finallyを使うと、正常終了でもエラー発生時でも、最後に必ず実行したい処理を書きやすくなります。

goto文を使う場合の注意点

実務では基本的に避けたいgoto文ですが、どうしても使う場合は、コードの読みやすさを損なわないように注意する必要があります。

ジャンプ先は近くに置く

gotoのジャンプ先が遠すぎると、コードの流れが追いにくくなります。

使う場合でも、ジャンプ元とジャンプ先はできるだけ近い位置に置くべきです。

<?php

if ($hasError) {
    goto error;
}

echo '通常処理';

error:
echo 'エラー処理';

この例は単純ですが、実際のコードではラベルが遠くにあるほど保守しづらくなります。

後ろから前へ戻るgotoは避ける

次のように、後ろから前へ戻るgotoを書くと、疑似的なループを作れてしまいます。

<?php

$count = 0;

start:
echo $count;
$count++;

if ($count < 10) {
    goto start;
}

このような書き方は避けるべきです。

同じ処理は、for文で自然に書けます。

<?php

for ($count = 0; $count < 10; $count++) {
    echo $count;
}

ループ処理には、forwhileforeachを使う方が読みやすくなります。

ラベル名を分かりやすくする

gotoを使う場合は、ラベル名を分かりやすくすることも重要です。

悪い例です。

<?php

goto x;

x:
echo '終了';

これでは、x:が何を意味するのか分かりません。

次のように、目的が分かる名前にした方がよいです。

<?php

goto cleanup;

cleanup:
echo '後処理';

ラベル名には、errorcleanupend_loopfinishなど、目的が分かる名前を使うと読みやすくなります。

代替手段を先に検討する

gotoを使いたくなったときは、まず他の構文で書けないかを検討するべきです。

目安としては、次のように考えるとよいです。

条件分岐なら if / elseif / else
複数パターンの分岐なら switch / match
ループを抜けるなら break
多重ループを抜けるなら break 2 / break 3
次のループへ進むなら continue
関数を終了するなら return
エラー処理なら throw / try-catch
後処理なら try-finally
複雑な処理なら関数・メソッド分割

このような構文で自然に書ける場合、gotoを使う必要はほとんどありません。

goto文の良い例と悪い例

ここでは、gotoを使ったコードと、それをより読みやすく書き換えた例を紹介します。

悪い例

<?php

goto start;

error:
echo 'エラー';
goto end;

start:
if (!isset($_GET['id'])) {
    goto error;
}

echo '処理中';

end:
echo '終了';

このコードでは、処理がstart:error:end:へ飛び回ります。

短いコードなのでまだ読めますが、処理が長くなると流れを追いにくくなります。

改善例

同じ処理は、次のように書いた方が自然です。

<?php

if (!isset($_GET['id'])) {
    echo 'エラー';
    echo '終了';
    exit;
}

echo '処理中';
echo '終了';

さらに、関数に分けるとより見通しがよくなります。

<?php

function handleRequest(): void
{
    if (!isset($_GET['id'])) {
        echo 'エラー';
        return;
    }

    echo '処理中';
}

handleRequest();
echo '終了';

returnを使うことで、異常な条件を早めに処理し、通常処理を読みやすくできます。

限定的に使える可能性がある例

gotoは、深いネストから共通の終了処理へ移動するような場面で使われることがあります。

<?php

if (!$conditionA) {
    goto end;
}

if (!$conditionB) {
    goto end;
}

if (!$conditionC) {
    goto end;
}

echo 'すべての条件を満たしました';

end:
echo '処理終了';

ただし、この場合でも、関数化してreturnを使う方が自然です。

<?php

function process(bool $conditionA, bool $conditionB, bool $conditionC): void
{
    if (!$conditionA) {
        return;
    }

    if (!$conditionB) {
        return;
    }

    if (!$conditionC) {
        return;
    }

    echo 'すべての条件を満たしました';
}

process($conditionA, $conditionB, $conditionC);
echo '処理終了';

このように、gotoを使わなくても、早期リターンや関数分割で読みやすく書けることが多いです。

PHPのgoto文まとめ

PHPのgoto文は、指定したラベルへ処理をジャンプさせるための制御構文です。

PHP 5.3.0以降で使用でき、同じファイル内かつ同じ実行コンテキスト内にあるラベルへジャンプできます。

ただし、関数やメソッドの外へ出入りすることはできません。

また、ループやswitchの外へ抜けることはできますが、外から中へ飛び込むことはできません。

gotoは仕様として存在する構文ですが、実務では基本的に積極的には使いません。

理由は、処理の流れが追いにくくなり、コードの可読性・保守性・デバッグ性が下がりやすいからです。

実務では、まず次のような構文を検討するのがおすすめです。

if
elseif
else
switch
match
break
continue
return
throw
try-catch
try-finally
関数・メソッド分割

PHPのgotoは、「使えるが、できるだけ使わない方がよい構文」と考えるのが実務的です。

学習上は仕様として理解しておく価値がありますが、Webアプリケーション開発では、gotoよりも読みやすく保守しやすい書き方を優先するべきです。

以上、PHPのgoto文についてでした。

最後までお読みいただき、ありがとうございました。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次