LEARN TO CODE | PHP | 03 FILES AND DATABASES

03.1 Reading files and JSON data

Read a known local file, decode JSON into arrays and validate its structure before displaying it.

01 | DATA CAN LIVE OUTSIDE THE PHP SOURCE

Use PHP 8.1 or later for these examples. Create a project folder with public and private subfolders. Start the local server from the project root with php -S 127.0.0.1:8000 -t public , then open the requested .php page through HTTP.

The public folder is the document root: files the browser can request. The private folder is a sibling, not a child of public. PHP can read it through filesystem paths, but it is not directly served by this configuration.

The examples are for local single-user practice. Database pages require a running MariaDB or MySQL server and the PDO MySQL driver. PHP’s built-in web server does not install or start the database.

Earlier pages assigned arrays directly in PHP. A file lets data be maintained separately: a simple welcome message in text or structured club records in JSON.

This lesson reads files only. It does not introduce file upload, concurrent file editing or a saved registration service. Small known data files make the reading process easier to understand.

02 | CREATE A PREDICTABLE STRUCTURE

Local filesystem layout
File Purpose
public/helpers.php HTML output helper
public/files.php Reads and displays the data
private/welcome.txt Plain text message
private/clubs.json JSON list of records

__DIR__ is the current source file’s directory. dirname(__DIR__) moves up to the project root from public; appending /private/clubs.json reaches the intended data file.

This is a server filesystem path, not a URL. Do not let a submitted path choose arbitrary files to read.

03 | PREPARE THE INPUT FILES

private/welcome.txt

Welcome to the club directory.
Choose something you would like to learn.

private/clubs.json

[
    {"name": "Robotics", "capacity": 12},
    {"name": "Art & Design", "capacity": 10},
    {"name": "Coding", "capacity": 16}
]

JSON strings and object keys use double quotes. A list is enclosed in square brackets; each object record uses braces. There is no trailing comma after the final item.

12 is a JSON number while "12" is a string. Valid syntax alone does not prove that the data matches our expected club structure.

04 | THE SHARED OUTPUT HELPER

All pages share public/helpers.php for HTML text and ordinary quoted-attribute output.

public/helpers.php

<?php
function escapeHtml(string $text): string
{
    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
}

A text file is not trusted HTML just because it is local. Display it as text unless the application explicitly intends to interpret carefully controlled markup.

05 | A COMPLETE READING PAGE

public/files.php

<?php
require_once __DIR__ . '/helpers.php';
$error = '';
$text = '';
$clubs = [];
try {
    $folder = dirname(__DIR__) . '/private';
    $textPath = $folder . '/welcome.txt';
    $jsonPath = $folder . '/clubs.json';
    if (!is_readable($textPath) || !is_readable($jsonPath)) {
        throw new RuntimeException('A required data file is not readable.');
    }
    $loadedText = file_get_contents($textPath);
    $rawJson = file_get_contents($jsonPath);
    if ($loadedText === false || $rawJson === false) {
        throw new RuntimeException('A file read failed.');
    }
    $decoded = json_decode($rawJson, true, 512, JSON_THROW_ON_ERROR);
    if (!is_array($decoded) || !array_is_list($decoded)) {
        throw new RuntimeException('Expected a JSON list.');
    }
    foreach ($decoded as $club) {
        if (!is_array($club) || !isset($club['name'], $club['capacity'])
            || !is_string($club['name']) || trim($club['name']) === ''
            || !is_int($club['capacity']) || $club['capacity'] < 1) {
            throw new RuntimeException('Invalid club record shape.');
        }
    }
    $text = $loadedText;
    $clubs = $decoded;
} catch (JsonException | RuntimeException $exception) {
    error_log($exception->getMessage());
    http_response_code(500);
    $error = 'The directory data could not be loaded.';
}
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>File-based clubs</title></head>
<body>
<h1>File-based clubs</h1>
<?php if ($error !== ''): ?>
    <p role="alert"><?= escapeHtml($error) ?></p>
<?php else: ?>
    <pre><?= escapeHtml($text) ?></pre>
    <ul>
        <?php foreach ($clubs as $club): ?>
            <li><?= escapeHtml($club['name']) ?>: <?= $club['capacity'] ?> places</li>
        <?php endforeach; ?>
    </ul>
<?php endif; ?>
</body>
</html>

06 | UNDERSTAND EACH CHECK

is_readable checks whether the expected files are available for reading. file_get_contents loads a file’s contents as a string, or returns false on failure. An empty string and false are different, so use === false.

json_decode(..., true, 512, JSON_THROW_ON_ERROR) decodes JSON objects into associative arrays and throws a JsonException for malformed JSON. The depth argument limits nesting.

array_is_list requires a PHP list structure. Decoding to associative arrays does not preserve every JSON object-versus-list distinction, so we also validate each expected record. This is a practical data-shape check, not a general JSON Schema validator.

07 | VALID JSON CAN STILL BE WRONG DATA

A JSON document containing 42 is valid JSON but not a club list. A record with capacity="twelve" also fails our rule, despite valid JSON syntax.

The foreach validation checks that each record has a non-blank name string and a positive integer capacity. The page accepts the whole dataset only after these checks pass.

The response escapes names and the welcome text, while the validated integer capacity can be displayed directly.

08 | HANDLE FAILURE WITHOUT A FALSE SUCCESS

The catch block logs a diagnostic for the developer, sends a 500 response and displays a general message. It does not pretend an invalid file is a successfully loaded empty directory.

The simple text preview uses a pre element to preserve line breaks. Escaping inside pre is still necessary: pre does not make unescaped tags harmless.

Filesystem warnings can occur if a file changes between a readability check and reading. Development logs help diagnose such issues; deployed error-display configuration belongs in the later errors-and-configuration lesson.

09 | FILES OR A DATABASE?

Choose based on requirements
Files can suit A database helps when
Small relatively static settings or content Many records must be searched and changed
Portable structured exports Relationships and constraints matter
A known local read-only dataset Several operations or users need coordinated writes

JSON is a format, not an automatic replacement for a relational database. Two people rewriting a whole JSON file can overwrite each other’s changes unless the application has a considered coordination strategy.

10 | PRACTISE FAILURE AND RECOVERY

TRY IT YOURSELF

Add a club with a name containing &. Then independently test a missing file, malformed JSON and a string capacity. Restore valid data after each test.

Reveal guidance

The valid new club appears as ordinary text. Missing or malformed data produces the controlled error response; a string capacity fails the shape check. Return to the original files before comparing the sample output.

Next, a database server will store the directory and answer SQL queries through PDO.

KEEP LEARNING

Previous page 03.2 Connecting to MariaDB or MySQL using PDO

Official references: JSON decoding · PDO connections · Prepared statements · MariaDB grants