php language basics
Project: Multi-File Price Report
A multi-file script is the next step after writing everything in one file. Real PHP projects split data, reusable functions, configuration, and entry-point scripts so each file has a clear job.
This mini-project builds a small price report from three files:
data.phpreturns product data.functions.phpcontains reusable helpers.report.phploads the other files and prints the report.
The aim is not to create a framework. The aim is to practise file boundaries, require_once, returned arrays, small functions, and terminal output in a structure a junior developer would recognise in real work.
Give each file one responsibility
Start with the data file. A data file can return an array directly.
<?php
declare(strict_types=1);
return [
['name' => 'Notebook', 'price' => 499],
['name' => 'Pen', 'price' => 129],
['name' => 'Desk pad', 'price' => 1299],
];
This file does not print anything. It only returns data to the script that requires it.
Put helper functions in their own file
Formatting and calculation rules belong in a helper file when they are reused or when the entry script is becoming cluttered.
<?php
declare(strict_types=1);
function formatMoney(int $pennies): string
{
return 'GBP ' . number_format($pennies / 100, 2);
}
function reportLine(array $product): string
{
return $product['name'] . ': ' . formatMoney($product['price']);
}
These functions still use plain arrays because this track has not moved into object-oriented design yet. The important point is that the names describe the rules clearly.
Load files from the entry script
The entry script is the file you run from the terminal. It loads the functions, loads the data, and coordinates the output.
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
$products = require __DIR__ . '/data.php';
foreach ($products as $product) {
echo reportLine($product) . PHP_EOL;
}
// Prints:
// Notebook: GBP 4.99
// Pen: GBP 1.29
// Desk pad: GBP 12.99
Use __DIR__ so the include path is based on the file location, not the directory the command was run from. That prevents confusing path bugs.
Use require_once for functions
Function files should normally be loaded with require_once. If the same function file is required twice with require, PHP tries to declare the functions twice and the script fails.
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
require_once __DIR__ . '/functions.php';
echo formatMoney(499);
// Prints:
// GBP 4.99
require_once makes repeated loading safe. It does not mean you should require files randomly; it means shared helper files are protected from accidental double loading.
Use require when you need returned data
A file required by PHP can return a value. This is common for simple configuration and small data files.
<?php
declare(strict_types=1);
$settings = require __DIR__ . '/settings.php';
echo $settings['currency'];
If settings.php returns ['currency' => 'GBP'], the required value is assigned to $settings.
Validate loaded data before reporting
Once data crosses a file boundary, check it before using it. A missing product name or negative price should not quietly appear in a report.
<?php
declare(strict_types=1);
function validateProduct(array $product): void
{
if (trim($product['name'] ?? '') === '') {
throw new InvalidArgumentException('Product name is required.');
}
if (($product['price'] ?? -1) < 0) {
throw new InvalidArgumentException('Product price cannot be negative.');
}
}
validateProduct(['name' => 'Notebook', 'price' => 499]);
echo 'Product accepted';
// Prints:
// Product accepted
This keeps the report honest. Bad data is caught near the place where the report is built.
Keep the entry script readable
A useful entry script is short and direct. It should show the flow without hiding important work.
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
$products = require __DIR__ . '/data.php';
$total = 0;
foreach ($products as $product) {
validateProduct($product);
$total += $product['price'];
echo reportLine($product) . PHP_EOL;
}
echo 'Total: ' . formatMoney($total) . PHP_EOL;
The entry script now reads like a small checklist: load helpers, load data, validate, print lines, print total.
What a good solution includes
A good multi-file script should use __DIR__ for reliable paths, require_once for helper files, require for returned data, small named helper functions, and validation before output.
Before moving on, make sure you can run the project with php report.php and explain which file owns the data, which file owns the helper rules, and which file coordinates the report.
Practice
Task: Build A Price Report
Create a three-file PHP script that prints a simple product price report.
Requirements
- Create
data.php,functions.php, andreport.php. data.phpmust return an array of products.functions.phpmust containformatMoney()andreportLine().report.phpmust load both files and print each product.- Use
declare(strict_types=1);in each PHP file. - Use
__DIR__when requiring files.
Use this product data:
<?php
declare(strict_types=1);
return [
['name' => 'Notebook', 'price' => 499],
['name' => 'Pen', 'price' => 129],
['name' => 'Desk pad', 'price' => 1299],
];
Running php report.php should print:
Notebook: GBP 4.99
Pen: GBP 1.29
Desk pad: GBP 12.99
Show solution
data.php
<?php
declare(strict_types=1);
return [
['name' => 'Notebook', 'price' => 499],
['name' => 'Pen', 'price' => 129],
['name' => 'Desk pad', 'price' => 1299],
];
functions.php
<?php
declare(strict_types=1);
function formatMoney(int $pennies): string
{
return 'GBP ' . number_format($pennies / 100, 2);
}
function reportLine(array $product): string
{
return $product['name'] . ': ' . formatMoney($product['price']);
}
report.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
$products = require __DIR__ . '/data.php';
foreach ($products as $product) {
echo reportLine($product) . PHP_EOL;
}
// Prints:
// Notebook: GBP 4.99
// Pen: GBP 1.29
// Desk pad: GBP 12.99
The entry script coordinates the work, while the data and helper rules live in separate files.
Task: Fix Config Require
The report needs a currency label from settings.php, but the entry script currently ignores the returned value.
Broken report.php:
<?php
declare(strict_types=1);
require __DIR__ . '/settings.php';
echo $settings['currency'] . PHP_EOL;
settings.php:
<?php
declare(strict_types=1);
return [
'currency' => 'GBP',
];
Fix report.php so it assigns the required value to $settings before using it.
Show solution
report.php
<?php
declare(strict_types=1);
$settings = require __DIR__ . '/settings.php';
echo $settings['currency'] . PHP_EOL;
// Prints:
// GBP
require evaluates the file and gives back the value returned by that file. Without the assignment, $settings does not exist in report.php.
Task: Add Report Line Helper
Extend the price report so functions.php validates each product and the report prints a final total.
Requirements
- Add
validateProduct()tofunctions.php. - Reject an empty product name.
- Reject a negative price.
- Keep
formatMoney()infunctions.php. - Keep
reportLine()infunctions.php. - In
report.php, validate each product before printing it. - Print a final total after all product lines.
Use this data:
<?php
declare(strict_types=1);
return [
['name' => 'Notebook', 'price' => 499],
['name' => 'Pen', 'price' => 129],
['name' => 'Desk pad', 'price' => 1299],
];
Running php report.php should print:
Notebook: GBP 4.99
Pen: GBP 1.29
Desk pad: GBP 12.99
Total: GBP 18.27
Show solution
functions.php
<?php
declare(strict_types=1);
function validateProduct(array $product): void
{
if (trim($product['name'] ?? '') === '') {
throw new InvalidArgumentException('Product name is required.');
}
if (($product['price'] ?? -1) < 0) {
throw new InvalidArgumentException('Product price cannot be negative.');
}
}
function formatMoney(int $pennies): string
{
return 'GBP ' . number_format($pennies / 100, 2);
}
function reportLine(array $product): string
{
return $product['name'] . ': ' . formatMoney($product['price']);
}
report.php
<?php
declare(strict_types=1);
require_once __DIR__ . '/functions.php';
$products = require __DIR__ . '/data.php';
$total = 0;
foreach ($products as $product) {
validateProduct($product);
$total += $product['price'];
echo reportLine($product) . PHP_EOL;
}
echo 'Total: ' . formatMoney($total) . PHP_EOL;
// Prints:
// Notebook: GBP 4.99
// Pen: GBP 1.29
// Desk pad: GBP 12.99
// Total: GBP 18.27
The helper file owns the validation and formatting rules. The entry script owns the order of work: load, validate, print, total.