What you'll learn
Functions turn working statements into reusable, testable units. You'll design typed contracts, understand scope and callables, then finish Phase 1 with a complete expense-summary program.
- Define focused functions with inputs and outputs
- Use default, named, variadic, nullable, and union-typed arguments
- Understand local scope and reference semantics
- Pass closures and arrow functions as behavior
- Decompose a mini project into a small pipeline
Define one clear responsibility
A function has a name, parameter list, optional return type, and body. A strong function performs one cohesive job and either returns a useful result or produces a deliberate side effect.
<?php
declare(strict_types=1);
function calculateTax(float $subtotal, float $rate = 0.11): float
{
if ($subtotal < 0 || $rate < 0) {
throw new InvalidArgumentException('Values cannot be negative.');
}
return $subtotal * $rate;
}
echo number_format(calculateTax(80), 2);Key idea
declare(strict_types=1) prevents scalar arguments from being silently coerced in calls made from that file.Flexible arguments without vague contracts
<?php
declare(strict_types=1);
function formatPrice(
float $amount,
string $currency = 'USD',
?string $label = null,
): string {
$price = $currency . ' ' . number_format($amount, 2);
return $label === null ? $price : "{$label}: {$price}";
}
function average(float ...$values): float
{
return array_sum($values) / count($values);
}
echo formatPrice(amount: 24.5, label: 'Total') . PHP_EOL;
echo average(8, 9.5, 10);| Feature | Purpose |
|---|---|
$currency = 'USD' | Optional parameter with a default |
amount: 24.5 | Named argument for a readable call |
?string | A string or null |
int|float | One of several declared types |
float ...$values | Zero or more arguments collected into an array |
Scope, values, and references
Variables created inside a function are local. Pass the information a function needs and return its result; reaching into global state makes testing and reuse harder.
<?php
$taxRate = 0.11;
function priceWithTax(float $price, float $rate): float
{
$tax = $price * $rate; // local
return $price + $tax;
}
echo priceWithTax(50, $taxRate);References are a special tool
&$value allows mutation of the caller's variable, but it hides an output. Prefer returning the new value unless in-place mutation is central to the API.Closures and arrow functions
<?php
$minimum = 10;
$isLarge = function (float $amount) use ($minimum): bool {
return $amount >= $minimum;
};
$prices = [5, 12, 18];
$large = array_filter($prices, $isLarge);
$withTax = array_map(fn ($price) => $price * 1.11, $large);
echo implode(', ', $withTax);A closure can capture outer values explicitly with use. An arrow function captures used outer values automatically by value and is ideal for one expression.
Phase project: expense summary
This program forms a small pipeline: validate the collection, calculate the overall total, group values by category, sort, and present the report. Each stage has a focused function.
<?php
declare(strict_types=1);
function validExpenses(array $expenses): array
{
return array_filter(
$expenses,
fn (array $expense): bool =>
isset($expense['category'], $expense['amount'])
&& is_numeric($expense['amount'])
&& (float) $expense['amount'] >= 0
);
}
function totalExpenses(array $expenses): float
{
return array_reduce(
$expenses,
fn (float $total, array $expense): float =>
$total + (float) $expense['amount'],
0.0
);
}
function totalsByCategory(array $expenses): array
{
$totals = [];
foreach ($expenses as $expense) {
$category = (string) $expense['category'];
$totals[$category] = ($totals[$category] ?? 0.0)
+ (float) $expense['amount'];
}
arsort($totals);
return $totals;
}
$expenses = validExpenses([
['category' => 'Food', 'amount' => 18.50],
['category' => 'Books', 'amount' => 32.00],
['category' => 'Food', 'amount' => 11.25],
['category' => 'Travel', 'amount' => 8.75],
]);
echo 'Total: $' . number_format(totalExpenses($expenses), 2) . PHP_EOL;
foreach (totalsByCategory($expenses) as $category => $amount) {
echo $category . ': $' . number_format($amount, 2) . PHP_EOL;
}Review the project like a professional
- Correctness: try empty input, zero amounts, decimals, invalid rows, and repeated categories
- Contracts: every function says what it accepts and returns
- Separation: validation, calculation, grouping, and presentation are distinct
- Readability: names communicate domain meaning; no unexplained temporary values
- Extension: add a date field and report only one month without rewriting every function
Stretch challenge
$argv[1]. Reject a missing file cleanly, decode the JSON, then reuse the same calculation functions unchanged.Recap & Phase 1 check
Key takeaways
- Small, cohesive functions make code reusable and testable.
- Parameter and return types turn assumptions into checked boundaries.
- Pass dependencies as arguments and return results instead of relying on globals or hidden reference mutation.
- Closures and arrow functions let array operations receive behavior.
- A clean program is a pipeline of validated data, domain calculation, and presentation.
Quick check
1. What does strict_types change for scalar arguments?
2. How does an arrow function capture used outer variables?
3. Which design is easiest to test?
4. What is the first stage of the expense project pipeline?