π PHP Control Structures and Functions
Variables let a program hold data; control structures let it make decisions and repeat work, and functions let it reuse logic. Together they turn a flat list of statements into a real, dynamic application. This lesson covers PHP's conditionals, loops, and function toolkit with modern, idiomatic code.
π― Learning Objectives
By the end of this lesson, you will be able to:
- Branch execution with if / elseif / else, the ternary and null-coalescing operators
- Choose between switch and the modern match expression
- Repeat work with while, do-while, for, and foreach, and control loops with
break/continue - Define functions with typed parameters, default values, and return types
- Explain variable scope and use closures and arrow functions
Estimated Time: 35β45 minutes β’ Difficulty: BeginnerβIntermediate
Hands-on: Write a reusable gradeFor() function and a small loop-driven report.
In This Lesson
Why Control Flow Matters
Control structures are the decision-makers and traffic directors of a program. They decide which statements run, when they run, and how many times they run.
π‘ Analogy: Control structures are the traffic signals of your code. Without them, every line runs once, top to bottom, no matter what. With them, your program can react β showing one page to a logged-in user and another to a guest, or looping over a thousand products to build a page.
Functions then let you take a useful chunk of that logic, give it a name, and call it from anywhere β the foundation of code you can maintain instead of copy-paste.
Conditional Statements
Conditionals run a block only when a condition is true. They are the forks in the road of your program.
if / elseif / else
<?php
$score = 85;
if ($score >= 90) {
$grade = "A";
} elseif ($score >= 80) {
$grade = "B";
} elseif ($score >= 70) {
$grade = "C";
} elseif ($score >= 60) {
$grade = "D";
} else {
$grade = "F";
}
echo "Your grade is {$grade}"; // Your grade is B
PHP evaluates each condition in order and stops at the first one that is true. The diagram below traces that grading logic:
Ternary & null coalescing
For a simple two-way choice, the ternary operator is compact and readable:
<?php
$age = 20;
$status = ($age >= 18) ? "adult" : "minor";
// The "Elvis" shorthand returns the left side if it is truthy:
$name = $input ?: "Anonymous";
// Null coalescing supplies a default for missing/undefined values:
$username = $_GET['user'] ?? "Guest";
π Truthiness
In a boolean context, PHP treats these as false: false, 0, 0.0, "", "0", [] (empty array), and null. Everything else is true. Knowing this makes conditions like if ($items) read naturally as "if there are any items".
switch vs match
When you compare one value against many possibilities, switch is cleaner than a long if chain:
<?php
$day = date("l"); // e.g. "Monday"
switch ($day) {
case "Saturday":
case "Sunday":
$message = "Weekend!";
break;
case "Friday":
$message = "Almost there.";
break;
default:
$message = "A working day.";
break;
}
Each case needs a break, or execution "falls through" to the next case β occasionally useful (as with Saturday/Sunday above) but a frequent source of bugs.
The modern match expression (PHP 8+)
match fixes switch's rough edges: it uses strict comparison (===), needs no break, and returns a value you can assign directly.
<?php
$day = date("l");
$message = match ($day) {
"Saturday", "Sunday" => "Weekend!",
"Friday" => "Almost there.",
default => "A working day.",
};
echo $message;
β
Prefer match for value selection
When each branch simply produces a value, reach for match. It's shorter, safer (no accidental fall-through, no loose-comparison surprises), and throws an error if no arm matches and there's no default β catching gaps early. Keep switch for branches that run multi-line side effects.
Looping Structures
Loops execute a block repeatedly β like an assembly line processing items until the job is done. PHP offers four.
while and do-while
<?php
// while: checks BEFORE each pass β may run zero times
$counter = 1;
while ($counter <= 3) {
echo "Pass {$counter}\n";
$counter++;
}
// do-while: checks AFTER each pass β always runs at least once
$n = 10;
do {
echo "n is {$n}\n";
$n++;
} while ($n < 3); // runs once even though 10 < 3 is false
for
Use for when you know the number of iterations up front. Its header bundles the initializer, condition, and step:
<?php
for ($i = 1; $i <= 5; $i++) {
echo "Iteration {$i}\n";
}
foreach β the workhorse
foreach is purpose-built for arrays and is the loop you'll use most in web code:
<?php
$fruits = ["apple", "banana", "cherry"];
foreach ($fruits as $fruit) {
echo "I like {$fruit}\n";
}
// Access keys and values of an associative array:
$person = ["name" => "Ada", "role" => "Engineer"];
foreach ($person as $key => $value) {
echo "{$key}: {$value}\n";
}
break and continue
<?php
foreach ($orders as $order) {
if ($order['status'] === 'cancelled') {
continue; // skip this one, keep looping
}
if ($order['total'] > 10000) {
echo "Big order found!\n";
break; // stop the loop entirely
}
processOrder($order);
}
continue jumps to the next iteration; break exits the loop completely.
Defining Functions
A function is a named, reusable block of code. Think of functions as labelled tools in a workshop β each does one job well, so you don't rebuild it every time.
Parameters, defaults, and return types
Modern PHP lets you annotate parameter and return types, which document intent and catch mistakes early:
<?php
function calculateArea(float $length, float $width): float {
return $length * $width;
}
echo calculateArea(5, 3); // 15
// A default value makes a parameter optional:
function greet(string $name, string $time = "day"): string {
return "Good {$time}, {$name}!";
}
echo greet("Alice"); // Good day, Alice!
echo greet("Bob", "evening"); // Good evening, Bob!
π Named arguments (PHP 8+)
You can pass arguments by name, in any order β handy when a function has several optional parameters:
<?php
echo greet(name: "Cleo", time: "morning");
Returning early
A function ends as soon as it hits a return. Returning early for the "bad" cases keeps the happy path flat and readable:
<?php
function priceWithTax(?float $price): ?float {
if ($price === null || $price < 0) {
return null; // guard clause
}
return round($price * 1.08, 2);
}
The ?float type means "a float or null" β a nullable type, useful for values that might be absent.
Scope, Closures & Arrow Functions
Variables created inside a function are local to it β invisible outside, and vice versa. This isolation is a feature: it stops functions from stepping on each other's data.
<?php
$message = "outside";
function show(): void {
// echo $message; // ERROR: $message is not visible here
$message = "inside";
echo $message; // "inside"
}
show();
echo $message; // still "outside"
β οΈ Avoid the global keyword
PHP lets you pull in outer variables with global $x;, but this creates hidden dependencies that make code hard to test and reason about. Pass what a function needs as parameters instead.
Closures
An anonymous function (closure) has no name and can be stored in a variable or passed to other functions. Use use to capture outer variables explicitly:
<?php
$tax = 0.08;
$addTax = function (float $amount) use ($tax): float {
return $amount + $amount * $tax;
};
echo $addTax(100); // 108
Arrow functions (PHP 7.4+)
Arrow functions are a terse form for one-expression closures. They capture outer variables automatically β no use needed β which shines with array helpers:
<?php
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(fn($n) => $n * 2, $numbers);
// [2, 4, 6, 8, 10]
$evens = array_filter($numbers, fn($n) => $n % 2 === 0);
// [2, 4]
π‘ Callbacks are everywhere
Functions like array_map, array_filter, and usort take a function as an argument. Passing behaviour around like data is a core skill you'll use in every PHP codebase.
Best Practices
β Do
- Give functions descriptive names β
calculateTotalWithTax(), notprocess(). - Keep each function to a single responsibility; split when it grows.
- Use type declarations on parameters and returns.
- Use guard clauses / early returns to avoid deep nesting.
- Prefer
matchandforeachfor their clarity and safety.
β οΈ Don't
- Nest control structures more than 2β3 levels deep β refactor into functions.
- Rely on
globalvariables inside functions. - Pack ten parameters into one function β pass an array or object instead.
- Forget
breakin aswitchwhen fall-through isn't intended.
Compare the two versions below β the second reads top to bottom with no pyramid of braces:
<?php
// Deeply nested β hard to follow
function processOrder(array $order): bool {
if ($order) {
if ($order['status'] === 'pending') {
if ($order['paid']) {
return true;
}
}
}
return false;
}
// Guard clauses β flat and clear
function processOrder(array $order): bool {
if (!$order) return false;
if ($order['status'] !== 'pending') return false;
if (!$order['paid']) return false;
return true;
}
Hands-on Exercise
ποΈ A Grade Report Generator
Objective: Combine a function, a match expression, and a foreach loop.
Instructions:
- Write a function
gradeFor(int $score): stringthat returns "A"β"F" using amatchon ranges (hint:match(true)). - Create an associative array of student names to scores.
- Loop over it with
foreach, printing "Name: score β grade". - Track and print the class average.
π‘ Hint
match(true) lets each arm be a boolean condition: $score >= 90 => "A". Accumulate a running total inside the loop, then divide by count($students) after it. Use number_format() for a tidy average.
β Example solution
<?php
function gradeFor(int $score): string {
return match (true) {
$score >= 90 => "A",
$score >= 80 => "B",
$score >= 70 => "C",
$score >= 60 => "D",
default => "F",
};
}
$students = [
"Ada" => 91,
"Bpel" => 74,
"Cleo" => 58,
"Devi" => 83,
];
$total = 0;
foreach ($students as $name => $score) {
$total += $score;
echo "{$name}: {$score} β " . gradeFor($score) . "\n";
}
$average = $total / count($students);
echo "Class average: " . number_format($average, 1) . "\n";
/*
Ada: 91 β A
Bpel: 74 β C
Cleo: 58 β F
Devi: 83 β B
Class average: 76.5
*/
π― Quick Quiz
Question 1: Which loop is guaranteed to run its body at least once?
Question 2: How does match compare its subject to each arm?
Question 3: Inside a loop, what does continue do?
Summary & Quiz
π Key Takeaways
if/elseif/else, the ternary, and??handle branching;matchbeatsswitchfor value selection.- Four loops:
while,do-while,for, and the array workhorseforeach; steer them withbreak/continue. - Functions with typed parameters, defaults, and return types make reusable, self-documenting code.
- Variables are local by default; pass data in rather than reaching for
global. - Closures and arrow functions pass behaviour to helpers like
array_mapandarray_filter.
π Further Reading
π What's Next?
You can now hold data, make decisions, and package logic. Next, Embedding PHP in HTML shows how to weave all of this directly into web pages to produce dynamic output.
π Nicely done!
Conditionals, loops, and functions are the muscle of every PHP program. You'll use them on every page from here on.