goto — when it's actually used (and why it's almost always wrong)
Concept
goto jumps execution to a labeled statement elsewhere in the same function or file. PHP allows forward and backward jumps within the same scope — but not into a function, a loop, or a switch from outside.
The case for never using goto: Almost every use of goto can be replaced by a function with an early return, a break with a numeric argument, or a restructured loop. goto makes control flow non-linear and hard to follow — static analysis tools, debuggers, and code reviewers all struggle with it.
The legitimate use case (rare): Breaking out of deeply nested loops when the logic cannot be extracted into a function (e.g., tightly coupled to many local variables). Even here, break 2 or refactoring is preferred.
Where PHP actually uses goto-like jumps internally: PHP's VM uses JMP instructions (essentially gotos) in compiled opcodes. When the JIT compiler analyzes code, it maps these jumps back to control flow graphs. So goto at the PHP level maps directly to opcodes — there is no indirection overhead. That said, this is not a reason to use it.
Historical context: goto was added in PHP 5.3, partly for generated code (e.g., parser generators, code emitting tools) where goto maps cleanly to state machine transitions. In hand-written application code, it has no place.
Code Example
<?php
declare(strict_types=1);
// Basic goto — forward jump
echo "Step 1\n";
goto step3;
echo "Step 2\n"; // NEVER RUNS
step3:
echo "Step 3\n";
// Output: "Step 1", "Step 3"
// Simulating break out of nested structure (one real use case)
$matrix = [[1,2,3],[4,5,6],[7,8,9]];
foreach ($matrix as $row) {
foreach ($row as $val) {
if ($val === 5) {
goto found;
}
}
}
found:
echo "Found 5 (or exhausted matrix)\n";
// The clean alternative — extract into a function
function findValue(array $matrix, int $target): bool
{
foreach ($matrix as $row) {
foreach ($row as $val) {
if ($val === $target) return true;
}
}
return false;
}
// Another goto alternative: break 2
foreach ($matrix as $row) {
foreach ($row as $val) {
if ($val === 5) break 2;
}
}
// Goto CANNOT jump:
// - into a function
// - into a loop
// - into a switch
// PHP will throw a fatal error for these
// Generated code example (acceptable use)
// State machine output from a parser generator:
// state_0:
// if (condition_a) goto state_2;
// goto state_1;
// state_1: ...
// state_2: ...