Skip to content
academia.sh

Lesson 14 / 14

Including Files

Through which channel the inclusion of a missing file is heard with include, require and include_once, followed by a course wrap-up that gathers the measurements of fourteen lessons in one table.

Published Updated

In this lesson

  • Measures through which channel a missing file is heard with include and require
  • Observes that include_once does not run the same file a second time
  • Shows that a syntax error in an included file halts execution for include as well
  • Compares the fourteen lessons of the course by channel in a single table
Contents

The File Uploads lesson ended with a file that arrived with the request being handed to a function that returns false. A script pulling another file into itself asks the same question through a different mechanism: if the file to be pulled in does not exist, through which channel is that heard? The answer changes with the form of inclusion used. The lesson measures the fourteenth observation of the core and its counterparts; then it gathers the whole course in a single table.

The measurement again uses the course core, the channel.php file. Because every lesson runs in its own working directory, the file is given verbatim here as well.

<?php
// channel.php — helpers that classify which channel a problem is heard through

// Level names of caught warnings collect here; the message text is not kept.
$GLOBALS['heard_warnings'] = [];

set_error_handler(function (int $level): bool {
    $names = [E_WARNING => 'E_WARNING', E_NOTICE => 'E_NOTICE', E_DEPRECATED => 'E_DEPRECATED'];
    $GLOBALS['heard_warnings'][] = $names[$level] ?? 'E_' . $level;
    return true;
});

// Turns a value into a short text together with its type.
function show(mixed $value): string
{
    return match (true) {
        $value === null => 'null',
        is_bool($value) => $value ? 'true' : 'false',
        is_array($value) => 'array(' . count($value) . ')',
        default => get_debug_type($value) . ' ' . $value,
    };
}

// Runs the job and asks, in order, which channel the problem is heard through:
// first a throw, then a warning, then a false/null return; if none of them, silent.
function listen(callable $job): array
{
    $GLOBALS['heard_warnings'] = [];
    try {
        $result = $job();
    } catch (Throwable $error) {
        return ['halts', get_class($error)];
    }
    if ($GLOBALS['heard_warnings']) {
        $levels = implode(',', array_unique($GLOBALS['heard_warnings']));
        return ['warning', $levels . ', result ' . show($result)];
    }
    if ($result === false || $result === null) {
        return ['return', 'result ' . show($result)];
    }
    return ['silent', 'result ' . show($result)];
}

// Runs the code in a separate PHP execution; exit code 255 means execution halted.
function run_separately(string $code): array
{
    exec('php -d display_errors=0 -r ' . escapeshellarg($code) . ' 2>&1', $lines, $exit);
    $channel = $exit === 255 ? 'halts' : 'silent';
    return [$channel, 'lines out ' . count($lines) . ', exit ' . $exit];
}

A Missing File Raises a Warning with include

The first script measures three things: pulling in a file that does not exist with include, pulling in a file that does exist twice with include_once, and how many times that file ran. The script writes the existing file itself, so the measurement does not depend on a file left over from an earlier run.

<?php
// includes.php — which channel is heard for a missing file and for include_once
require 'channel.php';

file_put_contents('part.php', "<?php\n\$GLOBALS['loaded'] = (\$GLOBALS['loaded'] ?? 0) + 1;\nreturn 7;\n");

$measurements = [
    'missing include file' => fn() => include 'missing.php',
    'first include_once'   => fn() => include_once 'part.php',
    'second include_once'  => fn() => include_once 'part.php',
];

foreach ($measurements as $name => $measurement) {
    [$channel, $evidence] = listen($measurement);
    printf("%-24s%-10s%s\n", $name, $channel, $evidence);
}
echo 'part.php run count ', $GLOBALS['loaded'], "\n";
missing include file    warning   E_WARNING, result false
first include_once      silent    result int 7
second include_once     silent    result true
part.php run count 1

The first row is the core’s missing include file observation: for the file it could not find, include reported an E_WARNING, returned false and execution went on. This is the warning channel, and it is the channel measured in this lesson. RR21. The returned false is there too, but the order in which listen() asks decides the channel: a warning is asked about before a return. That is why the row says warning, not return.

The next two rows are for include_once. The first call runs the file and gives the 7 that the file returns. The second call does not run the file again and returns only true; the last line confirms that the file ran once in total. RR22. This is the same behavior for include_once and require_once, and it prevents clashes such as defining the same function twice. But the second call giving true instead of 7 is a silent difference: code that uses the file’s return value gets a different value on the second call.

Require Halts After the Warning

require meets the same absence more harshly. It leaves no false for execution to carry on with; if the file is not found, it stops. In what form it does so is measured, not assumed. Two measurements are made: in the same process with a try, and in a separate execution through the exit code.

<?php
// required.php — which channel is heard for a missing file with require
require 'channel.php';

[$channel, $evidence] = listen(fn() => require 'missing.php');
printf("%-24s%-10s%s\n", 'missing require', $channel, $evidence);
echo 'warning heard first: ', implode(',', $GLOBALS['heard_warnings']), "\n";

[$channel, $evidence] = run_separately('echo "before\n"; require "missing.php"; echo "after\n";');
printf("%-24s%-10s%s\n", 'separate run', $channel, $evidence);
missing require         halts     Error
warning heard first: E_WARNING
separate run            halts     lines out 1, exit 255

RR23. The channel is halts: require threw a Throwable and the separate execution exited with code 255, and the after line was not printed. The channel gave more than one signal: require first reported an E_WARNING, then cut execution. The core’s question picks the strongest channel, so the channel of the row is halts, not warning. This is the shared rule of the course: if two channels speak in the same event, the measure is the channel that cuts execution.

Does the Path Depend on the Working Directory

A relative path may depend on the current working directory, not on the script’s own directory. When the same file is given with __DIR__, this dependency goes away. __DIR__ is the path of the directory where the running file sits; its value depends on the environment and is not printed in this lesson, only whether the path built with it works.

<?php
// path.php — whether the included path depends on the working directory
require 'channel.php';

file_put_contents('part.php', "<?php\nreturn 7;\n");
chdir('/');

$measurements = [
    'relative ./ path' => fn() => include './part.php',
    '__DIR__ path'     => fn() => include __DIR__ . '/part.php',
];

foreach ($measurements as $name => $measurement) {
    [$channel, $evidence] = listen($measurement);
    printf("%-24s%-10s%s\n", $name, $channel, $evidence);
}
relative ./ path        warning   E_WARNING, result false
__DIR__ path            silent    result int 7

RR24. After the working directory changed, the path that begins with ./ could not find the file and raised a warning; the path built with __DIR__ was found, and the file’s 7 came back. If the path ties the location of the included file to the location of the script, __DIR__ is used. This does not change the channel of the error; it only reduces the chance of the error occurring.

The Bounding Measurement

The warning channel of include depends on a condition: it applies when the file is not found. If the file is found and its syntax is broken, the situation changes. The script below pulls in a broken file with include.

<?php
// broken.php — how include is heard for a file that is found but has broken syntax
require 'channel.php';

file_put_contents('broken_part.php', "<?php\n\$x = ;\n");

[$channel, $evidence] = listen(fn() => include 'broken_part.php');
printf("%-24s%-10s%s\n", 'broken included file', $channel, $evidence);
broken included file    halts     ParseError

RR25. This time include threw a ParseError, and the channel is halts. Finding the file turned the channel from warning into halts. This is the measured form of the summary in the Syntax and Tags lesson: a syntax error in an included file is thrown, after the main file has started running, as a catchable ParseError. As a result, the sentence “an error does not cut execution” is not true for include; the true sentence is “a missing file does not cut execution”.

Summary

  • A file that cannot be found makes include report an E_WARNING and return false; execution continues. The channel measured in this lesson is warning (missing include file).
  • For the same absence, require first reports a warning and then cuts execution; its channel is halts.
  • On the second call, include_once does not run the file and returns true; the return value of the first call is lost.
  • A path that begins with ./ depends on the working directory; a path built with __DIR__ depends on the location of the script.
  • If the file is found but its syntax is broken, include also throws a ParseError and halts.

Course Wrap-Up

One question was asked throughout the course: through which channel is this problem heard? Fourteen lessons each measured a mechanism, and each named its channel with one of these four names. The table shows, on each row, the mechanism the lesson measured, its channel and the place where that channel ends.

Lesson Mechanism measured Channel Bounding measurement
The PHP Execution Model core with fourteen observations halts on a server every request starts from the beginning
Syntax and Tags parse error versus execution error halts a syntax error cuts execution before it starts
Variables and Data Types type depends on value silent strict_types binds the caller’s file
Operators numeric-prefix string warning ?? silences the warning
Control Flow unmatched match halts the default arm turns halting into silence
Functions extra argument silent a built-in function throws ArgumentCountError
Indexed and Associative Arrays key normalization silent a float key gives E_DEPRECATED
Multidimensional Arrays missing inner key warning ?? silences the whole path
Array Functions array_search finds nothing return 0 becomes false in a loose comparison
String Handling malformed JSON return JSON_THROW_ON_ERROR halts
Superglobals missing query key warning an array arrives and a TypeError is thrown, the status code changes
Form Handling invalid form number return a valid 0 looks the same as failure
File Uploads non-uploaded file return exceeding post_max_size empties both arrays
Including Files missing include file warning broken syntax gives ParseError

The distribution over the fourteen lessons is 3 silent · 4 return · 4 warning · 3 halts; because the core carries one observation for each lesson, the distribution of the core is the same. The four channels are four different costs of the same fault. In the silent channel the wrong result pays the cost, in return the caller’s attention, in warning the eye that reads the notice, and in halts the running execution.

The table has a second reading: the last column of each row shows that the channel depends on a condition. ?? turns a warning into silence, JSON_THROW_ON_ERROR moves a return into halts, and the default arm turns halting into silence. A channel is not a fixed property of the mechanism but a property that shifts with the code the caller writes. For that reason, instead of assuming the channel, the same question is asked for every new mechanism.

With this question the course separated the ways PHP lets problems be heard; the security consequences of these classifications are the subject of M24 and M25. The next course covers classes, interfaces and namespaces in PHP.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close