---
title: 'Including Files'
source: 'https://academia.sh/en/courses/php-fundamentals/including-files'
course: 'PHP Fundamentals'
language: en
updated: '2026-10-21T05:03:34+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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'
---

# 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.

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