Skip to content

Latest commit

 

History

History
288 lines (211 loc) · 9.09 KB

File metadata and controls

288 lines (211 loc) · 9.09 KB

Embedding PHP Dependencies in an Executable

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 install or carry a separate vendor directory;
  • 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.

Recommended setup

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/legacy

project-release.yml:

include: project.yml
optimize: 2

embedded-files:
  - vendor
  - resources/config

Build the release:

composer install --no-dev --classmap-authoritative
tpc project-release.yml

The 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
./myapp

Here, 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.

Relationship between sources, ignore, and embedded-files

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;
  • ignore does not remove files from embedded-files;
  • .stub.php files 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:
  - vendor

There 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.

Configuration syntax

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 >= 80500

Directories 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.

Build requirements

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:

  1. php or php.exe matching the target libphp;
  2. Zend OPcache loadable by that CLI;
  3. 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.php or 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.

Build output and logs

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.

Vendor opcode cache

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 --force

Composer 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.

Performance and release guidance

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.yml without embedded-files for development and tests;
  • enable it only in project-release.yml;
  • keep the same build-dir to reuse opcode, archive, and object caches;
  • reserve --force for 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.

Frequently asked questions

Why is Composer autoload still required?

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.

Can only vendor/autoload.php be embedded?

No. The autoload file contains loading rules, while the actual package files must also be present. Embed the complete vendor directory in normal projects.

Does the runtime host need OPcache?

No. PHPX provides the decoding and execution integration linked into the program. The OPcache extension is needed only to generate opcodes at build time.

Does this protect PHP source code?

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.

What if vendor code depends on another PHP extension?

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