Finding the real file when PHP's include_path bites you

这篇内容暂时只有 English 版本,以下是原文。

A 5.6-era app I inherited had this line in composer.json:

"autoload": { "files": ["lib/helpers.php"] }

On PHP 7.4/FPM with include_path = ".:/var/www/shared/lib", require_once 'lib/helpers.php' silently loaded /var/www/shared/lib/helpers.php instead of the repo copy. The failure looked like a missing function until I checked get_included_files(). The root cause: relative paths hit include_path before the current working directory, and getcwd() in FPM is the script’s directory, not the project root.

My fix was to stop trusting relative requires and resolve through a single bootstrap constant:

// bootstrap.php
define('APP_ROOT', dirname(__DIR__));
require_once APP_ROOT . '/lib/helpers.php';

Then I audited every include, require, include_once, and require_once with a small script:

grep -rnE "(include|require)(_once)?\s*\(?\s*['\"]" --include='*.php' . \
  | grep -vE "(dirname|__DIR__|APP_ROOT)"

Anything left was a candidate for the same bug. I also set include_path explicitly in the pool config rather than relying on the CLI default:

php_admin_value[include_path] = ".:/var/www/shared/lib"

Why this works: include_path is searched left to right, and . means the current working directory, which FPM sets per request. Absolute paths skip that search entirely, so the file you review is the file that runs. The trade-off is portability: hardcoding APP_ROOT means deployments must keep the same layout or define the constant from an environment value. __DIR__ is safer for files that may move, but it breaks when code is symlinked into a release directory, because __DIR__ resolves the symlink target. I hit that with Capistrano-style current symlinks, where __DIR__ pointed at the previous release.

Where it fails: CLI scripts and queue workers often have a different CWD and include_path than FPM, so a path that works in the browser can fail in artisan or a cron job. realpath_cache can also mask a moved file until the cache is cleared, which made one debugging session confusing. If you use open_basedir, absolute paths outside the allowed tree will fail loudly, which is usually better than silently loading the wrong file but still needs a config change.

The question: how do you detect a wrong-file include in CI before it reaches production, short of asserting get_included_files() on every request?

0
赞同度 100% · 0 条评论 · 17 阅读

0 条评论

支持 Markdown · 回复会实时推送给对方

还没有评论,写下第一条。