PHP Language Basics

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 example
<?php

declare(strict_types=1);

function formatProductLine(array $product): string
{
    return $product['name'] . ': ' . $product['price_cents'] . ' cents';
}

Then load it from report.php:

PHP example
<?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:

PHP example
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:

PHP example
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:

PHP example
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:

PHP example
$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:

PHP example
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 example
<?php

declare(strict_types=1);

return [
    'currency' => 'GBP',
    'items_per_page' => 20,
    'show_tax' => true,
];

Consume it in report.php:

PHP example
<?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 example
<?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 example
<?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 example
<?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 example
<?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 example
<?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.php is the entry point: load dependencies, obtain input, control flow, handle top-level failures, and output results.
  • helpers.php defines reusable functions without running the report.
  • config.php returns 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:

PHP example
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:

PHP example
$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 1299 cents and Pen at 199 cents;
  • 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 example
<?php

declare(strict_types=1);

function formatProductLine(array $product): string
{
    return $product['name'] . ': ' . $product['price_cents'] . ' cents';
}

report.php:

PHP example
<?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:

PHP example
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__ inside bin/report.php points to project/bin.
  • .. moves to project.
  • The final require should use $helperPath.
Show solution

Solution

PHP example
<?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 require expression.
  • Throw RuntimeException if the returned value is not an array.
  • Use array_merge() so the later override wins.
Show solution

Solution

config.php:

PHP example
<?php

declare(strict_types=1);

return [
    'currency' => 'GBP',
    'items_per_page' => 20,
    'show_tax' => true,
];

report.php:

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