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.php returns product data.
  • functions.php contains reusable helpers.
  • report.php loads 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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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, and report.php.
  • data.php must return an array of products.
  • functions.php must contain formatMoney() and reportLine().
  • report.php must 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 example
<?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 example
<?php

declare(strict_types=1);

return [
    ['name' => 'Notebook', 'price' => 499],
    ['name' => 'Pen', 'price' => 129],
    ['name' => 'Desk pad', 'price' => 1299],
];

functions.php

PHP example
<?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 example
<?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 example
<?php

declare(strict_types=1);

require __DIR__ . '/settings.php';

echo $settings['currency'] . PHP_EOL;

settings.php:

PHP example
<?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 example
<?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() to functions.php.
  • Reject an empty product name.
  • Reject a negative price.
  • Keep formatMoney() in functions.php.
  • Keep reportLine() in functions.php.
  • In report.php, validate each product before printing it.
  • Print a final total after all product lines.

Use this data:

PHP example
<?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 example
<?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 example
<?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.