require, require_once, include, include_once in PHP
Introduction
Four statements, two axes: require vs include is about severity when the file is missing; the _once suffix is about loading the same file twice. The differences look trivial until a rename on a Friday evening takes down the whole site — which is exactly what happened to us (see the last section).
The four constructs
require — file is mandatory. Missing file: warning plus a
thrown Error (fatal if uncaught). Use for code the page cannot
live without.
include — file is optional. Missing file: warning, expression
evaluates to false, execution continues. Use for
optional blocks (banners, widgets).
require_once / include_once — same as above, but a file
already loaded during this request is skipped. Use everywhere a file
may be reached through several paths (shared headers, class files).
What happens when the file is missing
Tested on PHP 8.1:
$ php -r '$r = @include "/nonexistent/x.php"; var_dump($r);'
bool(false) — and the script keeps running
$ php -r 'try { require "/nonexistent/x.php"; } catch (Throwable $e)
{ echo get_class($e); }'
Warning, then caught: Error — catch it and the script also survives
So on modern PHP a failed require is catchable — but only if you wrap it. An unwrapped require_once of a renamed module (our case) still kills the request exactly like the old fatal did.
_once dedupes by resolved path
require_once tracks files by their resolved real path, not by the spelling you used. Tested: three spellings of one file — ../f.php, an absolute path with /./ inside, and a path with /sub/../ — executed the file exactly once. Consequences: symlinked copies of one file load once too (usually what you want), while the same code reached through two genuinely different files still loads twice. Never rely on _once to deduplicate by content — only by path.
Included code sees your scope
An included file runs in the scope of the line that includes it: a require inside a function sees that function locals, and any variables the file sets leak back out. Convention that saves nerves: included files either declare functions/classes only, or document the exact variables they read and write (our footer fragments, for example, contract on $metrika).
Relative paths resolve against CWD, not the file
require "lib/util.php" looks in include_path and the current working directory — not the directory of the file containing the statement. A page that works in the browser (CWD = docroot) can break under cron or CLI with a different CWD. Robust forms, in order of preference: __DIR__ . '/lib/util.php' (anchored to the including file), or an absolute path built from a known root such as $_SERVER['DOCUMENT_ROOT'].
Remote includes are off for a reason
require "https://…/lib.php" executes third-party code inside your process with your permissions — a hijacked or lagging URL becomes code execution or a hung request. PHP ships allow_url_include=Off since 5.2 exactly because of that history. Leave it off; fetch remote data with HTTP clients and treat it as data, never as code.
Opcache can serve yesterday file
With opcache enabled and timestamp validation off (common production tuning), PHP keeps executing the cached bytecode after you replace the file: renames look half-applied, deleted files keep running. After every deploy that adds, renames, or deletes PHP files, invalidate explicitly (opcache_reset() or a web-server reload), otherwise you debug ghosts.
Checklist: include safely
1. Absolute paths from __DIR__ or a known root — never bare
relative paths.
2. _once for anything reachable twice; plain require
only when exactly-once is guaranteed by construction.
3. Optional modules: is_file() guard + baked-in default, so a
missing file degrades instead of fataling.
4. Keep allow_url_include=Off.
5. Reset opcache (or confirm timestamp validation) on deploy.
How this evolved across PHP versions
PHP 4/5: a failed require ended in an uncatchable fatal
(E_COMPILE_ERROR) — the whole request died, no recovery possible.
PHP 5.2: allow_url_include default switched to Off
after years of remote-include exploits.
PHP 5.3: __DIR__ arrived, giving every file a reliable
anchor for absolute includes (previously only verbose
dirname(__FILE__)).
PHP 7.0: engine errors became catchable Error
throwables — a failed require can now be caught with
try/catch (verified above on 8.1), while include keeps its
warn-and-continue contract. Uncaught, both still stop the page —
which is why the guard pattern matters on every version.
PHP 7.4+ / 8.x: include semantics unchanged; opcache
preloading and stricter engine typing raised the stakes of stale or
misspelled paths rather than changing the statements themselves.
Examples on this site
require on a missing file
fatals the page — the rename incident, guard pattern, fallback
chain.
Call function from another
file — everyday require_once with absolute paths.
PHP errors: case studies — more
failure modes and fixes.
Article author: Arthur Isaev
| Development with PHP | |
| require, require_once, include, include_once | |
| require_once on a missing file fatals the page |