\n";
}
}
return implode("\n", $rows);
}
private function getCompiledDefaults(): array
{
// Common defaults shared across PHP 8.2–8.5
$defaults = [
'opcache.blacklist_filename' => '',
'opcache.dups_fix' => '0',
'opcache.enable' => '1',
'opcache.enable_cli' => '0',
'opcache.enable_file_override' => '0',
'opcache.error_log' => '',
'opcache.file_cache' => '',
'opcache.file_cache_consistency_checks' => '1',
'opcache.file_cache_only' => '0',
'opcache.file_update_protection' => '2',
'opcache.force_restart_timeout' => '180',
'opcache.huge_code_pages' => '0',
'opcache.interned_strings_buffer' => '8',
'opcache.jit_bisect_limit' => '0',
'opcache.jit_blacklist_root_trace' => '16',
'opcache.jit_blacklist_side_trace' => '8',
'opcache.jit_debug' => '0',
'opcache.jit_hot_func' => '127',
'opcache.jit_hot_loop' => '64',
'opcache.jit_hot_return' => '8',
'opcache.jit_hot_side_exit' => '8',
'opcache.jit_max_exit_counters' => '8192',
'opcache.jit_max_loop_unrolls' => '8',
'opcache.jit_max_polymorphic_calls' => '2',
'opcache.jit_max_recursive_calls' => '2',
'opcache.jit_max_recursive_returns' => '2',
'opcache.jit_max_root_traces' => '1024',
'opcache.jit_max_side_traces' => '128',
'opcache.jit_prof_threshold' => '0.005',
'opcache.lockfile_path' => '/tmp',
'opcache.log_verbosity_level' => '1',
'opcache.max_accelerated_files' => '10000',
'opcache.max_file_size' => '0',
'opcache.max_wasted_percentage' => '5',
'opcache.memory_consumption' => '128',
'opcache.opt_debug_level' => '0',
'opcache.optimization_level' => '0x7FFEBFFF',
'opcache.preferred_memory_model' => '',
'opcache.preload' => '',
'opcache.preload_user' => '',
'opcache.protect_memory' => '0',
'opcache.record_warnings' => '0',
'opcache.restrict_api' => '',
'opcache.revalidate_freq' => '2',
'opcache.revalidate_path' => '0',
'opcache.save_comments' => '1',
'opcache.use_cwd' => '1',
'opcache.validate_permission' => '0',
'opcache.validate_root' => '0',
'opcache.validate_timestamps' => '1',
];
if (PHP_MAJOR_VERSION !== 8) {
return $defaults;
}
$minor = PHP_MINOR_VERSION;
// Per-version overrides
if ($minor <= 2) {
// PHP 8.2: jit=tracing, jit_buffer_size=0, has consistency_checks, no jit_max_trace_length
$defaults['opcache.consistency_checks'] = '0';
$defaults['opcache.jit'] = 'tracing';
$defaults['opcache.jit_buffer_size'] = '0';
} elseif ($minor === 3) {
// PHP 8.3: jit=tracing, jit_buffer_size=0, adds jit_max_trace_length
$defaults['opcache.jit'] = 'tracing';
$defaults['opcache.jit_buffer_size'] = '0';
$defaults['opcache.jit_max_trace_length'] = '1024';
} elseif ($minor === 4) {
// PHP 8.4: jit=disable, jit_buffer_size=64M
$defaults['opcache.jit'] = 'disable';
$defaults['opcache.jit_buffer_size'] = '64M';
$defaults['opcache.jit_max_trace_length'] = '1024';
} else {
// PHP 8.5+: same as 8.4 but adds file_cache_read_only, jit_hot_loop=61
$defaults['opcache.jit'] = 'disable';
$defaults['opcache.jit_buffer_size'] = '64M';
$defaults['opcache.jit_max_trace_length'] = '1024';
$defaults['opcache.file_cache_read_only'] = '0';
$defaults['opcache.jit_hot_loop'] = '61';
}
return $defaults;
}
private static function getConfigDocs(): array
{
return [
'opcache.enable' => 'Enables the opcode cache. When disabled, code is not optimised or cached. Cannot be enabled at runtime through ini_set(), only disabled.',
'opcache.enable_cli' => 'Enables the opcode cache for the CLI version of PHP.',
'opcache.memory_consumption' => 'The size of the shared memory storage used by OPcache, in megabytes. The minimum value is 8.',
'opcache.interned_strings_buffer' => 'The amount of memory used to store interned strings, in megabytes.',
'opcache.max_accelerated_files' => 'The maximum number of keys (and therefore scripts) in the OPcache hash table. The actual value used will be the first number in the set of prime numbers that is bigger than the number configured. The minimum value is 200. The maximum value is 1000000.',
'opcache.max_wasted_percentage' => 'The maximum percentage of wasted memory that is allowed before a restart is scheduled. The maximum value is 50.',
'opcache.use_cwd' => 'If enabled, OPcache appends the current working directory to the script key, thus eliminating possible collisions between files with the same base name.',
'opcache.validate_timestamps' => 'If enabled, OPcache will check for updated scripts every opcache.revalidate_freq seconds. When this directive is disabled, you must reset OPcache manually via opcache_reset(), opcache_invalidate() or by restarting the web server for changes to the filesystem to take effect.',
'opcache.revalidate_freq' => 'How often to check script timestamps for updates, in seconds. 0 will result in OPcache checking for updates on every request.',
'opcache.revalidate_path' => 'If disabled, existing cached files using unresolved include_path will be reused. Thus, if a file with the same name is elsewhere in the include_path, it won\'t be found.',
'opcache.save_comments' => 'If disabled, all documentation comments will be discarded from the opcode cache to reduce the size of the optimised code. Disabling may break applications and frameworks that rely on comment parsing for annotations, including Doctrine, Zend Framework 2, and PHPUnit.',
'opcache.fast_shutdown' => 'If enabled, a fast shutdown sequence is used that doesn\'t free each allocated block, but relies on the Zend Engine memory manager to deallocate the entire set of request variables en masse. Removed in PHP 7.2.0; integrated into PHP itself.',
'opcache.enable_file_override' => 'When enabled, the opcode cache will be checked for whether a file has already been cached when file_exists(), is_file() and is_readable() are called. This may increase performance in applications that check the readability of PHP scripts, but risks returning stale data if opcache.validate_timestamps is disabled.',
'opcache.optimization_level' => '
A bitmask integer that controls which optimisation passes the OPcache optimizer executes when compiling PHP scripts. The default value 0x7FFEBFFF enables all safe passes. Each bit enables one pass:
'
. '
'
. '
Bit
Pass
Description
'
. '
0x0001
Pre-evaluate constant operations
Evaluates constant expressions at compile time (e.g. 1+2 becomes 3, string concatenations of literals are merged).
'
. '
0x0004
Jump optimization
Converts sequences of jumps into direct jumps to the final target, eliminating unnecessary branches and unreachable code after unconditional jumps.
'
. '
0x0008
Optimize function calls
Replaces calls to certain internal functions with faster opcode equivalents (e.g. strlen(), defined(), call_user_func()).
'
. '
0x0010
CFG-based optimization
Builds a control flow graph and performs block-level optimisations: merging adjacent blocks, removing empty blocks, and simplifying conditional branches.
'
. '
0x0020
Data flow analysis
SSA-based optimisations using Static Single Assignment form: type inference, range propagation, and value numbering to eliminate redundant computations.
'
. '
0x0040
Call graph analysis
Analyses call relationships between functions to enable inter-procedural optimisations such as determining which functions can be inlined.
'
. '
0x0080
SCCP (constant propagation)
Sparse Conditional Constant Propagation — propagates known constant values through the program and eliminates branches that can never be taken.
'
. '
0x0100
Optimize temp variables
Reduces the number of temporary variables used by reusing slots, decreasing the memory footprint of each compiled function.
'
. '
0x0200
NOP removal
Removes NOP (no-operation) instructions left behind by earlier optimisation passes, compacting the opcode array.
'
. '
0x0400
Compact literals
De-duplicates identical literal values (strings, integers, floats) within a function, reducing memory usage in the literal table.
'
. '
0x0800
Adjust used stack
Recalculates the actual stack size needed by each function after optimisation, freeing over-allocated stack slots.
'
. '
0x1000
Compact unused variables
Removes variables that are assigned but never read, and compacts the remaining variable table to close gaps.
'
. '
0x2000
Dead code elimination
Removes instructions whose results are never used, including assignments to variables that are overwritten before being read.
'
. '
0x4000
Constant substitution ⚠
Replaces references to constants defined via define() with their values. Marked unsafe because it assumes constants are never redefined; can break code using conditionally defined constants.
'
. '
0x8000
Trivial function inlining
Inlines very simple functions that just return a constant or a parameter, eliminating function call overhead entirely.
'
. '
0x10000
Ignore operator overloading ⚠
Allows the optimizer to treat arithmetic operators as pure operations. Marked unsafe because classes like GMP and BCMath overload operators, and this pass may incorrectly optimize those expressions.
'
. '
'
. '
⚠ = disabled by default because it may change runtime behaviour in edge cases.
',
'opcache.inherited_hack' => 'This configuration directive is ignored.',
'opcache.dups_fix' => 'This hack should only be enabled to work around "Cannot redeclare class" errors.',
'opcache.blacklist_filename' => 'The location of the OPcache blacklist file. A blacklist file is a text file that contains the names of files that should not be accelerated, one per line. Wildcards are allowed, and prefixes can also be provided. Lines starting with a semicolon are ignored as comments.',
'opcache.max_file_size' => 'The maximum file size that OPcache will cache, in bytes. If this is 0, all files will be cached.',
'opcache.consistency_checks' => 'If non-zero, OPcache will verify the cache checksum every N requests. This should only be enabled when debugging, since it impacts performance. Removed in PHP 8.3.0.',
'opcache.force_restart_timeout' => 'The length of time to wait for a scheduled restart to begin if the cache is not being accessed, in seconds. If the timeout is hit, OPcache assumes that something has gone wrong and will kill the process holding the cache lock to permit a restart.',
'opcache.error_log' => 'OPcache error log. An empty string is treated the same as stderr, and will result in logs being sent to the standard error output (which will be the web server error log in most cases).',
'opcache.log_verbosity_level' => 'Log verbosity level. By default, only fatal errors (level 0) and errors (level 1) are logged. Other levels available are warnings (level 2), info messages (level 3), and debug messages (level 4).',
'opcache.preferred_memory_model' => 'The preferred memory model for OPcache to use. If left empty, OPcache will choose the most appropriate model, which is the correct behaviour in virtually all cases. Possible values include mmap, shm, posix, and win32.',
'opcache.protect_memory' => 'Protects shared memory from unexpected writes while executing scripts. Useful for internal debugging only.',
'opcache.restrict_api' => 'Allows calling OPcache API functions only from PHP scripts whose path starts with the specified string. An empty string means no restriction.',
'opcache.mmap_base' => 'The base address to be used for the shared memory mapping on Windows only. All PHP processes have to map the shared memory into the same address space. Use this setting to fix "Unable to reattach to base address" errors.',
'opcache.file_cache' => 'Enables and sets the second level cache directory. It should improve performance when SHM memory is full, at server restart, or SHM reset. The default empty string disables file-based caching.',
'opcache.file_cache_only' => 'Enables or disables opcode caching in shared memory.',
'opcache.file_cache_consistency_checks' => 'Enables or disables checksum validation when a script is loaded from the file cache.',
'opcache.file_cache_fallback' => 'Implies opcache.file_cache_only=1 for a certain process that failed to reattach to the shared memory. Windows only.',
'opcache.file_update_protection' => 'Prevents caching of files that are less than this number of seconds old. It protects from caching of incompletely updated files. If all file updates on your site are atomic, you may increase performance by setting this to 0.',
'opcache.huge_code_pages' => 'Enables or disables copying of PHP code (text segment) into HUGE PAGES. This should improve performance, but requires appropriate OS configuration.',
'opcache.lockfile_path' => 'Absolute path used to store shared lockfiles (for *nix only).',
'opcache.opt_debug_level' => 'Produces opcode dumps for debugging different stages of optimisations. 0x10000 will output opcodes as the compiler produced them before any optimisation occurs. 0x20000 will output opcodes after optimisation.',
'opcache.validate_permission' => 'Validates the cached file\'s permissions against the current user.',
'opcache.validate_root' => 'Prevents name collisions in chroot\'ed environments. Should be enabled in all chroot\'ed environments to prevent the access to files outside of the chroot.',
'opcache.preload' => 'Specifies a PHP script that is going to be compiled and executed at server start-up, and may preload other files by either including them or using the opcache_compile_file() function. All the entities (e.g. functions and classes) defined in these files will be available to requests out of the box, until the server is shut down.',
'opcache.preload_user' => 'Allows preloading to be run as the specified system user. This is useful for servers that start as root before switching to an unprivileged system user. Preloading as root is not allowed by default for security reasons unless this directive is explicitly set to root.',
'opcache.cache_id' => 'On Windows, all processes running the same PHP SAPI under the same user account having the same cache ID share a single OPcache instance. The value of the cache ID can be freely chosen.',
'opcache.record_warnings' => 'If enabled, OPcache will record compile-time warnings and replay them on the next include, even if it is served from cache.',
'opcache.file_cache_read_only' => 'Enables a read-only file cache. This is mainly useful in CI/Docker-style environments where a pre-warmed cache is mounted read-only.',
'opcache.jit' => 'For typical usage, this should be set to one of these string values: disable, off, tracing/on, or function. For advanced usage, a 4-digit integer CRTO is accepted where each digit controls a specific JIT optimization flag.',
'opcache.jit_buffer_size' => 'The amount of shared memory to reserve for compiled JIT code. A zero value disables the JIT.',
'opcache.jit_debug' => 'A bitmask specifying which JIT debug output to enable. Refer to zend_jit.h for possible values.',
'opcache.jit_bisect_limit' => 'Use a bisect search to debug issues with the JIT by disabling JIT compilation of functions above a specified limit. This requires compilation of a tracing function only, with trigger set to 0 or 1.',
'opcache.jit_prof_threshold' => 'When using the "profile on first request" trigger mode, this threshold determines whether a function is considered hot. The number of calls to the function divided by the number of all calls must be above this threshold.',
'opcache.jit_max_root_traces' => 'Maximum number of root traces. Setting this to 0 disables JIT trace compilation.',
'opcache.jit_max_side_traces' => 'Maximum number of side traces per root trace.',
'opcache.jit_max_exit_counters' => 'Maximum number of side trace exit counters. This limits the total number of side traces there can be (across all root traces).',
'opcache.jit_hot_loop' => 'After how many iterations a loop is considered hot.',
'opcache.jit_hot_func' => 'After how many calls a function is considered hot.',
'opcache.jit_hot_return' => 'After how many returns a return is considered hot.',
'opcache.jit_hot_side_exit' => 'After how many exits a side exit is considered hot.',
'opcache.jit_blacklist_root_trace' => 'Maximum number of attempts to compile a root trace before it is blacklisted.',
'opcache.jit_blacklist_side_trace' => 'Maximum number of attempts to compile a side trace before it is blacklisted.',
'opcache.jit_max_loop_unrolls' => 'Maximum number of attempts to unroll a loop in a side trace.',
'opcache.jit_max_recursive_calls' => 'Maximum number of unrolled recursive call loops.',
'opcache.jit_max_recursive_returns' => 'Maximum number of unrolled recursive return loops.',
'opcache.jit_max_polymorphic_calls' => 'Maximum number of attempts to inline a polymorphic (virtual or interface) call. Calls above this limit are treated as megamorphic and are not inlined.',
'opcache.jit_max_trace_length' => 'Maximum length of a single JIT trace.',
];
}
public function getConfigDataRows(): string
{
$defaults = $this->getCompiledDefaults();
$docs = self::getConfigDocs();
$iniValues = function_exists('ini_get_all') ? (ini_get_all('zend opcache', false) ?: []) : [];
$rows = [];
foreach ($this->configuration['directives'] as $key => $value) {
$changed = false;
if (array_key_exists($key, $defaults)) {
$cur = $iniValues[$key] ?? (string)$value;
$changed = (string)$defaults[$key] !== (string)$cur;
}
$class = $changed ? ' class="changed"' : '';
if ($value === false) {
$value = 'false';
} elseif ($value === true) {
$value = 'true';
}
// Format values with appropriate units
$value = match ($key) {
'opcache.memory_consumption' => $this->sizeForHumans((int)$value),
'opcache.jit_buffer_size' => $this->sizeForHumans((int)$value),
'opcache.interned_strings_buffer'=> ((int)$value) . ' MB',
'opcache.max_wasted_percentage' => number_format((float)$value * 100, 1) . '%',
'opcache.force_restart_timeout' => $value . 's',
'opcache.revalidate_freq' => $value . 's',
'opcache.file_update_protection' => $value . 's',
'opcache.optimization_level' => $this->formatOptimizationLevel((int)$value),
'opcache.jit' => $value === '' ? 'off' : $value,
default => $value,
};
$helpBtn = isset($docs[$key])
? ' ⓘ'
: '';
$rows[] = "
$key$helpBtn
$value
\n";
}
return implode("\n", $rows);
}
public function getBlacklistRows(): string
{
$blacklist = $this->configuration['blacklist'] ?? [];
if (empty($blacklist)) {
return "