php language basics
Control Flow
Control flow is how PHP decides which lines run, how many times they run, and when the script should stop doing one thing and move to another. Without control flow, a PHP file would only run from top to bottom once.
You use control flow whenever you validate input, choose a shipping rule, loop over orders, skip invalid rows, stop after finding a match, or show different output for different states. It is one of the first places bugs appear because a condition that looks small can change the whole path through the program.
if, elseif, And else
Use if when code should run only when a condition is true. Use elseif for additional branches, and else for the fallback.
<?php
$orderStatus = 'paid';
if ($orderStatus === 'paid') {
echo "Ship the order\n";
} elseif ($orderStatus === 'pending') {
echo "Wait for payment\n";
} else {
echo "Ask support to review\n";
}
// Prints:
// Ship the order
Each branch should represent a real rule. If the words in the condition are unclear, name the condition before the if.
<?php
$totalCents = 7200;
$country = 'GB';
$qualifiesForFreeShipping = $totalCents >= 5000 && $country === 'GB';
if ($qualifiesForFreeShipping) {
echo "Free shipping\n";
}
// Prints:
// Free shipping
The named variable makes the business rule easier to review than a long condition hidden inside the if.
Guard Clauses
A guard clause handles a case early so the main path stays clear. This is common in validation and permission checks.
<?php
$quantity = 0;
if ($quantity <= 0) {
echo "Quantity must be at least 1\n";
return;
}
echo "Quantity accepted\n";
// Prints:
// Quantity must be at least 1
The return stops the rest of the current file or function from running. Later, inside functions, guard clauses become one of the cleanest ways to reject invalid input before doing useful work.
match For Exact Choices
match is useful when one expression maps to one result. It uses strict comparison and returns a value.
<?php
$paymentStatus = 'failed';
$message = match ($paymentStatus) {
'paid' => 'Ship the order',
'pending' => 'Wait for payment',
'failed' => 'Ask the customer to try again',
default => 'Ask support to review',
};
echo $message . "\n";
// Prints:
// Ask the customer to try again
Use match for clear exact cases. Use if when each branch needs several checks or several statements.
Looping With foreach
foreach is the loop you will use most often with arrays. It runs once for each item.
<?php
$orders = [
['id' => 101, 'status' => 'paid'],
['id' => 102, 'status' => 'pending'],
['id' => 103, 'status' => 'paid'],
];
foreach ($orders as $order) {
if ($order['status'] !== 'paid') {
continue;
}
echo "Ship order {$order['id']}\n";
}
// Prints:
// Ship order 101
// Ship order 103
continue skips the rest of the current loop item and moves to the next one. Here it skips orders that are not paid.
break Stops A Loop
Use break when the loop has found what it needs and should stop.
<?php
$emails = [
'bad-address',
'admin@example.com',
'support@example.com',
];
$firstValidEmail = null;
foreach ($emails as $email) {
if (!str_contains($email, '@')) {
continue;
}
$firstValidEmail = $email;
break;
}
echo $firstValidEmail . "\n";
// Prints:
// admin@example.com
break is useful for searches. Do not keep looping once the answer is already known.
while And for
Use while when a loop should continue while a condition remains true. Use for when you have a simple counter.
<?php
$attemptsRemaining = 3;
while ($attemptsRemaining > 0) {
echo "Trying request, attempts left: {$attemptsRemaining}\n";
$attemptsRemaining--;
}
// Prints:
// Trying request, attempts left: 3
// Trying request, attempts left: 2
// Trying request, attempts left: 1
Be careful with while: the condition must eventually become false. Otherwise, the loop can run forever.
<?php
for ($page = 1; $page <= 3; $page++) {
echo "Fetch page {$page}\n";
}
// Prints:
// Fetch page 1
// Fetch page 2
// Fetch page 3
for is good for counters, but foreach is usually clearer when looping over arrays.
Common Mistakes
The most common mistake is writing the condition backwards. Read conditions out loud: "if status is not paid, continue" is easier to check than guessing from symbols alone.
Another common mistake is forgetting the fallback branch. If a status can be paid, pending, failed, or unknown, the code should say what happens when the value is unknown.
Be careful with loops that change the array they are looping over. That can be useful later, but for beginner code it is usually clearer to build a new array or print the result directly.
What You Should Be Able To Do
After this lesson, you should be able to:
- choose between
if,elseif,else, andmatch; - write a guard clause for invalid input;
- loop over arrays with
foreach; - use
continueto skip one item; - use
breakto stop after finding a result; - use
whileandforfor simple repeated work; - explain which branch or loop iteration will run before executing the code.
Practice
Task: Ship Paid Orders
Task
Write a PHP script that loops over orders and prints only the orders that are ready to ship.
Use this starting data:
<?php
$orders = [
['id' => 501, 'status' => 'paid'],
['id' => 502, 'status' => 'pending'],
['id' => 503, 'status' => 'paid'],
];
Your script should:
- loop over every order with
foreach; - skip orders where
statusis notpaid; - print
Ship order 501andShip order 503.
Hints
- Use
!==to check for orders that are not paid. - Use
continueto skip the current order. - Keep the output inside the loop.
Show solution
Solution
<?php
$orders = [
['id' => 501, 'status' => 'paid'],
['id' => 502, 'status' => 'pending'],
['id' => 503, 'status' => 'paid'],
];
foreach ($orders as $order) {
if ($order['status'] !== 'paid') {
continue;
}
echo "Ship order {$order['id']}\n";
}
// Prints:
// Ship order 501
// Ship order 503
Explanation
The foreach loop visits every order. The if condition catches orders that are not paid, and continue skips them. Only paid orders reach the echo line.
Task: Predict Loop Output
Task
Before running this code, predict exactly what it prints.
<?php
$codes = ['draft', 'paid', 'cancelled', 'paid'];
foreach ($codes as $code) {
if ($code === 'draft') {
continue;
}
if ($code === 'cancelled') {
break;
}
echo "Process {$code}\n";
}
Then run the code and compare the real output with your prediction.
Hints
continueskips only the current loop item.breakstops the whole loop.- The second
paidvalue is aftercancelled.
Show solution
Solution
<?php
$codes = ['draft', 'paid', 'cancelled', 'paid'];
foreach ($codes as $code) {
if ($code === 'draft') {
continue;
}
if ($code === 'cancelled') {
break;
}
echo "Process {$code}\n";
}
// Prints:
// Process paid
Explanation
The first value is draft, so continue skips it. The next value is paid, so it is printed. The next value is cancelled, so break stops the loop before the final paid value is reached.
Task: Fix Shipping Branch
Task
Fix this shipping message script. It currently checks the broad condition first, so express orders never reach the express branch.
<?php
$shippingMethod = 'express';
if ($shippingMethod !== '') {
echo "Standard shipping\n";
} elseif ($shippingMethod === 'express') {
echo "Express shipping\n";
} else {
echo "Choose a shipping method\n";
}
Rewrite the branches so:
expressprintsExpress shipping;standardprintsStandard shipping;- an empty string prints
Choose a shipping method; - any other value prints
Unknown shipping method.
Hints
- Put the most specific checks before broader checks.
- Use
elseiffor the second and third known paths. - Keep an
elsebranch for unexpected values.
Show solution
Solution
<?php
$shippingMethod = 'express';
if ($shippingMethod === 'express') {
echo "Express shipping\n";
} elseif ($shippingMethod === 'standard') {
echo "Standard shipping\n";
} elseif ($shippingMethod === '') {
echo "Choose a shipping method\n";
} else {
echo "Unknown shipping method\n";
}
// Prints:
// Express shipping
Explanation
The original code checked $shippingMethod !== '' first. That condition is true for both express and standard, so the more specific express branch could never run.
The fixed version checks exact known values first, then handles the empty value, then keeps a final fallback for unexpected input.