Technical Documentation: php/php-src
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (`README.md`)
The PHP Interpreter
PHP is a popular general-purpose scripting language that is especially suited to
web development. Fast, flexible and pragmatic, PHP powers everything from your
blog to the most popular websites in the world.
PHP is distributed under the Modified BSD License
(SPDX-License-Identifier: BSD-3-Clause).
Documentation
The PHP manual is available at php.net/docs.
Installation
Prebuilt packages and binaries
Prebuilt packages and binaries can be used to get up and running fast with PHP.
For Windows, the PHP binaries can be obtained from
windows.php.net. After extracting the archive the*.exe files are ready to use.
For other systems, see the installation chapter.
Building PHP source code
For Windows, see Build your own PHP on Windows.
For a minimal PHP build from Git, you will need autoconf, bison, and re2c. For
a default build, you will additionally need libxml2 and libsqlite3.
On Ubuntu, you can install these using:
sudo apt install -y pkg-config build-essential autoconf bison re2c libxml2-dev libsqlite3-devOn Fedora, you can install these using:
sudo dnf install re2c bison autoconf make ccache libxml2-devel sqlite-develOn MacOS, you can install these using brew:
brew install autoconf bison re2c libiconv libxml2 sqliteor with MacPorts:
sudo port install autoconf bison re2c libiconv libxml2 sqlite3Generate configure:
./buildconfConfigure your build. --enable-debug is recommended for development, see./configure --help for a full list of options.
# For development
./configure --enable-debug
# For production
./configureBuild PHP. To speed up the build, specify the maximum number of jobs using the-j argument:
make -j4The number of jobs should usually match the number of available cores, which
can be determined using nproc.
Testing PHP source code
PHP ships with an extensive test suite, the command make test is used after
successful compilation of the sources to run this test suite.
Tests run in parallel by default, using up to 10 detected logical processors.
Set -jN in TEST_PHP_ARGS or TESTS to override the worker count:
make TEST_PHP_ARGS=-j4 testThis runs make test with a maximum of 4 concurrent jobs. Alternatively,
use -j1 to run tests sequentially.
Use the TEST_PHP_ARGS or TESTS variable to test only specific directories:
make TESTS=tests/lang/ testThe qa.php.net site provides more detailed info about
testing and quality assurance.
Installing PHP built from source
After a successful build (and test), PHP may be installed with:
make installDepending on your permissions and prefix, make install may need superuser
permissions.
PHP extensions
Extensions provide additional functionality on top of PHP. PHP consists of many
essential bundled extensions. Additional extensions can be found on the
PIE Extensions list, and installed with
🥧 PIE, the PHP Installer for Extensions or the
deprecated PHP Extension Community Library - PECL.
Contributing
The PHP source code is located in the Git repository at
github.com/php/php-src. Contributions are most
welcome by forking the repository and sending a pull request.
Discussions are done on GitHub, but depending on the topic can also be relayed
to the official PHP developer mailing list [email protected].
New features require an RFC and must be accepted by the developers. See
Request for comments - RFC and
Voting on PHP features for more information
on the process.
Bug fixes don't require an RFC. If the bug has a GitHub issue, reference it in
the commit message using GH-NNNNNN. Use #NNNNNN for tickets in the old
bugs.php.net bug tracker.
Fix GH-7815: php_uname doesn't recognise latest Windows versions
Fix #55371: get_magic_quotes_gpc() throws deprecation warningSee Git workflow for details on how pull
requests are merged.
Guidelines for contributors
See further documents in the repository for more information on how to
contribute:
- Contributing to PHP
- PHP coding standards
- Internal documentation
- Mailing list rules
- PHP release process
Credits
For the list of people who've put work into PHP, please see the
PHP credits page.
Overview (`Zend/README.md`)
Zend Engine
Zend memory manager
General
The goal of the new memory manager (available since PHP 5.2) is to reduce memory
allocation overhead and speedup memory management.
Debugging
Normal:
sapi/cli/php -r 'leak();'Zend MM disabled:
USE_ZEND_ALLOC=0 valgrind --leak-check=full sapi/cli/php -r 'leak();'Shared extensions
Since PHP 5.3.11 it is possible to prevent shared extensions from unloading so
that valgrind can correctly track the memory leaks in shared extensions. For
this there is the ZEND_DONT_UNLOAD_MODULES environment variable. If set, thenDL_UNLOAD() is skipped during the shutdown of shared extensions.
ZEND_VM
ZEND_VM architecture allows specializing opcode handlers according toop_type fields and using different execution methods (call threading, switch
threading and direct threading). As a result ZE2 got more than 20% speedup on
raw PHP code execution (with specialized executor and direct threading execution
method). As in most PHP applications raw execution speed isn't the limiting
factor but system calls and database calls are, your mileage with this patch
will vary.
Most parts of the old zend_execute.c go into zend_vm_def.h. Here you can find
opcode handlers and helpers. The typical opcode handler template looks like
this:
ZEND_VM_HANDLER(<OPCODE-NUMBER>, <OPCODE>, <OP1_TYPES>, <OP2_TYPES>)
{
<HANDLER'S CODE>
}<OPCODE-NUMBER> is a opcode number (0, 1, ...)<OPCODE> is an opcode name (ZEN_NOP, ZEND_ADD, :)<OP1_TYPES> and <OP2_TYPES> are masks for allowed operand op_types.
Specializer will generate code only for defined combination of types. You can
use any combination of the following op_types UNUSED, CONST, VAR, TMP and CV
also you can use ANY mask to disable specialization according operand's op_type.<HANDLER'S CODE> is a handler's code itself. For most handlers it stills the
same as in old zend_execute.c, but now it uses macros to access opcode
operands and some internal executor data.
You can see the conformity of new macros to old code in the following list:
EXECUTE_DATA
execute_data
ZEND_VM_DISPATCH_TO_HANDLER(<OP>)
return <OP>_helper(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)
ZEND_VM_DISPATCH_TO_HELPER(<NAME>)
return <NAME>(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)
ZEND_VM_DISPATCH_TO_HELPER_EX(<NAME>,<PARAM>,<VAL>)
return <NAME>(<VAL>, ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)
ZEND_VM_CONTINUE()
return 0
ZEND_VM_NEXT_OPCODE()
NEXT_OPCODE()
ZEND_VM_SET_OPCODE(<TARGET>
SET_OPCODE(<TARGET>
ZEND_VM_INC_OPCODE()
INC_OPCOD()
ZEND_VM_RETURN_FROM_EXECUTE_LOOP()
RETURN_FROM_EXECUTE_LOOP()
ZEND_VM_C_LABEL(<LABEL>):
<LABEL>:
ZEND_VM_C_GOTO(<LABEL>)
goto <LABEL>
OP<X>_TYPE
opline->op<X>.op_type
GET_OP<X>_ZVAL_PTR(<TYPE>)
get_zval_ptr(&opline->op<X>, EX(Ts), &free_op<X>, <TYPE>)
GET_OP<X>_ZVAL_PTR_PTR(<TYPE>)
get_zval_ptr_ptr(&opline->op<X>, EX(Ts), &free_op<X>, <TYPE>)
GET_OP<X>_OBJ_ZVAL_PTR(<TYPE>)
get_obj_zval_ptr(&opline->op<X>, EX(Ts), &free_op<X>, <TYPE>)
GET_OP<X>_OBJ_ZVAL_PTR_PTR(<TYPE>)
get_obj_zval_ptr_ptr(&opline->op<X>, EX(Ts), &free_op<X>, <TYPE>)
IS_OP<X>_TMP_FREE()
IS_TMP_FREE(free_op<X>)
FREE_OP<X>()
FREE_OP(free_op<X>)
FREE_OP<X>_IF_VAR()
FREE_VAR(free_op<X>)
FREE_OP<X>_VAR_PTR()
FREE_VAR_PTR(free_op<X>)Executor's helpers can be defined without parameters or with one parameter. This
is done with the following constructs:
ZEND_VM_HELPER(<HELPER-NAME>, <OP1_TYPES>, <OP2_TYPES>)
{
<HELPER'S CODE>
}
ZEND_VM_HELPER_EX(<HELPER-NAME>, <OP1_TYPES>, <OP2_TYPES>, <PARAM_SPEC>)
{
<HELPER'S CODE>
}The executors code is generated by the PHP script zend_vm_gen.php. It useszend_vm_def.h and zend_vm_execute.skl as input and produceszend_vm_opcodes.h and zend_vm_execute.h. The first file is a list of opcode
definitions. It is included from zend_compile.h. The second one is an executor
code itself. It is included from zend_execute.c.
zend_vm_gen.php can produce different kind of executors. You can select a
different opcode threading model using --with-vm-kind=CALL|SWITCH|GOTO|HYBRID.
You can disable opcode specialization using --without-specializer.
At last you can debug the executor using the original zend_vm_def.h or the
generated zend_vm_execute.h file. Debugging with the original file requires
the --with-lines option. By default, Zend Engine uses the following
command to generate the executor:
# Default VM kind is HYBRID
php zend_vm_gen.php --with-vm-kind=HYBRIDOverview (`docs/README.md`)
php-src docs
This is the home of the php-src internal documentation, hosted at
php.github.io/php-src/. It is in very early
stages, but is intended to become the primary place where new information about
php-src is documented. Over time, it is expected to replace various mediums
like:
- https://www.phpinternalsbook.com/
- https://wiki.php.net/internals
- Blogs from contributors
How to build
python 3 and pip are required.
cd docs
# Recommended: Initialize and activate a Python virtual environment
pip install --upgrade pip
pip install -r requirements.txt
make htmlThat's it! You can view the documentation under ./build/html/index.html in
your browser.
Formatting
The files in this documentation are formatted using therstfmt tool.
rstfmt -w 100 sourceThis tool is not perfect. It breaks on custom directives, so we might switch to
either a fork or something else in the future.
Overview (`ext/readline/README.md`)
readline
Provides generic line editing, history, and tokenization functions.
See https://www.php.net/manual/en/book.readline.php
Implementation Details
C variables starting with rl_* are declared by the readline library
(or are macros referring to variables from the libedit library).
See http://web.mit.edu/gnu/doc/html/rlman_2.html
This should only be used in the CLI SAPI.
Historically, the code lived in sapi/cli,
but many distributions build readline as a shared extension.
Therefore, that code was split into ext/readline so that this can dynamically
be loaded. With other SAPIs, readline is/should be disabled.
readline_cli.c implements most of the interactive shell(php -a).
Overview (`ext/sodium/README.md`)
libsodium-php
A simple, low-level PHP extension for
libsodium.
Full documentation here:
Using Libsodium in PHP Projects,
a guide to using the libsodium PHP extension for modern, secure, and
fast cryptography.
Overview (`sapi/embed/README.md`)
The embed SAPI
A server application programming interface (SAPI) is the entry point into the Zend Engine. The embed SAPI is a lightweight SAPI for calling into the Zend Engine from C or other languages that have C bindings.
Basic Example
Below is a basic example in C that uses the embed SAPI to boot up the Zend Engine, start a request, and print the number of functions loaded in the function table.
/* embed_sapi_basic_example.c */
#include <sapi/embed/php_embed.h>
int main(int argc, char **argv)
{
/* Invokes the Zend Engine initialization phase: SAPI (SINIT), modules
* (MINIT), and request (RINIT). It also opens a 'zend_try' block to catch
* a zend_bailout().
*/
PHP_EMBED_START_BLOCK(argc, argv)
php_printf(
"Number of functions loaded: %d\n",
zend_hash_num_elements(EG(function_table))
);
/* Close the 'zend_try' block and invoke the shutdown phase: request
* (RSHUTDOWN), modules (MSHUTDOWN), and SAPI (SSHUTDOWN).
*/
PHP_EMBED_END_BLOCK()
}To compile this, we must point the compiler to the PHP header files. The paths to the header files are listed from php-config --includes.
We must also point the linker and the runtime loader to the libphp.so shared lib for linking PHP (-lphp) which is located at $(php-config --prefix)/lib. So the complete command to compile ends up being:
$ cc \
$(php-config --includes) \
-L$(php-config --prefix)/lib \
embed_sapi_basic_example.c \
-lphp \
-Wl,-rpath,$(php-config --prefix)/lib:memo: The embed SAPI is disabled by default. In order for the above example to compile, PHP must be built with the embed SAPI enabled. To see what SAPIs are installed, run
php-config --php-sapis. If you don't seeembedin the list, you'll need to rebuild PHP with./configure --enable-embed. The PHP shared librarylibphp.sois built when the embed SAPI is enabled.
If all goes to plan you should be able to run the program.
$ ./a.out
Number of functions loaded: 1046Function call example
The following example calls mt_rand() and var_dump()s the return value.
#include <main/php.h>
#include <ext/standard/php_var.h>
#include <sapi/embed/php_embed.h>
int main(int argc, char **argv)
{
PHP_EMBED_START_BLOCK(argc, argv)
zval retval = {0};
zend_fcall_info fci = {0};
zend_fcall_info_cache fci_cache = {0};
zend_string *func_name = zend_string_init(ZEND_STRL("mt_rand"), 0);
ZVAL_STR(&fci.function_name, func_name);
fci.size = sizeof fci;
fci.retval = &retval;
if (zend_call_function(&fci, &fci_cache) == SUCCESS) {
php_var_dump(&retval, 1);
}
zend_string_release(func_name);
PHP_EMBED_END_BLOCK()
}Execute a PHP script example
<?php
# example.php
echo 'Hello from userland!' . PHP_EOL;#include <sapi/embed/php_embed.h>
int main(int argc, char **argv)
{
PHP_EMBED_START_BLOCK(argc, argv)
zend_file_handle file_handle;
zend_stream_init_filename(&file_handle, "example.php");
if (php_execute_script(&file_handle) == FAILURE) {
php_printf("Failed to execute PHP script.\n");
}
PHP_EMBED_END_BLOCK()
}INI defaults
The default value for 'error_prepend_string' is 'NULL'. The following example sets the INI default for 'error_prepend_string' to 'Embed SAPI error:'.
#include <sapi/embed/php_embed.h>
/* This callback is invoked as soon as the configuration hash table is
* allocated so any INI settings added via this callback will have the lowest
* precedence and will allow INI files to overwrite them.
*/
static void example_ini_defaults(HashTable *configuration_hash)
{
zval ini_value;
ZVAL_NEW_STR(&ini_value, zend_string_init(ZEND_STRL("Embed SAPI error:"), /* persistent */ 1));
zend_hash_str_update(configuration_hash, ZEND_STRL("error_prepend_string"), &ini_value);
}
int main(int argc, char **argv)
{
php_embed_module.ini_defaults = example_ini_defaults;
PHP_EMBED_START_BLOCK(argc, argv)
zval retval;
/* Generates an error by accessing an undefined variable '$a'. */
if (zend_eval_stringl(ZEND_STRL("var_dump($a);"), &retval, "example") == FAILURE) {
php_printf("Failed to eval PHP.\n");
}
PHP_EMBED_END_BLOCK()
}After compiling and running, you should see:
Embed SAPI error:
Warning: Undefined variable $a in example on line 1
NULLThis default value is overwritable from INI files. We'll update one of the INI files (which can be found by running $ php --ini), and set error_prepend_string="Oops!". We don't have to recompile the program, we can just run it again and we should see:
Oops!
Warning: Undefined variable $a in example on line 1
NULLOverview (`sapi/fuzzer/README.md`)
Fuzzing SAPI for PHP
The following ./configure options can be used to enable the fuzzing SAPI, as well as all available fuzzers. If you don't build the exif/json/mbstring extensions, fuzzers for these extensions will not be built.
CC=clang CXX=clang++ \
./configure \
--disable-all \
--enable-fuzzer \
--enable-pic \
--enable-debug-assertions \
--enable-address-sanitizer \
--enable-exif \
--enable-mbstringThe --enable-pic option is required to avoid a linking failure. The --enable-debug-assertions option can be used to enable debug assertions despite the use of a release build.
You can combine fuzzing with --enable-address-sanitizer, --enable-undefined-sanitizer or --enable-memory-sanitizer. The first two options can also be used together.
You will need a recent version of clang that supports the -fsanitize=fuzzer-no-link option.
When running make it creates these binaries in sapi/fuzzer/:
php-fuzz-parser: Fuzzing language parser and compilerphp-fuzz-unserialize: Fuzzing unserialize() functionphp-fuzz-unserializehash: Fuzzing unserialize() for HashContext objectsphp-fuzz-json: Fuzzing JSON parserphp-fuzz-exif: Fuzzingexif_read_data()function (requires --enable-exif)php-fuzz-mbstring: Fuzzingmb_convert_encoding()(requires--enable-mbstring)php-fuzz-mbregex: Fuzzingmb_ereg[i]()(requires --enable-mbstring)php-fuzz-execute: Fuzzing the executorphp-fuzz-function-jit: Fuzzing the function JITphp-fuzz-tracing-jit: Fuzzing the tracing JIT
Some fuzzers have a seed corpus in sapi/fuzzer/corpus. You can use it as follows:
cp -r sapi/fuzzer/corpus/exif ./my-exif-corpus
sapi/fuzzer/php-fuzz-exif ./my-exif-corpusFor the unserialize fuzzer, a dictionary of internal classes should be generated first:
sapi/cli/php sapi/fuzzer/generate_unserialize_dict.php
cp -r sapi/fuzzer/corpus/unserialize ./my-unserialize-corpus
sapi/fuzzer/php-fuzz-unserialize -dict=$PWD/sapi/fuzzer/dict/unserialize ./my-unserialize-corpusFor the unserializehash fuzzer, generate a corpus of initial hash serializations:
sapi/cli/php sapi/fuzzer/generate_unserializehash_corpus.php
cp -r sapi/fuzzer/corpus/unserializehash ./my-unserialize-corpus
sapi/fuzzer/php-fuzz-unserializehash ./my-unserialize-corpusFor the parser fuzzer, a corpus may be generated from Zend test files:
sapi/cli/php sapi/fuzzer/generate_parser_corpus.php
mkdir ./my-parser-corpus
sapi/fuzzer/php-fuzz-parser -merge=1 ./my-parser-corpus sapi/fuzzer/corpus/parser
sapi/fuzzer/php-fuzz-parser -only_ascii=1 ./my-parser-corpusFor the execute, function-jit and tracing-jit fuzzers, a corpus may be generated from any set of test files:
sapi/cli/php sapi/fuzzer/generate_execute_corpus.php ./execute-corpus Zend/tests ext/opcache/tests/jit
sapi/fuzzer/php-fuzzer-function-jit ./execute-corpusFor the mbstring fuzzer, a dictionary of encodings should be generated first:
sapi/cli/php sapi/fuzzer/generate_mbstring_dict.php
sapi/fuzzer/php-fuzz-mbstring -dict=$PWD/sapi/fuzzer/dict/mbstring ./my-mbstring-corpusFor the mbregex fuzzer, you may want to build the libonig dependency with instrumentation. At this time, libonig is not clean under ubsan, so only the fuzzer and address sanitizers may be used.
git clone https://github.com/kkos/oniguruma.git
pushd oniguruma
autoreconf -vfi
./configure CC=clang CFLAGS="-fsanitize=fuzzer-no-link,address -O2 -g"
make
popd
export ONIG_CFLAGS="-I$PWD/oniguruma/src"
export ONIG_LIBS="-L$PWD/oniguruma/src/.libs -l:libonig.a"This will link an instrumented libonig statically into the PHP binary.
Overview (`sapi/litespeed/README.md`)
Introduction
LiteSpeed SAPI module is a dedicated interface for PHP integration with
LiteSpeed Web Server. LiteSpeed SAPI has similar architecture to the FastCGI
SAPI with there major enhancements: better performance, dynamic spawning and PHP
configuration modification through web server configuration and .htaccess
files.
A simple benchmark test ("hello world") shows that PHP with LiteSpeed SAPI has
30% better performance over PHP with FastCGI SAPI, which is nearly twice the
performance that Apache mod_php can deliver.
A major drawback of FastCGI PHP comparing to Apache mod_php is lacking the
flexibilities in PHP configurations. PHP configurations cannot be changed at
runtime via configuration files like .htaccess files or web server's virtual
host configuration. In shared hosting environment, each hosting account will has
its own open_basedir overridden in server configuration to enhance server
security when mod_php is used. Usually, FastCGI PHP is not an option in shared
hosting environment due to lacking of this flexibility. LiteSpeed SAPI is
carefully designed to address this issue. PHP configurations can be modified the
same way as that in mod_php with the same configuration directives.
PHP with LiteSpeed SAPI is highly recommended over FastCGI PHP for PHP scripting
with LiteSpeed web server.
Building PHP with LiteSpeed SAPI
You need to add --enable-litespeed to the configure command to build PHP with
LiteSpeed SAPI, all other SAPI related configure options should be removed.
For example:
./configure --enable-litespeed
makeYou should find an executable called lsphp under sapi/litespeed/ directory
after the compilation succeeds. Copy it to lsws/fcgi-bin/lsphp or wherever you
prefer, if LiteSpeed web server has been configured to run PHP with LiteSpeed
SAPI already, you just need to overwrite the old executable with this one and
you are all set.
Start PHP from command line
Usually, lsphp is managed by LiteSpeed web server in a single server
installation. lsphp can be used in clustered environment with one LiteSpeed web
server at the front, load balancing lsphp processes running on multiple backend
servers. In such environment, lsphp can be start manually from command with
option -b <socket_address>, socket address can be IPv4, IPv6 or Unix Domain
Socket address.
For example:
./lsphp -b [::]:3000have lsphp bind to port 3000 on all IPv4 and IPv6 address,
./lsphp -b *:3000have lsphp bind to port 300 on all IPv4 address,
./lsphp -b 192.168.0.2:3000have lsphp bind to address 192.168.0.2:3000,
./lsphp -b /tmp/lsphp_manual.sockhave lsphp accept request on Unix domain socket /tmp/lsphp_manual.sock.
Using LiteSpeed PHP with LiteSpeed Web Server
Detailed information about how to configure LiteSpeed web server with PHP
support is available from
LiteSpeed website.
Usually, PHP support has been configured out of box, you don't need to change it
unless you want to change PHP interface from FastCGI to LiteSpeed SAPI or vice
versa.
Brief instructions are as follow:
Log into web administration interface, go to 'Server'->'Ext App' tab, add an
external application of type "LSAPI app", "Command" should be set to a shell
command that executes the PHP binary you just built. "Instances" should be
set to "1". Add "LSAPI_CHILDREN" environment variable to match the value of
"Max Connections". More tunable environment variable described below can be
added.Go to 'Server'->'Script Handler' tab, add a script handler configuration: set
'suffix' to 'php', 'Handler Type' to 'LiteSpeed API', 'Handler Name' should
be the name of external application just defined.Click 'Apply Changes' link on the top left of the page, then click
'graceful restart'. Now PHP is running with LiteSpeed SAPI.
Tunings
There are a few environment variables that can be tweaked to control the
behavior of LSAPI application.
LSAPI_CHILDRENorPHP_LSAPI_CHILDREN(default: 0)There are two ways to let PHP handle multiple requests concurrently, Server
Managed Mode and Self Managed Mode. In Server Managed Mode, LiteSpeed web
server dynamically spawn/stop PHP processes, in this mode "Instances" should
match "Max Connections" configuration for PHP external application. To start
PHP in Self Managed Mode, "Instances" should be set to "1", whileLSAPI_CHILDRENenvironment variable should be set to match the value of "Max
Connections" and greater than 1. Web Server will start one PHP process, this
process will start/stop children PHP processes dynamically based on on demand.
IfLSAPI_CHILDRENless or equal to 1, PHP will be started in server managed
mode.Self Managed Mode is preferred because all PHP processes can share one shared
memory block for the opcode cache.Usually, there is no need to set value of
LSAPI_CHILDRENover 100 in most
server environments.LSAPI_AVOID_FORK(default: 0)LSAPI_AVOID_FORKspecifies the policy of the internal process manager in
"Self Managed Mode". When set to 0, the internal process manager will stop and
start children process on demand to save system resource. This is preferred in
a shared hosting environment. When set to 1, the internal process manager will
try to avoid frequently stopping and starting children process. This might be
preferred in a dedicate hosting environment.LSAPI_EXTRA_CHILDREN(default: 1/3 ofLSAPI_CHILDRENor 0)LSAPI_EXTRA_CHILDRENcontrols the maximum number of extra children processes
can be started when some or all existing children processes are in
malfunctioning state. Total number of children processes will be reduced toLSAPI_CHILDRENlevel as soon as service is back to normal. WhenLSAPI_AVOID_FORKis set to 0, the default value is 1/3 ofLSAPI_CHILDREN,
WhenLSAPI_AVOID_FORKis set to 1, the default value is 0.LSAPI_MAX_REQSorPHP_LSAPI_MAX_REQUESTS(default value: 10000)This controls how many requests each child process will handle before it exits
automatically. Several PHP functions have been identified having memory leaks.
This parameter can help reducing memory usage of leaky PHP functions.LSAPI_MAX_IDLE(default value: 300 seconds)In Self Managed Mode, LSAPI_MAX_IDLE controls how long a idle child process
will wait for a new request before it exits. This option help releasing system
resources taken by idle processes.LSAPI_MAX_IDLE_CHILDREN(default value: 1/3 ofLSAPI_CHILDRENorLSAPI_CHILDREN)In Self Managed Mode,
LSAI_MAX_IDLE_CHILDRENcontrols how many idle children
processes are allowed. Excessive idle children processes will be killed by the
parent process immediately. WhenLSAPI_AVOID_FORKis set to 0, the default
value is 1/3 ofLSAPI_CHILDREN, WhenLSAPI_AVOID_FORKis set to 1, the
default value isLSAPI_CHILDREN.LSAPI_MAX_PROCESS_TIME(default value: 300 seconds)In Self Managed Mode,
LSAPI_MAX_PROCESS_TIMEcontrols the maximum processing
time allowed when processing a request. If a child process can not finish
processing of a request in the given time period, it will be killed by the
parent process. This option can help getting rid of dead or runaway child
process.LSAPI_PGRP_MAX_IDLE(default value: FOREVER)In Self Managed Mode,
LSAPI_PGRP_MAX_IDLEcontrols how long the parent
process will wait before exiting when there is no child process. This option
helps releasing system resources taken by an idle parent process.LSAPI_PPID_NO_CHECKBy default a LSAPI application check the existence of its parent process and
exits automatically if the parent process died. This is to reduce orphan
process when web server is restarted. However, it is desirable to disable this
feature, such as when a LSAPI process was started manually from command line.LSAPI_PPID_NO_CHECKshould be set when you want to disable the checking of
existence of parent process. When PHP is started by-boption, it is
disabled automatically.
Compatibility with Apache mod_php
LSAPI PHP supports PHP configuration overridden via web server configuration
as well as .htaccess.
Since 4.0 release apache_response_headers function is supported.
Contact
For support questions, please post to the free support
forum:
For bug report, please send bug report to bug [at] litespeedtech.com.
Overview (`ext/bcmath/libbcmath/README.md`)
The bcmath library
This is a fork of the bcmath library initially created by Phil Nelson in May
2000.
Bcmath is a library of arbitrary precision math routines. These routines, in a
different form, are the routines that to the arbitrary precision calculations
for GNU bc and GNU dc.
This library is provided to make these routines useful in a larger context with
less restrictions on the use of them.
These routines do not duplicate functionality of the GNU gmp library. The gmp
library is similar, but the actual computation is different.
Initial library (version 0.1) has been created in 2000-05-21 and then forked and
bundled into PHP with version 0.2 released in 2000-06-07.
FAQ
Why BCMATH?
The math routines of GNU bc become more generally useful in a library form. By
separating the BCMATH library from GNU bc, GNU bc can be under the GPL and
BCMATH can be under the LGPL.Why BCMATH when GMP exists?
GMP has "integers" (no digits after a decimal), "rational numbers" (stored as
2 integers) and "floats". None of these will correctly represent a POSIX BC
number. Floats are the closest, but will not behave correctly for many
computations. For example, BC numbers have a "scale" that represent the number
of digits to represent after the decimal point. The multiplying two of these
numbers requires one to calculate an exact number of digits after the decimal
point regardless of the number of digits in the integer part. GMP floats have
a "fixed, but arbitrary" mantissa and so multiplying two floats will end up
dropping digits BC must calculate.
Credits
Phil Nelson ([email protected]) wrote bcmath library.
License
The bcmath library is released under the GNU Lesser General Public License v2.1.
Overview (`ext/ldap/tests/README.md`)
The ldap extension tests
To ease testing LDAP-Setups we've created a vagrant-setup.
Prerequisites
You will need vagrant on your box.
Usage
To use it follow these steps:
- Create a
Vagrantfilewith the following content. - Go to that directory and run "vagrant up"
$setup = <<<SETUP
apt-get update
DEBIAN_FRONTEND=noninteractive aptitude install -q -y slapd ldap-utils
export SLAPPASS=`slappasswd -s password`
echo "dn: olcDatabase={1}hdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=nodomain
-
replace: olcRootDN
olcRootDN: dc=admin,dc=nodomain
-
replace: olcRootPW
olcRootPW: ${SLAPPASS}" | ldapmodify -Y EXTERNAL -H ldapi:///
echo "dn: dc=nodomain
objectClass: dcObject
objectClass: organization
o: Example
dc: example
dn: ou=extldap,dc=nodomain
objectClass: organizationalUnit
ou: extldap" | ldapadd -c -x -H ldap://localhost:389 -D "dc=admin,dc=nodomain" -w password
SETUP
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "private_network", ip: "192.168.33.10"
config.vm.provision "shell", inline: $setup
endNow you will have a virtual machine up and running on IP-Address 192.168.10.33
listening on port 369 for incoming LDAP-connections. The machine is already
configured to execute the LDAP-Tests.
The next step is to go into the PHP-Source-directory. Configure and make the
source as appropriate.
Before running the LDAP-Tests you need to set some environment-variables:
export LDAP_TEST_PASSWD="password"
export LDAP_TEST_BASE="ou=extldap,dc=nodomain"
export LDAP_TEST_USER="dc=admin,dc=nodomain"
export LDAP_TEST_HOST=192.168.33.10Now you can run the test-suite by calling make test. To test only the
LDAP-Tests, run make test TESTS=ext/ldap.
CAVEAT: The current setup does not (yet) test secure connections.
Old README
Most tests here rely on the availability of an LDAP server configured with TLS.
Client/Server configuration
OpenLDAP 2.4.31 has been used with the configuration below.
Notes:
A self signed certificate can be generated using:
bashopenssl req -newkey rsa:1024 -x509 -nodes -out server.pem -keyout server.pem -days 3650It is used for testing ldap_start_tls(), which also requires
TLS_REQCERT neverin client configuration.An empty LDAP structure is required for the tests to be PASSed (except for
base and admin)
If you use a debian based distribution, prefer the use of dpkg-reconfigure.
Otherwise you may alter these configuration files:
(/etc/openldap/)slapd.conf
TLSCACertificateFile /etc/openldap/ssl/server.pem
TLSCertificateFile /etc/openldap/ssl/server.pem
TLSCertificateKeyFile /etc/openldap/ssl/server.pem
TLSVerifyClient never
# hdb is used instead of bdb as it enables the usage of referrals & aliases
database hdb
suffix "dc=my-domain,dc=com"
checkpoint 32 30
rootdn "cn=Manager,dc=my-domain,dc=com"
rootpw secret
directory /var/lib/openldap-data
index objectClass eq
authz-regexp
uid=Manager,cn=digest-md5,cn=auth
cn=Manager,dc=my-domain,dc=com(/etc/openldap/)ldap.conf
TLS_REQCERT neverTests configuration
The following environment variables may be defined:
LDAP_TEST_HOST (default: localhost) Host to connect to
LDAP_TEST_PORT (default: 389) Port to connect to
LDAP_TEST_BASE (default: dc=my-domain,dc=com) Base to use. May be the ldap root or a subtree. (ldap_search_variation6 will fail if a subtree is used)
LDAP_TEST_USER (default: cn=Manager,dc=my-domain,dc=com) DN used for binding
LDAP_TEST_SASL_USER (default: Manager) SASL user used for SASL binding
LDAP_TEST_PASSWD (default: secret) Password used for plain and SASL binding
LDAP_TEST_OPT_PROTOCOL_VERSION (default: 3) Version of LDAP protocol to use
LDAP_TEST_SKIP_BIND_FAILURE (default: true) Whether to fail the test or not in case binding failsCredits
- Davide Mendolia ([email protected])
- Patrick Allaert ([email protected])
- Côme Bernigaud ([email protected])
Overview (`ext/lexbor/patches/README.md`)
Lexbor patches
[!IMPORTANT]
This process was largely automated in update-lexbor.sh.
Upon syncing the Lexbor sources, the patches in this directory need to be applied.
The current Lexbor version is 2.7.0.
Overview
This contains the following patch files in mailbox format.
0001-Expose-line-and-column-information-for-use-in-PHP.patch
A PHP specific patch to expose the line and column number to PHP.0002-Track-implied-added-nodes-for-options-use-in-PHP.patch
A PHP specific patch to track implied added nodes for options.0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch
A PHP specific patch to patch utilities and data structure to be able to generate smaller lookup tables.
This patch won't be upstreamed because it breaks generality of those data structures, i.e. it only works
because we only use it for character encoding.0004-Remove-unused-upper-case-tag-static-data.patch
A PHP specific patch to remove unused upper case tag static data. This shrinks the static data size.0005-Shrink-size-of-static-binary-search-tree.patch
A PHP specific patch to shrink the size of the static binary search tree for entities.
This shrinks the static data size and reduces data cache pressure.0006-Patch-out-unused-CSS-style-code.patch
A PHP specific patch to remove CSS style and selector bindings from the Lexbor document.Note for this patch the utilities to generate the tables are also patched.
Make sure to apply on a fresh Lexbor clone and run (inlexbor/utils/encoding):python3 single-byte.pyto generate the tables.
Also run (inlexbor/utils/lexbor/html)python3 tokenizer_entities_bst.pyto generate the static binary search tree for entities.
How to apply
- cd into
ext/lexbor/lexbor git am -3 ../patches/0001-Expose-line-and-column-information-for-use-in-PHP.patchgit am -3 ../patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patchgit am -3 ../patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patchgit am -3 ../patches/0004-Remove-unused-upper-case-tag-static-data.patchgit am -3 ../patches/0005-Shrink-size-of-static-binary-search-tree.patchgit am -3 ../patches/0006-Patch-out-unused-CSS-style-code.patchgit reset HEAD~6# 6 is the number of commits created by the above commands
Overview (`ext/mbstring/libmbfl/README.md`)
libmbfl
This is libmbfl, a streamable multibyte character code filter and converter
library, written by Shigeru Kanemoto.
The original version of libmbfl is developed and distributed at
https://github.com/moriyoshi/libmbfl under the LGPL 2.1 license. See the LICENSE
file for licensing information.
The libmbfl library is bundled with PHP as a fork of the original repository and
is not in sync with the upstream. As
such, the libmbfl directory is directly modified in the php-src repository.
Changelog
October 2017
- Since 2017, it is forked and bundled in the php-src repository. For the list
of changes related to PHP see the PHP NEWS change logs.
Version 1.3.2 August 20, 2011
- Added JISX-0213:2004 based encoding : Shift_JIS-2004, EUC-JP-2004,
ISO-2022-JP-2004 (rui). - Added gb18030 encoding (rui).
- Added CP950 with user user defined area based on Big5 (rui).
- Added mapping for user defined character area to CP936 (rui).
- Added UTF-8-Mobile to support the pictogram characters defined by mobile phone
carrier in Japan (rui).
Version 1.3.1 August 5, 2011
- Added check for invalid/obsolete utf-8 encoding (rui).
Version 1.3.0 August 1, 2011
Added encoding conversion between Shift_JIS and Unicode (6.0 or PUA) for
pictogram characters defined by mobile phone carrier in Japan (rui).Fixed encoding conversion of cp5022x for user defined area (rui).
Added MacJapanese (SJIS-mac) for legacy encoding support (rui).
Backport from PHP 5.2 (rui).
Version 1.1.0 March 02, 2010
- Added cp5022x encoding (moriyoshi)
- Added ISO-2022-JP-MS (moriyoshi)
- Moved to github.com from sourceforge.jp (moriyoshi)
Earlier versions
- 1998/11/10 sgk implementation in C++
- Rewriting with sgk C 1999/4/25.
- 1999/4/26 Implemented sgk input filter. Add filter while estimating kanji
code. - 1999/6 Unicode support.
- 1999/6/22 Changed sgk license to LGPL.
Credits
Marcus Boerger [email protected]
Hayk Chamyan [email protected]
Wez Furlong [email protected]
Rui Hirokawa [email protected]
Shigeru Kanemoto [email protected]
U. Kenkichi [email protected]
Moriyoshi Koizumi [email protected]
Hironori Sato [email protected]
Tsukada Takuya [email protected]
Tateyama [email protected]
Den V. Tsopa
Maksym Veremeyenko [email protected]
Haluk AKIN [email protected]
Overview (`ext/mysqli/tests/README.md`)
The mysqli extension tests
To run the tests, a test database must be created in the MySQL command-line:
CREATE DATABASE test;The test suite will create the necessary tables for testing, and then delete them when testing is complete. Creating a dedicated table prior to running the tests is unnecessary. There are default values for MYSQL_TEST_HOST, MYSQL_TEST_USER, MYSQL_TEST_DB and MYSQL_TEST_PASSWD. If your values differ from the defaults, then environment variables must be set.
# Database host
# Default: localhost
MYSQL_TEST_HOST
# Database port
MYSQL_TEST_PORT
# Database user
# Default: root
MYSQL_TEST_USER
# Database user password
# The default password is empty (no password).
MYSQL_TEST_PASSWD
# Database name
# Default: test
MYSQL_TEST_DB
# Storage engine to use
MYSQL_TEST_ENGINE
# Database server socket
MYSQL_TEST_SOCKETMySQL User Permissions
The MySQL user used to run the tests must have full permissions on the test database.
Overview (`ext/opcache/jit/README.md`)
Opcache JIT
This is the implementation of Opcache's JIT (Just-In-Time compiler),
This converts the PHP Virtual Machine's opcodes into Intermediate
Representation and uses IR - Lightweight JIT Compilation Framework
to produce optimized native code. The necessary part of the IR
Framework is embedded into php-src.
Running tests of the JIT
Then, to test the JIT, e.g. with opcache.jit=tracing, an example command
based on what is used to test in CI:
make test TESTS="-d opcache.jit_buffer_size=16M -d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.protect_memory=1 -d opcache.jit=tracing --repeat 2 --show-diff -j$(nproc) ext/opcache Zend"opcache.jit_buffer_size=16Menables the JIT in tests by providing 16 megabytes of
memory to use with the JIT to test with.opcache.protect_memory=1will detect writing to memory that is meant to be
read-only, which is sometimes the cause of opcache bugs.--repeat 2is optional, but used in CI since some JIT bugs only show up after processing a
request multiple times (the first request compiles the trace and the second executes it)-j$(nproc)runs as many workers to run tests as there are CPUs.ext/opcache/andZendare the folders with the tests to run, in this case opcache
and the Zend engine itself. If no folders are provided, all tests are run.
When investigating test failures such as segmentation faults,
configuring the build of php with --enable-address-sanitizer to enable
AddressSanitizer is often useful.
Some of the time, adding -m --show-mem to the TESTS configuration is also useful to test with valgrind to detect out of bounds memory accesses.
Using valgrind is slower at detecting invalid memory read/writes than AddressSanitizer when running large numbers of tests, but does not require rebuilding php.
Note that the JIT supports 3 different architectures: X86_64, i386, and arm64.
Miscellaneous
How to build 32-bit builds on x86_64 environments
Refer to ../../../.github/workflows/push.yml for examples of
dependencies to install.
If you are running this natively (outside of Docker or a VM):
- Consider running in docker/a VM instead if you are unfamiliar with this.
- Avoid purging packages.
- Avoid
-y- if the package manager warns you that the dependencies conflict
then don't try to force install them.
Prerequisites for 32-bit builds
This assumes you are using a Debian-based Linux distribution and have already
set up prerequisites for regular development.
sudo dpkg --add-architecture i386
sudo apt-get update -y
# As well as anything else from .github/actions/apt-x32/action.yml that you're testing locally
sudo apt-get install \
gcc-multilib g++-multilib \
libxml2-dev:i386 \
libc6:i386Compiling 32-bit builds
This assumes you are using a Debian-based Linux distribution and have already
set up prerequisites for 32-bit development.
export LDFLAGS=-L/usr/lib/i386-linux-gnu
export CFLAGS='-m32'
export CXXFLAGS='-m32'
export PKG_CONFIG=/usr/bin/i686-linux-gnu-pkg-config
./configure --disable-all --build=i686-pc-linux-gnu
make -j$(nproc)Running tests of the JIT on 32-bit builds
See the section "Running tests of the JIT".
Testing the jit with arm64 on x86 computers
https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
may be useful for local development.
Note that this is slower than compiling and testing natively.
# After following steps in https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
cp .gitignore .dockerignore
echo .git >> .dockerignore
docker build --network=host -t php-src-arm64-example -f ext/opcache/jit/Dockerfile.arm64.example .
docker run -it --rm php-src-arm64-exampleThen, the docker image can be used to run tests with make test.
For example, to test ext/opcache in parallel with the tracing JIT enabled:
docker run -it php-src-arms-example make test TESTS="-d opcache.jit_buffer_size=16M -d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.protect_memory=1 -d opcache.jit=tracing --repeat 2 --show-diff -j$(nproc) ext/opcache"Overview (`ext/pdo_dblib/tests/README.md`)
The pdo_mysql extension tests
This extension can be tested using Microsoft's Docker image for SQL Server on Linux:
docker run -e "ACCEPT_EULA=Y" -e "SA_PASSWORD=<YourStrong@Passw0rd>" -p 1433:1433 --name sql1 -h sql1 -d mcr.microsoft.com/mssql/server:2019-latest
docker exec sql1 /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U SA -P "<YourStrong@Passw0rd>" -Q "create login pdo_test with password='password', check_policy=off; create user pdo_test for login pdo_test; grant alter, control to pdo_test;"
# and then from the root of php-src
PDO_DBLIB_TEST_DSN="dblib:host=127.0.0.1;dbname=master;version=7.0" PDO_DBLIB_TEST_USER="pdo_test" PDO_DBLIB_TEST_PASS="password" TESTS=ext/pdo_dblib make testOverview (`ext/pdo_mysql/tests/README.md`)
The pdo_mysql extension tests
You must set the following environment variables to run the tests:
# DSN; For example: mysql:dbname=test;host=localhost;port=3306
PDO_MYSQL_TEST_DSN
# Database host
PDO_MYSQL_TEST_HOST
# Database (schema) name
PDO_MYSQL_TEST_DB
# Database server socket
PDO_MYSQL_TEST_SOCKET
# Storage engine to use
PDO_MYSQL_TEST_ENGINE
# Database user
PDO_MYSQL_TEST_USER
# Database user password
PDO_MYSQL_TEST_PASS
# Database charset
PDO_MYSQL_TEST_CHARSETNOTE: if any of PDO_MYSQL_TEST_[HOST|DB|SOCKET|ENGINE|CHARSET] is part ofPDO_MYSQL_TEST_DSN, the values must match. That is, for example, forPDO_MYSQL_TEST_DSN = mysql:dbname=test you MUST set PDO_MYSQL_TEST_DB=test.
MySQL User Permissions
The MySQL user used to run the tests must have full permissions on the test
database, plus the following additional permissions:
- SUPER: Required to create functions if binary logging is enabled
- SELECT permissions on performance_schema.session_connect_attrs
Overview (`ext/pgsql/tests/README.md`)
The pgsql extension tests
Test scripts assumes:
- PostgreSQL server is installed locally
- There is a PostgreSQL account for the users running the test scripts
- There is database named
test
For instance, if your login name is testuser, you should have PostgreSQL user
account named testuser and grant that user access to the database test.
If you have account and database, type createdb test from command prompt to
create the database to execute the test scripts. By executing the above command
as the same user running the tests you ensure that the user is granted access to
the database.
If you find problems in PostgreSQL extension, please report a
bug.
Overview (`ext/snmp/tests/README.md`)
The snmp extension tests
To enable these tests, you must have:
- PHP compiled with SNMP
--with-snmp - An SNMP server running.
How to test
You need to give credentials with environment vars if default ones are not
suitable (see snmp_include.inc for more info):
SNMP_HOSTNAME : IPv4 of remote SNMP agent
SNMP_HOSTNAME : IPv6 or remote SNMP agent
SNMP_PORT : SNMP port for queries
SNMP_COMMUNITY : community name
SNMP_COMMUNITY_WRITE : community used for write tests (snmpset()).
SNMP_MIBDIR : Directory containing MIBSTo run test suite you may use this command (presuming that you pwd is where this
README file is located):
make -C ../../.. test TESTS="`cd ../../..; /bin/ls -1 ext/snmp/tests/*.phpt | xargs echo`"Running run-tests.php directly will clear your environment and therefore tests
will fail if your SNMP configuration does not fit into default values specified
in snmp_include.inc.
Configuring the SNMPD server
On Linux/FreeBSD
- On Ubuntu, install
snmpdandsnmp-mibs-downloader. (May benet-snmpd
on other distributions.) - Replace config file (by default this is
/etc/snmp/snmpd.confon Linux and/usr/local/etc/snmp/snmpd.confon FreeBSD) withsnmpd.confsupplied.
Before launching daemon make sure that there is no file/var/net-snmp/snmpd.conf. Delete it if exists. Forgetting to do so will fail
SNMPv3 tests.
Place
ext/snmp/tests/bigtestnearsnmpd.conf, tune path to it insnmpd.conf.Launch snmpd (service snmpd start or /etc/init.d/snmpd start). Alternatively
you can start snmpd daemon using following command line:bashsudo snmpd -C -c ./snmpd.conf -f -Le
Overview (`ext/standard/libavifinfo/README.md`)
AVIF-info
There is no compact, reliable way to determine the size of an AVIF image. A
standalone C snippet called
libavifinfo was created to
partially parse an AVIF payload and to extract the width, height, bit depth and
channel count without depending on the full libavif library.
avifinfo.h, avifinfo.c, LICENSE and PATENTS were copied verbatim from:
https://aomedia.googlesource.com/libavifinfo/+/2b924defa4c2cd227540efe164067a8cc913eeba
They can easily be kept up-to-date the same way.
Mailinglist Rules (`docs/mailinglist-rules.md`)
Mailing list rules
This is the first file you should be reading before doing any posts on PHP
mailing lists. Following these rules is considered imperative to the success of
the PHP project. Therefore, expect your contributions to be of much less positive
impact if you do not follow these rules. More importantly you can actually
assume that not following these rules will hurt the PHP project.
PHP is developed through the efforts of a large number of people.
Collaboration is a Good Thing(tm), and mailing lists lets us do this. Thus,
following some basic rules with regards to mailing list usage will:
a. Make everybody happier, especially those responsible for developing PHP
itself.
b. Help in making sure we all use our time more efficiently.
c. Prevent you from making a fool of yourself in public.
d. Increase the general level of good will on planet Earth.
Having said that, here are the organizational rules:
Respect other people working on the project.
Do not post when you are angry. Any post can wait a few hours. Review
your post after a good breather, or a good nights sleep.Make sure you pick the right mailing list for your posting. Please review
the descriptions on the
mailing list overview page. When
in doubt ask a friend or someone you trust on IRC.Make sure you know what you are talking about. PHP is a very large project
that strives to be very open. The flip side is that the core developers
are faced with a lot of requests. Make sure that you have done your
research before posting to the entire developer community.Patches have a much greater chance of acceptance than just asking the
PHP developers to implement a feature for you. For one, it makes the
discussion more concrete, and it shows that the poster put thought and time
into the request.If you are posting to an existing thread, make sure that you know what
previous posters have said. This is even more important the longer the
thread is already.Please configure your email client to use a real name and keep message
signatures to a maximum of 2 lines if at all necessary.
The next few rules are more some general hints:
If you notice that your posting ratio is much higher than that of other
people, double-check the above rules. Try to wait a bit longer before
sending your replies to give other people more time to digest your answers
and more importantly give you the opportunity to make sure that you
aggregate your current position into a single mail instead of multiple
ones.Consider taking a step back from a very active thread now and then. Maybe
talking to some friends and fellow developers will help in understanding
the other opinions better.Do not top post. Place your answer underneath anyone you wish to quote
and remove any previous comment that is not relevant to your post.Do not high-jack threads, by bringing up entirely new topics. Please
create an entirely new thread copying anything you wish to quote into the
new thread.
Finally, additional hints on how to behave inside the virtual community can be
found in RFC 1855.
Happy hacking,
PHP Team
Release Process (`docs/release-process.md`)
PHP Release Process
A release manager's role includes making packaged source code from the canonical
repository available according to the release schedule.
The release schedule for each version is published on the
PHP wiki:
The PHP project publishes builds every two weeks.
We publish general availability (GA) releases for major and minor versions of
PHP on the fourth Thursday of November each year. Following the GA release, we
publish patch-level releases every four weeks, with at least one release
candidate (RC) published two weeks before each patch-level release.
Each major and minor version undergoes a 20-week pre-release cycle before GA
release. The pre-release cycle begins on the second Thursday of July with the
first alpha release of the new major/minor version (usually; count back from the
GA release date). The pre-release cycle consists of at least:
- 3 alpha releases
- 3 beta releases
- 4 release candidates
Feature freeze for the next major/minor occurs with the first beta release.
We refer to alpha, beta, and RC as non-stable releases, while GA are stable.
The process of making packaged source available and announcing availability is
explained in detail below. The process differs slightly for non-stable and
stable releases.
New release managers should review New release manager
checklist at the end of this document. This
section explains the procedures for getting ready to begin work on managing PHP
releases.
General notes and tips
Do not release on Fridays, Saturdays, or Sundays as this gives poor lead
time for downstream consumers adhering to a typical work week.Our general procedure is to release on Thursdays, whenever possible.
Package two days before a release.
If the release is to be on Thursday, package on Tuesday. Think about
timezones as well.Ensure that the relevant tests on CI are green.
💡 Tip
We recommend checking the build status a couple of days before packaging day
to allow enough time to investigate failures, communicate with the authors,
and commit any fixes.Check the CI status for your branch periodically and resolve the failures
ASAP.See more in https://wiki.php.net/rfc/travis_ci.
Follow all steps to the letter.
💡 Tip
When you are unsure about anything, ask a previous RM before proceeding.
Ideally, make sure a previous RM is available to answer questions during
the first few releases of your version. For the steps related to theweb-phpandweb-php-distributionsrepositories, try to have
someone from the webmaster team on hand.Verify the tags to be extra sure everything was tagged properly.
There is a PHP release Docker image https://github.com/sgolemon/php-release
with forks available to help with releases.Communicate with release managers via [email protected].
References to repositories in this document refer to the canonical source
located at https://github.com/php.It might be helpful to name your remote repositories something other than
"origin" to avoid accidentally pushing changes to "origin" with muscle
memory.It might also be helpful to set up conditional includes in your global
~/.gitconfigto set the properuser.name,user.email, anduser.signingKeyvalues to use with your local PHP repositories. See
Conditional Includes For Git Config for more information.Any time you see a placeholder like
php-X.Y.ZRCn, theRCnis not always
a release candidate number. The placeholder could also represent any of:- php-8.4.0alpha1 (initial alpha version)
- php-8.4.0beta2 (one of the beta versions)
- php-8.4.0 (initial GA)
- php-8.4.9 (periodic bugfix or security release)
Ensure you are familiar with our procedure for merging upwards.
Packaging a non-stable release (alpha/beta/RC)
All releases during the pre-release cycle (alpha/beta/RC) leading up to the GA
release for a version are non-stable releases. Following the GA release, all
RCs are non-stable releases.
All non-stable releases follow a similar pattern, though pre-GA releases have
slightly different steps. We'll call attention where the steps differ.
Check that CI is passing (see above).
Run
./scripts/dev/creditsin php-src and commit the changes to the credits
files inext/standard.💬 Hint
It's rare this script will make any changes, so if you rungit diff
and do not see anything, there's no need to panic. That means there are no
changes to the credits files.Check out the release branch for this release from the version branch.
🔶 Non-stable version branches: pre-GA
There is no version branch for alpha or beta releases. Instead, treat the
main branch as the version branch. You will create a local-only release
branch from the main branch. Do not push it!shellgit checkout -b php-X.Y.0alpha1-local-release-branch upstream/masterDuring the first RC release, you will create (and push!) the version
branch for the pre-GA release, e.g.,PHP-8.2. See
"Forking a new version branch" below.
For the last RC release, and the subsequent GA release, follow the post-GA
procedure, but until that point, pre-GA release branches will be created from
this version branch. Again, these release branches are local-only. Do not
push them!shellgit checkout -b php-X.Y.0RC3-local-release-branch upstream/PHP-X.Y💬 Hint
The patch-level version branch for GA (PHP X.Y.0) is created as part of the
last planned RC release (currently PHP X.Y.0RC4). After the last RC is released,
additional bug fixes in the PHP X.Y branch will not be a part of PHP X.Y.0. If a regression
is found that warrants including a fix in PHP X.Y.0, use the same process as
for other patch releases - merge the patch as normal to PHP X.Y, and then cherry-pick
the patch to the patch-level PHP X.Y.0 branch.🔷 Non-stable version branches: post-GA
After GA, you will create (and push) a new patch-level version branch
along with each non-stable release. For example, if you are building a
release for PHP 8.2.8 RC1, you will create thePHP-8.2.8patch-level
version branch from thePHP-8.2version branch.shellgit checkout -b PHP-X.Y.Z upstream/PHP-X.Y git push upstream PHP-X.Y.ZWe will use the patch-level version branch to commit any critical bug or
security fixes before this version's GA.Then, from the patch-level version branch, you will create another
local-only release branch. Do not push this one!shellgit checkout -b php-X.Y.ZRC1-local-release-branch upstream/PHP-X.Y.ZUsing your local-only release branch, bump the version numbers in
main/php_version.h,Zend/zend.h,configure.ac, and possiblyNEWS.The date for NEWS should be the date of the announcement (Thursday),
not the date of the tagging (Tuesday).For examples, see Update versions for PHP 8.1.0beta3 (for a pre-GA
example) or Update versions for PHP 8.1.6RC1 along with
Update NEWS for PHP 8.1.6RC1 (for a post-GA example).⚠️ Important
Do not use abbreviations for alpha or beta. Do not use dashes as
separators.Do this:
c#define PHP_VERSION "7.4.22RC1"Not this:
c#define PHP_VERSION "7.4.22-RC1"🗒 Note
We updateZend/zend.honly when preparing RC and GA releases. We do not
updateZEND_VERSIONfor alpha or beta releases.🚨 API version bump for pre-GA
When releasing the first release candidate of a pre-GA release, you must
also bump the API version numbers inZend/zend_extensions.h,Zend/zend_modules.h, andmain/php.h. See Prepare for PHP 8.1.0RC1,
for example.The API versions between the alpha, beta, and X.Y.0RCn releases may remain
the same, or be bumped as little as possible because PHP extensions need to
be rebuilt with each bump.Do not bump the API versions after RC1.
Compile and run
make test, with and without ZTS (Zend Thread Safety), using
the correct Bison and re2c versions, e.g., for PHP 8.5, Bison 3.0.0 and re2c
1.0.3 are required, as a minimum.For example:
shell# With ZTS make distclean || \ ./buildconf --force \ && ./configure --enable-zts --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v # Without ZTS make distclean || \ ./buildconf --force \ && ./configure --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -vAfter each build, check the output of
./sapi/cli/php -vto ensure the
versions match the release.If all is correct, commit the changes to your local-only release branch.
shellgit add -p git commit --gpg-sign=YOURKEYID -m "Update versions for PHP X.Y.ZRCn"Tag your local-only release branch with the release version.
shellgit tag -s -u YOURKEYID php-X.Y.ZRCn -m "Tag for php-X.Y.ZRCn"🔸 For pre-GA releases only, switch back to the
master(for alphas and betas),
orPHP-X.Y(for RCs), and update aNEWSfor the new version. See
Update NEWS for PHP 8.2.0 alpha2 and Update NEWS for PHP 8.2.0RC6 for a real example.Commit the changes to the
master(orPHP-X.Y) branch.shellgit add -p git commit --gpg-sign=YOURKEYID -m "[ci skip] Update NEWS for PHP X.Y.0 alpha2"The NEWS is updated at the start of the cycle for the next tag, e.g.
Update NEWS for PHP 8.2.0 alpha2 was sent as part of tagging 8.2.0 alpha 1.🔷 For post-GA releases, and the final pre-GA RC switch back to the version branch for
your release (e.g.,PHP-8.2) and bump the version numbers inmain/php_version.h,Zend/zend.h,configure.acandNEWS. This prepares
the version branch for the next version.For example, if the RC is
8.2.1RC1then the version numbers in the version
branch should be bumped to8.2.2-dev. We do this regardless of whether we
build a new RC to make sureversion_compare()works correctly. See
PHP 8.3 is now for PHP 8.3.21-dev commit for a real example.Commit the changes to the version branch.
shellgit add -p git commit --gpg-sign=YOURKEYID -m "PHP-X.Y is now for PHP X.Y.Z-dev"💡 Tip
Version branches (e.g.PHP-8.1) will always have version numbers inmain/php_version.h,Zend/zend.h, andconfigure.acthat end in-dev.
Patch-level version branches (e.g.PHP-8.1.7) will also always have
version numbers that end in-devin these files. The main branch (i.e.master) will always have version numbers that end in-devin these
files.Only release tags should have version numbers in these files that do not
end in-dev(e.g.,8.1.7,8.1.7RC1,8.2.0alpha1, etc.).Do not forget to merge up PHP-X.Y all the way to master.
shellgit switch PHP-X.Y+1 # starting from your release branch git merge PHP-X.Y # repeat # Merge up all the way git switch master git merge PHP-X.Y+n # latest release branchWhen resolving the conflicts, ignore the changes from PHP-X.Y in higher
branches when on PHP.X.Y+1 or master after the merge resulting in the
conflicts.shellgit checkout --ours main/php_version.h Zend/zend.h configure.ac git add main/php_version.h Zend/zend.h configure.ac git merge --continueBe sure to set up a merge driver for the
NEWSfile as described in
the Git FAQ page on the PHP wiki.Push the changes to the
php-src.shellgit push upstream php-X.Y.ZRCn # tag name git push upstream PHP-X.Y.Z # patch-level version branch (beginning with the last RC before GA) git push upstream PHP-X.Y # version branch (post-branch creation only) git push upstream master # version branch (pre-branch creation only)🚨 Attention
Do not push with--tags, as this will push all local tags, including
tags you might not wish to push.Local-only release branches should not be pushed!
Run the following using the release tag to export the tree, create the
configurescript, and build and compress three tarballs (.tar.gz,.tar.bz2and.tar.xz).shell./scripts/dev/makedist php-X.Y.ZRCnRun the following using the release tag and your GPG key ID to sign the
tarballs and save the signatures tophp-X.Y.ZRCn.manifest, which you can
upload to GitHub and include in the announcement emails.shell./scripts/dev/gen_verify_stub X.Y.ZRCn YOURKEYID > php-X.Y.ZRCn.manifestIf you have the GitHub command line tool installed, run the following to
create a public Gist for the manifest file:shellgh gist create --public php-X.Y.ZRCn.manifestOr you may go to https://gist.github.com to create it manually.
Copy the tarballs (using scp, rsync, etc.) to your
public_html/folder on
downloads.php.net.shellscp php-X.Y.ZRCn.tar.* downloads.internal.php.net:~/public_html/💬 Hint
If you do not have apublic_htmldirectory, create it and set its
permissions to0755.Now the tarballs and signatures may be found at
https://downloads.php.net/~yourname/, e.g. https://downloads.php.net/~derick/.Once the release is tagged, contact the [email protected] distribution
list so that Windows binaries can be created. Once those are made, they may
be found at https://windows.php.net/qa/.Here is an example "ready for builds" message to [email protected]:
textSubject: PHP 8.1.6RC1 ready for builds Hi, all! Tag: php-8.1.6RC1 Tarballs: https://downloads.php.net/~ramsey/ Manifest: https://gist.github.com/ramsey/5d73f0717effb6d8d17699381361e4b1 Cheers, Ben << PASTE FULL MANIFEST CONTENTS HERE >>
Announcing a non-stable release (alpha/beta/RC)
Switch to your local clone of the
web-phprepository and update the
information in the$QA_RELEASESarray ininclude/release-qa.php.Follow the documentation in the file for editing the QA release information.
🚨 Attention
For pre-GA releases only, don't commit yet, because you need to add an
announcement with the release. After updating$QA_RELEASES, skip to step 2 below.Add, commit, and push your changes, when finished.
shellgit add -p git commit --gpg-sign=YOURKEYID -m "Announce PHP X.Y.ZRCn" git push upstream master🔶 For pre-GA releases only, add a short notice to
web-phpstating
there is a new release, and highlight the major changes (e.g., security
fixes).To help produce the files for this, use the
bin/createNewsEntrytool. When
you run it, it will ask several questions (see below). For pre-GA non-stable
releases, use only the "frontpage" category.shellcd /path/to/repos/php/web-php ./bin/createNewsEntry # Please type in the title: PHP X.Y.0 Alpha n available for testing # Categories: # 0: PHP.net frontpage news [frontpage] # 1: New PHP release [releases] # 2: Conference announcement [conferences] # 3: Call for Papers [cfp] # Please select appropriate categories, separated with space: 0 # Will a picture be accompanying this entry? no # And at last; paste/write your news item here. # To end it, hit <enter>.<enter> # # [[ Here you will paste the full HTML of the post. ]] # . # git add -p git add public/archive/entries/*.xml git commit --gpg-sign=YOURKEYID -m "Announce PHP X.Y.0RCn" git push upstream masterEach news entry for pre-GA releases will be similar, though we change the
text slightly to indicate progression through the pre-release cycle. For
example, here are all the news posts for the pre-GA releases of PHP 8.1.0:💬 Hint
If you are going to base your language on one of these old announcements,
remember thatqa.php.nethas been replaced with https://www.php.net/release-candidates.phpbugs.php.nethas been replaced with GitHub issues, use
https://github.com/php/php-src/issues/new?template=bug_report.yml
to link directly to the form for creating a new bug report.- Since 8.4 there have only been 4 release candidates for PHP X.Y.0, rather than 6.
- Announce 8.1.0alpha1
- Announce 8.1.0alpha2
- Announce 8.1.0alpha3
- Announce 8.1.0beta1
- Announce 8.1.0beta2
- Announce 8.1.0beta3
- Announce 8.1.0RC1
- Announce 8.1.0RC2
- Announce 8.1.0RC3
- Announce 8.1.0RC4
- Announce 8.1.0RC5
- Announce 8.1.0RC6
⚠️ Important
In your announcement news entry, be sure to include the following text or
text similar to the following:Please DO NOT use this version in production, it is an early test version.
🗒 Note
When a version is in its post-GA phase, we do not post news entries for
non-stable releases.Wait for the php site to update with the new information before
sending announcements. This could take up to an hour.Send separate announcement emails to:
In the announcement message, point out the location of the release and the
possible release date of either the next RC or the final release. Also
include the manifest generated bygen_verify_stubwhen you packaged the
build.Here are a few examples of non-stable release announcement emails:
💬 Hint
If you are going to base your language on one of these old announcements,
remember thatqa.php.nethas been replaced with https://www.php.net/release-candidates.phpbugs.php.nethas been replaced with GitHub issues, use
https://github.com/php/php-src/issues/new?template=bug_report.yml
to link directly to the form for creating a new bug report.- Since 8.4 there have only been 4 release candidates for PHP X.Y.0, rather than 6.
- PHP 8.1.0alpha1 is available for testing
- PHP 8.1.0beta3 available for testing
- PHP 8.1.0RC6 available for testing
- PHP 8.1.7RC1 Ready for testing
🚨 Send separate emails!
Do not send a single email message with all addresses in theTo,Cc,
orBccheaders. If a user replies to one of these messages, we do not
want their email client to automatically send the reply to each list, as
often occurs.💬 Hint
We send emails to the followers of these mailing lists to notify them of
new releases, so they can make sure their projects keep working and can
report any potential bugs that should be fixed before the upcoming GA
release.🔶 For alphas, betas, and pre-GA RCs, coordinate with the
social media team (i.e., Derick and Sergey) to post a toot containing the
release announcement and link to the news entry on php.net.
You can send a PR to toot-together
with highlights from the NEWS file yourself, if you want.We post to @[email protected].
Packaging a stable release
Check out the patch-level version branch for the release.
textgit switch PHP-X.Y.Z💬 Hint
You should have created this branch when packaging the non-stable release
candidate for this version. If it is for a PHP-X.Y.0 version, then the branch
was created as part of the final planned release candidate, PHP-X.Y.0RC4.If the upcoming release is a security release, you will have been informed
about it by the security release manager (SRM) by Tuesday noon (UTC).💬 Hint
If you haven't set up a git remote for the security repo yet, do so:bashgit remote add security [email protected]:php/php-src-security.gitThe SRM will provide you with a branch to merge in your
patch-level version branch.bashgit fetch security git merge security/PHP-X.Y.Z-security git push upstream PHP-X.Y.Z💬 Hint
You do not need to merge this back into PHP-X.Y; the SRM will take care
of it.Run the
./scripts/dev/creditsscript in the patch-level version branch,
and commit the changes in the credits files inext/standard.🗒 Note
It's very rare this will make changes at this point, but we run it here
in case the credits changed as a result of a bug fix that was
cherry-picked into this branch.Create a local-only release branch for this release from the patch-level
version branch.shellgit checkout -b php-X.Y.Z-local-release-branch upstream/PHP-X.Y.ZUsing your local-only release branch, bump the version numbers in
main/php_version.h,Zend/zend.h,configure.ac, and possiblyNEWS.For example, if you're releasing a stable version for PHP 8.1.8, then all
the version numbers in the patch-level version branch should be8.1.8-dev. In your local-only release branch, you will change them all to8.1.8.See Update versions for PHP 8.1.7 and Update NEWS for PHP 8.1.7 for
an example.Compile and run
make test, with and without ZTS (Zend Thread Safety), using
the correct Bison and re2c versions, e.g., for PHP 8.5, Bison 3.0.0 and re2c
1.0.3 are required, as a minimum.For example:
shell# With ZTS make distclean || \ ./buildconf --force \ && ./configure --enable-zts --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -v # Without ZTS make distclean || \ ./buildconf --force \ && ./configure --disable-all --enable-debug --enable-opcache-jit \ && make -j$(nproc) \ && make test TEST_PHP_ARGS="-q -j$(nproc)" \ || ./sapi/cli/php -vAfter each build, check the output of
./sapi/cli/php -vto ensure the
versions match the release.If all is correct, commit the changes to your local-only release branch.
shellgit add -p git commit --gpg-sign=YOURKEYID -m "Update versions for PHP X.Y.Z"Tag your local-only release branch with the release version and push the tag.
shellgit tag -s -u YOURKEYID php-X.Y.Z -m "Tag for php-X.Y.Z" git push upstream php-X.Y.ZRun the following using the release tag to export the tree, create the
configurescript, and build and compress three tarballs (.tar.gz,.tar.bz2and.tar.xz).shell./scripts/dev/makedist php-X.Y.Z💬 Hint
Check if the PEAR files are updated (Phar).💡 Tip
On some systems the behavior of GNU tar can default to produce POSIX
compliant archives with PAX headers. As not every application is
compatible with that format, creation of archives with PAX headers should
be avoided. When packaging on such a system, the GNU tar can be influenced
by defining the environment variableTAR_OPTIONS='--format=gnu'.Run the following using the release tag and your GPG key ID to sign the
tarballs and save the signatures tophp-X.Y.Z.manifest, which you can
upload to GitHub and include in the announcement emails.shell./scripts/dev/gen_verify_stub X.Y.Z YOURKEYID > php-X.Y.Z.manifestIf you have the GitHub command line tool installed, run the following to
create a public Gist for the manifest file:shellgh gist create --public php-X.Y.Z.manifestOr you may go to https://gist.github.com to create it manually.
Switch to your local clone of the
web-php-distributionsrepository and
copy the tarballs and signature files into the repository. Add, commit, and
push them.shellcd /path/to/repos/php/web-php-distributions mv /path/to/repos/php/php-src/php-X.Y.Z.tar.* . git add php-X.Y.Z.tar.* git commit --gpg-sign=YOURKEYID -m "Add tarballs for php-X.Y.Z" git push upstream masterOnce the release is tagged, contact the [email protected] distribution
list so that Windows binaries can be created. Once those are made, they may
be found at https://windows.php.net/qa/.⚠️ Important
Do not send this announcement to any public lists.Here is an example "ready for builds" message to [email protected]:
textSubject: PHP 8.1.6 ready for builds Hi, all! Tag: php-8.1.6 Tarballs: web-php-distributions Manifest: https://gist.github.com/ramsey/432fcf8afcbfb1f1de6c3ab47d82e366 Cheers, Ben << PASTE FULL MANIFEST CONTENTS HERE >>
Announcing a stable release
This steps applies only for releases after PHP-X.Y.0.
Switch to your local clone of
web-phpand add the information for the
previous release toinclude/releases.inc.For example, if you are preparing to announce version 8.2.2, then the
previous release is 8.2.1, so you will add the information for 8.2.1 to this
file. Most of the time, you can do this using thebin/bumpReleasetool.shell./bin/bumpRelease 8 2The first number is the major version, and the second number is the minor
version. In this example, we're bumping the release information for version
8.2. There is no need to provide the patch level.💡 Tip
If this fails for any reason, you can manually copy the information
for the previous release frominclude/version.incintoinclude/releases.inc.Update the version information for the new release in
include/version.inc.Find the part of the
$dataarray that is related to your version (e.g.,$data['8.2']for 8.2.x releases) or create a new section if releasing
PHP-X.Y.0 version, and make the following edits / additions:- Set
versionto the full version number (e.g. '8.2.1') - Set
dateto the release date inj M Yformat (e.g. '5 Jan 2021') - Update the
tagsarray to include'security'if this is a security release - Set the
sha256array with the hashes for each of the release tarballs
- Set
Create the release file and news entry for the new version.
shell./bin/createReleaseEntry -v X.Y.Z -r # --security for security releasesThis will create a release file (i.e.,
public/releases/X_Y_Z.php) and a news
entry file (i.e.,public/archive/entries/YYYY-MM-DD-n.xml), while also
updatingpublic/archive/archive.xml.Within these files, it will generate standard messages for the new version.
You may edit the generated files to expand on the base message, if needed.The edits are necessary for PHP-X.Y.0 version where the format is different.
See for example PHP-8.2 announcementUpdate the ChangeLog file for the given major version (e.g.,
ChangeLog-8.php).If PHP-X.Y.0 is released, modify the
ChangeLog-X.php(whereXis the major
version) file manually first. The$MINOR_VERSIONSfield needs to be extended
with the new version and initial anchor (e.g.<a id="PHP_8_4"></a>if added
for PHP 8.4) added above the first anchor of the previous version.shell./bin/news2html 'https://github.com/php/php-src/raw/php-X.Y.Z/NEWS' 'X.Y.Z' 'public/ChangeLog-X.php'Update the information in the
$QA_RELEASESarray ininclude/release-qa.php.The array probably contains information about the RC released two weeks ago
in preparation for the current release. Since the current release is now GA,
it's time to remove the RC build from the release candidates page.It is sufficient to set the
numberproperty for the release to0to
stop displaying the RC build on the release candidates page. You may also remove the
sha256 hashes for the RC tarballs, but it's not necessary.Review all the changes in
web-php, commit, and push them.shellgit add -p git add public/archive/entries/*.xml public/releases/*.php git commit --gpg-sign=YOURKEYID -m "Announce PHP X.Y.Z" git push upstream masterSee Announce PHP 8.1.6 for an example commit.
🚨 Before sending announcement emails, check to make sure the websites have
synced.
- Make sure the tarballs are available from, e.g.,
https://www.php.net/distributions/php-X.Y.Z.tar.gz - Check the "downloads" page to make sure the new version appears:
https://www.php.net/downloads - Does the news entry show up on the home page? https://www.php.net
- Do the updates to the ChangeLog appear?
e.g., https://www.php.net/ChangeLog-8.php - Is there a release page for the new version?
e.g.,https://www.php.net/releases/X_Y_Z.php - Does the RC for this version still appear on the Release Candidate Builds page?
https://www.php.net/release-candidates.php
Keep in mind it may take up to an hour for the websites to sync.
Please note down the sha256 and the PGP signature (.asc). These must be
included in the release mail.Send separate announcement emails to:
Release announcement emails must include the manifest generated when
packaging the build, along with links to the sources, Windows binaries, and
changelog. Here are a few examples of stable release announcement emails:
⚠️ Important
For standard patch-level releases, we will note "This is a bugfix release."
If it is a security release, we must note "This is a security release."
🚨 Send separate emails!
Do not send a single email message with all addresses in theTo,Cc,
orBccheaders. If a user replies to one of these messages, we do not
want their email client to automatically send the reply to each list, as
often occurs.
- Coordinate with the social media team (i.e., Derick) to
create a PR request
for posting the release announcement to Mastodon. Posts need to be
approved.
Re-releasing the same version or a patch-level (i.e., `-plN`)
While unlikely, there may be times we need to re-release the same version. This
might happen if the tarballs have a corrupted file, for example.
Should this occur before announcing the release, you may choose to delete the
tag and go through the full packaging process again, as described above.
💬 Hint
This is one of the reasons we package releases two days before announcing
them.
If this happens after announcing the release, you may choose to tag, package,
and release a patch-level (i.e., pl) release. If it is not critical and/or
affects a very limited subset of users, then you may choose to wait until the
next release.
If you choose to create a patch-level release, follow these steps:
Commit the new binaries to
web-php-distributionsUpdate $data['X.Y'] in
web-php:/include/version.inc
(X.Y=major.minor release, e.g. '8.0'):versionto the full version number (e.g. '8.0.1-pl1')dateto the release date inj M Yformat (e.g. '9 Jan 2021')tagsarray should includesecurityif this is a security releasesha256array and sub-elements for all SHA256 sums
Add a short notice to
web-phpstating that there is a new release, and
highlight the major important things (security fixes) and when it is
important to upgrade.- Call
php bin/createReleaseEntry -v <version> [ --security ]in your
local web-php checkout.
- Call
Commit all the changes (
include/version.inc,public/archive/archive.xml,public/archive/entries/YYYY-MM-DD-N.xml).Wait an hour or two, then send a mail to [email protected],
[email protected] and [email protected] with a text similar to
the news entry.Please make sure that the mail to php-announce@ is its own completely
separate email. This is to make sure that replies to the announcement on
php-general@ or internals@ will not accidentally hit the php-announce@
mailinglist.
Soft feature freeze
A major/minor version soft feature freeze occurs with the first beta release.
This is a soft feature freeze because features can still be merged with RM
approval.
For any RFCs to be included in the new release, they should be discussed and
have their voting polls closed no later than when the first beta is released.
However, this does not mean the new feature must have a complete implementation
by this date. Such implementation can be merged only with RM approval and must
be done before the hard feature freeze.
As a courtesy to the community, the release managers should remind others about
the upcoming soft feature freeze by posting reminders to
[email protected] at 5 weeks, 4 weeks, 3 weeks, 2 weeks, and 1 week prior
to this feature freeze. This is a recommendation and the intervals may vary
based on workload. The reminder should also contain a note with dates for
the last allowed RFC to start voting.
Hard feature freeze
A major/minor version hard feature freeze occurs with the first RC release.
Specifically, it occurs when the first RC release is packaged, which means the
hard feature freeze occurs two days before the first RC release.
The hard feature freeze for php-src means that we will not accept any new
features after the date of the hard feature freeze.
Following the hard feature freeze, the focus of work for the new version will
be on fixing bugs, writing tests, and preparing documentation for all accepted
features.
Forking a new version branch
When the new version has reached the first RC, it is time to create a new
version branch. This frees up the main branch (i.e., master) for any new
feature development that cannot go into the new version.
One week prior to tagging
X.Y.0RC1, warn internals@ that your version's
branch is about to be created. Be specific about when the branch creation
will occur. For example: https://news-web.php.net/php.internals/99864Just prior to tagging
X.Y.0RC1, create the new version branch locally,
i.e.PHP-X.Y.Add a commit on the main branch (i.e.,
master) after the branch point.This commit should:
- clear the
NEWS,UPGRADING, andUPGRADING.INTERNALSfiles; - update the version numbers in
configure.ac,main/php_version.h,Zend/zend.h, andwin32/build/confutils.js; and - update the API version numbers in
Zend/zend_extensions.h,Zend/zend_modules.h, andmain/php.h.
See Prepare for PHP 8.2 and Prepare for PHP 8.2 (bis) for an example
of what this commit should include.💬 Hint
The API version numbers inZend/zend_extensions.h,Zend/zend_modules.h, andmain/php.hinmasterneed to be greater than the ones in the newPHP-X.Y
branch. Generally, use theYYYYMMDDdate for the Thursday as the value for thePHP-X.YAPI numbers, and for the Friday as the value for themasterAPI
numbers. This ensures thatmasteris always considered newer than the version branch.- clear the
Push the new version branch and the changes to the
masterbranch, with an
appropriate commit message (e.g., "master is now for PHP 8.3.0-dev").Immediately notify internals@ of the new branch and advise on the new merging
order. For example: https://news-web.php.net/php.internals/99903Update
web-php:git.phpand https://wiki.php.net/vcs/gitworkflow to reflect
the new branch.For example:
💬 Hint
We create the new version branch at the first release candidate rather than at
feature freeze to allow a period of time where the focus is on making the new
version ready for RC and GA. During this time, the main branch is only for
minor improvements and bug fixes. All major improvements and new features must
wait.
Most CI configuration is maintained on the lowest supported branch; update the
applicable files there to reflect the branch cut, and then upmerge that commit
5 times to get it tomaster. The following files need to be updated:.github/matrix.php(add new branch, update version formaster)
Updating after 8.5 branch cut.github/scripts/windows/find-target-branch.bat(update version formaster)
Updating after 8.5 branch cut
Update the
.github/workflows/push.ymlfile with the new branch. This file
does not need to be updated on the lowest supported branch; just update the
newly cut branch to add the new branch to the branches that the workflow
runs on (and then upmerge once tomaster). For example:Update the version of
php/php-sdk-binary-toolsused for themasterbranch for thenightly
andpushjobs to account for the new version; this can be done before the branch cut if
the new version is ready. If the new version is not ready when the branch is cut, file an
issue reporting the problem. For example:
Preparing for the initial stable version (PHP X.Y.0)
When you release the first pre-GA RC, remind the documentation team
([email protected]) to write the migration guide. Make sure the guide
is available before releasing the initial stable version, since you should
link to it in the release announcements.Timely get used to the differences in preparing and announcing a stable
release.Before releasing X.Y.0, merge the
NEWSentries of the pre-releases, so that
there is only a single section about PHP X.Y.0, instead of individual
sections for each pre-release.All the changes that are already present in the previous version NEWS should be
removed. It means all bug fixes that went to the previous version as well should
have their entries removed. It is possible to usegrepto compare the changes.
For example if82/NEWSis NEWS for PHP 8.2 and83/NEWSis NEWS file for PHP 8.3,
then following command will show changes present in both files:shgrep -Fxf 82/NEWS 83/NEWSOn the announcement day for the initial stable version (or shortly before),
update theExpiresfield in the https://www.php.net/.well-known/security.txt
file. TheExpiresfield should be set to the expected date of the next X.Y.0
release (following the one currently being prepared), which is usually the
fourth Thursday of November in the next year.Following the recommendation of RFC 9116,
we maintain anExpirestime of about a year for our security policies. This
provides security researchers with confidence they are using our most
up-to-date reporting policies.The
security.txtfile is located in the web-php repository
under the.well-known/directory. We may make changes to this file at other
times, as needed, but we will always advance theExpirestimestamp on a
yearly cadence, coinciding with our X.Y.0 releases.Please see the instructions for
making changes to security.txt.
Prime the selection of release managers for the next version
About three months prior to the scheduled release of the first alpha release of
the next minor or major version (around March 1st or shortly thereafter), the
release managers for the latest version branch should issue a call for
volunteers to begin the selection process for the next release managers.
Issue the call for volunteers on [email protected] on or around March
1st. See, for example: https://news-web.php.net/php.internals/113334There is no rule for how long the call for volunteers must remain open. We
should aim to select the release managers by early April, so announcing the
call in early March gives people about a month to decide whether they wish to
volunteer.There should be two or more volunteers. Typically, one should be a veteran
release manager (having previously served as aphp-srcrelease manager),
while the other one (or two) should be rookies. Hold a vote if necessary (see
https://wiki.php.net/rfc/releaseprocess#release_managers_selection).Help the new release managers with their first steps.
New release manager checklist
Fill out the form
to get a PHP account (if you don't already have one).Request membership to the
release managers group on GitHub.Make sure you are subscribed to all of the mailing lists that you will need to send
announcements to, since you cannot post to the lists otherwise:- [email protected] (email [email protected])
- [email protected] (email [email protected])
- [email protected] (email [email protected])
- [email protected] (email [email protected])
File a ticket in the infrastructure
project and provide an SSH key, your @php.net email address, your GitHub
account name, and your preferred system account name. Preferably they're
all the same!Read Logging into Servers to set up
access to downloads.php.net through jump hosts with 2FA.Then create a Google Authenticator file,
and provide the.google_authenticatorfile that this created, as
attachment to an email to [email protected]. In this email you should also
provide a link to the ticket in the infrastructure project that you have
created in the previous step💬 Hint
To send email from your @php.net address, you will need to use a custom
SMTP server. If you use Gmail, you may
"Send emails from a different address or alias."Create a GPG key for your @php.net address.
💡 Tip
If you're new to GPG, follow GitHub's instructions for
Generating a new GPG key.Publish your key by editing
include/gpg-keys.incin theweb-php
repository. Add acasefor your username to thegpg_key_get()function,
and paste the output fromgpg --fingerprint. You may also need to update
the$branchesarray in thegpg_key_get_branches()function to include
your username alongside your branch.console❯ gpg --fingerprint [email protected] pub rsa4096 2021-04-26 [SC] [expires: 2025-11-24] 39B6 4134 3D8C 104B 2B14 6DC3 F9C3 9DC0 B969 8544 uid [ultimate] Ben Ramsey <[email protected]> sub rsa4096 2021-04-26 [E] [expires: 2025-11-24]Have one or more of the other RMs sign your GPG key, and publish your
public key to a keyserver:shellgpg --keyserver keys.openpgp.org --send-keys YOURKEYID gpg --keyserver keyserver.ubuntu.com --send-keys YOURKEYIDAdd your public key to
php-keyring.gpginweb-php-distributions. To do
this, you will need to import all keys from the current PHP keyring file to
your local GPG keyring. You will need to take note of the key IDs for each of
the release managers listed inphp-keyring.gpg. Then, you will export,
specifying your key ID in addition to the key IDs of every other release
manager. Save this export back tophp-keyring.gpg, commit the changes,
and push.shellcd /path/to/repos/php/web-php-distributions gpg php-keyring.gpg # lists all keys in the keyring gpg --import php-keyring.gpg # imports all keys to your local keyring gpg --export \ --export-options export-minimal \ --armor \ YOURKEYID F9C39DC0B9698544 DBDB397470D12172 MORE RM KEY IDS ... \ > php-keyring.gpg gpg php-keyring.gpg # verify all the keys are present, including yours git add -p git commit --gpg-sign=YOURKEYID -m "Update PHP release manager keyring" git pushweb-php-distributionsis a submodule ofweb-php. You'll now have to update
the commit reference to reflect the change made in web-php-distributions.shellcd /path/to/repos/php/web-php git submodule update cd distributions # This is the submodule referring to web-php-distributions git pull origin master cd .. git add distributions git commit --gpg-sign=YOURKEYID -m "Update php-keyring.gpg in distributions" git pushMake sure you have the following repositories cloned locally:
Source Index (`docs/source/index.rst`)
##############
php-src docs
##############
.. toctree::
:caption: Introduction
:hidden:
introduction/high-level-overview
introduction/ides/index
.. toctree::
:caption: Core
:hidden:
core/data-structures/index
.. toctree::
:caption: Miscellaneous
:hidden:
miscellaneous/stubs
miscellaneous/writing-tests
miscellaneous/running-tests
Welcome to the php-src documentation!
.. warning::
This documentation is work in progress.
At this point in time, there are other guides that provide a more complete picture of the PHP
project. Check the CONTRIBUTING.md <https://github.com/php/php-src/blob/master/CONTRIBUTING.md#technical-resources>__ file for a
list of technical resources.
php-src is the canonical implementation of the interpreter for the PHP programming language, as well
as various extensions that provide common functionality. This documentation is intended to help you
understand how the interpreter works, how you can build and test changes, and how you can create
extensions yourself.
This documentation is not intended to be comprehensive, but is meant to explain core concepts that
are not easy to grasp by reading code alone. It describes best practices, and will frequently omit
APIs that are discouraged for general use.
How to get help?
Getting started with a new and complicated project like php-src can be overwhelming. While there's
no way around reading lots and lots of code, asking questions of somebody with experience can save a
lot of time. Luckily, many core developers are eager to help. Here are some ways you can get in
touch.
Discord <https://phpc.chat>__ (#php-internalschannel)R11 on StackOverflow <https://chat.stackoverflow.com/rooms/11/php>__
Prerequisites
The php-src interpreter is written in C, and so are most of the bundled extensions. While extensions
may also be written in C++, ext-intl is currently the only bundled extension to do so. It is
advisable that you have some knowledge of C before jumping into php-src.
It is also advisable to get familiar with the semantics of PHP itself, so that you may better
differentiate between bugs and expected behavior, and model new language features.
Source Introduction High Level Overview (`docs/source/introduction/high-level-overview.rst`)
#####################
High-level overview
#####################
PHP is an interpreted language. Interpreted languages differ from compiled ones in that they aren't
compiled into machine-readable code ahead of time. Instead, the source files are read, processed and
interpreted when the program is executed. This can be very convenient for developers for rapid
prototyping, as it skips a lengthy compilation phase. However, it also poses some unique challenges
to performance, which is one of the primary reasons interpreters can be complex. php-src borrows
many concepts from other compilers and interpreters.
Pipeline
The goal of the interpreter is to read the users source files, and to simulate the users intent.
This process can be split into distinct phases that are easier to understand and implement.
- Tokenization - splitting whole source files into words, called tokens.
- Parsing - building a tree structure from tokens, called AST (abstract syntax tree).
- Compilation - traversing the AST and building a list of operations, called opcodes.
- Interpretation - reading and executing opcodes.
php-src as a whole can be seen as a pipeline consisting of these stages, using the input of the
previous phase and producing some output for the next.
.. code:: haskell
source_code
|> tokenizer -- tokens
|> parser -- ast
|> compiler -- opcodes
|> interpreter
Let's go into each phase in a bit more detail.
Tokenization
Tokenization, often called "lexing" or "scanning", is the process of taking an entire program file
and splitting it into a list of words and symbols. Tokens generally consist of a type, a simple
integer constant representing the token, and a lexeme, the literal string used in the source code.
.. code:: php
if ($cond) {
echo "Cond is true\n";
}
.. code:: text
T_IF "if"
T_WHITESPACE " "
"("
T_VARIABLE "$cond"
")"
T_WHITESPACE " "
"{"
T_WHITESPACE "\n "
T_ECHO "echo"
T_WHITESPACE " "
T_CONSTANT_ENCAPSED_STRING '"Cond is true\n"'
";"
T_WHITESPACE "\n"
"}"
While tokenizers are not difficult to write by hand, PHP uses a tool called re2c to automate
this process. It takes a definition file and generates efficient C code to build these tokens from a
stream of characters. The definition for PHP lives in Zend/zend_language_scanner.l. Check there2c documentation_ for details.
.. _re2c documentation: https://re2c.org/
Parsing
Parsing is the process of reading the tokens generated from the tokenizer and building a tree
structure from it. To humans, how source code elements are grouped seems obvious through whitespace
and the usage of symbols like () and {}. However, computers cannot visually glance over the
code to determine these boundaries quickly. To make it easier and faster to work with, we build a
tree structure from the tokens to more closely reflect the source code the way humans see it.
Here is a simplified example of what an AST from the tokens above might look like.
.. code:: text
ZEND_AST_IF {
ZEND_AST_IF_ELEM {
ZEND_AST_VAR {
ZEND_AST_ZVAL { "cond" },
},
ZEND_AST_STMT_LIST {
ZEND_AST_ECHO {
ZEND_AST_ZVAL { "Cond is true\n" },
},
},
},
}
Each AST node has a type and may have children. They also store their original position in the
source code, and may define some arbitrary flags. These are omitted for brevity.
Like with tokenization, we use a tool called Bison to generate the parser implementation from a
grammar specification. The grammar lives in the Zend/zend_language_parser.y file. Check theBison documentation_ for details. Luckily, the syntax is quite approachable.
.. _bison documentation: https://www.gnu.org/software/bison/manual/
Parsing is described in more detail in its dedicated chapter <todo>__.
Compilation
Computers don't understand human language, or even programming languages. They only understand
machine code, which are sequences of simple, mostly atomic instructions for doing one thing. For
example, they may add two numbers, load some memory from RAM, jump to an instruction under a certain
condition, etc. It turns out that even the most complex expressions can be reduced to a number of
these simple instructions.
PHP is a bit different, in that it does not execute machine code directly. Instead, instructions run
on a "virtual machine", often abbreviated to VM. This is just a fancy way of saying that there is no
physical machine you can buy that understands these instructions, but that this machine is
implemented in software. This is our interpreter. This also means that we are free to make up
instructions ourselves at will. Some of these instructions look very similar to something you'd find
in an actual CPU instruction set (e.g. adding two numbers), while others are much more high-level
(e.g. load property of object by name).
With that little detour out of the way, the job of the compiler is to read the AST and translate it
into our virtual machine instructions, also called opcodes. The code responsible for this
transformation lives in Zend/zend_compile.c. It essentially traverses the AST and generates a
number of instructions, before going to the next node.
Here's what the surprisingly compact opcodes for the AST above might look like:
.. code:: text
0000 JMPZ CV0($cond) 0002
0001 ECHO string("Cond is true\n")
0002 RETURN int(1)
Interpretation
Finally, the opcodes are read and executed by the interpreter. PHPs uses three-address code_ for
instructions. This essentially means that each instructions may have a result value, and at most two
operands. Most modern CPUs also use this format. Both result and operands in PHP are :doc:zvals <../core/data-structures/zval>.
.. _three-address code: https://en.wikipedia.org/wiki/Three-address_code
How exactly each opcode behaves depends on its purpose. You can find a complete list of opcodes in
the generated Zend/zend_vm_opcodes.h file. The behavior of each instruction is defined inZend/zend_vm_def.h.
Let's step through the opcodes form the example above:
We start at the top, i.e.
JMPZ. If its first operand contains a "falsy" value, it will jump
to the instruction encoded in its second operand. If it is truthy, it will simply fall-through to
the next instruction.The
ECHOinstruction prints its first operand.The
RETURNoperand terminates the current function.
With these simple rules, we can see that the interpreter will echo only when $cond is
truthy, and skip over the echo otherwise.
That's it! This is how PHP works, fundamentally. Of course, we skipped over a ton of details. The VM
is quite complex, and will be discussed separately in the virtual machine <todo>__ chapter.
Opcache
As you may imagine, running this whole pipeline every time PHP serves a request is time consuming.
Luckily, it is also not necessary. We can cache the opcodes in memory between requests, to skip over
all of the phases, except for the execution phase. This is precisely what the opcache extension
does. It lives in the ext/opcache directory.
Opcache also performs some optimizations on the opcodes before caching them. As opcaches are
expected to be reused many times, it is profitable to spend some additional time simplifying them if
possible to improve performance during execution. The optimizer lives in Zend/Optimizer.
JIT
The opcache also implements a JIT compiler, which stands for just-in-time compiler. This compiler
takes the virtual PHP opcodes and turns it into actual machine instructions, with additional
information gained at runtime. JITs are very complex pieces of software, so this book will likely
barely scratch the surface of how it works. It lives in ext/opcache/jit.
Source Miscellaneous Running Tests (`docs/source/miscellaneous/running-tests.rst`)
###############
Running Tests
###############
The easiest way to test your PHP build is to run make test from the command line after successfully
compiling. This will run the all tests for all enabled functionalities and extensions located in
tests folders under the source root directory using the PHP CLI binary.
make test executes the run-tests.php script under the source root (parallel builds will not
work). Therefore you can execute the script as follows:
.. code:: shell
sapi/cli/php [-c /path/to/php.ini] run-tests.php [ext/foo/tests/GLOB]
Which php executable does make test use?
If you are running the run-tests.php script from the command line (as above) you can set theTEST_PHP_EXECUTABLE environment variable to explicitly select the PHP executable that is to be
tested, that is, used to run the test scripts, otherwise it will use the PHP CLI binary that you
have compiled (sapi/cli/php).
If you run the tests using make test, the PHP CLI and CGI executables are automatically set for you.make test executes run-tests.php script with the CLI binary. Some test scripts such as
session must be executed by CGI SAPI. Therefore, you must build PHP with CGI SAPI to perform all
tests.
Note: The PHP binary executing run-tests.php and the PHP binary used for executing test
scripts may differ. If you use different PHP binary for executing run-tests.php script, you may
get errors.
Which php.ini is used?
make test uses the same php.ini file as it would once installed. The tests have been written
to be independent of that php.ini file, so if you find a test that is affected by a setting,
please report this, so we can address the issue.
Which test scripts are executed?
The run-tests.php (make test), without any arguments executes all test scripts by extracting
all directories named tests from the source root and any subdirectories below. If there are files,
which have a phpt extension, run-tests.php looks at the sections in these files, determines
whether it should run it, by evaluating the SKIPIF section. If the test is eligible for
execution, the FILE section is extracted into a .php file (with the same name besides the
extension) and gets executed. When an argument is given or TESTS environment variable is set,
the GLOB is expanded by the shell and any file with extension *.phpt is regarded as a test file.
Tester can easily execute tests selectively with as follows:
.. code:: shell
./sapi/cli/php run-tests.php ext/mbstring/*
./sapi/cli/php run-tests.php ext/mbstring/020.phpt
Test Runner Options
The run-tests.php test runner has many options. You can see these options by using the -h
option with run-tests.php.
You can set options by specifying them on the command line when you run php run-tests.php or if
you use make test through the TEST_PHP_ARGS environment variable:
.. code:: shell
php run-tests.php -j24
or
TEST_PHP_ARGS="-j24" make test
Running Tests in Parallel
The test runner can run tests in parallel, by using the -j option:
.. code:: shell
php run-tests.php -j24 ext/date/*.phpt
Test results
Test results are printed to standard output. If there is a failed test, the run-tests.php script
saves the result, the expected result and the code executed to the test script directory. For
example, if ext/myext/tests/myext.phpt fails to pass, the following files are created:
ext/myext/tests/myext.php- actual test file executedext/myext/tests/myext.log- log of test execution (L)ext/myext/tests/myext.exp- expected output (E)ext/myext/tests/myext.out- output from test script (O)ext/myext/tests/myext.diff- diff of .out and .exp (D)
Failed tests are always bugs. Either the test is bugged or not considering factors applying to the
tester's environment, or there is a bug in PHP. If this is a known bug, we strive to provide bug
numbers, in either the test name or the file name. You can check the status of such a bug, by going
to: https://bugs.php.net/12345 where 12345 is the bug number. For clarity and automated
processing, bug numbers are prefixed by a hash sign '#' in test names and/or test cases are namedbug12345.phpt.
Note: The files generated by tests can be selected by setting the environment variableTEST_PHP_LOG_FORMAT. For each file you want to be generated use the character in brackets as
shown above (default is LEOD). The php file will be generated always.
Note: You can set environment variable TEST_PHP_DETAILED to enable detailed test
information.
Automated testing
If you like to keep up to speed, with latest developments and quality assurance, setting the
environment variable NO_INTERACTION to 1, will not prompt the tester for any user input.
Normally, the exit status of make test is zero, regardless of the results of independent tests. Set
the environment variable REPORT_EXIT_STATUS to 1, and make test will set the exit status
("$?") to non-zero, when an individual test has failed.
Example script to be run by cron:
.. code:: shell
========== qa-test.sh =============
#!/bin/sh
CO_DIR=$HOME/cvs/php7
MYMAIL=[email protected]
TMPDIR=/var/tmp
TODAY=date +"%Y%m%d"
Make sure compilation environment is correct
CONFIGURE_OPTS='--disable-all --enable-cli --with-pcre'
export MAKE=gmake
export CC=gcc
Set test environment
export NO_INTERACTION=1
export REPORT_EXIT_STATUS=1
cd $CO_DIR
cvs update . >>$TMPDIR/phpqatest.$TODAY
./cvsclean ; ./buildconf ; ./configure $CONFIGURE_OPTS ; $MAKE
$MAKE test >>$TMPDIR/phpqatest.$TODAY 2>&1
if test $? -gt 0
then
cat $TMPDIR/phpqatest.$TODAY | mail -s"PHP-QA Test Failed for $TODAY" $MYMAIL
fi
========== end of qa-test.sh =============
Note: The exit status of run-tests.php will be 1 when REPORT_EXIT_STATUS is set. The
result of make test may be higher than that. At present, gmake 3.79.1 returns 2, so it is advised to
test for non-zero, rather then a specific value.
When make test finished running tests, and if there are any failed tests, the script asks to
send the logs to the PHP QA mailing list. Please answer y to this question so that we can
efficiently process the results, entering your e-mail address (which will not be transmitted in
plain text to any list) enables us to ask you some more information if a test failed. Note that this
script also uploads php -i output so your hostname may be transmitted.
Specific tests can also be executed, like running tests for a certain extension. To do this you can
do like so (for example the standard library):
.. code:: shell
make test TESTS=ext/standard.
Where TESTS= points to a directory containing .phpt files or a single .phpt file like:
.. code:: shell
make test TESTS=tests/basic/001.phpt.
You can also pass options directly to the underlying script that runs the test suite
(run-tests.phpt) using TESTS=, for example to check for memory leaks using Valgrind, the-m option can be passed along: make test TESTS="-m Zend/". For a full list of options that
can be passed along, then run make test TESTS=-h.
Windows users: On Windows the make command is called nmake instead of make. This means
that on Windows you will have to run nmake test, to run the test suite.
Source Miscellaneous Stubs (`docs/source/miscellaneous/stubs.rst`)
#######
Stubs
#######
Stub files are pieces of PHP code which only contain declarations. They do not include runnable
code, but instead contain empty function and method bodies. A very basic stub looks like this:
.. code:: php
ce_flags |= ZEND_ACC_DEPRECATED|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; class_entry->doc_comment = zend_string_init_interned("/**\n * This is a comment\n * @see https://www.php.net */", 55, 1); ... return class_entry; } ******************************************** Generating Global Constants and Attributes ******************************************** Although global constants and function attributes do not relate to classes, they require the ``/** @generate-class-entries */`` file-level PHPDoc block. If a global constant or function attribute are present in the stub file, the generated C-code will include a ``register_{$stub_file_name}_symbols()`` file. Given the following file: .. code:: php // example.stub.php = 80000) # include "example_arginfo.h" #else # include "example_legacy_arginfo.h" #endif When ``@generate-legacy-arginfo`` is passed the minimum PHP version ID that needs to be supported, then only one arginfo file is going to be generated, and ``#if`` preprocessor directives will ensure compatibility with all the required PHP 8 versions. PHP Version IDs are as follows: ``80000`` for PHP 8.0, ``80100`` for PHP PHP 8.1, ``80200`` for PHP 8.2, ``80300`` for PHP 8.3, and ``80400`` for PHP 8.4, In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a previous example: .. code:: php = ...)`` conditions in the generated arginfo file: .. code:: c ... #if (PHP_VERSION_ID >= 80100) static zend_class_entry *register_class_Number(void) { zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods); zend_enum_add_case_cstr(class_entry, "One", NULL); return class_entry; } #endif static zend_class_entry *register_class_Elephant(void) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); #if (PHP_VERSION_ID >= 80100) class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; #elif (PHP_VERSION_ID >= 80000) class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; #endif zval const_PI_value; ZVAL_DOUBLE(&const_PI_value, M_PI); zend_string *const_PI_name = zend_string_init_interned("PI", sizeof("PI") - 1, 1); #if (PHP_VERSION_ID >= 80300) zend_declare_typed_class_constant(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE)); #else zend_declare_class_constant_ex(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL); #endif zend_string_release(const_PI_name); zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); #if (PHP_VERSION_ID >= 80100) zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); #elif (PHP_VERSION_ID >= 80000) zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); #endif zend_string_release(property_name_name); return class_entry; } The preprocessor conditions are necessary because enumerations (``enum``), ``readonly`` properties, and the ``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0. The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not added for``Elephpant::$name`` for PHP versions before 8.1. Additionally, typed class constants are new in PHP 8.3, and hence a different registration function is used for versions before 8.3. ****************************************** Generating Information for the Optimizer ****************************************** A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This file contains extra information about the return type and the cardinality of the return value. This can enable more accurate optimizations (i.e. better type inference). Previously, the file was maintained manually, but since PHP 8.1, ``gen_stub.php`` can take care of this with the ``--generate-optimizer-info`` option. This feature is only available for built-in stubs inside php-src, since currently there is no way to provide the function list for the optimizer other than overwriting ``zend_func_infos.h`` directly. A function is added to ``zend_func_infos.h`` if either the ``@return`` or the ``@refcount`` PHPDoc tag supplies more information than what is available based on the return type declaration. By default, scalar return types have a ``refcount`` of ``0``, while non-scalar values are ``N``. If a function can only return newly created non-scalar values, its ``refcount`` can be set to ``1``. An example from the built-in functions: .. code:: php /** * @return array * @refcount 1 */ function get_declared_classes(): array {} Functions can be evaluated at compile-time if their arguments are known in compile-time, and their behavior is free from side-effects and is not affected by the global state. The list of such functions in the optimizer was maintained manually until PHP 8.2. Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms to the above restrictions in order for them to qualify as evaluable at compile-time. The feature internally works by adding the ``ZEND_ACC_COMPILE_TIME_EVAL`` function flag. In PHP 8.4, arity-based frameless functions were introduced. This is another optimization technique, which results in faster internal function calls by eliminating unnecessary checks for the number of passed parameters—if the number of passed arguments is known at compile-time. To take advantage of frameless functions, add the ``@frameless-function`` PHPDoc tag with some configuration. Since only arity-based optimizations are supported, the tag has the form: ``@frameless-function {"arity": NUM}``. ``NUM`` is the number of parameters for which a frameless function is available. The stub of ``in_array()`` is a good example: .. code:: php /** * @compile-time-eval * @frameless-function {"arity": 2} * @frameless-function {"arity": 3} */ function in_array(mixed $needle, array $haystack, bool $strict = false): bool {} Apart from being compile-time evaluable, it has a frameless function counterpart for both the 2 and the 3-parameter signatures: .. code:: c /* The regular in_array() function */ PHP_FUNCTION(in_array) { php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* The frameless version of the in_array() function when 2 arguments are passed */ ZEND_FRAMELESS_FUNCTION(in_array, 2) { zval *value, *array; Z_FLF_PARAM_ZVAL(1, value); Z_FLF_PARAM_ARRAY(2, array); _php_search_array(return_value, value, array, false, 0); flf_clean:; } /* The frameless version of the in_array() function when 3 arguments are passed */ ZEND_FRAMELESS_FUNCTION(in_array, 3) { zval *value, *array; bool strict; Z_FLF_PARAM_ZVAL(1, value); Z_FLF_PARAM_ARRAY(2, array); Z_FLF_PARAM_BOOL(3, strict); _php_search_array(return_value, value, array, strict, 0); flf_clean:; } ************************************** Generating Signatures for the Manual ************************************** The manual should reflect the exact same signatures which are represented by the stubs. This is not exactly the case yet for built-in symbols, but ``gen_stub.php`` has multiple features to automate the process of synchronization. Newly added functions or methods can be documented by providing the ``--generate-methodsynopses`` option. Running ``./build/gen_stub.php --generate-methodsynopses ./ext/mbstring ../doc-en/reference/mbstring`` will create a dedicated page for each ``ext/mbstring`` function which is not yet documented, and saves them into the ``../doc-en/reference/mbstring/functions`` directory. Since these are stub documentation pages, many of the sections are empty. Relevant descriptions have to be added, and irrelevant sections should be removed. Functions or methods that are already available in the manual, the documented signatures can be updated by providing the ``--replace-methodsynopses`` option. Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or method signatures in the English documentation whose stub counterpart is found. Class signatures can be updated in the manual by providing the ``--replace-classsynopses`` option. Running ``./build/gen_stub.php --replace-classsynopses ./ ../doc-en/`` will update all the class signatures in the English documentation whose stub counterpart is found. If a symbol is not intended to be documented, the ``@undocumentable`` PHPDoc tag should be added to it. Doing so will prevent any documentation to be created for the given symbol. To avoid a whole stub file to be added to the manual, this PHPDoc tag should be applied to the file itself. These flags are useful for symbols which exist only for testing purposes (e.g. the ones declared for ``ext/zend_test``), or by some other reason documentation is not possible. ************ Validation ************ You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method signatures are correct. An alias function/method should have the exact same signature as its aliased function/method counterpart, apart from the name. In some cases this is not possible. For example. ``bzwrite()`` is an alias of ``fwrite()``, but the name of the first parameter is different because the resource types differ. In order to suppress the error when the check is false positive, the ``@no-verify`` PHPDoc tag should be applied to the alias: .. code:: php /** * @param resource $bz * @implementation-alias fwrite * @no-verify Uses different parameter name */ function bzwrite($bz, string $data, ?int $length = null): int|false {} Besides aliases, the contents of the documentation can also be validated by providing the ``--verify-manual`` option to ``gen_stub.php``. This flag requires the directory with the source stubs, and the target manual directory, as in ``./build/gen_stub.php --verify-manual ./ ../doc-en/``. For this validation, all ``php-src`` stubs and the full English documentation should be available by the specified path. This feature performs the following validations: - Detecting missing global constants - Detecting missing classes - Detecting missing methods - Detecting incorrectly documented alias functions or methods Running it with the stub examples that are used in this guide, the following warnings are shown: .. code:: shell Warning: Missing class synopsis for Number Warning: Missing class synopsis for Elephant Warning: Missing class synopsis for Atmosphere Warning: Missing method synopsis for fahrenheitToCelsius() Warning: Missing method synopsis for Atmosphere::calculateBar() ********************** Parameter Statistics ********************** The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the codebase. A JSON object is displayed, containing the parameter names and the number of their occurrences in descending order. --- ### Source Miscellaneous Writing Tests (`docs/source/miscellaneous/writing-tests.rst`) ############### Writing Tests ############### ****************** phpt Test Basics ****************** The first thing you need to know about tests is that we need more!!! Although PHP works just great 99.99% of the time, not having a very comprehensive test suite means that we take more risks every time we add to or modify the PHP implementation. The second thing you need to know is that if you can write PHP you can write tests. Thirdly — we are a friendly and welcoming community, don't be scared about writing to ([email protected]) — we won't bite! So what are phpt tests? A phpt test is a little script used by the php internal and quality assurance teams to test PHP's functionality. It can be used with new releases to make sure they can do all the things that previous releases can, or to help find bugs in current releases. By writing phpt tests you are helping to make PHP more stable. What skills are needed to write a phpt test? All that is really needed to write a phpt test is a basic understanding of the PHP language, a text editor, and a way to get the results of your code. That is it. So if you have been writing and running PHP scripts already — you have everything you need. What do you write phpt tests on? Basically you can write a phpt test on one of the various php functions available. You can write a test on a basic language function (a string function or an array function) , or a function provided by one of PHP's numerous extensions (a mysql function or a image function or a mcrypt function). You can find out what functions already have phpt tests by looking in the `html version --EXPECT--
string(32) "# hello All, I sAid hi planet! #"
As you can see the file is divided into several sections. The TEST section holds a one line title of
the phpt test, this should be a simple description and shouldn't ever exceed one line, if you need
to write more explanation add comments in the body of the test case. The phpt files name is used
when generating a .php file. The FILE section is used as the body of the .php file, so don't forget
to open and close your php tags. The EXPECT section is the part used as a comparison to see if the
test passes. It is a good idea to generate output with var_dump() calls.
PHPT structure details
A phpt test can have many more parts than just the minimum. In fact some of the mandatory parts have
alternatives that may be used if the situation warrants it. The phpt sections are documented here.
Analyzing failing tests
While writing tests you will probably run into tests not passing while you think they should. The
'make test' command provides you with debug information. Several files will be added per test in the
same directory as the .phpt file itself. Considering your test file is named foo.phpt, these files
provide you with information that can help you find out what went wrong:
foo.diff
A diff file between the expected output (be it in EXPECT, EXPECTF or another option) and the
actual output.
foo.exp
The expected output.
foo.log
A log containing expected output, actual output and results. Most likely very similar to info in
the other files.
foo.out
The actual output of your .phpt test part.
foo.php
The php code that was executed for this test.
foo.sh
An executable file that executes the test for you as it was executed during failure.
Testing your test cases
Most people who write tests for PHP don't have access to a huge number of operating systems but the
tests are run on every system that runs PHP. It's good to test your test on as many platforms as you
can — Linux and Windows are the most important, it's increasingly important to make sure that tests
run on 64 bit as well as 32 bit platforms. If you only have access to one operating system — don't
worry, if you have karma, commit the test but watch [email protected] for reports of failures on
other platforms. If you don't have karma to commit have a look at the next section.
When you are testing your test case it's really important to make sure that you clean up any
temporary resources (eg files) that you used in the test. There is a special --CLEAN-- section
to help you do this — see here <#clean>_.
Tests run in parallel by default. Mutable resources such as files, directories, ports, database
objects, and IPC identifiers must therefore be unique to each test. Read-only fixtures may be
shared. If a resource cannot be isolated, declare the narrowest applicable conflict using--CONFLICTS-- or a CONFLICTS file.
Another good check is to look at what lines of code in the PHP source your test case covers. This is
easy to do, there are some instructions on the PHP Wiki <https://wiki.php.net/doc/articles/writing-tests>_.
What should I do with my test case when I've written and tested it?
The next step is to get someone to review it. If it's short you can paste it into a note and send it
to [email protected]. If the test is a bit too long for that then put it somewhere were people
can download it (pastebin <https://pastebin.com/>_ is sometimes used). Appending tests to notes as
files doesn't work well - so please don't do that. Your note to [email protected] should say what
level of PHP you have tested it on and what platform(s) you've run it on. Someone from the PHP QA
group will review your test and reply to you. They may ask for some changes or suggest better ways
to do things, or they may commit it to PHP.
Writing Portable PHP Tests
Writing portable tests can be hard if you don't have access to all the many platforms that PHP can
run on. Do your best. If in doubt, don't disable a test. It is better that the test runs in as many
environments as possible.
If you know a new test won't run in a specific environment, try to write the complementary test for
that environment.
Make sure sets of data are consistently ordered. SQL queries are not guaranteed to return results in
the same order unless an ORDER BY clause is used. Directory listings are another example that can
vary: use an appropriate PHP function to sort them before printing. Both of these examples have
affected PHP tests in the past.
Make sure that any test touching parsing or display of dates uses a hard-defined timezone —
preferable 'UTC'. It is important that this is defined in the file section using:
.. code:: php
date_default_timezone_set('UTC');
and not in the INI section. This is because of the order in which settings are checked which is:
.. code::
date_default_timezone_set() -> TZ environmental -> INI setting -> System Setting
If a TZ environmental variable is found the INI setting will be ignored.
Tests that run, or only have matching EXPECT output, on 32bit platforms can use a SKIPIF section
like:
.. code:: php
--SKIPIF--
Tests for 64bit platforms can use:
.. code:: php
--SKIPIF--
To run a test only on Windows:
.. code:: php
--SKIPIF--
To run a test only on Linux:
.. code:: php
--SKIPIF--
To skip a test on Mac OS X Darwin:
.. code:: php
--SKIPIF--
Examples
EXPECTF
/ext/standard/tests/strings/str_shuffle.phpt is a good example for using EXPECTF instead ofEXPECT. From time to time the algorithm used for shuffle changed and sometimes the machine used
to execute the code has influence on the result of shuffle. But it always returns a three character
string detectable by %s (that matches any string until the end of the line). Other scan-able
forms are %a for any amount of chars (at least one), %i for integers, %d for numbers
only, %f for floating point values, %c for single characters, %x for hexadecimal values,%w for any number of whitespace characters and %e for DIRECTORY_SEPARATOR ('\' or'/').
See also EXPECTF <#expectf>_ details.
/ext/standard/tests/strings/str_shuffle.phpt
.. code:: php
--TEST--
Testing str_shuffle.
--FILE--
--EXPECTF--
string(3) "%s"
string(3) "123"
EXPECTREGEX
/ext/standard/tests/strings/strings001.phpt is a good example for using EXPECTREGEX instead
of EXPECT. This test also shows that in EXPECTREGEX some characters need to be escaped since
otherwise they would be interpreted as a regular expression.
/ext/standard/tests/strings/strings001.phpt
.. code:: php
--TEST--
Test whether strstr() and strrchr() are binary safe.
--FILE--
--EXPECTREGEX--
string(18) "nica\x00turska panica"
string(19) " nica\x00turska panica"
EXTENSIONS
Some tests depend on PHP extensions that may be unavailable. These extensions should be listed in
the EXTENSIONS section. If an extension is missing, PHP will try to find it in a shared module
and skip the test if it's not there.
/ext/sodium/tests/crypto_scalarmult.phpt
.. code:: php
--TEST--
Check for libsodium scalarmult
--EXTENSIONS--
sodium
--FILE--
<?php
$n = sodium_hex2bin("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb");
SKIPIF
Some tests depend on modules or functions available only in certain versions or they even require
minimum version of php or zend. These tests should be skipped when the requirement cannot be
fulfilled. To achieve this you can use the SKIPIF section. To tell run-tests.php that your
test should be skipped the SKIPIF section must print out the word "skip" followed by a reason
why the test should skip.
ext/sodium/tests/pwhash_argon2i.phpt
.. code:: php
--TEST--
Check for libsodium argon2i
--EXTENSIONS--
sodium
--SKIPIF--
--FILE--
[snip]
Test script and SKIPIF code should be directly written into \*.phpt. However, it is
recommended to use include files when more test scripts depend on the same SKIPIF code or when
certain test files need the same values for some input.
Note: no file used by any test should have one of the following extensions: ".php", ".log", ".mem",
".exp", ".out" or ".diff". When you use an include file for the SKIPIF section it should be
named "skipif.inc" and an include file used in the FILE section of many tests should be named
"test.inc".
Final Notes
Cleaning up after running a test
Sometimes test cases create files or directories as part of the test case and it's important to
remove these after the test ends, the --CLEAN-- section is provided to help with this.
The PHP code in the --CLEAN-- section is executed separately from the code in the --FILE--
section. For example, this code:
.. code:: php
--TEST--
Will fail to clean up
--FILE--
--CLEAN--
--EXPECT--
will not remove the temporary file because the variable $temp_filename is not defined in the--CLEAN-- section.
Here is a better way to write the code:
.. code:: php
--TEST--
This will remove temporary files
--FILE--
--CLEAN--
--EXPECT--
Note the use of the __DIR__ construct which will ensure that the temporary file is created in
the same directory as the phpt test script.
When creating temporary files it is a good idea to use an extension that indicates the use of the
file, eg .tmp. It's also a good idea to avoid using extensions that are already used for other
purposes, eg .inc, .php. Similarly, it is helpful to give the temporary file a name that is clearly
related to the test case. For example, mytest.phpt should create mytest.tmp (or mytestN.tmp, N=1,
2,3,...) then if by any chance the temporary file isnt't removed properly it will be obvious which
test case created it.
When writing and debugging a test case with a --CLEAN-- section it is helpful to remember that
the php code in the --CLEAN-- section is executed separately from the code in the --FILE--
section. For example, in a test case called mytest.phpt, code from the --FILE-- section is run
from a file called mytest.php and code from the --CLEAN-- section is run from a file called
mytest.clean.php. If the test passes, both the .php and .clean.php files are removed byrun-tests.php. You can prevent the removal by using the --keep option of run-tests.php, this
is a very useful option if you need to check that the --CLEAN-- section code is working as you
intended.
Finally — if you are using CVS it's helpful to add the extension that you use for test-related
temporary files to the .cvsignore file — this will help to prevent you from accidentally checking
temporary files into CVS.
Redirecting tests
Using --REDIRECTTEST-- it is possible to redirect from one test to a bunch of other tests. That
way multiple extensions can refer to the same set of test scripts probably using it with a different
configuration.
The block is eval'd and supposed to return an array describing how to redirect. The resulting array
must contain the key 'TEST' that stores the redirect target as a string. This string usually is the
directory where the test scripts are located and should be relative. Optionally you can use the
'ENV' as an array configuring the environment to be set when executing the tests. This way you can
pass configuration to the executed tests.
Redirect tests may especially contain --SKIPIF--, --ENV--, and --ARGS-- sections but
they no not use any --EXPECT-- section.
The redirected tests themselves are just normal tests.
Error reporting in tests
All tests should run correctly with error_reporting(E_ALL) and display_errors=1. This is the default
when called from run-tests.php. If you have a good reason for lowering the error reporting, use--INI-- section and comment this in your testcode.
If your test intentionally generates a PHP warning message use $php_errormsg variable, which you can
then output. This will result in a consistent error message output across all platforms and PHP
configurations, preventing your test from failing due inconsistencies in the error message content.
Alternatively you can use --EXPECTF-- and check for the message by replacing the path of the
source of the message with %s and the line number with %d. The end of a message in a test
file example.phpt then looks like in %sexample.php on line %d. We explicitly dropped the
last path divider as that is a system dependent character / or \.
Last bit
Often you want to run test scripts without run-tests.php by executing them on command line like
any other php script. But sometimes it disturbs having a long --EXPECT-- block, so that you
don't see the actual output as it scrolls away overwritten by the blocks following the actual file
block. The workaround is to use terminate the --FILE-- section with the two lines ===DONE===
and <?php exit(0); ?>. When doing so run-tests.php does not execute the line containing the
exit call as that would suppress leak messages. Actually run-tests.php ignores any part after a
line consisting only of ===DONE===.
Here is an example:
.. code:: php
--TEST--
Test hypot() — dealing with mixed number/character input
--INI--
precision=14
--FILE--
===DONE===
--EXPECTF--
23abc :-33 float(40.224370722238)
===DONE===
If executed as PHP script the output will stop after the code on the --FILE-- section has been
run.
Reference
PHPT Sections
``--TEST--``
Description: Title of test as a single line short description.
Required: Yes
Format: Plain text. We recommend a single line only.
Example 1 (snippet):
.. code:: text
--TEST--
Test filter_input() with GET and POST data.
Example 1 (full): :ref:sample001.phpt
``--DESCRIPTION--``
Description: If your test requires more than a single line title to adequately describe it, you
can use this section for further explanation. Multiple lines are allowed and besides being used for
information, this section is completely ignored by the test binary.
Required: No
Format: Plain text, multiple lines.
Example 1 (snippet):
.. code:: text
--DESCRIPTION--
This test covers both valid and invalid usages of filter_input() with INPUT_GET and INPUT_POST data and several different filter sanitizers.
Example 1 (full): :ref:sample001.phpt
``--CREDITS--``
Description: Used to credit contributors without CVS commit rights, who put their name and email
on the first line. If the test was part of a TestFest event, then # followed by the name of the
event and the date (YYYY-MM-DD) on the second line.
Required: No. For newly created tests the section should no longer be used for simple authorship
claims or listing all contributors who edited the test; as it is already accurately tracked by Git.
It may be used if more specific attribution is useful, for example to credit the original reporter
of a bug or a contributor who is not credited via Co-authored-by tag.
Format: Name Email [Event]
Example 1 (snippet):
.. code:: text
--CREDITS--
Felipe Pena
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: text
--CREDITS--
Zoe Slattery [email protected]
TestFest Munich 2009-05-19
Example 2 (full): :ref:sample002.phpt
``--SKIPIF--``
Description: A condition or set of conditions used to determine if a test should be skipped.
Tests that are only applicable to a certain platform, extension or PHP version are good reasons for
using a --SKIPIF-- section.
A common practice for extension tests is to write your --SKIPIF-- extension criteria into a file
call skipif.inc and then including that file in the --SKIPIF-- section of all your extension
tests. This promotes the DRY principle and reduces future code maintenance.
Required: No.
Format: PHP code enclosed by PHP tags. If the output of this scripts starts with "skip", the
test is skipped. If the output starts with "xfail", the test is marked as expected failure. If the
output starts with "flaky", the test is marked as flaky test. The "xfail" convention is supported as
of PHP 7.2.0. The "flaky" convention is supported as of PHP 8.2.25 and PHP 8.3.13, respectively.
Example 1 (snippet):
.. code:: php
--SKIPIF--
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: php
--SKIPIF--
Example 2 (full): :ref:sample003.phpt
Example 3 (snippet):
.. code:: php
--SKIPIF--
Example 3 (full): :ref:xfailif.phpt
Example 4 (snippet):
.. code:: php
--SKIPIF--
<?php
if (getenv("GITHUB_ACTIONS") && PHP_OS_FAMILY === "Darwin") {
die("flaky Occasionally segfaults on macOS for unknown reasons");
}
``--CONFLICTS--``
Description: This section is only relevant for parallel test execution (available as of PHP
7.4.0), and allows to specify conflict keys. While a test that conflicts with key K is running, no
other test that conflicts with K is run. For tests conflicting with "all", no other tests are run in
parallel.
An alternative to have a --CONFLICTS-- section is to add a file named CONFLICTS to the
directory containing the tests. The contents of the CONFLICTS file must have the same format as
the contents of the --CONFLICTS-- section.
Required: No.
Format: One conflict key per line. Comment lines starting with # are also allowed.
Example 1 (snippet):
.. code:: text
--CONFLICTS--
server
Example 1 (full): :ref:conflicts_1.phpt
``--WHITESPACE_SENSITIVE--``
Description: This flag is used to indicate that the test should not be changed by automated
formatting changes. Available as of PHP 7.4.3.
Required: No.
Format: No value, just the --WHITESPACE_SENSITIVE-- statement.
``--CAPTURE_STDIO--``
Description: This section enables which I/O streams the run-tests.php test script will use
when comparing executed file to the expected output. The STDIN is the standard input stream.
When STDOUT is enabled, the test script will also check the contents of the standard output.When STDERR is enabled, the test script will also compare the contents of the standard error I/O
stream.
If this section is left out of the test, by default, all three streams are enabled, so the tests
without this section capture all and is the same as enabling all three manually.
Required: No.
Format: A case insensitive space, newline or otherwise delimited list of one or more strings of
STDIN, STDOUT, and/or STDERR.
Example 1 (snippet):
.. code:: text
--CAPTURE_STDIO--
STDIN STDERR
Example 1 (full): :ref:capture_stdio_1.phpt
Example 2 (snippet):
.. code:: text
--CAPTURE_STDIO--
STDIN STDOUT
Example 2 (full): :ref:capture_stdio_2.phpt
Example 3 (snippet):
.. code:: text
--CAPTURE_STDIO--
STDIN STDOUT STDERR
Example 3(full): :ref:capture_stdio_3.phpt
``--EXTENSIONS--``
Description: Additional required shared extensions to be loaded when running the test. When therun-tests.php script is executed it loads all the extensions that are available and enabled for
that particular PHP at the time. If the test requires additional extension to be loaded and they
aren't loaded prior to running the test, this section loads them.
Required: No.
Format: A case sensitive newline separated list of extension names.
Example 1 (snippet):
.. code:: text
--EXTENSIONS--
curl
imagick
tokenizer
Example 1 (full): :ref:extensions.phpt
``--POST--``
Description: POST variables or data to be passed to the test script. This section forces the use
of the CGI binary instead of the usual CLI one.
Required: No.
Requirements: PHP CGI binary.
Format: Follows the HTTP post data format.
Example 1 (snippet):
.. code:: text
--POST--
c=string&d=12345.7
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: xml
--POST--
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://soapinterop.org/xsd">
SOAP-ENV:Body
<ns1:test xmlns:ns1="http://testuri.org" />
Example 2 (full): :ref:sample005.phpt
``--POST_RAW--``
Description: Raw POST data to be passed to the test script. This differs from the section above
because it doesn't automatically set the Content-Type, this leaves you free to define your own
within the section. This section forces the use of the CGI binary instead of the usual CLI one.
Required: No.
Requirements: PHP CGI binary.
Test Script Support: run-tests.php
Format: Follows the HTTP post data format.
Example 1 (snippet):
.. code:: text
--POST_RAW--
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
abcdef123456789
--AaB03x--
Example 1 (full): :ref:sample006.phpt
``--PUT--``
Description: Similar to the section above, PUT data to be passed to the test script. This
section forces the use of the CGI binary instead of the usual CLI one.
Required: No.
Requirements: PHP CGI binary.
Test Script Support: run-tests.php
Format: Raw data optionally preceded by a Content-Type header.
Example 1 (snippet):
.. code:: text
--PUT--
Content-Type: text/json
{"name":"default output handler","type":0,"flags":112,"level":0,"chunk_size":0,"buffer_size":16384,"buffer_used":3}
``--GZIP_POST--``
Description: When this section exists, the POST data will be gzencode()'d. This section forces
the use of the CGI binary instead of the usual CLI one.
Required: No.
Test Script Support: run-tests.php
Format: Just add the content to be gzencode()'d in the section.
Example 1 (snippet):
.. code:: xml
--GZIP_POST--
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://soapinterop.org/xsd">
SOAP-ENV:Body
<ns1:test xmlns:ns1="http://testuri.org" />
Example 1 (full): :ref:sample005.phpt
``--DEFLATE_POST--``
Description: When this section exists, the POST data will be gzcompress()'ed. This section
forces the use of the CGI binary instead of the usual CLI one.
Required: No.
Requirements:
Test Script Support: run-tests.php
Format: Just add the content to be gzcompress()'ed in the section.
Example 1 (snippet):
.. code:: xml
--DEFLATE_POST--
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://soapinterop.org/xsd">
SOAP-ENV:Body
<ns1:test xmlns:ns1="http://testuri.org" />
Example 1 (full): :ref:sample007.phpt
``--GET--``
Description: GET variables to be passed to the test script. This section forces the use of the
CGI binary instead of the usual CLI one.
Required: No.
Requirements: PHP CGI binary.
Format: A single line of text passed as the GET data to the script.
Example 1 (snippet):
.. code:: text
--GET--
a=test&b=http://example.com
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: text
--GET--
ar[elm1]=1234&ar[elm2]=0660&a=0234
Example 2 (full): :ref:sample008.phpt
``--COOKIE--``
Description: Cookies to be passed to the test script. This section forces the use of the CGI
binary instead of the usual CLI one.
Required: No.
Requirements: PHP CGI binary.
Test Script Support: run-tests.php
Format: A single line of text in a valid HTTP cookie format.
Example 1 (snippet):
.. code::
--COOKIE--
hello=World;goodbye=MrChips
Example 1 (full): :ref:sample002.phpt
``--STDIN--``
Description: Data to be fed to the test script's standard input.
Required: No.
Test Script Support: run-tests.php
Format: Any text within this section is passed as STDIN to PHP.
Example 1 (snippet):
.. code:: text
--STDIN--
fooBar
use this to input some thing to the php script
Example 1 (full): :ref:sample009.phpt
``--INI--``
Description: To be used if you need a specific php.ini setting for the test.
Required: No.
Format: Key value pairs including automatically replaced tags. One setting per line. Content
that is not a valid ini setting may cause failures.
The following is a list of all tags and what they are used to represent:
{PWD}: Represents the directory of the file containing the--INI--section.{TMP}: Represents the system's temporary directory. Available as of PHP 7.2.19 and 7.3.6.
Example 1 (snippet):
.. code:: text
--INI--
precision=14
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: text
--INI--
session.use_cookies=0
session.cache_limiter=
register_globals=1
session.serialize_handler=php
session.save_handler=files
Example 2 (full): :ref:sample003.phpt
``--ARGS--``
Description: A single line defining the arguments passed to PHP.
Required: No.
Format: A single line of text that is passed as the argument(s) to the PHP CLI.
Example 1 (snippet):
.. code:: text
--ARGS--
--arg value --arg=value -avalue -a=value -a value
Example 1 (full): :ref:sample010.phpt
``--ENV--``
Description: Configures environment variables such as those found in the $_SERVER global
array.
Required: No.
Format: Key value pairs. One setting per line.
Example 1 (snippet):
.. code:: text
--ENV--
SCRIPT_NAME=/frontcontroller10.php
REQUEST_URI=/frontcontroller10.php/hi
PATH_INFO=/hi
Example 1 (full): :ref:sample018.phpt
``--PHPDBG--``
Description: This section takes arbitrary phpdbg commands and executes the test file according
to them as it would be run in the phpdbg prompt.
Required: No.
Format: Arbitrary phpdbg commands
Example 1 (snippet):
.. code:: text
--PHPDBG--
b
4
b
del
0
b
5
r
b
del
1
r
y
q
Example 1 (full): :ref:phpdbg_1.phpt
``--FILE--``
Description: The test source code.
Required: One of the FILE type sections is required.
Format: PHP source code enclosed by PHP tags.
Example 1 (snippet):
.. code:: php
--FILE--
Example 1 (full): :ref:sample001.phpt
``--FILEEOF--``
Description: An alternative to --FILE-- where any trailing line breaks (\n || \r || \r\n
found at the end of the section) are omitted. This is an extreme edge-case feature, so 99.99% of the
time you won't need this section.
Required: One of the FILE type sections is required.
Test Script Support: run-tests.php
Format: PHP source code enclosed by PHP tags.
Example 1 (snippet):
.. code:: php
--FILEEOF--
<?php
eval("echo 'Hello'; // comment");
echo " World";
//last line comment
Example 1 (full): :ref:sample011.phpt
``--FILE_EXTERNAL--``
Description: An alternative to --FILE--. This is used to specify that an external file
should be used as the --FILE-- contents of the test file, and is designed for running the same
test file with different ini, environment, post/get or other external inputs. Basically it allows
you to DRY up some of your tests. The file must be in the same directory as the test file, or in a
subdirectory.
Required: One of the FILE type sections is required.
Test Script Support: run-tests.php
Format: path/to/file. Single line.
Example 1 (snippet):
.. code:: text
--FILE_EXTERNAL--
files/file012.inc
Example 1 (full): :ref:sample012.phpt
``--REDIRECTTEST--``
Description: This block allows you to redirect from one test to a bunch of other tests. It also
allows you to set configurations which are used on all tests in your destination.
Required: One of the FILE type sections is required.
Test Script Support: run-tests.php
Format: PHP source which is run through eval(). The tests destination is the value of an
array index 'TESTS'. Also, keep in mind, you can not use a REDIRECTTEST which is being pointed
to by another test which contains a REDIRECTTEST. In other words, no nesting.
The relative path declared in 'TESTS' is relative to the base directory for the PHP source code, not
relative to the current directory.
Last note, the array in this section must be returned to work.
Example 1 (snippet):
.. code:: php
--REDIRECTTEST--
return array(
'ENV' => array(
'PDOTEST_DSN' => 'sqlite2::memory:'
),
'TESTS' => 'ext/pdo/tests'
);
Example 1 (full): :ref:sample013.phpt Note: The destination tests for this example are not
included. See the PDO extension tests for reference to live tests using this section.
Example 2 (snippet):
.. code:: php
--REDIRECTTEST--
magic auto-configuration
$config = array(
'TESTS' => 'ext/pdo/tests'
);
if (false !== getenv('PDO_MYSQL_TEST_DSN')) {
# user set them from their shell
$config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');
$config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
$config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
$config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
}
} else {
$config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
$config['ENV']['PDOTEST_USER'] = 'root';
$config['ENV']['PDOTEST_PASS'] = '';
}
return $config;
Example 2 (full): :ref:sample014.phpt
Note: The destination tests for this example are not included. See the PDO extension tests for
reference to live tests using this section.
``--CGI--``
Description: This section takes no value. It merely provides a simple marker for tests that MUST
be run as CGI, even if there is no --POST-- or --GET-- sections in the test file.
Required: No.
Format: No value, just the --CGI-- statement.
Example 1 (snippet):
.. code:: text
--CGI--
Example 1 (full): :ref:sample016.phpt
``--XFAIL--``
Description: This section identifies this test as one that is currently expected to fail. It
should include a brief description of why it's expected to fail. Reasons for such expectations
include tests that are written before the functionality they are testing is implemented or notice of
a bug which is due to upstream code such as an extension which provides PHP support for some other
software.
Please do NOT include an --XFAIL-- without providing a text description for the reason it's
being used.
Required: No.
Test Script Support: run-tests.php
Format: A short plain text description of why this test is currently expected to fail.
Example 1 (snippet):
.. code:: text
--XFAIL--
This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64
Example 1 (full): :ref:sample017.phpt
``--FLAKY--``
Description: This section identifies this test as one that occasionally fails. If the test
actually fails, it will be retried one more time, and that result will be reported. The section
should include a brief description of why the test is flaky. Reasons for this include tests that
rely on relatively precise timing, or temporary disc states. Available as of PHP 8.1.22 and 8.2.9,
respectively.
Please do NOT include a --FLAKY-- section without providing a text description for the reason it
is being used.
Required: No.
Test Script Support: run-tests.php
Format: A short plain text description of why this test is flaky.
Example 1 (snippet):
.. code::
--FLAKY--
This test frequently fails in CI
Example 1 (full): flaky.phpt
``--EXPECTHEADERS--``
Description: The expected headers. Any header specified here must exist in the response and have
the same value or the test fails. Additional headers found in the actual tests while running are
ignored.
Required: No.
Format: HTTP style headers. May include multiple lines.
Example 1 (snippet):
--EXPECTHEADERS-- Status: 404
Example 1 (snippet):
.. code:: text
--EXPECTHEADERS--
Content-type: text/html; charset=UTF-8
Status: 403 Access Denied
Example 1 (full): :ref:sample018.phpt
Note: The destination tests for this example are not included. See the phar extension tests for
reference to live tests using this section.
``--EXPECT--``
Description: The expected output from the test script. This must match the actual output from
the test script exactly for the test to pass.
Required: One of the EXPECT type sections is required.
Format: Plain text. Multiple lines of text are allowed.
Example 1 (snippet):
.. code:: text
--EXPECT--
array(2) {
["hello"]=>
string(5) "World"
["goodbye"]=>
string(7) "MrChips"
}
Example 1 (full): :ref:sample002.phpt
``--EXPECT_EXTERNAL--``
Description: Similar to --EXPECT-- section, but just stating a filename where to load the
expected output from.
Required: One of the EXPECT type sections is required.
Test Script Support: run-tests.php
Example 1 (snippet):
.. code:: text
--EXPECT_EXTERNAL--
test001.expected.txt
test001.expected.txt
.. code:: php
array(2) {
["hello"]=>
string(5) "World"
["goodbye"]=>
string(7) "MrChips"
}
``--EXPECTF--``
Description: An alternative of --EXPECT--. Where it differs from --EXPECT-- is that it
uses a number of substitution tags for strings, spaces, digits, etc. that appear in test case output
but which may vary between test runs. The most common example of this is to use %s and %d to match
the file path and line number which are output by PHP Warnings.
Required: One of the EXPECT type sections is required.
Format: Plain text including tags which are inserted to represent different types of output
which are not guaranteed to have the same value on subsequent runs or when run on different
platforms.
The following is a list of all tags and what they are used to represent:
%e: Represents a directory separator, for example / on Linux.%s: One or more of anything (character or white space) except the end of line character.%S: Zero or more of anything (character or white space) except the end of line character.%a: One or more of anything (character or white space) including the end of line
character.%A: Zero or more of anything (character or white space) including the end of line
character.%w: Zero or more white space characters.%i: A signed integer value, for example +3142, -3142, 3142.%d: An unsigned integer value, for example 123456.%x: One or more hexadecimal character. That is, characters in the range 0-9, a-f, A-F.%f: A floating point number, for example: 3.142, -3.142, 3.142E-10, 3.142e+10.%c: A single character of any sort (.).%r...%r: Any string (...) enclosed between two%rwill be treated as a regular
expression.
Example 1 (snippet):
.. code:: text
--EXPECTF--
string(4) "test"
string(18) "http://example.com"
string(27) "<b>test</b>"
Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
bool(false)
string(6) "string"
float(12345.7)
string(29) "<p>string</p>"
bool(false)
Warning: filter_var() expects parameter 2 to be long, string given in %s011.php on line %d
NULL
Warning: filter_input() expects parameter 3 to be long, string given in %s011.php on line %d
NULL
Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d
NULL
Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d
NULL
Done
Example 1 (full): :ref:sample001.phpt
Example 2 (snippet):
.. code:: text
--EXPECTF--
Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d NULL
Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(): filename cannot be empty in %s on line %d
bool(false)
Warning: bzopen(): filename cannot be empty in %s on line %d
bool(false)
Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d
bool(false)
resource(%d) of type (stream) Done
Example 2 (full): :ref:sample019.phpt
Example 3 (snippet):
.. code:: text
--EXPECTF--
object(DOMNodeList)#%d (0) {
}
int(0)
bool(true)
bool(true)
string(0) ""
bool(true)
bool(true)
bool(false)
bool(false)
Example 2 (full): :ref:sample020.phpt
``--EXPECTF_EXTERNAL--``
Description: Similar to --EXPECTF-- section, but like the --EXPECT_EXTERNAL-- section
just stating a filename where to load the expected output from.
Required: One of the EXPECT type sections is required.
Test Script Support: run-tests.php
``--EXPECTREGEX--``
Description: An alternative of --EXPECT--. This form allows the tester to specify the result
in a regular expression.
Required: One of the EXPECT type sections is required.
Format: Plain text including regular expression patterns which represent data that can vary
between subsequent runs of a test or when run on different platforms.
Example 1 (snippet):
.. code:: text
--EXPECTREGEX--
M_E : 2.718281[0-9]*
M_LOG2E : 1.442695[0-9]*
M_LOG10E : 0.434294[0-9]*
M_LN2 : 0.693147[0-9]*
M_LN10 : 2.302585[0-9]*
M_PI : 3.141592[0-9]*
M_PI_2 : 1.570796[0-9]*
M_PI_4 : 0.785398[0-9]*
M_1_PI : 0.318309[0-9]*
M_2_PI : 0.636619[0-9]*
M_SQRTPI : 1.772453[0-9]*
M_2_SQRTPI: 1.128379[0-9]*
M_LNPI : 1.144729[0-9]*
M_EULER : 0.577215[0-9]*
M_SQRT2 : 1.414213[0-9]*
M_SQRT1_2 : 0.707106[0-9]*
M_SQRT3 : 1.732050[0-9]*
Example 1 (full): :ref:sample021.phpt
Example 2 (snippet):
.. code:: text
--EXPECTF--
*** Testing imap_append() : basic functionality ***
Create a new mailbox for test
Create a temporary mailbox and add 0 msgs
.. mailbox '%s' created
Add a couple of msgs to new mailbox {%s}INBOX.%s
bool(true)
bool(true)
Msg Count after append : 2
List the msg headers
array(2) {
[0]=>
string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)"
[1]=>
string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)"
}
Example 2 (full): :ref:sample025.phpt
Example 3 (snippet):
.. code:: text
--EXPECTREGEX--
string(4) "-012"
string(8) "2d303132"
(string(13) " 4294967284"|string(20) "18446744073709551604")
(string(26) "20202034323934393637323834"|string(40) "3138343436373434303733373039353531363034")
Example 3 (full): :ref:sample023.phpt
``--EXPECTREGEX_EXTERNAL--``
Description: Similar to --EXPECTREGEX-- section, but like the --EXPECT_EXTERNAL--
section just stating a filename where to load the expected output from.
Required: One of the EXPECT type sections is required.
Test Script Support: run-tests.php
``--CLEAN--``
Description: Code that is executed after a test completes. It's main purpose is to allow you to
clean up after yourself. You might need to remove files created during the test or close sockets or
database connections following a test. Infact, even if a test fails or encounters a fatal error
during the test, the code found in the --CLEAN-- section will still run.
Code in the clean section is run in a completely different process than the one the test was run in.
So do not try accessing variables you created in the --FILE-- section from inside the--CLEAN-- section, they won't exist.
Using the switch --no-clean on run-tests.php, you can prevent the code found in the--CLEAN-- section of a test from running. This allows you to inspect generated data or files
without them being removed by the --CLEAN-- section.
Required: No.
Test Script Support: run-tests.php
Format: PHP source code enclosed by PHP tags.
Example 1 (snippet):
.. code:: php
--CLEAN--
Example 1 (full): :ref:sample024.phpt
Example 2 (snippet):
.. code:: php
--CLEAN--
Example 2 (full): :ref:sample025.phpt
Example 3 (snippet):
.. code:: php
--CLEAN--
Example 3 (full): :ref:sample022.phpt
Samples
capture_stdio_1.phpt
.. code:: php
--TEST--
Test covering the I/O stdin and stdout streams.
--DESCRIPTION--
This tests checks if the output of stdin and stdout I/O streams match the
expected content.
--CAPTURE_STDIO--
STDIN STDERR
--FILE--
--EXPECT--
This is error sent to the stderr I/O stream
capture_stdio_2.phpt
.. code:: php
--TEST--
Test covering the I/O stdin and stderr streams.
--DESCRIPTION--
This tests checks if the output of stdin and stderr I/O streams match the
expected content.
--CAPTURE_STDIO--
STDIN STDOUT
--FILE--
--EXPECT--
Hello, world. This is sent to the stdout I/O stream
capture_stdio_3.phpt
.. code:: php
--TEST--
Test covering the all standard I/O streams.
--DESCRIPTION--
This tests checks if the output of stdin, stdout and stderr I/O streams match
the expected content.
--CAPTURE_STDIO--
STDIN STDOUT STDERR
--FILE--
--EXPECT--
Hello, world. This is sent to the stdout I/O stream
This is error sent to the stderr I/O stream
clean.php
.. code:: php
Nmsgs; $i++) { imap_delete($imap_stream, $i); } $mailboxes = imap_getmailboxes($imap_stream, $server, '*'); foreach($mailboxes as $value) { // Only delete mailboxes with our prefix if (preg_match('/\{.*?\}INBOX\.(.+)/', $value->name, $match) == 1) { if (strlen($match[1]) >= strlen($mailbox_prefix) && substr_compare($match[1], $mailbox_prefix, 0, strlen($mailbox_prefix)) == 0) { imap_deletemailbox($imap_stream, $value->name); } } } imap_close($imap_stream, CL_EXPUNGE); ?>conflicts_1.phpt
.. code:: php
--TEST--
Test get_headers() function : test with context
--CONFLICTS--
server
--FILE--
<?php
include DIR."/../../../../sapi/cli/tests/php_cli_server.inc";
php_cli_server_start('header("X-Request-Method: ".$_SERVER["REQUEST_METHOD"]);');
$opts = array(
'http' => array(
'method' => 'HEAD'
)
);
$context = stream_context_create($opts);
$headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1, $context);
echo $headers["X-Request-Method"]."\n";
stream_context_set_default($opts);
$headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1);
echo $headers["X-Request-Method"]."\n";
echo "Done";
?>
--EXPECT--
HEAD
HEAD
Done
extensions.phpt
.. code:: php
--TEST--
phpt EXTENSIONS directive with shared extensions
--DESCRIPTION--
This test covers the presence of some loaded extensions with a list of additional
extensions to be loaded when running test.
--EXTENSIONS--
curl
imagick
tokenizer
--FILE--
--EXPECT--
bool(true)
bool(true)
bool(true)
file012.phpt
.. code:: php
phpdbg_1.phpt
.. code:: php
--TEST--
Test deleting breakpoints
--PHPDBG--
b 4
b del 0
b 5
r
b del 1
r
y
q
--EXPECTF--
[Successful compilation of %s]
prompt> [Breakpoint #0 added at %s:4]
prompt> [Deleted breakpoint #0]
prompt> [Breakpoint #1 added at %s:5]
prompt> 12
[Breakpoint #1 at %s:5, hits: 1]
00005: echo $i++;
00006: echo $i++;
00007:
prompt> [Deleted breakpoint #1]
prompt> Do you really want to restart execution? (type y or n): 1234
[Script ended normally]
prompt>
--FILE--
<?php
$i = 1;
echo $i++;
echo $i++;
echo $i++;
echo $i++;
sample001.phpt
.. code:: php
--TEST--
Test filter_input() with GET and POST data.
--DESCRIPTION--
This test covers both valid and invalid usages of
filter_input() with INPUT_GET and INPUT_POST data
and several different filter sanitizers.
--CREDITS--
Felipe Pena [email protected]
--INI--
precision=14
--SKIPIF--
--GET--
a=test&b=https://example.com
--POST--
c=string&d=12345.7
--FILE--
--EXPECTF--
string(4) "test"
string(19) "https://example.com"
string(27) "<b>test</b>"
Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
bool(false)
string(6) "string"
float(12345.7)
string(29) "<p>string</p>"
bool(false)
Warning: filter_var() expects parameter 2 to be long, string given in %ssample001.php on line %d
NULL
Warning: filter_input() expects parameter 3 to be long, string given in %ssample001.php on line %d
NULL
Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
NULL
Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
NULL
Done
sample002.phpt
.. code:: php
--TEST--
Test receipt of cookie data.
--CREDITS--
Zoe Slattery [email protected]
TestFest Munich 2009-05-19
--COOKIE--
hello=World;goodbye=MrChips
--FILE--
--EXPECT--
array(2) {
["hello"]=>
string(5) "World"
["goodbye"]=>
string(7) "MrChips"
}
sample003.phpt
.. code:: php
--TEST--
session object deserialization
--SKIPIF--
--INI--
session.use_cookies=0
session.cache_limiter=
register_globals=1
session.serialize_handler=php
session.save_handler=files
--FILE--
<?php
error_reporting(E_ALL);
class foo {
public $bar = "ok";
function method() { $this->yes++; }
}
session_id("abtest");
session_start();
session_decode('baz|O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}arr|a:1:{i:3;O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}}');
$baz->method();
$arr[3]->method();
var_dump($baz);
var_dump($arr);
session_destroy();
--EXPECT--
object(foo)#1 (2) {
["bar"]=>
string(2) "ok"
["yes"]=>
int(2)
}
array(1) {
[3]=>
object(foo)#2 (2) {
["bar"]=>
string(2) "ok"
["yes"]=>
int(2)
}
}
sample005.phpt
.. code:: php
--TEST--
SOAP Server 19: compressed request (gzip)
--SKIPIF--
--INI--
precision=14
--GZIP_POST--
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://soapinterop.org/xsd">
SOAP-ENV:Body
<ns1:test xmlns:ns1="http://testuri.org" />
--FILE--
<?php
function test() {
return "Hello World";
}
$server = new soapserver(null,array('uri'=>"http://testuri.org"));
$server->addfunction("test");
$server->handle();
echo "ok\n";
?>
--EXPECT--
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://testuri.org" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">SOAP-ENV:Bodyns1:testResponse
ok
sample006.phpt
.. code:: php
--TEST--
is_uploaded_file() function
--CREDITS--
Dave Kelsey [email protected]
--SKIPIF--
--POST_RAW--
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
abcdef123456789
--AaB03x--
--FILE--
<?php
// uploaded file
var_dump(is_uploaded_file($_FILES['pics']['tmp_name']));
// not an uploaded file
var_dump(is_uploaded_file($_FILES['pics']['name']));
// not an uploaded file
var_dump(is_uploaded_file('random_filename.txt'));
// not an uploaded file
var_dump(is_uploaded_file('FILE'));
// Error cases
var_dump(is_uploaded_file());
var_dump(is_uploaded_file('a', 'b'));
?>
--EXPECTF--
bool(true)
bool(false)
bool(false)
bool(false)
Warning: is_uploaded_file() expects exactly 1 parameter, 0 given in %s on line %d
NULL
Warning: is_uploaded_file() expects exactly 1 parameter, 2 given in %s on line %d
NULL
sample007.phpt
.. code:: php
--TEST--
SOAP Server 20: compressed request (deflate)
--SKIPIF--
--INI--
precision=14
--DEFLATE_POST--
<SOAP-ENV:Envelope
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:si="http://soapinterop.org/xsd">
SOAP-ENV:Body
<ns1:test xmlns:ns1="http://testuri.org" />
--FILE--
<?php
function test() {
return "Hello World";
}
$server = new soapserver(null,array('uri'=>"http://testuri.org"));
$server->addfunction("test");
$server->handle();
echo "ok\n";
?>
--EXPECT--
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://testuri.org" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">SOAP-ENV:Bodyns1:testResponse
ok
sample008.phpt
.. code:: php
--TEST--
GET/POST/REQUEST Test with input_filter
--SKIPIF--
--POST--
d=379
--GET--
ar[elm1]=1234&ar[elm2]=0660&a=0234
--FILE--
<?php
$ret = filter_input(INPUT_GET, 'a', FILTER_VALIDATE_INT);
var_dump($ret);
$ret = filter_input(INPUT_GET, 'a', FILTER_VALIDATE_INT, array('flags'=>FILTER_FLAG_ALLOW_OCTAL));
var_dump($ret);
$ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_REQUIRE_ARRAY));
var_dump($ret);
$ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_FLAG_ALLOW_OCTAL|FILTER_REQUIRE_ARRAY));
var_dump($ret);
?>
--EXPECT--
bool(false)
int(156)
array(2) {
["elm1"]=>
int(1234)
["elm2"]=>
bool(false)
}
array(2) {
["elm1"]=>
int(1234)
["elm2"]=>
int(432)
}
sample009.phpt
.. code:: php
--TEST--
STDIN input
--FILE--
--STDIN--
fooBar
use this to input some thing to the php script
--EXPECT--
string(54) "fooBar
use this to input some thing to the php script
"
sample010.phpt
.. code:: php
--TEST--
getopt#005 (Required values)
--ARGS--
--arg value --arg=value -avalue -a=value -a value
--INI--
register_argc_argv=On
variables_order=GPS
--FILE--
--EXPECT--
array(2) {
["arg"]=>
array(2) {
[0]=>
string(5) "value"
[1]=>
string(5) "value"
}
["a"]=>
array(3) {
[0]=>
string(5) "value"
[1]=>
string(5) "value"
[2]=>
string(5) "value"
}
}
sample011.phpt
.. code:: php
--TEST--
Bug #35382 (Comment in end of file produces fatal error)
--FILEEOF--
<?php
eval("echo 'Hello'; // comment");
echo " World";
//last line comment
--EXPECTF--
Hello World
sample012.phpt
.. code:: php
--TEST--
sample test for file_external
--FILE_EXTERNAL--
files/file012.inc
--EXPECT--
hello world
sample013.phpt
.. code:: php
--TEST--
SQLite2
--SKIPIF--
--REDIRECTTEST--
return array(
'ENV' => array(
'PDOTEST_DSN' => 'sqlite2::memory:'
),
'TESTS' => 'ext/pdo/tests'
);
sample014.phpt
.. code:: php
--TEST--
MySQL
--SKIPIF--
--REDIRECTTEST--
magic auto-configuration
$config = array(
'TESTS' => 'ext/pdo/tests'
);
if (false !== getenv('PDO_MYSQL_TEST_DSN')) {
# user set them from their shell
$config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');
$config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
$config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
$config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
}
} else {
$config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
$config['ENV']['PDOTEST_USER'] = 'root';
$config['ENV']['PDOTEST_PASS'] = '';
}
return $config;
sample016.phpt
.. code:: php
--TEST--
Test get variables with CGI binary
--GET--
hello=World&goodbye=MrChips
--CGI--
--FILE--
--EXPECT--
array(2) {
["hello"]=>
string(5) "World"
["goodbye"]=>
string(7) "MrChips"
}
sample017.phpt
.. code:: php
--TEST--
PDO Common: Bug #34630 (inserting streams as LOBs)
--SKIPIF--
--FILE--
<?php
if (getenv('REDIR_TEST_DIR') === false) putenv('REDIR_TEST_DIR='.DIR . '/../../pdo/tests/');
require_once getenv('REDIR_TEST_DIR') . 'pdo_test.inc';
$db = PDOTest::factory();
$driver = $db->getAttribute(PDO::ATTR_DRIVER_NAME);
$is_oci = $driver == 'oci';
if ($is_oci) {
$db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val BLOB)');
} else {
$db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(256))');
}
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$fp = tmpfile();
fwrite($fp, "I am the LOB data");
rewind($fp);
if ($is_oci) {
/* oracle is a bit different; you need to initiate a transaction otherwise
* the empty blob will be committed implicitly when the statement is
* executed */
$db->beginTransaction();
$insert = $db->prepare("insert into test (id, val) values (1, EMPTY_BLOB()) RETURNING val INTO :blob");
} else {
$insert = $db->prepare("insert into test (id, val) values (1, :blob)");
}
$insert->bindValue(':blob', $fp, PDO::PARAM_LOB);
$insert->execute();
$insert = null;
$db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC));
?>
--XFAIL--
This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64
--EXPECT--
array(1) {
[0]=>
array(2) {
["id"]=>
string(1) "1"
["val"]=>
string(17) "I am the LOB data"
}
}
sample018.phpt
.. code:: php
--TEST--
Phar front controller rewrite access denied [cache_list]
--INI--
default_charset=UTF-8
phar.cache_list={PWD}/frontcontroller10.php
--SKIPIF--
--ENV--
SCRIPT_NAME=/frontcontroller10.php
REQUEST_URI=/frontcontroller10.php/hi
PATH_INFO=/hi
--FILE_EXTERNAL--
files/frontcontroller4.phar
--EXPECTHEADERS--
Content-type: text/html; charset=UTF-8
Status: 403 Access Denied
--EXPECT--
<h1>403 - File /hi Access Denied</h1>sample019.phpt
.. code:: php
--TEST--
bzopen() and invalid parameters
--SKIPIF--
--FILE--
<?php
var_dump(bzopen());
var_dump(bzopen("", ""));
var_dump(bzopen("", "r"));
var_dump(bzopen("", "w"));
var_dump(bzopen("", "x"));
var_dump(bzopen("", "rw"));
var_dump(bzopen("no_such_file", "r"));
$fp = fopen(FILE,"r");
var_dump(bzopen($fp, "r"));
echo "Done\n";
?>
--EXPECTF--
Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d
NULL
Warning: bzopen(): '' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(): filename cannot be empty in %s on line %d
bool(false)
Warning: bzopen(): filename cannot be empty in %s on line %d
bool(false)
Warning: bzopen(): 'x' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(): 'rw' is not a valid mode for bzopen(). Only 'w' and 'r' are supported. in %s on line %d
bool(false)
Warning: bzopen(no_such_file): failed to open stream: No such file or directory in %s on line %d
bool(false)
resource(%d) of type (stream)
Done
sample020.phpt
.. code:: php
--TEST--
Bug #42082 (NodeList length zero should be empty)
--FILE--
<?php
$doc = new DOMDocument();
$xpath = new DOMXPath($doc);
$nodes = $xpath->query('*');
var_dump($nodes);
var_dump($nodes->length);
$length = $nodes->length;
var_dump(empty($nodes->length), empty($length));
$doc->loadXML("
var_dump($doc->firstChild->nodeValue, empty($doc->firstChild->nodeValue), isset($doc->firstChild->nodeValue));
var_dump(empty($doc->nodeType), empty($doc->firstChild->nodeType))
?>
--EXPECTF--
object(DOMNodeList)#%d (0) {
}
int(0)
bool(true)
bool(true)
string(0) ""
bool(true)
bool(true)
bool(false)
bool(false)
sample021.phpt
.. code:: php
--TEST--
Math constants
--INI--
precision=14
--FILE--
--EXPECTREGEX--
M_E : 2.718281[0-9]*
M_LOG2E : 1.442695[0-9]*
M_LOG10E : 0.434294[0-9]*
M_LN2 : 0.693147[0-9]*
M_LN10 : 2.302585[0-9]*
M_PI : 3.141592[0-9]*
M_PI_2 : 1.570796[0-9]*
M_PI_4 : 0.785398[0-9]*
M_1_PI : 0.318309[0-9]*
M_2_PI : 0.636619[0-9]*
M_SQRTPI : 1.772453[0-9]*
M_2_SQRTPI: 1.128379[0-9]*
M_LNPI : 1.144729[0-9]*
M_EULER : 0.577215[0-9]*
M_SQRT2 : 1.414213[0-9]*
M_SQRT1_2 : 0.707106[0-9]*
M_SQRT3 : 1.732050[0-9]*
sample022.phpt
.. code:: php
--TEST--
shm_detach() tests
--SKIPIF--
--FILE--
<?php
$key = ftok(DIR.'/003.phpt', 'q');
var_dump(shm_detach());
var_dump(shm_detach(1,1));
$s = shm_attach($key);
var_dump(shm_detach($s));
var_dump(shm_detach($s));
shm_remove($s);
var_dump(shm_detach(0));
var_dump(shm_detach(1));
var_dump(shm_detach(-1));
echo "Done\n";
?>
--CLEAN--
--EXPECTF--
Warning: shm_detach() expects exactly 1 parameter, 0 given in %ssample022.php on line %d
NULL
Warning: shm_detach() expects exactly 1 parameter, 2 given in %ssample022.php on line %d
NULL
bool(true)
Warning: shm_detach(): %d is not a valid sysvshm resource in %ssample022.php on line %d
bool(false)
Warning: shm_remove(): %d is not a valid sysvshm resource in %ssample022.php on line %d
Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
NULL
Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
NULL
Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
NULL
Done
sample023.phpt
.. code:: php
--TEST--
Bug #23894 (sprintf() decimal specifiers problem)
--FILE--
--EXPECTREGEX--
string(4) "-012"
string(8) "2d303132"
(string(13) " 4294967284"|string(20) "18446744073709551604")
(string(26) "20202034323934393637323834"|string(40) "3138343436373434303733373039353531363034")
sample024.phpt
.. code:: php
--TEST--
DOMDocument::save Test basic function of save method
--SKIPIF--
--FILE--
<?php
$doc = new DOMDocument('1.0');
$doc->formatOutput = true;
$root = $doc->createElement('book');
$root = $doc->appendChild($root);
$title = $doc->createElement('title');
$title = $root->appendChild($title);
$text = $doc->createTextNode('This is the title');
$text = $title->appendChild($text);
$temp_filename = DIR.'/DomDocument_save_basic.tmp';
echo 'Wrote: ' . $doc->save($temp_filename) . ' bytes'; // Wrote: 72 bytes
?>
--CLEAN--
--EXPECTF--
Wrote: 72 bytes
sample025.phpt
.. code:: php
--TEST--
Test imap_append() function : basic functionality
--SKIPIF--
--FILE--
<?php
/* Prototype : bool imap_append ( resource $imap_stream , string $mailbox , string $message [, string $options ] )
* Description: Append a string message to a specified mailbox.
* Source code: ext/imap/php_imap.c
*/
echo "*** Testing imap_append() : basic functionality ***\n";
require_once(DIR.'/imap_include.inc');
echo "Create a new mailbox for test\n";
$imap_stream = setup_test_mailbox("", 0);
if (!is_resource($imap_stream)) {
exit("TEST FAILED: Unable to create test mailbox\n");
}
$mb_details = imap_mailboxmsginfo($imap_stream);
echo "Add a couple of msgs to new mailbox " . $mb_details->Mailbox . "\n";
var_dump(imap_append($imap_stream, $mb_details->Mailbox
, "From: [email protected]\r\n"
. "To: [email protected]\r\n"
. "Subject: Test message\r\n"
. "\r\n"
. "this is a test message, please ignore\r\n"
));
var_dump(imap_append($imap_stream, $mb_details->Mailbox
, "From: [email protected]\r\n"
. "To: [email protected]\r\n"
. "Subject: Another test\r\n"
. "\r\n"
. "this is another test message, please ignore it too!!\r\n"
));
$check = imap_check($imap_stream);
echo "Msg Count after append : ". $check->Nmsgs . "\n";
echo "List the msg headers\n";
var_dump(imap_headers($imap_stream));
imap_close($imap_stream);
?>
--CLEAN--
--EXPECTF--
*** Testing imap_append() : basic functionality ***
Create a new mailbox for test
Create a temporary mailbox and add 0 msgs
.. mailbox '%s' created
Add a couple of msgs to new mailbox {%s}INBOX.%s
bool(true)
bool(true)
Msg Count after append : 2
List the msg headers
array(2) {
[0]=>
string(%d) "%w%s 1)%s webmaster@something. Test message (%d chars)"
[1]=>
string(%d) "%w%s 2)%s webmaster@something. Another test (%d chars)"
}
sample026.phpt
.. code:: php
--TEST--
SPL: ArrayIterator implementing RecursiveIterator
--FILE--
<?php
$array = array(1, 2 => array(21, 22 => array(221, 222), 23 => array(231)), 3);
$dir = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY);
foreach ($dir as $file) {
print "$file\n";
}
?>
===DONE===
--EXPECT--
1
21
221
222
231
3
skipif2.phpt
.. code:: php
skipif.phpt
.. code:: php
xfailif.phpt
.. code:: php
--TEST--
Handling of errors during linking
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.preload={PWD}/preload_inheritance_error_ind.inc
--SKIPIF--
--FILE--
--EXPECTF--
Fatal error: Declaration of B::foo($bar) must be compatible with A::foo() in %spreload_inheritance_error.inc on line 8
Source Core Data Structures Fci Fcc (`docs/source/core/data-structures/fci-fcc.rst`)
#######################################################
PHP Callables (zend_fcall_info/zend_fcall_info_cache)
#######################################################
A common pattern for custom behaviour is to accept a user callback. As such we need a way to
represent PHP callables in C. For this purpose there exists two structures zend_fcall_info andzend_fcall_info_cache. zend_fcall_info_cache is a pure representation of a PHP callable, and
should be the structure used to store PHP callables. zend_fcall_info contains all the required
information to perform a call to a PHP callable. We will use the commonly used abbreviation of FCI
and FCC when talking about zend_fcall_info and zend_fcall_info_cache respectively. The most
likely way to encounter those structs is by using the ZPP f argument specifier.
Generic callable API
There is a set of common parameters used within the callable APIs which are:
.. list-table:: Common callable API parameter meaning
:header-rows: 1
- Name
- Type
- Description
fnzend_function*- The function pointer to call, usually stored in
EG(function_table)for global functions
orzend_class_entry->function_tablefor class methods.
this_ptrzend_object*- Object pointer representing
$this.
called_scopezend_class_entry*- Scope where the method is called,
NULLfor global functions.
retvalzval*- zval pointer where the return value of the called function will be stored.
param_countuint32_t- Number of parameters passed to the function via the
paramsargument.
paramszval*- Pointer to a C zval array containing the arguments to the function, may be
NULLifparam_countis 0.
named_paramsHashTable*HashTable containing named argument as name=>val, can also contain positional argument if
the key is numeric (in which case the positional argument is pushed as the next argument
regardless of its index)
consumed_argsuint32_t- Arguments consumed by the call. See
zend_fcall_info.
There are some functions that do not require the use of either the FCI or the FCC structs:
.. list-table:: Basic callable API
:header-rows: 1
- Function/Macro
- Description
.. code:: c
bool zend_is_callable(
const zval *callable,
zend_fcall_info_cache *fcc,
char **error
)Check if the
callablezval is a PHP callable. Returns true if it is, false otherwise.fccis an out parameter constructing the FCC struct, see below for details.error
is an optional out parameter holding the error message ifcallableis not a PHP
callable. Note: the signature used to bebool zend_is_callable(zval *callable, uint32_t check_flags, zend_string **callable_name)prior to PHP 8.7.
.. code:: c
bool zend_is_callable_ex(
zval *callable,
zend_object *object,
uint32_t check_flags,
zend_string **callable_name,
zend_fcall_info_cache *fcc,
char **error
)Same as
zend_is_callable()with additional arguments.objectifcallableis a
string, thiszend_object*represents the class instance to check if such a method
exists.check_flagsis a bit mask ofIS_CALLABLE_CHECK_SYNTAX_ONLYandIS_CALLABLE_SUPPRESS_DEPRECATIONS, generally should be left to 0.callable_nameis
an optional out parameter, it will be set to the string representation of thecallable
zval, set even in case of failure.
.. code:: c
zend_call_known_function(
zend_function *fn,
zend_object *this_ptr,
zend_class_entry *called_scope,
zval *retval,
uint32_t param_count,
zval *params,
HashTable *named_params
)Call a known function with given parameters.
retvalmay beNULLin which case the
return value of the function is discarded. Ifthis_ptrisNULLit must be a global
function or static method.called_scopemust not beNULLiffnis a method
(instanced or static).
.. code:: c
zend_call_known_function_ex(
zend_function *fn,
zend_object *this_ptr,
zend_class_entry *called_scope,
zval *retval,
uint32_t param_count,
zval *params,
HashTable *named_params
uint32_t consumed_args
)Same as
zend_call_known_function(), with an additionalconsumed_argsparameter (seezend_fcall_info).
.. code:: c
zend_call_known_instance_method(
zend_function *fn,
zend_object *this_ptr,
zval *retval,
uint32_t param_count,
zval *params
)Same as
zend_call_known_function(), where thecalled_scopeis derived fromthis_ptr->ce.
.. code:: c
zend_call_known_instance_method_with_0_params(
zend_function *fn,
zend_object *this_ptr,
zval *retval,
)Helper function around
zend_call_known_instance_method().
.. code:: c
zend_call_known_instance_method_with_1_params(
zend_function *fn,
zend_object *this_ptr,
zval *retval,
zval *param
)Helper function around
zend_call_known_instance_method().
.. code:: c
zend_call_known_instance_method_with_2_params(
zend_function *fn,
zend_object *this_ptr,
zval *retval,
zval *param1,
zval *param2
)Helper function around
zend_call_known_instance_method().
.. code:: c
zend_call_method_if_exists(
zend_object *this_ptr,
zend_string *method_name,
zval *retval,
uint32_t param_count,
zval *params
)Returns
FAILUREif themethod_namedoes not exists on the object,SUCCESS
otherwise. The retval will be UNDEF if the return value is ``FAILURE```, as such the
retval can be unconditionally destroyed. However it may also be UNDEF in an exception was
thrown.
.. code:: c
call_user_function(
function_table,
object,
function_name,
retval,
param_count,
params
)Deprecated API. Checks that
function_name(and optionallyobject) is a valid PHP
callable viazend_is_callable_ex()and calls it with the given parameters and return
value.function_tableis ignored and should always beNULL. It is recommended to
instead usezend_is_callable()to fetch an FCC and call it withzend_call_known_fcc(), see below for more details.
.. code:: c
call_user_function_named(
function_table,
object,
function_name,
retval,
param_count,
params,
named_params
)Deprecated API. Same as
call_user_function()with the extranamed_paramsargument.
zend_fcall_info_cache
.. code:: c
typedef struct _zend_fcall_info_cache {
zend_function *function_handler;
zend_class_entry *calling_scope;
zend_class_entry *called_scope;
zend_object object; / Pointer for object representing $this */
zend_object closure; / Closure reference, only if the callable is the object */
} zend_fcall_info_cache;
function_handler:
The actual body of a PHP function that will be used by the VM, can be retrieved from the global
function table or a class function table (zend_class_entry->function_table). May be NULL
for trampolines, see details below.
object:
If the function is an instance object method, this field represents the value of $this. Should
be NULL for static methods and global functions.
called_scope:
The scope in which to call the method, generally it's object->ce.
calling_scope:
The scope in which this call is made, only used by zend_is_callable_ex() to resolve the
actual scope for the deprecated partially supported callables (e.g. [$obj, "parent::method"]). Can be ignored as it is not used when actually calling a function.
closure:
Storage field for the Closure object or the object containing the __invoke() /
__call() / __callStatic() method pointed at in function_handler.
.. note::
Prior to PHP 8.3.0, the FCC did not contain the closure field, and the relevant FCC API
didn't exist.
FCCs are the goto structure to handle and store PHP callables as most of the time they don't need
any reference counting. The one exception is when the FCC represents a trampoline. A trampoline is a
heap-allocated zend_function*, the most common trampolines are calls to a non existing class
method handled by the __call() or __callStatic() magic methods. A trampoline releases thezend_function* when it is called, as the op array will be consumed. However the f ZPP
argument specifier will free the trampoline and assign NULL to function_handler, making
the FCC uninitialized. The trampoline can be manually fetched by using zend_is_callable() or to
prevent ZPP from freeing it one can use the F argument specifier`. However, in that case careful
consideration need to be done to free the potential trampoline in any failure path, including ZPP
failure.
The only case where an FCC may be uninitialized is if the function is a trampoline, i.e. when the
method of a class does not exist but is handled by the magic methods__call()/__callStatic(). This is because a trampoline is freed by ZPP as it is a newly
allocated zend_function struct with the op array copied, and is freed when called. To retrieve
it manually use zend_is_callable_ex(). To free a trampoline the void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) should be used to release it. This
function can be called even if the FCC is not a trampoline. However, if a reference to the closure
is kept, this must be called prior to freeing the closure, as the trampoline will partially
refer to a zend_function * entry in the closure CE.
.. note::
There is investigative work being done to simplify the handling of trampoline in the future.
FCC API
The FCC API is defined in Zend/zend_API.h.
.. list-table:: zend_fcall_info_cache API
:header-rows: 1
- Function/Macro
- Description
ZEND_FCC_INITIALIZED(fcc)- Check if an FCC is initialized.
.. code:: c
zend_fcc_addref(
zend_fcall_info_cache *fcc
)Adds necessary reference increments and copies to an FCC pointer.
.. code:: c
zend_fcc_dup(
zend_fcall_info_cache *dest,
const zend_fcall_info_cache *src,
)Duplicates the source FCC pointer into the destination FCC pointer.
zend_fcc_equals(fcc_1, fcc_2)- Determines if two FCCs represent the same function.
zend_fcc_closure_equals_ex(fcc_1, fcc_2)- Determine if both FCCs represent the same function as a
Closuremay be aZEND_ACC_FAKE_CLOSURE
.. code:: c
zend_fcc_dtor(
zend_fcall_info_cache *fcc
)Releases data hold by an FCC, including freeing trampolines.
zend_get_gc_buffer_add_fcc(gc_buffer, fcc)- For use with the
get_gcobject handler in case the FCC is stored by an internal object.
.. code:: c
zend_call_known_fcc(
zend_fcall_info_cache *fcc,
zval *retval,
uint32_t param_count,
zval *params,
HashTable *named_params
)Calls an initialized FCC, all other parameters are optional.
zend_get_callable_zval_from_fcc(fcc, callable)- Creates a representation of the FCC into the given callable
zval*. Note static class
methods that can be represented as strings or an array pair are returned as an array pair
.. code:: c
zend_release_fcall_info_cache(
zend_fcall_info_cache *fcc
)Release potential trampoline held by the FCC.
zend_fcall_info
.. code:: c
typedef struct _zend_fcall_info {
size_t size;
zval function_name;
zval *retval;
zval *params;
zend_object object;
uint32_t param_count;
uint32_t consumed_args;
/ This hashtable can also contain positional arguments (with integer keys),
* which will be appended to the normal params[]. This makes it easier to
* integrate APIs like call_user_func_array(). The usual restriction that
* there may not be position arguments after named arguments applies. */
HashTable *named_params;
} zend_fcall_info;
size:
Mandatory field, which is the size of an FCI structure, thus always: sizeof(zend_fcall_info)
function_name:
The actual callable, do not be fooled by the name of this field as this is a leftover when PHP
didn't have objects and class methods. It must be a string zval or an array following the same
rules as callables in PHP, namely the first index is a class or instance object, and the second
one is the method name. If an initialized FCC representing the function exists, it may be
omitted.
object:
Only used for callability checks in conjunction with function_name, see
zend_is_callable_ex() for more details. If an initialized FCC representing the function
exists, it may be omitted.
retval:
Mandatory field, which will contain the result of the PHP function.
param_count:
Mandatory field, the number of arguments that will be provided to this call to the function.
consumed_args:
Specifies positional arguments whose ownership is transfered to the function being called. This
is used as an optimization to avoid an unnecessary Z_ADDREF_P()/zval_ptr_dtor() when the
value is not used after the call. Only one argument can be specified at once with
zend_fci_consumed_arg(n) (zero-based), and the value must not be a reference. Must be set to
0 when no parameter applies. New since PHP 8.6.
params:
contains positional arguments that will be provided to this call to the function. If
param_count = 0, it can be NULL.
named_params:
A HashTable containing named (or positional) arguments.
FCI API
The FCI API is defined in Zend/zend_API.h.
.. list-table:: zend_fcall_info API
:header-rows: 1
- Function/Macro
- Description
ZEND_FCI_INITIALIZED(fci)- Check if an FCI is initialized.
zend_call_function(fci, fcc)Calls a function,
fccis a pointer to an FCC that may beNULL, in this case the FCC
will be derived from the FCIfunction_namezval, including a callability check. This
function only returnsFAILUREifEG(active)is false during late engine shutdown.
If the call or call setup throws, EG(exception) will be set and the retval will be UNDEF.
Otherwise, the retval will be a non-UNDEF value.
.. code:: c
zend_call_function_with_return_value(
zend_fcall_info *fci,
zend_fcall_info_cache *fcc,
zval *retval
)Same as
zend_call_functionbut sets the FCI return value toretval.
.. code:: c
zend_fcall_info_init(
const zval *callable,
uint32_t check_flags,
zend_fcall_info *fci,
zend_fcall_info_cache *fcc,
zend_string **callable_name,
char **error
)Build a FCI/FCC pair from a
zval*by forwardingcallable,check_flags,fcc,callable_name, anderrortozend_is_callable_ex(). As suchcallable_name
anderrorare optional and may beNULL. ReturnsFAILUREifcallableis not
a PHP callable,SUCCESSotherwise.
Deprecated FCI API
There is also a set of FCI API that is now considered deprecated as it is unintuitive and does
unnecessary parameter copying.
In general the IS_ARRAY zval should be assigned to the named_params field of the FCI instead of
copying the arguments.
.. list-table:: Deprecated zend_fcall_info API
:header-rows: 1
- Function
- Description
.. code:: c
zend_fcall_info_call(
zend_fcall_info *fci,
zend_fcall_info_cache *fcc,
zval *retval,
zval *args
)Call a function using information created by zend_fcall_info_init()/args() functions. If
args is given then those replace the argument info in FCI is temporarily.argsmust
be an IS_ARRAY zval. It is recommended to replace calls to this function byzend_call_function_with_return_value()and ifargsis notNULLto set thefci.named_params = Z_ARR_P(args);.
.. code:: c
zend_fcall_info_args_clear(
zend_fcall_info *fci,
bool free_mem
)Clear arguments connected with the FCI. If free_mem is true then the params array gets
free'd as well.
.. code:: c
zend_fcall_info_args_save(
zend_fcall_info *fci,
uint32_t *param_count,
zval **params
)Save current arguments from the FCI, the params array will be set to
NULL.
.. code:: c
zend_fcall_info_args_restore(
zend_fcall_info *fci,
uint32_t param_count,
zval *params
)Free arguments connected with the FCI, and set back saved ones.
.. code:: c
zend_fcall_info_args(
zend_fcall_info *fci,
zval *args
)Sets the arguments in the FCI taking care of refcount.
argsmust be an IS_ARRAY zval.
IfargsisNULLthe FCI arguments are cleared. It is recommended to migrate away
from this API by settingfci.named_params=Z_ARR_P(args)and callingzend_call_function(fci, NULL).
.. code:: c
zend_fcall_info_args_ex(
zend_fcall_info *fci,
zend_function *func,
zval *args
)Same as
zend_fcall_info_args()but iffuncis notNULLit will check if a
by-value argument needs to be passed by-ref and wraps it in a reference. This behaviour is
NON STANDARD PHP behaviour and should never be used.
.. code:: c
zend_fcall_info_argp(
zend_fcall_info *fci,
uint32_t argc,
zval *argv
)Sets the arguments in the FCI taking care of refcount. If
argcis0the FCI
arguments are cleared, else pass a variable amount of zval** arguments.
.. code:: c
zend_fcall_info_argv(
zend_fcall_info *fci,
uint32_t argc,
va_list *argv
)Sets the arguments in the FCI taking care of refcount. If
argcis0the FCI
arguments are cleared, else pass a variable amount of zval** arguments.
.. code:: c
zend_fcall_info_argn(
zend_fcall_info *fci,
uint32_t argc,
...
)Sets the arguments in the FCI taking care of refcount. If
argcis0the FCI
arguments are cleared, else pass a variable amount of zval** arguments.
Source Core Data Structures Index (`docs/source/core/data-structures/index.rst`)
#################
Data structures
#################
.. toctree::
:hidden:
zval
reference-counting
zend_string
zend_constant
fci-fcc
This section provides an overview of the core data structures used in php-src.
Source Core Data Structures Reference Counting (`docs/source/core/data-structures/reference-counting.rst`)
####################
Reference counting
####################
In languages like C, when you need memory for storing data for an indefinite period of time or in a
large amount, you call malloc and free to acquire and release blocks of memory of some size.
This sounds simple on the surface but turns out to be quite tricky, mainly because the data may not
be freed for as long as it is used anywhere in the program. Sometimes this makes it unclear who is
responsible for freeing the memory, and when to do so. Failure to handle this correctly may result
in a use-after-free, double-free, or memory leak.
In PHP you usually do not need to think about memory management. The engine takes care of allocating
and freeing memory for you by tracking which values are no longer needed. It does this by assigning
a reference count to each allocated value, often abbreviated as refcount or RC. Whenever a reference
to a value is passed somewhere else, its reference count is increased to indicate the value is now
used by another party. When the party no longer needs the value, it is responsible for decreasing
the reference count. Once the reference count reaches zero, we know the value is no longer needed
anywhere, and that it may be freed.
.. code:: php
$a = new stdClass; // RC 1
$b = $a; // RC 2
unset($a); // RC 1
unset($b); // RC 0, free
Reference counting is needed for types that store auxiliary data, which are the following:
- Strings
- Arrays
- Objects
- References
- Resources
These are either reference types (objects, references and resources) or they are large types that
don't fit in a single zend_value directly (strings, arrays). Simpler types either don't store a
value at all (null, false, true) or their value is small enough to fit directly inzend_value (int, float).
All of the reference counted types share a common initial struct sequence.
.. code:: c
typedef struct _zend_refcounted_h {
uint32_t refcount; /* reference counter 32-bit */
union {
uint32_t type_info;
} u;
} zend_refcounted_h;
struct _zend_string {
zend_refcounted_h gc;
// ...
};
struct _zend_array {
zend_refcounted_h gc;
// ...
};The zend_refcounted_h struct is simple. It contains the reference count, and a type_info
field that repeats some of the type information that is also stored in the zval, for situations
where we're not dealing with a zval directly. It also stores some additional fields, described
under GC flags_.
Macros
As with zval, zend_refcounted_h members should not be accessed directly. Instead, you should
use the provided macros. There are macros that work with reference counted types directly, prefixed
with GC_, or macros that work on zval values, usually prefixed with Z_. Unfortunately,
naming is not always consistent.
.. list-table:: zval macros
:header-rows: 1
- Macro
- Non-RC [#non-rc]_
- Description
Z_REFCOUNT[_P]- No
- Returns the reference count.
Z_ADDREF[_P]- No
- Increases the reference count.
Z_TRY_ADDREF[_P]- Yes
- Increases the reference count. May be called on any
zval.
zval_ptr_dtor- Yes
- Decreases the reference count and frees the value if the reference count reaches zero.
.. [#non-rc]
Whether the macro works with non-reference counted types. If it does, the operation is usually a
no-op. If it does not, using the macro on these values is undefined behavior.
.. list-table:: zend_refcounted_h macros
:header-rows: 1
- Macro
- Immutable [#immutable]_
- Description
GC_REFCOUNT[_P]- Yes
- Returns the reference count.
GC_ADDREF[_P]- No
- Increases the reference count.
GC_TRY_ADDREF[_P]- Yes
- Increases the reference count.
GC_DTOR[_P]- Yes
- Decreases the reference count and frees the value if the reference count reaches zero.
.. [#immutable]
Whether the macro works with immutable types, described under Immutable reference counted types_.
Separation
PHP has value and reference types. Reference types are types that are shared through a reference, a
"pointer" to the value, rather than the value itself. Modifying such a value in one place changes it
for all of its observers. For example, writing to a property changes the property in every place the
object is referenced. Value types, on the other hand, are copied when passed to another party.
Modifying the original value does not affect the copy, and vice versa.
In PHP, arrays and strings are value types. Since they are also reference counted types, this
requires some special care when modifying values. In particular, we need to make sure that modifying
the value is not observable from other places. Modifying a value with RC 1 is unproblematic, since
we are the values sole owner. However, if the value has a reference count of >1, we need to create a
fresh copy before modifying it. This process is called separation or CoW (copy on write).
.. code:: php
$a = [1, 2, 3]; // RC 1
$b = $a; // RC 2
$b[] = 4; // Separation, $a RC 1, $b RC 1
var_dump($a); // [1, 2, 3]
var_dump($b); // [1, 2, 3, 4]
Immutable reference counted types
Sometimes, even a reference counted type is not reference counted. When PHP runs in a multi-process
or multi-threaded environment with opcache enabled, it shares some common values between processes
or threads to reduce memory consumption. As you may know, sharing memory between processes or
threads can be tricky and requires special care when modifying values. In particular, modification
usually requires exclusive access to the memory so that the other processes or threads wait until
the value is done being updated. In this case, this synchronization is avoided by making the value
immutable and never modifying the reference count. Such values will receive the GC_IMMUTABLE
flag in their gc->u.type_info field.
Some macros like GC_TRY_ADDREF will guard against immutable values. You should not use immutable
values on some macros, like GC_ADDREF. This will result in undefined behavior, because the macro
will not check whether the value is immutable before performing the reference count modifications.
You may execute PHP with the -d opcache.protect_memory=1 flag to mark the shared memory as
read-only and trigger a hardware exception if the code accidentally attempts to modify it.
Cycle collector
Sometimes, reference counting is not enough. Consider the following example:
.. code:: php
$a = new stdClass;
$b = new stdClass;
$a->b = $b;
$b->a = $a;
unset($a);
unset($b);
When this code finishes, the reference count of both instances of stdClass will still be 1, as
they reference each other. This is called a reference cycle.
PHP implements a cycle collector that detects such cycles and frees values that are only reachable
through their own references. The cycle collector will record values that may be involved in a
cycle, and run when this buffer becomes full. It is also possible to invoke it explicitly by calling
the gc_collect_cycles() function. The cycle collectors design is described in the Cycle collector <todo>_ chapter.
GC flags
.. code:: c
/* zval_gc_flags(zval.value->gc.u.type_info) (common flags) /
#define GC_NOT_COLLECTABLE (1<<4)
#define GC_PROTECTED (1<<5) / used for recursion detection /
#define GC_IMMUTABLE (1<<6) / can't be changed in place /
#define GC_PERSISTENT (1<<7) / allocated using malloc /
#define GC_PERSISTENT_LOCAL (1<<8) / persistent, but thread-local */
The GC_NOT_COLLECTABLE flag indicates that the value may not be involved in a reference cycle.
This allows for a fast way to detect values that don't need to be added to the cycle collector
buffer. Only arrays and objects may actually be involved in reference cycles.
The GC_PROTECTED flag is used to protect against recursion in various internal functions. For
example, var_dump recursively prints the contents of values, and marks visited values with theGC_PROTECTED flag. If the value is recursive, it prevents the same value from being visited
again.
GC_IMMUTABLE has been discussed in Immutable reference counted types_.
The GC_PERSISTENT flag indicates that the value was allocated using malloc, instead of PHPs
own allocator. Usually, such values are alive for the entire lifetime of the process, instead of
being freed at the end of the request. See the Zend allocator <todo>_ chapter for more
information.
The GC_PERSISTENT_LOCAL flag indicates that a GC_PERSISTENT value is only accessible in one
thread, and is thus still safe to modify. This flag is only used in debug builds to satisfy anassert.
Source Core Data Structures Zend Constant (`docs/source/core/data-structures/zend_constant.rst`)
###############
zend_constant
###############
PHP constants (referring to non-class constants) are stored in a dedicated structurezend_constant, which holds both the value of the constant and details for using it.
definition
.. code:: c
typedef struct _zend_constant {
zval value;
zend_string *name;
zend_string *filename;
HashTable *attributes;
} zend_constant;
The value field stores both the value itself and some metadata. The name and filename
store the name of the constant and the name of the file in which it was defined. The attributes
field stores the attributes applied to the constant.
value
The value of the constant is stored in the :doc:./zval value. However, since the zval
structure has extra space, for constants this is used to store both the number of the module that
the constant was defined in, and a combination of the flags that affect the usage of the constant.
This extra information is placed in the uint32_t field value.u2.constant_flags.
The bottom 16 bits are used to hold flags about the constant
.. code:: c
#define CONST_PERSISTENT (1<<0) /* Persistent /
#define CONST_NO_FILE_CACHE (1<<1) / Can't be saved in file cache /
#define CONST_DEPRECATED (1<<2) / Deprecated /
#define CONST_OWNED (1<<3) / constant should be destroyed together
with class */
These bottom 16 bits can be accessed with the ZEND_CONSTANT_FLAGS() macro, which is given azend_constant pointer as a parameter.
On the other hand, the top 16 bits are used to store the number of the PHP module that registered
the constant. For constants defined by the user, the module number stored will bePHP_USER_CONSTANT. This module number can be accessed with the ZEND_CONSTANT_MODULE_NUMBER()
macro, which is likewise given a zend_constant pointer as a parameter.
name
The name holds a :doc:zend_string with the name of the constant, to allow searching for
constants that have already been defined. This string is released when the constant itself is freed.
filename
The filename holds another zend_string with the name of the file in which the constant was
defined, or NULL if not defined userland code. This field provides the foundation for the PHP
method ReflectionConstant::getFileName().
attributes
The attributes holds a HashTable (essentially an array) with the details of the attributes
that were applied to the constant. Note that attributes can only be added to constants declared at
compile time via const, e.g. const EXAMPLE = 123, not those declared at runtime, e.g.define( 'EXAMPLE', 123 );.
Source Core Data Structures Zend String (`docs/source/core/data-structures/zend_string.rst`)
#############
zend_string
#############
In C, strings are represented as sequential lists of characters, char* or char[]. The end of
the string is usually indicated by the special NUL character, '\0'. This comes with a few
significant downsides:
- Calculating the length of the string is expensive, as it requires walking the entire string to
look for the terminating NUL character. - The string may not contain the NUL character itself.
- It is easy to run into buffer overflows if the NUL byte is accidentally missing.
php-src uses the zend_string struct as an abstraction over char*, which explicitly stores
the strings length, along with some other fields. It looks as follows:
.. code:: c
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
The gc field is used for :doc:./reference-counting. The h field contains a hash value,
which is used for hash table <todo>__ lookups. The len field stores the length of the string
in bytes, and the val field contains the actual string data.
You may wonder why the val field is declared as char val[1]. This is called the struct hack_ in C. It is used to create structs with a flexible size, namely by allowing the last element
to be expanded arbitrarily. In this case, the size of zend_string depends on the string's
length, which is determined at runtime (see _ZSTR_STRUCT_SIZE). When allocating the string, we
append enough bytes to the allocation to hold the strings content.
.. _struct hack: https://www.geeksforgeeks.org/struct-hack/
Here's a basic example of how to use zend_string:
.. code:: c
// Allocate the string.
zend_string string = ZSTR_INIT_LITERAL("Hello world!", / persistent */ false);
// Write it to the output buffer.
zend_write(ZSTR_VAL(string), ZSTR_LEN(string));
// Decrease the reference count and free it if necessary.
zend_string_release(string);
ZSTR_INIT_LITERAL creates a zend_string from a string literal. It is just a wrapper aroundzend_string_init(char *string, size_t length, bool persistent) that provides the length of the
string at compile time. The persistent parameter indicates whether the string is allocated usingmalloc (persistent == true) or emalloc, PHPs custom allocator <todo>__ (persistent == false) that is emptied after each request.
When you're done using the string, you must call zend_string_release, or the memory will leak.zend_string_release will automatically call malloc or emalloc, depending on how the
string was allocated. After releasing the string, you must not access any of its fields anymore, as
it may have been freed if you were its last user.
API
The string API is defined in Zend/zend_string.h. It provides a number of functions for creating
new strings.
.. list-table:: zend_string creation
:header-rows: 1
- Function/Macro [#persistent]_
- Description
ZSTR_INIT_LITERAL(s, p)- Creates a new string from a string literal.
zend_string_init(s, l, p)- Creates a new string from a character buffer.
zend_string_alloc(l, p)- Creates a new string of a given length without initializing its content.
zend_string_concat2(s1, l1, s2, l2)- Creates a non-persistent string by concatenating two character buffers.
zend_string_concat3(...)- Same as
zend_string_concat2, but for three character buffers.
ZSTR_EMPTY_ALLOC()- Gets an immutable, empty string. This does not allocate memory.
ZSTR_CHAR(char)- Gets an immutable, single-character string. This does not allocate memory.
ZSTR_KNOWN(ZEND_STR_const)Gets an immutable, predefined string. Used for string common within PHP itself, e.g.
"class". SeeZEND_KNOWN_STRINGSinZend/zend_string.h. This does not allocate
memory.
.. [#persistent]
s = zend_string, l = length, p = persistent.
As per php-src fashion, you are not supposed to access the zend_string fields directly. Instead,
use the following macros. There are macros for both zend_string and zvals known to contain
strings.
.. list-table:: Accessor macros
:header-rows: 1
zend_stringzval- Description
ZSTR_LENZ_STRLEN[_P]- Returns the length of the string in bytes.
ZSTR_VALZ_STRVAL[_P]- Returns the string data as a
char*.
ZSTR_HASHZ_STRHASH[_P]- Computes the string hash if it hasn't already been, and returns it.
ZSTR_H- -
- Returns the string hash. This macro assumes that the hash has already been computed.
.. list-table:: Reference counting macros
:header-rows: 1
- Macro
- Description
zend_string_copy(s)- Increases the reference count and returns the same string. The reference count is not
increased if the string is interned.
zend_string_release(s)- Decreases the reference count and frees the string if it goes to 0.
zend_string_dup(s, p)- Creates a true copy of the string in a new allocation, except if the string is interned.
zend_string_separate(s)- Duplicates the string if the reference count is greater than 1. See
:doc:./reference-countingfor details.
zend_string_realloc(s, l, p)Changes the size of the string. If the string has a reference count greater than 1 or if
the string is interned, a new string is created. You must always use the return value of
this function, as the original array may have been moved to a new location in memory.
There are various functions to compare strings. The zend_string_equals function compares two
strings in full, while zend_string_starts_with checks whether the first argument starts with the
second. There are variations for _ci and _literal, i.e. case-insensitive comparison and
literal strings, respectively. We won't go over all variations here, as they are straightforward to
use.
Interned strings
Programs use some strings many times. For example, if your program declares a class calledMyClass, it would be wasteful to allocate a new string "MyClass" every time it is referenced
within your program. Instead, when repeated strings are expected, php-src uses a technique called
string interning. Essentially, this is just a simple HashTable <todo>__ where existing interned
strings are stored. When creating a new interned string, php-src first checks the interned string
buffer. If it finds it there, it can return a pointer to the existing string. If it doesn't, it
allocates a new string and adds it to the buffer.
.. code:: c
zend_string str1 = zend_new_interned_string(
ZSTR_INIT_LITERAL("MyClass", / persistent */ false));
// In some other place entirely.
zend_string str2 = zend_new_interned_string(
ZSTR_INIT_LITERAL("MyClass", / persistent */ false));
assert(ZSTR_IS_INTERNED(str1));
assert(ZSTR_IS_INTERNED(str2));
assert(str1 == str2);
Interned strings are not reference counted, as they are expected to live for the entire request,
or longer.
With opcache, this goes one step further by sharing strings across different processes. For example,
if you're using php-fpm with 8 workers, all workers will share the same interned strings buffer. It
gets a bit more complicated. During requests, no interned strings are actually created. Instead,
this is delayed until the script is persisted to shared memory. This means thatzend_new_interned_string may not actually return an interned string if opcache is enabled.
Usually you don't have to worry about this.
Source Core Data Structures Zval (`docs/source/core/data-structures/zval.rst`)
zval
PHP is a dynamic language. A variable can typically contain a value of any type, and the type of the
variable may even change during the execution of the program. Under the hood, this is implemented
through the zval struct. It is one of the most important data structures in php-src. It is
implemented as a "tagged union", meaning it stores what type of value it contains, and the value
itself. Let's look at the type first.
zval types
.. code:: c
#define IS_UNDEF 0 /* A variable that was never written to. /
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4 / An integer value. /
#define IS_DOUBLE 5 / A floating point value. */
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
These simple integer constants determine what value is currently stored in a variable. If you are a
PHP developer, these types should sound fairly familiar. They are pretty much an exact reflection of
the types you may use in regular PHP code. One small oddity is that IS_FALSE and IS_TRUE are
implemented as separate types, instead of as a IS_BOOL type.
Some of these types are self-contained, they don't store any auxiliary data. This includesIS_UNDEF, IS_NULL, IS_FALSE and IS_TRUE. For the rest of the types, we are going to
require some additional memory to store the actual value of the variable.
zend_value
.. code:: c
typedef union _zend_value {
zend_long lval; /* long value, i.e. int. /
double dval; / double value, i.e. float. */
zend_refcounted *counted;
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
// Less important for now.
zend_ast_ref *ast;
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
A C union is a data type that may store any one of its members at a time, by being (at least) as big
as its biggest member. For example, zend_value may store the lval member, or the dval
member, but never both at the same time. However, it doesn't know which member is being stored.
Remembering this is our job, and that's exactly what the IS_* constants are for.
The top members of zend_value mostly mirror the IS_* constants, with the exception ofcounted. counted polymorphically refers to any reference counted <todo>__ value, including
strings, arrays, objects, resources and references. null and bool are missing fromzend_value because their types are self-contained.
The rest of the fields aren't important for now.
zval
Together, the value and the tag make up the zval, along with some other fields. It may look
intimidating at first. We'll go over it step by step.
.. code:: c
typedef struct _zval_struct zval;
struct _zval_struct {
zend_value value;
union {
uint32_t type_info;
struct {
ZEND_ENDIAN_LOHI_3(
uint8_t type, /* active type /
uint8_t type_flags,
union {
uint16_t extra; / not further specified /
} u)
} v;
} u1;
union {
uint32_t next; / hash collision chain /
uint32_t cache_slot; / cache slot (for RECV_INIT) /
uint32_t opline_num; / opline number (for FAST_CALL) /
uint32_t lineno; / line number (for ast nodes) /
uint32_t num_args; / arguments number for EX(This) /
uint32_t fe_pos; / foreach position /
uint32_t fe_iter_idx; / foreach iterator index /
uint32_t guard; / recursion and single property guard /
uint32_t constant_flags; / constant flags /
uint32_t extra; / not further specified */
} u2;
};
zval.value reserves space for the actual variable data, as discussed above.
zval.u1 stores the variable type, the given IS_* constant, along with some other flags. It's
definition looks a bit complicated. You can think of the entire field as a 4 byte integer, split
into 3 parts. v.type stores the actual variable type, v.type_flags is used for somereference counting <todo>__ flags, and v.u.extra is pretty much unused.
zval.u2 defines some more storage for various contexts that is often unoccupied. It's there
because the memory would otherwise be wasted due to padding, so we may as well make use of it. We'll
go over the relevant ones in their corresponding chapters.
Macros
The fields in zval should never be accessed directly. Instead, there are a plethora of macros to
access them, concealing some of the implementation details of the zval struct. For many macros,
there's a _P-suffixed variant that performs the same operation on a pointer to the givenzval.
.. list-table:: zval macros
:header-rows: 1
- Macro
- Description
Z_TYPE[_P]- Access the
zval.u1.v.typepart of the type flags, containing theIS_*type.
Z_LVAL[_P]- Access the underlying
intvalue.
Z_DVAL[_P]- Access the underlying
floatvalue.
Z_STR[_P]- Access the underlying
zend_stringpointer.
Z_STRVAL[_P]- Access the strings raw
char *pointer.
Z_STRLEN[_P]- Access the strings length.
ZVAL_COPY_VALUE(t, s)- Copy one
zvalto another, including type and value.
ZVAL_COPY(t, s)- Same as
ZVAL_COPY_VALUE, but if the value is reference counted, increase the counter.
..
_todo: There are many more.
Other zval types
zval\ s are sometimes used internally with types that don't exist in userland.
.. code:: c
#define IS_CONSTANT_AST 11
#define IS_INDIRECT 12
#define IS_PTR 13
#define IS_ALIAS_PTR 14
#define _IS_ERROR 15
IS_CONSTANT_AST is used to represent constant values (the right hand side of const,
property/parameter initializers, etc.) before they are evaluated. The evaluation of a constant
expression is not always possible during compilation, because they may contain references to values
only available at runtime. Until that evaluation is possible, the constants contain the AST of the
expression rather than the concrete values. Check the parser <todo>__ chapter for more information
on ASTs. When this flag is set, the zval.value.ast union member is set accordingly.
IS_INDIRECT indicates that the zval.value.zv member is populated. This field stores a
pointer to some other zval. This type is mainly used in two situations, namely for intermediate
values between FETCH and ASSIGN instructions, and for the sharing of variables in the symbol
table.
..
_todo: There are many more.
IS_PTR is used for pointers to arbitrary data. Most commonly, this type is used internally forHashTable, as HashTable may only store zval values. For example, EG(class_table)
represents the class table, which is a hash map of class names to the correspondingzend_class_entry, representing the class. The same goes for functions and many other data types.IS_ALIAS_PTR is used for class aliases registered via class_alias. Essentially, it just
allows differencing between members in the class table that are aliases, or actual classes.
Otherwise, it is essentially the same as IS_PTR. Arbitrary data is accessed throughzval.value.ptr, and casted to the correct type depending on context. If ptr stores a class
or function, the zval.value.ce or zval.value.func fields may be used, respectively.
_IS_ERROR is used as an error value for some object handlers <todo>__. It is described in more
detail in its own chapter.
.. code:: c
/* Fake types used only for type hinting.
* These are allowed to overlap with the types below. */
#define IS_CALLABLE 12
#define IS_ITERABLE 13
#define IS_VOID 14
#define IS_STATIC 15
#define IS_MIXED 16
#define IS_NEVER 17
/* used for casts */
#define _IS_BOOL 18
#define _IS_NUMBER 19
These flags are never actually stored in zval.u1. They are used for type hinting and in theobject handler <todo>__ API.
This only leaves the zval.value.ww field. In short, this field is used on 32-bit platforms when
copying data from one zval to another. Normally, zval.value.counted is copied as a generic
value, no matter what the actual underlying type is. zend_value always consists of 8 bytes due
to the double field. Pointers, however, consist only of 4. Because we would otherwise miss the
other 4 bytes, they are copied manually using z->value.ww.w2 = _w2;. This happens in theZVAL_COPY_VALUE_EX macro, you won't ever have to care about this.
Source Introduction Ides Index (`docs/source/introduction/ides/index.rst`)
IDEs
.. toctree::
:hidden:
visual-studio-code
Here you can find instructions on how to effectively use common IDEs for php-src development.
Source Introduction Ides Visual Studio Code (`docs/source/introduction/ides/visual-studio-code.rst`)
####################
Visual Studio Code
####################
.. note::
These instructions have been tested on Linux. macOS should mostly work the same. For Windows,
ymmv.
An IDE can make navigating large code bases tremendously easier. Visual Studio Code is a popular and
free IDE that is well-suited for C development. It contains syntax highlighting, navigation,
auto-completion and a debugger. Check the official website <https://code.visualstudio.com/>__ for
installation instructions.
.. note::
The settings.json file referenced below can be opened in the Settings page by pressing the
"Open Settings (JSON)" button in the top right corner. Most of these settings can also be
adjusted through the GUI.
C/C++ extension
The C/C++ extension_ provides most of the features we'll need for php-src development. You can
find it in the extensions marketplace. You will also need gcc or clang installed. The
extension will mostly work out of the box, but it is advisable to use the compile_commands.json
file. It contains a list of all compiled files, along with the commands used to compile them. It
provides the extension with the necessary information about include paths and other compiler flags.
.. _c/c++ extension: https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools
To generate the compile_commands.json file, you can use the compiledb_ tool. Install it usingpip, and then prefix your make command accordingly:
.. _compiledb: https://github.com/nickdiego/compiledb
.. code:: bash
Install compiledb
pip install compiledb
Compile php-src and generate compile_commands.json
compiledb make -j8
To tell the C/C++ extension to use the compile_commands.json file, add the following to yoursettings.json file:
.. code:: json
{
"C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json"
}
clangd
The C/C++ extension usually works well enough. Some people find that clangd works better.clangd is a language server built on top of the clang compiler. It only provides navigation
and code completion but no syntax highlighting and no debugger. As such, it should be used in
conjunction with the C/C++ extension. For the two extensions not to clash, add the following to yoursettings.json file:
.. code:: json
{
"C_Cpp.intelliSenseEngine": "disabled"
}
Follow the official installation instructions for clangd <https://clangd.llvm.org/installation.html>_, and then install the clangd extension.
Alternatively, you can let the extension install clangd for you. clangd requires acompile_commands.json file, so make sure to follow the instructions from the previous section.
By default, clangd will auto-include header files on completion. php-src headers are somewhat
peculiar, so you might want to disable this option in your settings.json file:
.. _clangd extension: https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd
.. code:: json
{
"clangd.arguments": [
"-header-insertion=never"
]
}
gdb
The C/C++ extension provides the ability to use Visual Studio Code as a frontend for gdb. Of
course, you will need gdb installed on your system, and php-src must be compiled with the--enable-debug configure flag. Copy the following into your projects .vscode/launch.json
file:
.. code:: json
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/sapi/cli/php",
"args": [
// Any options you want to test with
// "-dopcache.enable_cli=1",
"${relativeFile}",
],
"stopAtEntry": false,
"cwd": "${workspaceFolder}",
// Useful if you build with --enable-address-sanitizer
"environment": [
{ "name": "USE_ZEND_ALLOC", "value": "0" },
{ "name": "USE_TRACKED_ALLOC", "value": "1" },
{ "name": "LSAN_OPTIONS", "value": "detect_leaks=0" },
],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{ "text": "source ${workspaceFolder}/.gdbinit" },
]
}
]
}
Set any breakpoint in your C code, open a php (or phpt) file and start debugging from the
"Run and Debug" tab in the sidebar.
..
_todo: lldb should work mostly the same, I believe. It's available by default on macOS, and as such might be more convenient.
--- METRICS ---
- Files Extracted: 34
- Estimated Token Budget: ~72209 tokens
- Canonical Reference: https://codewiki.google/github.com/php/php-src