You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(cli): check the PHP extensions and functions ee needs before a command runs - #1957
Fixes the core side of #1956: ee now checks the PHP extensions and functions it needs before a command runs, and says what to fix instead of dying with a PHP fatal error.
Problem
On PHP 8 a function listed in disable_functions is removed, so calling it is a fatal Call to undefined function. HestiaCP writes pcntl_* into the CLI php.ini, which made every ee site command fatal (#1956). The installer now refuses such a PHP (EasyEngine/installer#49), but nothing protected a host whose PHP changes later: a panel upgrade that rewrites php.ini, an extension removed by an apt upgrade or a PHP version switch, a Homebrew or manual phar install, or ee cli update.
What changes
New EE\Requirements (php/EE/Requirements.php) lists what ee calls without a guard and checks it with function_exists() / extension_loaded() only, so the check can't hit a disabled function itself. The happy path is about 30 function_exists() calls (a few microseconds); ini files are read only when something is missing.
Runner::check_requirements() runs it first, so it covers every command that already ran the Docker checks (everything except cli, config, help). A missing requirement is an Error: (exit 1) that names:
the disabled functions and the ini files whose disable_functions lists them, how to fix it (remove them for the PHP CLI), and, when it would be read last, a conf.d override that survives tools rewriting php.ini, e.g. /etc/php/8.3/cli/conf.d/99-easyengine.ini with the rest of the list;
missing extensions, with the Debian/Ubuntu package (apt-get install php8.3-curl php8.3-zip).
Optional functions only log a debug line: pcntl_signal (no rollback of an interrupted site command), pcntl_signal_dispatch, shell_exec (ee site share).
ee cli info shows a PHP requirements row (and the full message as a warning) and skips the migration until the requirements are met.
ee cli update only requires what it needs to run the new phar (proc_open, proc_close, stream_select, stream_set_blocking) and warns about the rest, so a host can still update to a fixed release. Downloads already fall back from curl to fsockopen.
ee cli self-uninstall only requires those and pdo_sqlite, and works before any migration created the sites table.
Two calls that ran before any check are gone: init-ee.php uses PHP_OS instead of php_uname(), and the first-run mkdir no longer uses shell_exec().
A failed requirement (including Docker or docker-compose missing) now exits 1 instead of 0 (die became EE::halt( 1 )).
Required vs optional
Required (refuses every command up front) = called without a guard on paths most commands or a main feature hit, so a command would otherwise fail part way:
Optional: pcntl_signal and pcntl_signal_dispatch (site-command degrades gracefully, EasyEngine/site-command#523), shell_exec (only ee site share; passthru and the other shell_exec uses are on the refused remote/vagrant paths). Already guarded: posix_isatty. Not checked: json (Monolog needs it while booting, before any check can run; built into PHP 8) and basic file functions.
On PHP 7 a disabled function only warns and returns null, so ee used to limp on with misleading errors (with exec/proc_open disabled it said Config root: /opt/easyengine is not writable). The check applies the same rule there, which means a PHP 7 host with a required function disabled is now refused with the real reason.
Merge order
Merge and tag EasyEngine/site-command#523 first. This PR treats pcntl as optional, so on its own (with the current site-command) a HestiaCP host would still hit the site-command fatal; the release that ships this should pin the fixed site-command.
Tests
Throwaway ubuntu:24.04 containers with ondrej PHP 8.3 and 7.4 and stubbed Docker, the released 4.13.3 phar vs a phar built from this branch and the site-command fix, with: HestiaCP's own CLI disable_functions line (v-add-web-php:145), Hestia's FPM list applied to the CLI (adds exec, proc_open, …), php_uname disabled, posix disabled (phpdismod), curl and zip disabled, and the suggested conf.d override:
8.3 Hestia CLI list: before ee site list is a fatal in class-ee-site.php:87; after it runs (No sites found!), with PHP requirements: OK (optional, unavailable: pcntl_signal) in ee cli info.
Hestia FPM list: before every command (even cli info) is a fatal on shell_exec() in Runner.php:603 (8.3) or Config root ... is not writable (7.4); after cli info works and other commands get Error: EasyEngine needs the PHP functions exec, proc_open, which PHP 8.3.x (/usr/bin/php8.3) disables with disable_functions in /etc/php/8.3/cli/php.ini. plus the fix.
php_uname disabled: before a fatal in init-ee.php:7 on every command; after cli info works and other commands get the clean error.
posix or curl/zip missing: before site create died in HTML.php:227 (posix) or would fail later; after a clean error with apt-get install php8.3-common / php8.3-curl php8.3-zip.
ee cli update --stable with curl/zip missing or php_uname disabled warns and updates; with proc_open disabled it refuses cleanly. ee cli self-uninstall works with curl/zip missing and refuses cleanly without proc_open.
Unit tests (the local PHPUnit layer, not in this repo yet; PHP 7.4 and 8.5, child PHPs with their own ini files): classification, the ini file and override in the message (including a later conf.d file, inline comments and -d), unknown names, and that check_requirements() refuses before any Docker command.
Behat (fork CI, green): the core suite and the site-command, site-type-php and site-type-wp suites on PHP 7.4–8.5, plus the same suites with HestiaCP's disable_functions on 7.4, 8.3 and 8.5, and a job checking the clean errors with Hestia's FPM list, without curl and with php_uname disabled.
…mmand runs
On PHP 8 a function listed in disable_functions is removed, so calling one is a fatal "Call to undefined function". A host whose CLI php.ini disables functions ee calls (HestiaCP disables pcntl_*; other panels disable exec, proc_open, chown, symlink, curl_exec, ...) or whose PHP lacks an extension got a PHP fatal in the middle of a command instead of an error saying what to fix (EasyEngine#1956).
EE\Requirements lists the extensions and functions ee calls without a guard and checks them with function_exists()/extension_loaded() only, so the check itself can't hit a disabled function. Runner::check_requirements() runs it first, for every command it already covers: a missing requirement is an error naming the extensions or functions, the ini files whose disable_functions lists them, and a fix (remove them for the PHP CLI, with a conf.d override when one would be read last; the Debian/Ubuntu package for a missing extension). Optional functions (pcntl_signal, pcntl_signal_dispatch, shell_exec) only log a debug line.
ee cli info shows a "PHP requirements" row and the error as a warning, and skips the migration until the requirements are met. ee cli update only requires what it needs to run the new phar and warns about the rest, so a host can still update to a fixed release. ee cli self-uninstall only requires the process functions and pdo_sqlite, and works before any migration created the sites table.
Two boot-time calls that ran before any check are gone: init-ee.php uses PHP_OS instead of php_uname(), and the first-run mkdir no longer shells out. A failed requirement now exits 1 instead of 0.
…as PHP does
PHP splits disable_functions on spaces as well as commas, so a space-separated list was read as one name: the functions were reported as missing from the build instead of disabled, and the ini file and override were not named. When no ini file is scanned, the override hint now follows PHP_INI_SCAN_DIR (empty: no hint) instead of the compiled-in directory.
…lable
Requests selects its curl transport when curl_init() and curl_exec() exist, then calls curl_setopt() and curl_version() unchecked, so a PHP that disables one of those died with a fatal error in ee cli update (release lookup and downloads) instead of downloading.
Adds PHP requirement checks to EasyEngine’s command startup, replacing fatal errors with actionable diagnostics for disabled functions and missing extensions.
Changes:
Introduces required and optional PHP capability checks with configuration and installation guidance.
Adds requirement reporting to ee cli info and narrower checks for update and self-uninstall.
Removes early calls to potentially disabled functions and makes requirement failures exit with status 1.
File
Description
php/init-ee.php
Uses PHP_OS instead of calling php_uname() during startup.
php/EE/Runner.php
Checks PHP before Docker, returns failure status, and uses native directory creation.
php/EE/Requirements.php
Defines PHP requirements and generates diagnostics and remediation hints.
php/commands/src/CLI_Command.php
Reports requirements and adjusts update and uninstall checks and handling.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The configuration hint overlooks command-line overrides, and the recovery update path can still fail fatally when curl_setopt is disabled instead of falling back to Fsockopen.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Clarify CLI overrides can keep exec disabled
php/EE/Requirements.php:314
An ini file listing the function does not rule out a command-line override. With disable_functions = exec in php.ini and PHP launched with -d disable_functions=exec, this hint recommends 99-easyengine.ini, but applying that override still leaves exec disabled because -d wins. Qualify the hint and explain that the function must also be removed from any command-line disable_functions option.
Fall back to Fsockopen when curl_setopt is disabled
php/commands/src/CLI_Command.php:313
If curl is loaded but curl_setopt is disabled, ee cli update --stable now passes this reduced check and only warns, then dies during the download. Requests' Curl transport tests only curl_init and curl_exec before selecting itself, but its constructor calls curl_setopt unconditionally; it does not fall back to Fsockopen for this case. Select Fsockopen whenever a function needed by the Curl transport is unavailable, including for the release lookup and both downloads, so the recovery update path does not produce another PHP fatal.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The override hint can recommend an ineffective ini change when -d disable_functions=exec takes precedence, so it must clarify that users need to change the command-line setting.
No open issues 🎉
1 previously missed issue
In code that hasn't changed since last review
Explain that CLI disable_functions overrides php.ini settings
php/EE/Requirements.php:314
If php.ini lists exec and PHP is also started with -d disable_functions=exec, $files is nonempty and this recommends an override that cannot fix the problem. Applying the suggested disable_functions = "" override still leaves exec disabled because -d takes precedence over every ini file. Qualify the hint and tell users to change the command-line setting in this case.
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the core side of #1956: ee now checks the PHP extensions and functions it needs before a command runs, and says what to fix instead of dying with a PHP fatal error.
Problem
On PHP 8 a function listed in
disable_functionsis removed, so calling it is a fatalCall to undefined function. HestiaCP writespcntl_*into the CLIphp.ini, which made everyee sitecommand fatal (#1956). The installer now refuses such a PHP (EasyEngine/installer#49), but nothing protected a host whose PHP changes later: a panel upgrade that rewritesphp.ini, an extension removed by an apt upgrade or a PHP version switch, a Homebrew or manual phar install, oree cli update.What changes
EE\Requirements(php/EE/Requirements.php) lists what ee calls without a guard and checks it withfunction_exists()/extension_loaded()only, so the check can't hit a disabled function itself. The happy path is about 30function_exists()calls (a few microseconds); ini files are read only when something is missing.Runner::check_requirements()runs it first, so it covers every command that already ran the Docker checks (everything exceptcli,config,help). A missing requirement is anError:(exit 1) that names:disable_functionslists them, how to fix it (remove them for the PHP CLI), and, when it would be read last, a conf.d override that survives tools rewritingphp.ini, e.g./etc/php/8.3/cli/conf.d/99-easyengine.iniwith the rest of the list;apt-get install php8.3-curl php8.3-zip).pcntl_signal(no rollback of an interrupted site command),pcntl_signal_dispatch,shell_exec(ee site share).ee cli infoshows aPHP requirementsrow (and the full message as a warning) and skips the migration until the requirements are met.ee cli updateonly requires what it needs to run the new phar (proc_open,proc_close,stream_select,stream_set_blocking) and warns about the rest, so a host can still update to a fixed release. Downloads already fall back from curl to fsockopen.ee cli self-uninstallonly requires those andpdo_sqlite, and works before any migration created the sites table.init-ee.phpusesPHP_OSinstead ofphp_uname(), and the first-runmkdirno longer usesshell_exec().diebecameEE::halt( 1 )).Required vs optional
Required (refuses every command up front) = called without a guard on paths most commands or a main feature hit, so a command would otherwise fail part way:
proc_open,proc_close,stream_select,stream_set_blockingEE::exec()/EE::launch()exec,escapeshellargEE_DOCKER::container_status(), every shell commandchown,chgrp,symlinkupdate --php, v4.13.3 container migrationsdisk_free_space,getmypid,php_uname,gethostname,parse_ini_stringcurl_init,curl_setopt,curl_exec,curl_getinfo,curl_error,curl_errno,curl_close,curl_versionopenssl_x509_parse,openssl_x509_check_private_key,openssl_pkey_get_privateposix_geteuid,posix_getegid,posix_getpwuidgzopen,gzgets,gzwrite,gzeof,gzclosePDO,ZipArchiveupdate --php, admin-toolsOptional:
pcntl_signalandpcntl_signal_dispatch(site-command degrades gracefully, EasyEngine/site-command#523),shell_exec(onlyee site share;passthruand the othershell_execuses are on the refused remote/vagrant paths). Already guarded:posix_isatty. Not checked: json (Monolog needs it while booting, before any check can run; built into PHP 8) and basic file functions.On PHP 7 a disabled function only warns and returns null, so ee used to limp on with misleading errors (with
exec/proc_opendisabled it saidConfig root: /opt/easyengine is not writable). The check applies the same rule there, which means a PHP 7 host with a required function disabled is now refused with the real reason.Merge order
Merge and tag EasyEngine/site-command#523 first. This PR treats pcntl as optional, so on its own (with the current site-command) a HestiaCP host would still hit the site-command fatal; the release that ships this should pin the fixed site-command.
Tests
ubuntu:24.04containers with ondrej PHP 8.3 and 7.4 and stubbed Docker, the released 4.13.3 phar vs a phar built from this branch and the site-command fix, with: HestiaCP's own CLIdisable_functionsline (v-add-web-php:145), Hestia's FPM list applied to the CLI (addsexec,proc_open, …),php_unamedisabled,posixdisabled (phpdismod),curlandzipdisabled, and the suggested conf.d override:ee site listis a fatal inclass-ee-site.php:87; after it runs (No sites found!), withPHP requirements: OK (optional, unavailable: pcntl_signal)inee cli info.cli info) is a fatal onshell_exec()inRunner.php:603(8.3) orConfig root ... is not writable(7.4); aftercli infoworks and other commands getError: EasyEngine needs the PHP functions exec, proc_open, which PHP 8.3.x (/usr/bin/php8.3) disables with disable_functions in /etc/php/8.3/cli/php.ini.plus the fix.php_unamedisabled: before a fatal ininit-ee.php:7on every command; aftercli infoworks and other commands get the clean error.site createdied inHTML.php:227(posix) or would fail later; after a clean error withapt-get install php8.3-common/php8.3-curl php8.3-zip.ee cli update --stablewith curl/zip missing orphp_unamedisabled warns and updates; withproc_opendisabled it refuses cleanly.ee cli self-uninstallworks with curl/zip missing and refuses cleanly withoutproc_open.-d), unknown names, and thatcheck_requirements()refuses before any Docker command.disable_functionson 7.4, 8.3 and 8.5, and a job checking the clean errors with Hestia's FPM list, without curl and withphp_unamedisabled.