Includes And File Organization
Loading another PHP file is the first mechanical step from one script toward an organised codebase. A main script can load reusable functions, configuration, or deliberate bootstrap code while retaining ownership of the current command or request.
File separation helps only when responsibilities and dependencies are already clear. Includes execute code; they are not namespaces, modules, security boundaries, or automatic dependency management.
Load Required Helper Code
Create helpers.php:
<?php
declare(strict_types=1);
function formatProductLine(array $product): string
{
return $product['name'] . ': ' . $product['price_cents'] . ' cents';
}
Then load it from report.php:
<?php
declare(strict_types=1);
require __DIR__ . '/helpers.php';
$product = ['name' => 'Notebook', 'price_cents' => 1299];
echo formatProductLine($product), PHP_EOL;
PHP evaluates helpers.php at the require statement. The function definition becomes available before the call.
Use require for code the application cannot operate without. On PHP 8 and later, failure to load a required file raises an Error after diagnostic warnings. Normal execution cannot continue as if the dependency existed.
Build Stable Paths With __DIR__
A plain relative path can depend on the process's current working directory and include-path configuration:
require 'helpers.php';
That may work when a command is launched from one directory and fail from another.
__DIR__ is the directory containing the current source file:
require __DIR__ . '/helpers.php';
This path remains anchored to report.php regardless of where the user runs php /path/to/report.php.
For a parent or child directory, continue from that anchor:
require __DIR__ . '/../src/helpers.php';
Use dirname(__DIR__) when it reads more clearly for several levels. Avoid scattering assumptions about a deployment's absolute root path through the project.
Choose require Or include From Failure Policy
include also evaluates another file, but a missing include emits warnings and returns false, allowing execution to continue:
$result = include __DIR__ . '/optional-banner.php';
Use it only when the application truly has a meaningful path without that file and the warning behavior is understood.
For application source, configuration, and required templates, require usually expresses the contract better. Continuing after required code failed to load often causes a less useful undefined-function or missing-variable failure later.
Do not suppress load failures with @. Diagnostics should be fixed or handled at an intentional top-level boundary, not hidden.
Prevent Duplicate Definitions With _once
Loading a function file twice normally attempts to declare the same function twice and fails. require_once loads a file only if PHP has not already included it:
require_once __DIR__ . '/helpers.php';
require_once __DIR__ . '/helpers.php';
This is useful in legacy include graphs where several files may reach the same definitions.
Do not use require_once as a substitute for understanding dependencies. A small entry script with one obvious bootstrap require is easier to reason about than many files conditionally loading one another.
Composer autoloading later replaces manual includes for classes. Simple returned config files and explicit entry-point bootstraps still use require commonly.
Return Data From A PHP File
A loaded PHP file can return a value. config.php:
<?php
declare(strict_types=1);
return [
'currency' => 'GBP',
'items_per_page' => 20,
'show_tax' => true,
];
Consume it in report.php:
<?php
declare(strict_types=1);
$config = require __DIR__ . '/config.php';
echo $config['currency'], PHP_EOL;
A successful included file without an explicit return produces integer 1. Assign the result only when the file contract says it returns data.
Validate returned configuration before relying on it:
<?php
$config = require __DIR__ . '/config.php';
if (!is_array($config)) {
throw new RuntimeException('Configuration must return an array.');
}
An array check still does not guarantee required keys or value types. Validate those according to the application's configuration schema.
Understand Included-File Scope
Code in an included file inherits the variable scope at the line where it is loaded.
At top level:
<?php
$currency = 'GBP';
require __DIR__ . '/price-line.php';
price-line.php can see $currency. If the require occurs inside a function, included variable assignments live in that function's local scope.
This behavior is real but easy to abuse. Helper files should define functions or return data rather than depend on whichever variables happened to exist at the include line. Hidden include-scope dependencies make load order and callers difficult to change.
Function and class declarations from an included file are defined globally within their namespace, even when the include statement appears inside a function. Do not use include placement as a substitute for namespacing.
Keep Strict Types In Every File
declare(strict_types=1); applies per file. Requiring a strict helper file does not make calls in the entry file strict, and a strict entry file does not add the declaration to the loaded file's own calls.
Place the declaration in each project PHP file according to the project's convention:
<?php
declare(strict_types=1);
This avoids type behavior changing when code moves between files or when a different entry point calls the same helper.
return Ends The Loaded File, Not The Caller
A return inside a required file stops evaluation of that file and sends a value back to the require expression. It does not automatically terminate the entry script.
<?php
// feature.php
if (!extension_loaded('json')) {
return ['enabled' => false];
}
return ['enabled' => true];
The caller receives either array and continues after the require statement. This can support a clear returned-data contract, but avoid burying complex control flow in configuration files.
A top-level return in the entry script itself ends that file. If another file required the entry script, control returns there. Use exit() only when the entire process must terminate and keep that decision at an application boundary.
Inspect Paths When Diagnosing Load Failures
When a path fails, inspect the exact value the code constructed:
<?php
$helperPath = __DIR__ . '/helpers.php';
var_dump(__DIR__);
var_dump($helperPath);
var_dump(is_file($helperPath));
Check filename case as well as spelling; case-sensitive filesystems treat Helpers.php and helpers.php as different paths. A deployment can fail even when development occurred on a case-insensitive filesystem.
realpath() can resolve an existing path to its canonical absolute form, but it returns false when the target does not exist. Do not pass that result to require without checking it. For normal project source, a direct __DIR__ path and a clear failure are usually preferable to elaborate path probing.
Organise By Responsibility
A small script might use:
project/
report.php
helpers.php
config.php
report.phpis the entry point: load dependencies, obtain input, control flow, handle top-level failures, and output results.helpers.phpdefines reusable functions without running the report.config.phpreturns configuration data without printing.
As the project grows, group files under directories such as src/, config/, templates/, and bin/ according to their roles. Do not create one catch-all helper file containing unrelated functions indefinitely.
Avoid Side Effects During Loading
Loading a helper file should not unexpectedly print text, process records, modify globals, or terminate the process. A source file that runs substantial work merely because it was required makes ordering important and tests difficult.
Some files intentionally bootstrap an application by registering handlers or loading environment configuration. Name and document that role clearly, and require the bootstrap from a controlled entry point.
Returned config files should return data. Definition files should define behavior. Entry points should execute the workflow.
Never Build Include Paths From User Input
This is unsafe:
require $_GET['page'];
An attacker may influence which local file is evaluated or exploit enabled stream wrappers. Map external choices to known application behavior instead of turning input into a filesystem path:
$page = $_GET['page'] ?? 'home';
$template = match ($page) {
'home' => __DIR__ . '/templates/home.php',
'about' => __DIR__ . '/templates/about.php',
default => __DIR__ . '/templates/not-found.php',
};
require $template;
The application controls every path. External text selects from a fixed policy rather than naming arbitrary files.
Check A Multi-File Script From Different Directories
A stable script should work when launched from its project directory and by absolute path from elsewhere:
php report.php
php /absolute/path/to/project/report.php
If the second command breaks, a load path probably depends on the working directory. __DIR__ fixes source-relative dependencies.
Also syntax-check every file individually. A required helper with a parse error prevents the entry point from starting.
Common Include Mistakes
| Symptom | Likely cause |
|---|---|
| Script works only from one directory | Relative path depends on current working directory |
| Function redeclaration error | Definition file loaded more than once |
| Undefined function after warning | Used include for a required dependency and continued |
Config variable equals 1 |
Required file did not explicitly return data |
| Helper behaves differently by caller | It relies on inherited include-scope variables |
| Strict call behavior changes | strict_types missing from one calling file |
| Requiring helper prints unexpected text | Loaded file has hidden side effects |
| Arbitrary file can be evaluated | Include path was built from user input |
| Dependency graph is difficult to trace | Many files require one another opportunistically |
What You Should Be Able To Do
After this lesson, you should be able to load required definitions with require, anchor paths with __DIR__, distinguish include failure from require failure, use require_once to prevent duplicate evaluation when appropriate, assign a value returned from a config file, explain included-file variable scope, keep strict typing declarations per file, separate definition and entry-point responsibilities, avoid hidden load-time side effects, and reject user-controlled include paths.
Official references: PHP's manual pages for require, include, and require_once.
Practice
Task: Load Helper File
Task
Create two strict PHP files in the same directory.
helpers.php must define formatProductLine(array $product): string and contain no output.
report.php must:
- load the helper with
require __DIR__ . '/helpers.php';; - loop over Notebook at
1299cents and Pen at199cents; - print one formatted line for each product.
Run report.php from its directory and by absolute path from another directory. Both runs must produce the same two lines.
Hints
- Put
declare(strict_types=1);in both files. - The helper returns; the report echoes.
- Source-relative paths should not depend on the shell's working directory.
Show solution
Solution
helpers.php:
<?php
declare(strict_types=1);
function formatProductLine(array $product): string
{
return $product['name'] . ': ' . $product['price_cents'] . ' cents';
}
report.php:
<?php
declare(strict_types=1);
require __DIR__ . '/helpers.php';
$products = [
['name' => 'Notebook', 'price_cents' => 1299],
['name' => 'Pen', 'price_cents' => 199],
];
foreach ($products as $product) {
echo formatProductLine($product), PHP_EOL;
}
Explanation
report.php is the entry point and loads the definition before calling it. __DIR__ anchors the dependency to the report file, so changing the process working directory does not change the resolved helper path.
Both files declare strict types because that setting is per file. The helper has no load-time output; all workflow and output remain in the report.
Task: Fix Require Path
Task
A project has this structure:
project/
src/
helpers.php
bin/
report.php
bin/report.php currently contains the fragile and incorrect path:
require 'src/helpers.php';
Rewrite it so the path starts from the directory containing report.php, moves to the parent project directory, and then enters src/.
Also assign the path to $helperPath and use is_file() to print Helper found before requiring it.
Hints
__DIR__insidebin/report.phppoints toproject/bin...moves toproject.- The final require should use
$helperPath.
Show solution
Solution
<?php
declare(strict_types=1);
$helperPath = __DIR__ . '/../src/helpers.php';
if (!is_file($helperPath)) {
throw new RuntimeException('Helper file is missing.');
}
echo "Helper found\n";
require $helperPath;
Explanation
The path begins at the source file's own directory, moves from bin to the project root, and then enters src. It remains correct regardless of the shell's working directory.
The is_file() check provides a controlled diagnostic for the exercise. Required application code is then loaded with require; suppressing a bad path or continuing without the helper would leave the entry point unusable.
Task: Return And Validate Config
Task
Create config.php that returns:
[
'currency' => 'GBP',
'items_per_page' => 20,
'show_tax' => true,
]
Create report.php that requires the file into $config, verifies the result is an array, merges ['items_per_page' => 50], then prints:
Currency: GBP
Items per page: 50
Show tax: yes
The config file must not print anything.
Hints
- Assign the
requireexpression. - Throw
RuntimeExceptionif the returned value is not an array. - Use
array_merge()so the later override wins.
Show solution
Solution
config.php:
<?php
declare(strict_types=1);
return [
'currency' => 'GBP',
'items_per_page' => 20,
'show_tax' => true,
];
report.php:
<?php
declare(strict_types=1);
$config = require __DIR__ . '/config.php';
if (!is_array($config)) {
throw new RuntimeException('Configuration must return an array.');
}
$config = array_merge($config, ['items_per_page' => 50]);
$showTax = $config['show_tax'] ? 'yes' : 'no';
echo "Currency: {$config['currency']}\n";
echo "Items per page: {$config['items_per_page']}\n";
echo "Show tax: $showTax\n";
Explanation
The require expression receives the array returned by config.php. The entry point verifies the broad return type before consuming it and applies a later override with explicit precedence.
The config file owns data only. It produces no output and performs no report workflow, so loading it has one predictable effect: returning configuration.