I wrote phpfilemerger, a small CLI tool that takes a PHP entry point and all of its class dependencies and produces a single self-contained PHP file. It uses an AST parser to resolve dependencies properly, so the output is ordered and ready to run.
Use case: PHP-CRUD-API
I maintain a PHP project that is distributes as a single file (PHP-CRUD-API) and phpfilemerger automates the process of building. It produces that one-file distribution, keeping class order and namespaces intact so the merged file works just like the original project. It statically analyzes your codebase using PSR-4/PSR-0 mappings and inlines all dependencies in the correct order.
Why not PHAR?
PHAR archives can bundle code, autoloaders and metadata and are generally a more
robust single-file format. They do what phpfilemerger does, but better. However,
PHAR execution is frequently disabled or restricted on web hosting. The phar
stream wrapper may be turned off and/or execution of PHAR archives is blocked
for security reasons. Using phpfilemerger allows you to work around these
restrictions. It provides you the convenience of single file deployment whenever
PHAR is not an option.
Note: A less elegant way to work around the PHAR execution limitation is
pharscript that creates a PHP wrapper script that bootstraps an embedded PHAR
archive. See:
https://github.com/mevdschee/pharscript
How it works
- Parse the entry point with
nikic/php-parserand extract all class references (extends, implements, trait use, type hints,new, etc.). - Resolve class names to files using Composer autoload mappings and
vendor/composermetadata. - Build a dependency graph and topologically sort files so dependencies come before dependents.
- Emit a single output file with
declare(strict_types=1)once, inlined vendorfilesentries, and per-file// file:annotations. - Validate the generated file with
php -l.
Examples
Merge src/index.php and all its dependencies into one file:
php phpfilemerger.phar merge src/index.php
Produce an includeable library (no entry logic):
php phpfilemerger.phar merge src/index.php --exclude-entry --output dist/lib.php
This can be used to include complex dependencies.
Limitations
Where PHAR archives can contain more or less any PHP project, phpfilemerger
expects projects to be prepared with merging in mind. That usually means keeping
class declarations and procedural bootstrap code separate, using PSR-style
autoloading only, avoid usage of include/require and don’t do clever
top-level return/exit/die tricks. These constraints do not conflict with
common best practices for well-structured PHP libraries, but are a strict
requirement for producing a merged single-file output with phpfilemerger.
- Dynamic class loading —
new $className()and similar dynamic patterns cannot be statically analyzed and will not be automatically included. - Multiple namespaces — files using more than one namespace block may not be handled correctly.
- File inclusions — any
include/requireother thanvendor/autoload.phpwill trigger a warning and be skipped, since relative paths would break in the merged file. - Top-level returns — these are disallowed in dependency files (they would abort execution of the entire merged file) and will cause an error.
Self compilation
Note that phpfilemerger project itself cannot be compiled into a single-file
output (yet). The essential nikic/php-parser package uses a lot of
include/require and even contains a top-level return. Attempting to merge
the tool’s own source results in errors telling you which files offend the
rules:
git clone https://github.com/mevdschee/phpfilemerger.git
cd phpfilemerger
composer install
php src/index.php merge src/index.php
This show that some complex projects are not easily turned into a single PHP file with this tool. If you want to help improve this tool, this is a great place to start!
See: https://github.com/mevdschee/phpfilemerger
Enjoy!