Lesson 11 of 12

PHP Include and Files

So far each example has lived in a single file. Real websites have dozens of pages that share the same header, footer and helper functions. Copying that code into every page would be a maintenance nightmare: change the menu once and you would have to edit every file. This lesson shows how to split code into reusable files with PHP include and require, and then how PHP reads and writes ordinary files such as logs, JSON settings and CSV exports.

PHP include and require

Four statements pull the contents of another PHP file into the current one, as if you had pasted it in at that point:

StatementIf the file is missingLoads the file more than once?
requireFatal error: the script stopsYes, every time it runs
require_onceFatal error: the script stopsNo, only the first time
includeWarning: the script carries onYes, every time it runs
include_onceWarning: the script carries onNo, only the first time

Use require_once for files that define functions, classes or constants: the page cannot work without them, and loading them twice would cause a “cannot redeclare” error. Use require for template parts such as a header that you deliberately print. include is for genuinely optional pieces, which are rare.

Whenever you write a PHP include or require, build the path from __DIR__, a magic constant that holds the folder of the current file. A plain relative path like 'includes/helpers.php' is resolved from the working directory, which changes depending on which page started the request, so it can break as soon as one file includes another.

<?php
declare(strict_types=1);

const SITE_NAME = 'Marina Tea House';

function e(string $text): string
{
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

function rupees(int $amount): string
{
    return 'Rs. ' . number_format($amount);
}
Output
(No output. This file only defines a constant and two functions for other pages to use.)
<?php
declare(strict_types=1);
require_once __DIR__ . '/includes/helpers.php';

$items = ['Ginger tea' => 30, 'Sulaimani' => 25, 'Bun butter jam' => 60];
?>
<h1><?= e(SITE_NAME) ?> menu</h1>
<ul>
<?php foreach ($items as $name => $price): ?>
  <li><?= e($name) ?> – <?= rupees($price) ?></li>
<?php endforeach; ?>
</ul>
Output
<h1>Marina Tea House menu</h1>
<ul>
  <li>Ginger tea – Rs. 30</li>
  <li>Sulaimani – Rs. 25</li>
  <li>Bun butter jam – Rs. 60</li>
</ul>

The short helper e() saves typing htmlspecialchars() everywhere, which makes it more likely you will escape every value, as recommended in PHP Forms. The same idea gives you a shared layout: put the top of the page in includes/header.php, the bottom in includes/footer.php, and require them at the start and end of every page. WordPress themes work the same way with get_header() and get_footer().

Watch out: Never build a PHP include path from user input, as in require $_GET['page'] . '.php';. An attacker can change the value to load files you never intended. If pages are chosen by a parameter, map allowed values to file names with a match expression or an array, and reject everything else.

Reading and writing whole files

For small files, two functions do almost everything:

  • file_get_contents($path) reads the whole file into a string, or returns false on failure.
  • file_put_contents($path, $data, $flags) writes a string to a file, creating it if needed. By default it replaces the contents; the FILE_APPEND flag adds to the end instead, and LOCK_EX stops two visitors writing at the same moment.

file($path) is a handy relative: it returns the file as an array of lines. Check that a file exists with is_file() before reading it, and handle the false return value instead of assuming success.

<?php
declare(strict_types=1);

$logFile = __DIR__ . '/visits.log';

$visits = ['Gokul visited /menu', 'Rehana visited /contact', 'Gokul visited /order'];
foreach ($visits as $line) {
    file_put_contents($logFile, $line . "\n", FILE_APPEND | LOCK_EX);
}

$lines = file($logFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
echo "The log has " . count($lines) . " lines.\n";
echo "Last entry: " . end($lines) . "\n";
echo "File size: " . filesize($logFile) . " bytes";

unlink($logFile); // delete the demo file
Output
The log has 3 lines.
Last entry: Gokul visited /order
File size: 65 bytes

Storing data as JSON

Plain text is fine for logs, but structured data such as settings is better stored as JSON. json_encode() turns a PHP array into a JSON string, and json_decode($json, true) turns it back into an associative array. Pass JSON_THROW_ON_ERROR so that broken JSON throws an exception instead of quietly returning null.

<?php
declare(strict_types=1);

$file = __DIR__ . '/settings.json';

$settings = ['shopName' => 'Marina Tea House', 'openHour' => 7, 'cities' => ['Chennai', 'Puducherry']];
file_put_contents($file, json_encode($settings, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR));

$loaded = json_decode(file_get_contents($file), true, flags: JSON_THROW_ON_ERROR);
echo "{$loaded['shopName']} opens at {$loaded['openHour']} am\n";
echo "Branches: " . implode(' and ', $loaded['cities']);

unlink($file);
Output
Marina Tea House opens at 7 am
Branches: Chennai and Puducherry

Working line by line with fopen()

For large files, or for CSV spreadsheets, open the file as a stream with fopen() and process it piece by piece. This keeps memory use low because the whole file is never loaded at once. The mode letter says what you plan to do: 'r' to read, 'w' to write (emptying the file first) and 'a' to append. Always fclose() the handle when you are finished. fputcsv() and fgetcsv() take care of commas and quotes inside values for you. Pass escape: '' to both: it gives standard (RFC 4180) CSV, and since PHP 8.4 leaving the escape argument out triggers a deprecation notice.

<?php
declare(strict_types=1);

$csvFile = __DIR__ . '/orders.csv';

$out = fopen($csvFile, 'w');
fputcsv($out, ['Customer', 'City', 'Amount'], escape: '');
fputcsv($out, ['Nithya', 'Salem', 540], escape: '');
fputcsv($out, ['Abdul', 'Kochi, Kerala', 1200], escape: '');
fclose($out);

$in = fopen($csvFile, 'r');
$header = fgetcsv($in, escape: '');
$total = 0;
while (($row = fgetcsv($in, escape: '')) !== false) {
    $order = array_combine($header, $row);
    echo "{$order['Customer']} ({$order['City']}): Rs. {$order['Amount']}\n";
    $total += (int) $order['Amount'];
}
fclose($in);
echo "Total: Rs. $total";

unlink($csvFile);
Output
Nithya (Salem): Rs. 540
Abdul (Kochi, Kerala): Rs. 1200
Total: Rs. 1740

Tip: Keep data files such as logs and JSON settings outside your public web folder, or block access to them in the server configuration. Otherwise anyone who guesses the file name can download them. For anything bigger than a few hundred records, or data that many visitors change at once, use a database instead; that is the topic of the next lesson, PHP MySQL with PDO.

Further reading

Key takeaways

  • require_once loads essential shared code once; require suits template parts; include only warns if a file is missing.
  • Build include paths with __DIR__ and never from user input.
  • file_get_contents() and file_put_contents() (with FILE_APPEND | LOCK_EX) handle small files.
  • Use json_encode() / json_decode(..., true) with JSON_THROW_ON_ERROR for structured data.
  • Use fopen(), fgetcsv() and fclose() for large or CSV files, and store data files outside the web root.

Quick quiz

Check what you learned – pick an answer.

1. What happens when require cannot find the file?

2. Why use __DIR__ in require_once __DIR__ . '/includes/db.php'?

3. Which call adds a line to the end of log.txt without deleting what is already there?

Practice exercise

Write a small "guest book" script: append a name and a message as one JSON object per line to guestbook.txt, then read the file back and print every entry as "Name: message".

Show a hint

Use file_put_contents() with FILE_APPEND | LOCK_EX to write json_encode($entry) . PHP_EOL, and file() with FILE_IGNORE_NEW_LINES to read the lines back.

Show the solution
<?php
declare(strict_types=1);

$file = __DIR__ . '/guestbook.txt';

$entry = ['name' => 'Harini', 'message' => 'Lovely filter coffee!'];
file_put_contents($file, json_encode($entry, JSON_THROW_ON_ERROR) . PHP_EOL, FILE_APPEND | LOCK_EX);

foreach (file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) as $line) {
    $item = json_decode($line, true, flags: JSON_THROW_ON_ERROR);
    echo htmlspecialchars($item['name']) . ': ' . htmlspecialchars($item['message']) . "n";
}

Progress is saved in this browser only.