Putting it together: an expense report
A capstone that uses the whole course once: a class for the records, functions for the work, array functions for the questions, formatting for the report.
The shape of a small program
Eleven chapters of pieces, arranged into one file. The data is a class — a value that carries its own meaning — and the report is built by functions that each answer one question: what is the total, what is the total per category, which is the largest, what share is travel. Each function is short enough to check by reading, and none of them knows more than it needs to.
The one new piece of syntax is ($groups[$key] ?? 0): the null-coalescing operator, which means "the value if it is set, the fallback otherwise". It is the accumulator idiom from the arrays chapter, written without the isset dance around it.
The file reads in the order the program runs: the class first, then the functions that ask questions of a list of them, then the data, then the report. That order is not a rule of PHP — functions can be declared after they are used — but it is the order a reader needs, because each section can be understood from the ones above it.
Sorting, filtering and formatting
Array functions carry the report: array_filter picks the rows a question is about, array_reduce folds them to one number, usort with the spaceship puts the largest first, and array_column-style mapping turns objects back into lines. arsort is the one new name here: a sort by value, highest first, that keeps the keys — exactly what a per-category summary wants.
Formatting is a decision, not decoration. Money goes through number_format at the moment it is shown and never before, so arithmetic stays arithmetic and only the reader gets punctuation. printf with a width — %8s — is what makes a plain-text table line up in a fixed-width font.
Where a real application goes next
This report reads its rows from the file; a real one reads them from a form or a database, and the class becomes the place where that row is validated. The composer tool and the framework community — Laravel, Symfony — are the next two rooms, and both are the PHP you have just written, arranged in larger numbers.
The habit to keep from this file: values in classes, questions in functions, and the output built by formatting at the very end. Almost every confusing program is missing one of those three separations, and almost every clear one has all three.
The next step after this file is usually a test. The functions are easy to test because they take a list and return a value; the class is easy to test because its methods do one thing. That is not an accident of the capstone. Separating the data, the questions and the formatting is what makes each piece small enough to check without running the whole report. A report that prints while it computes is much harder to test, because the only way to see the answer is to read the output.
The class, and the questions asked of the list
<?php
declare(strict_types=1);
class Expense
{
private string $label;
private string $category;
private float $amount;
public function __construct(string $label, string $category, float $amount)
{
$this->label = $label;
$this->category = $category;
$this->amount = $amount;
}
public function label(): string { return $this->label; }
public function category(): string { return $this->category; }
public function amount(): float { return $this->amount; }
}
function total(array $expenses): float
{
return array_reduce($expenses, fn ($sum, $expense) => $sum + $expense->amount(), 0.0);
}
function byCategory(array $expenses): array
{
$groups = [];
foreach ($expenses as $expense) {
$key = $expense->category();
// ?? is "the value, or 0 when it is not there yet".
$groups[$key] = ($groups[$key] ?? 0) + $expense->amount();
}
arsort($groups); // highest first, keys kept
return $groups;
}The report, in one file
Run it and read the three sections. Then add a fourth section: the average expense, and a line listing every expense above that average, joined with implode.
Notice what the report does not do: no function prints while computing, and nothing is formatted until the last lines. Change the currency and only the formatting changes; change the data source and only the class changes.
One report, several questions
The capstone works because the data and the questions are separate. An Expense object knows its own fields and nothing about totals; a function takes a list of them and returns a number or a new list. That means the same expenses can feed a category summary, an average, or a budget check without any of those questions knowing about the others.
Formatting is the last step, not a property of the data. number_format() returns a string, and strings cannot be added without PHP converting them back — so the arithmetic stays in floats or integer cents, and only the printed line is formatted. ?? is the accumulator’s shorthand: it supplies a starting value when the key is missing or null, without an isset() around every addition.
Three reporting mistakes spoil otherwise good code: formatting money before summing it, sorting the only copy of a list and then needing the original order, and treating a missing budget as zero without deciding whether that is a real zero or a typo. Keep numbers as numbers, copy before an in-place sort, and make the missing case explicit.
Try these next
- Add a budget for each category and list the categories that are over it.
- Compute the average expense and print every expense above that average.
- Show the largest expense in each category without writing one loop per category.
A budget check
Run the budget, then add a `fun` expense that pushes the category over. Change one budget so a second category joins the over-budget line.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
How does the capstone keep the report readable?
