Skip to main content

πŸ”€ 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:

flowchart TD A[Start] --> B{score >= 90?} B -->|Yes| C[grade = A] B -->|No| D{score >= 80?} D -->|Yes| E[grade = B] D -->|No| F{score >= 70?} F -->|Yes| G[grade = C] F -->|No| H{score >= 60?} H -->|Yes| I[grade = D] H -->|No| J[grade = F] C --> K[End] E --> K G --> K I --> K J --> K

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.

flowchart LR A[PHP Loops] --> B[while] A --> C[do-while] A --> D[for] A --> E[foreach] B --> F[Check condition, then run] C --> G[Run once, then check] D --> H[Known number of iterations] E --> I[Iterate arrays & objects]

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(), not process().
  • 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 match and foreach for their clarity and safety.

⚠️ Don't

  • Nest control structures more than 2–3 levels deep β€” refactor into functions.
  • Rely on global variables inside functions.
  • Pack ten parameters into one function β€” pass an array or object instead.
  • Forget break in a switch when 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:

  1. Write a function gradeFor(int $score): string that returns "A"–"F" using a match on ranges (hint: match(true)).
  2. Create an associative array of student names to scores.
  3. Loop over it with foreach, printing "Name: score β†’ grade".
  4. 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; match beats switch for value selection.
  • Four loops: while, do-while, for, and the array workhorse foreach; steer them with break/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_map and array_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.