embedded-files packages Composer dependencies, PHP fallback files, and
runtime resources into a TypePHP executable. It addresses these deployment
problems:
- a third-party library cannot be compiled by TypePHP AOT;
- Composer autoload must still load that library lazily;
- the release host should not run
composer installor carry a separatevendordirectory; - read-only configuration, templates, certificates, or similar resources must ship with the application.
During the build, TypePHP converts PHP files that were not compiled through
sources into OPcache opcodes and stores every selected file in the executable.
At runtime, require, require_once, and Composer autoload load PHP scripts
from the in-memory opcode table. Other read-only files are served by the
in-memory file table.
Keep embedded-files disabled for development so builds use the workspace
vendor directory directly. Reuse the common configuration through YAML
include, and enable embedding only for release builds.
project.yml:
name: myapp
mode: bin
build-dir: build
sources:
- app.php
- src
ignore:
- src/legacyproject-release.yml:
include: project.yml
optimize: 2
embedded-files:
- vendor
- resources/configBuild the release:
composer install --no-dev --classmap-authoritative
tpc project-release.ymlThe project-root app.php starts Composer autoload in the usual way:
<?php
function main(): void
{
require_once __DIR__ . '/vendor/autoload.php';
$application = new App\Application();
$application->run();
}The resulting executable can run without the project source tree or a disk
vendor directory:
mkdir -p /tmp/myapp-release
cp myapp /tmp/myapp-release/
cd /tmp/myapp-release
./myappHere, standalone means that PHP CLI, Composer installation, and PHP project
files are no longer needed. The executable still has the native dependencies
selected by its build, such as libphp, PHPX, and other shared libraries.
Ship those dependencies in the release package or use a supported static-link
configuration.
The three settings have separate responsibilities:
| Setting | Purpose |
|---|---|
sources |
Select PHP/C/C++ files translated to C++ and native machine code. |
ignore |
Remove files from the sources scan result. |
embedded-files |
Package files verbatim and generate opcodes for PHP files that were not successfully compiled by AOT. |
A file may be selected by both sources and embedded-files:
- a successfully AOT-compiled PHP file uses its native implementation and does not receive a duplicate opcode blob;
- a PHP file excluded by
ignore, or rejected because it uses unsupported AOT syntax, enters the opcode table; ignoredoes not remove files fromembedded-files;.stub.phpfiles remain raw API declarations and never receive executable opcodes.
This lets the application enter sources while the complete vendor tree is
embedded. AOT-compatible code continues to run as machine code, and ZendVM
executes the remaining dependencies lazily.
sources:
- src
- vendor/acme/optimized-package/src
embedded-files:
- vendorThere is no separate exclusion list for embedded-files. List the required
subdirectories or files instead of their common parent when some content must
not be packaged.
The setting is explicitly enabled in a YAML project and must be a list. Each entry may be a file, a directory, or a conditional path. Relative paths use the outermost project YAML directory as their base.
embedded-files:
- vendor
- resources/app.json
- path: resources/windows
if: PHP_OS_FAMILY == "Windows"
- path: resources/php85
if: PHP_VERSION_ID >= 80500Directories are scanned recursively and all regular files are archived. JSON, YAML, templates, and other non-PHP resources retain their original bytes.
Read embedded resources through their original paths:
$config = file_get_contents(__DIR__ . '/resources/app.json');Embedded files are read-only. Logs, caches, uploads, and databases belong in a
separate runtime data directory. Do not rely on glob() or directory iteration
over an embedded directory; read resources through known file paths.
embedded-files is available only for a regular mode: bin build. It is not
available for ext, lib, Nano, WASI, iOS, or Android targets.
The build host needs:
phporphp.exematching the targetlibphp;- Zend OPcache loadable by that CLI;
- the same full PHP version, ZTS/NTS mode, Debug mode, and integer width as the final runtime.
Check the build PHP before compiling:
php -r 'var_dump(PHP_VERSION, PHP_ZTS, extension_loaded("Zend OPcache"), function_exists("opcache_compile_file"));'TypePHP probes OPcache with -n and tries the standard extension locations.
A standalone tpc without the matching PHP CLI and OPcache cannot build an
embedded-files project.
OPcache is only the build-time serializer. The generated program does not need:
- the OPcache extension;
- PHP CLI;
- a Composer installation;
- disk copies of
vendor/autoload.phpor any other embedded file.
Embedded opcodes are tied to the complete PHP version. If the runtime PHP does not match the PHP that generated the opcodes, the program reports the mismatch at startup instead of executing incompatible bytecode.
A first build prints messages similar to:
embedded-files: found 3012 files (2886 PHP)
Generating embedded opcodes for 2886 PHP files
Vendor opcode cache: 0 reused, 2886 to generate
Packed 3012 files and 2886 opcode blobs
A later build can reuse Composer vendor opcodes:
Vendor opcode cache: 2886 reused, 0 to generate
Packed 3012 files and 2886 opcode blobs
Opcode blobs, the archive, and object files live under build-dir/cache.
Generated embedded-opcodes-<name>.cc contains only readable index code; large
file contents are not expanded into C++ arrays.
A PHP file that OPcache cannot compile and is not expected to be required is
reported as Skipping non-executable embedded PHP file; its original bytes are
still archived. If the application can load that file, fix the build error
instead of ignoring the message.
Persistent opcode caching is enabled only for a directory named vendor whose
root contains autoload.php. Its key includes:
- the vendor root path and directory mtime;
- PHP CLI and OPcache binary signatures;
- PHP version, ZTS/Debug mode, and integer width.
Other embedded-files directories regenerate their opcodes on every build
because the compiler has no reliable invalidation boundary for them.
Editing an existing nested file does not necessarily change the vendor root
mtime. Use --force when this happens:
tpc project-release.yml --forceComposer install or update normally rebuilds root entries and autoload
files, but a release build should still use --force after manual vendor
changes or whenever a cached result is questionable.
Embedding a large vendor tree increases the executable size, linker input, and process startup cost. That fixed cost is visible in PHPT, unit tests, and local workflows that start many short-lived processes. Recommended practice:
- use
project.ymlwithoutembedded-filesfor development and tests; - enable it only in
project-release.yml; - keep the same
build-dirto reuse opcode, archive, and object caches; - reserve
--forcefor vendor changes that did not invalidate the cache.
Composer autoload remains lazy. ZendVM executes a vendor opcode only when code first requests the corresponding class; embedding the complete vendor tree does not execute every PHP file during process startup.
embedded-files changes where files are stored and how PHP scripts are
compiled. It does not replace Composer's class map and PSR-4 rules. Require the
same vendor/autoload.php; the autoloader and later class files are loaded from
the executable's memory tables.
No. The autoload file contains loading rules, while the actual package files
must also be present. Embed the complete vendor directory in normal projects.
No. PHPX provides the decoding and execution integration linked into the program. The OPcache extension is needed only to generate opcodes at build time.
Do not treat it as encryption or source protection. The archive includes the original bytes of selected files to support ordinary file reads. Someone who can analyze the executable may still extract them.
embedded-files packages PHP files; it does not embed implementations such as
curl or pdo_mysql. Declare them through ext-deps or
extension-dependencies and provide them in the target runtime.
ext-deps:
- curl
- pdo_mysql