php-src Documentation

C repository by @php. Model Context Protocol (MCP) server & tools with 40k stars.

40,440 stars C C 1 file · ~72,284 tokens
RAW Doc

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:

shell
sudo apt install -y pkg-config build-essential autoconf bison re2c libxml2-dev libsqlite3-dev

On Fedora, you can install these using:

shell
sudo dnf install re2c bison autoconf make ccache libxml2-devel sqlite-devel

On MacOS, you can install these using brew:

shell
brew install autoconf bison re2c libiconv libxml2 sqlite

or with MacPorts:

shell
sudo port install autoconf bison re2c libiconv libxml2 sqlite3

Generate configure:

shell
./buildconf

Configure your build. --enable-debug is recommended for development, see
./configure --help for a full list of options.

shell
# For development
./configure --enable-debug
# For production
./configure

Build PHP. To speed up the build, specify the maximum number of jobs using the
-j argument:

shell
make -j4

The 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:

shell
make TEST_PHP_ARGS=-j4 test

This 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:

shell
make TESTS=tests/lang/ test

The 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:

shell
make install

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

text
Fix GH-7815: php_uname doesn't recognise latest Windows versions
Fix #55371: get_magic_quotes_gpc() throws deprecation warning

See 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:

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:

bash
sapi/cli/php -r 'leak();'

Zend MM disabled:

bash
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, then
DL_UNLOAD() is skipped during the shutdown of shared extensions.

ZEND_VM

ZEND_VM architecture allows specializing opcode handlers according to
op_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:

c
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:

c
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:

c
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 uses
zend_vm_def.h and zend_vm_execute.skl as input and produces
zend_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:

bash
# Default VM kind is HYBRID
php zend_vm_gen.php --with-vm-kind=HYBRID

Overview (`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:

How to build

python 3 and pip are required.

bash
cd docs
# Recommended: Initialize and activate a Python virtual environment
pip install --upgrade pip
pip install -r requirements.txt
make html

That's it! You can view the documentation under ./build/html/index.html in
your browser.

Formatting

The files in this documentation are formatted using the
rstfmt tool.

bash
rstfmt -w 100 source

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

c
/* 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:

bash
$  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 see embed in the list, you'll need to rebuild PHP with ./configure --enable-embed. The PHP shared library libphp.so is built when the embed SAPI is enabled.

If all goes to plan you should be able to run the program.

bash
$ ./a.out
Number of functions loaded: 1046

Function call example

The following example calls mt_rand() and var_dump()s the return value.

c
#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
<?php

# example.php

echo 'Hello from userland!' . PHP_EOL;
c
#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:'.

c
#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:

text
Embed SAPI error:
Warning: Undefined variable $a in example on line 1
NULL

This 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:

text
Oops!
Warning: Undefined variable $a in example on line 1
NULL

Overview (`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.

sh
CC=clang CXX=clang++ \
./configure \
    --disable-all \
    --enable-fuzzer \
    --enable-pic \
    --enable-debug-assertions \
    --enable-address-sanitizer \
    --enable-exif \
    --enable-mbstring

The --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 compiler
  • php-fuzz-unserialize: Fuzzing unserialize() function
  • php-fuzz-unserializehash: Fuzzing unserialize() for HashContext objects
  • php-fuzz-json: Fuzzing JSON parser
  • php-fuzz-exif: Fuzzing exif_read_data() function (requires --enable-exif)
  • php-fuzz-mbstring: Fuzzing mb_convert_encoding() (requires --enable-mbstring)
  • php-fuzz-mbregex: Fuzzing mb_ereg[i]() (requires --enable-mbstring)
  • php-fuzz-execute: Fuzzing the executor
  • php-fuzz-function-jit: Fuzzing the function JIT
  • php-fuzz-tracing-jit: Fuzzing the tracing JIT

Some fuzzers have a seed corpus in sapi/fuzzer/corpus. You can use it as follows:

sh
cp -r sapi/fuzzer/corpus/exif ./my-exif-corpus
sapi/fuzzer/php-fuzz-exif ./my-exif-corpus

For the unserialize fuzzer, a dictionary of internal classes should be generated first:

sh
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-corpus

For the unserializehash fuzzer, generate a corpus of initial hash serializations:

sh
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-corpus

For the parser fuzzer, a corpus may be generated from Zend test files:

sh
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-corpus

For the execute, function-jit and tracing-jit fuzzers, a corpus may be generated from any set of test files:

sh
sapi/cli/php sapi/fuzzer/generate_execute_corpus.php ./execute-corpus Zend/tests ext/opcache/tests/jit
sapi/fuzzer/php-fuzzer-function-jit ./execute-corpus

For the mbstring fuzzer, a dictionary of encodings should be generated first:

sh
sapi/cli/php sapi/fuzzer/generate_mbstring_dict.php
sapi/fuzzer/php-fuzz-mbstring -dict=$PWD/sapi/fuzzer/dict/mbstring ./my-mbstring-corpus

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

sh
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:

bash
./configure --enable-litespeed
make

You 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:

bash
./lsphp -b [::]:3000

have lsphp bind to port 3000 on all IPv4 and IPv6 address,

bash
./lsphp -b *:3000

have lsphp bind to port 300 on all IPv4 address,

bash
./lsphp -b 192.168.0.2:3000

have lsphp bind to address 192.168.0.2:3000,

bash
./lsphp -b /tmp/lsphp_manual.sock

have 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:

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

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

  3. 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_CHILDREN or PHP_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", while
    LSAPI_CHILDREN environment 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.
    If LSAPI_CHILDREN less 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_CHILDREN over 100 in most
    server environments.

  • LSAPI_AVOID_FORK (default: 0)

    LSAPI_AVOID_FORK specifies 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 of LSAPI_CHILDREN or 0)

    LSAPI_EXTRA_CHILDREN controls 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 to
    LSAPI_CHILDREN level as soon as service is back to normal. When
    LSAPI_AVOID_FORK is set to 0, the default value is 1/3 of LSAPI_CHILDREN,
    When LSAPI_AVOID_FORK is set to 1, the default value is 0.

  • LSAPI_MAX_REQS or PHP_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 of LSAPI_CHILDREN or
    LSAPI_CHILDREN)

    In Self Managed Mode, LSAI_MAX_IDLE_CHILDREN controls how many idle children
    processes are allowed. Excessive idle children processes will be killed by the
    parent process immediately. When LSAPI_AVOID_FORK is set to 0, the default
    value is 1/3 of LSAPI_CHILDREN, When LSAPI_AVOID_FORK is set to 1, the
    default value is LSAPI_CHILDREN.

  • LSAPI_MAX_PROCESS_TIME (default value: 300 seconds)

    In Self Managed Mode, LSAPI_MAX_PROCESS_TIME controls 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_IDLE controls 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_CHECK

    By 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_CHECK should be set when you want to disable the checking of
    existence of parent process. When PHP is started by -b option, 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 Vagrantfile with the following content.
  • Go to that directory and run "vagrant up"
vagrantfile
$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
end

Now 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:

bash
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.10

Now 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:

  1. A self signed certificate can be generated using:

    bash
    openssl req -newkey rsa:1024 -x509 -nodes -out server.pem -keyout server.pem -days 3650

    It is used for testing ldap_start_tls(), which also requires
    TLS_REQCERT never in client configuration.

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

txt
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

txt
TLS_REQCERT never

Tests configuration

The following environment variables may be defined:

txt
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 fails

Credits


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 (in lexbor/utils/encoding): python3 single-byte.py to generate the tables.
    Also run (in lexbor/utils/lexbor/html) python3 tokenizer_entities_bst.py to 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.patch
  • git am -3 ../patches/0002-Track-implied-added-nodes-for-options-use-in-PHP.patch
  • git am -3 ../patches/0003-Patch-utilities-and-data-structure-to-be-able-to-gen.patch
  • git am -3 ../patches/0004-Remove-unused-upper-case-tag-static-data.patch
  • git am -3 ../patches/0005-Shrink-size-of-static-binary-search-tree.patch
  • git am -3 ../patches/0006-Patch-out-unused-CSS-style-code.patch
  • git 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).

    Detailed info

  • 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:

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

bash
# 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_SOCKET

MySQL 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:

text
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=16M enables the JIT in tests by providing 16 megabytes of
    memory to use with the JIT to test with.
  • opcache.protect_memory=1 will detect writing to memory that is meant to be
    read-only, which is sometimes the cause of opcache bugs.
  • --repeat 2 is 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/ and Zend are 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.

text
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:i386

Compiling 32-bit builds

This assumes you are using a Debian-based Linux distribution and have already
set up prerequisites for 32-bit development.

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

text
# 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-example

Then, 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:

text
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:

bash
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 test

Overview (`ext/pdo_mysql/tests/README.md`)

The pdo_mysql extension tests

You must set the following environment variables to run the tests:

bash
# 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_CHARSET

NOTE: if any of PDO_MYSQL_TEST_[HOST|DB|SOCKET|ENGINE|CHARSET] is part of
PDO_MYSQL_TEST_DSN, the values must match. That is, for example, for
PDO_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:


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):

txt
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 MIBS

To run test suite you may use this command (presuming that you pwd is where this
README file is located):

bash
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 snmpd and snmp-mibs-downloader. (May be net-snmpd
    on other distributions.)
  • Replace config file (by default this is /etc/snmp/snmpd.conf on Linux and
    /usr/local/etc/snmp/snmpd.conf on FreeBSD) with snmpd.conf supplied.

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/bigtest near snmpd.conf, tune path to it in
    snmpd.conf.

  • Launch snmpd (service snmpd start or /etc/init.d/snmpd start). Alternatively
    you can start snmpd daemon using following command line:

    bash
    sudo 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:

  1. Respect other people working on the project.

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

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

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

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

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

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

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

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

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

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

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

  2. Package two days before a release.

    If the release is to be on Thursday, package on Tuesday. Think about
    timezones as well.

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

  4. 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 the
    web-php and web-php-distributions repositories, try to have
    someone from the webmaster team on hand.

  5. Verify the tags to be extra sure everything was tagged properly.

  6. There is a PHP release Docker image https://github.com/sgolemon/php-release
    with forks available to help with releases.

  7. Communicate with release managers via [email protected].

  8. References to repositories in this document refer to the canonical source
    located at https://github.com/php.

  9. It might be helpful to name your remote repositories something other than
    "origin" to avoid accidentally pushing changes to "origin" with muscle
    memory
    .

  10. It might also be helpful to set up conditional includes in your global
    ~/.gitconfig to set the proper user.name, user.email, and
    user.signingKey values to use with your local PHP repositories. See
    Conditional Includes For Git Config for more information.

  11. Any time you see a placeholder like php-X.Y.ZRCn, the RCn is 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)
  12. 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.

  1. Check that CI is passing (see above).

  2. Run ./scripts/dev/credits in php-src and commit the changes to the credits
    files in ext/standard.

    💬 Hint
    It's rare this script will make any changes, so if you run git diff
    and do not see anything, there's no need to panic. That means there are no
    changes to the credits files.

  3. 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!

    shell
    git checkout -b php-X.Y.0alpha1-local-release-branch upstream/master

    During 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!

    shell
    git 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 the PHP-8.2.8 patch-level
    version branch from the PHP-8.2 version branch.

    shell
    git checkout -b PHP-X.Y.Z upstream/PHP-X.Y
    git push upstream PHP-X.Y.Z

    We 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!

    shell
    git checkout -b php-X.Y.ZRC1-local-release-branch upstream/PHP-X.Y.Z
  4. Using your local-only release branch, bump the version numbers in
    main/php_version.h, Zend/zend.h, configure.ac, and possibly
    NEWS.

    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 update Zend/zend.h only when preparing RC and GA releases. We do not
    update ZEND_VERSION for 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 in Zend/zend_extensions.h,
    Zend/zend_modules.h, and main/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.

  5. 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 -v
  6. After each build, check the output of ./sapi/cli/php -v to ensure the
    versions match the release.

  7. If all is correct, commit the changes to your local-only release branch.

    shell
    git add -p
    git commit --gpg-sign=YOURKEYID -m "Update versions for PHP X.Y.ZRCn"
  8. Tag your local-only release branch with the release version.

    shell
    git tag -s -u YOURKEYID php-X.Y.ZRCn -m "Tag for php-X.Y.ZRCn"
  9. 🔸 For pre-GA releases only, switch back to the master (for alphas and betas),
    or PHP-X.Y (for RCs), and update a NEWS for 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 (or PHP-X.Y) branch.

    shell
    git 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 in
    main/php_version.h, Zend/zend.h, configure.ac and NEWS. This prepares
    the version branch for the next version.

    For example, if the RC is 8.2.1RC1 then the version numbers in the version
    branch should be bumped to 8.2.2-dev. We do this regardless of whether we
    build a new RC to make sure version_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.

    shell
    git 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 in
    main/php_version.h, Zend/zend.h, and configure.ac that end in -dev.
    Patch-level version branches (e.g. PHP-8.1.7) will also always have
    version numbers that end in -dev in these files. The main branch (i.e.
    master) will always have version numbers that end in -dev in 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.

    shell
    git 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 branch

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

    shell
    git checkout --ours main/php_version.h Zend/zend.h configure.ac
    git add main/php_version.h Zend/zend.h configure.ac
    git merge --continue

    Be sure to set up a merge driver for the NEWS file as described in
    the Git FAQ page on the PHP wiki.

  10. Push the changes to the php-src.

    shell
    git 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!

  11. Run the following using the release tag to export the tree, create the
    configure script, and build and compress three tarballs (.tar.gz,
    .tar.bz2 and .tar.xz).

    shell
    ./scripts/dev/makedist php-X.Y.ZRCn
  12. Run the following using the release tag and your GPG key ID to sign the
    tarballs and save the signatures to php-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.manifest
  13. If you have the GitHub command line tool installed, run the following to
    create a public Gist for the manifest file:

    shell
    gh gist create --public php-X.Y.ZRCn.manifest

    Or you may go to https://gist.github.com to create it manually.

  14. Copy the tarballs (using scp, rsync, etc.) to your public_html/ folder on
    downloads.php.net.

    shell
    scp php-X.Y.ZRCn.tar.* downloads.internal.php.net:~/public_html/

    💬 Hint
    If you do not have a public_html directory, create it and set its
    permissions to 0755.

  15. Now the tarballs and signatures may be found at
    https://downloads.php.net/~yourname/, e.g. https://downloads.php.net/~derick/.

  16. 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]:

    text
    Subject: 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)

  1. Switch to your local clone of the web-php repository and update the
    information in the $QA_RELEASES array in include/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.

    shell
    git add -p
    git commit --gpg-sign=YOURKEYID -m "Announce PHP X.Y.ZRCn"
    git push upstream master
  2. 🔶 For pre-GA releases only, add a short notice to web-php stating
    there is a new release, and highlight the major changes (e.g., security
    fixes).

    To help produce the files for this, use the bin/createNewsEntry tool. When
    you run it, it will ask several questions (see below). For pre-GA non-stable
    releases, use only the "frontpage" category.

    shell
    cd /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 master

    Each 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 that

    • qa.php.net has been replaced with https://www.php.net/release-candidates.php
    • bugs.php.net has 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.

    ⚠️ 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.

  3. Wait for the php site to update with the new information before
    sending announcements. This could take up to an hour.

  4. 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 by gen_verify_stub when 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 that

    • qa.php.net has been replaced with https://www.php.net/release-candidates.php
    • bugs.php.net has 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.

    🚨 Send separate emails!
    Do not send a single email message with all addresses in the To, Cc,
    or Bcc headers. 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.

  5. 🔶 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

  1. Check out the patch-level version branch for the release.

    text
    git 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.

  2. 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:

    bash
    git remote add security [email protected]:php/php-src-security.git

    The SRM will provide you with a branch to merge in your
    patch-level version branch.

    bash
    git 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.

  3. Run the ./scripts/dev/credits script in the patch-level version branch,
    and commit the changes in the credits files in ext/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.

  4. Create a local-only release branch for this release from the patch-level
    version branch
    .

    shell
    git checkout -b php-X.Y.Z-local-release-branch upstream/PHP-X.Y.Z
  5. Using your local-only release branch, bump the version numbers in
    main/php_version.h, Zend/zend.h, configure.ac, and possibly
    NEWS.

    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 be
    8.1.8-dev. In your local-only release branch, you will change them all to
    8.1.8.

    See Update versions for PHP 8.1.7 and Update NEWS for PHP 8.1.7 for
    an example.

  6. 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 -v
  7. After each build, check the output of ./sapi/cli/php -v to ensure the
    versions match the release.

  8. If all is correct, commit the changes to your local-only release branch.

    shell
    git add -p
    git commit --gpg-sign=YOURKEYID -m "Update versions for PHP X.Y.Z"
  9. Tag your local-only release branch with the release version and push the tag.

    shell
    git tag -s -u YOURKEYID php-X.Y.Z -m "Tag for php-X.Y.Z"
    git push upstream php-X.Y.Z
  10. Run the following using the release tag to export the tree, create the
    configure script, and build and compress three tarballs (.tar.gz,
    .tar.bz2 and .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 variable TAR_OPTIONS='--format=gnu'.

  11. Run the following using the release tag and your GPG key ID to sign the
    tarballs and save the signatures to php-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.manifest
  12. If you have the GitHub command line tool installed, run the following to
    create a public Gist for the manifest file:

    shell
    gh gist create --public php-X.Y.Z.manifest

    Or you may go to https://gist.github.com to create it manually.

  13. Switch to your local clone of the web-php-distributions repository and
    copy the tarballs and signature files into the repository. Add, commit, and
    push them.

    shell
    cd /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 master
  14. 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/.

    ⚠️ Important
    Do not send this announcement to any public lists.

    Here is an example "ready for builds" message to [email protected]:

    text
    Subject: 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

  1. This steps applies only for releases after PHP-X.Y.0.

    Switch to your local clone of web-php and add the information for the
    previous release to include/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 the bin/bumpRelease tool.

    shell
    ./bin/bumpRelease 8 2

    The 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 from include/version.inc into
    include/releases.inc.

  2. Update the version information for the new release in include/version.inc.

    Find the part of the $data array 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 version to the full version number (e.g. '8.2.1')
    • Set date to the release date in j M Y format (e.g. '5 Jan 2021')
    • Update the tags array to include 'security' if this is a security release
    • Set the sha256 array with the hashes for each of the release tarballs
  3. Create the release file and news entry for the new version.

    shell
    ./bin/createReleaseEntry -v X.Y.Z -r # --security for security releases

    This 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
    updating public/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 announcement

  4. Update 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 (where X is the major
    version) file manually first. The $MINOR_VERSIONS field 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'
  5. Update the information in the $QA_RELEASES array in include/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 number property for the release to 0 to
    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.

  6. Review all the changes in web-php, commit, and push them.

    shell
    git 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 master

    See Announce PHP 8.1.6 for an example commit.

  7. 🚨 Before sending announcement emails, check to make sure the websites have
    synced.

Keep in mind it may take up to an hour for the websites to sync.

  1. Please note down the sha256 and the PGP signature (.asc). These must be
    included in the release mail.

  2. 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 the To, Cc,
or Bcc headers. 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.

  1. 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:

  1. Commit the new binaries to web-php-distributions

  2. Update $data['X.Y'] in web-php:/include/version.inc
    (X.Y=major.minor release, e.g. '8.0'):

    • version to the full version number (e.g. '8.0.1-pl1')
    • date to the release date in j M Y format (e.g. '9 Jan 2021')
    • tags array should include security if this is a security release
    • sha256 array and sub-elements for all SHA256 sums
  3. Add a short notice to web-php stating 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.
  4. Commit all the changes (include/version.inc, public/archive/archive.xml,
    public/archive/entries/YYYY-MM-DD-N.xml).

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

  1. 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/99864

  2. Just prior to tagging X.Y.0RC1, create the new version branch locally,
    i.e. PHP-X.Y.

  3. Add a commit on the main branch (i.e., master) after the branch point.

    This commit should:

    • clear the NEWS, UPGRADING, and UPGRADING.INTERNALS files;
    • update the version numbers in configure.ac, main/php_version.h,
      Zend/zend.h, and win32/build/confutils.js; and
    • update the API version numbers in Zend/zend_extensions.h,
      Zend/zend_modules.h, and main/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 in Zend/zend_extensions.h, Zend/zend_modules.h, and
    main/php.h in master need to be greater than the ones in the new PHP-X.Y
    branch. Generally, use the YYYYMMDD date for the Thursday as the value for the
    PHP-X.Y API numbers, and for the Friday as the value for the master API
    numbers. This ensures that master is always considered newer than the version branch.

  4. Push the new version branch and the changes to the master branch, with an
    appropriate commit message (e.g., "master is now for PHP 8.3.0-dev").

  5. Immediately notify internals@ of the new branch and advise on the new merging
    order. For example: https://news-web.php.net/php.internals/99903

  6. Update web-php:git.php and 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.

  1. 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 to master. The following files need to be updated:

  2. Update the .github/workflows/push.yml file 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 to master). For example:

  3. Update the version of php/php-sdk-binary-tools used for the master branch for the nightly
    and push jobs 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)

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

  2. Timely get used to the differences in preparing and announcing a stable
    release.

  3. Before releasing X.Y.0, merge the NEWS entries 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 use grep to compare the changes.
    For example if 82/NEWS is NEWS for PHP 8.2 and 83/NEWS is NEWS file for PHP 8.3,
    then following command will show changes present in both files:

    sh
    grep -Fxf 82/NEWS 83/NEWS
  4. On the announcement day for the initial stable version (or shortly before),
    update the Expires field in the https://www.php.net/.well-known/security.txt
    file. The Expires field 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 an Expires time 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.txt file 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 the Expires timestamp 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.

  1. Issue the call for volunteers on [email protected] on or around March
    1st. See, for example: https://news-web.php.net/php.internals/113334

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

  2. There should be two or more volunteers. Typically, one should be a veteran
    release manager (having previously served as a php-src release 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).

  3. Help the new release managers with their first steps.

New release manager checklist

  1. Fill out the form
    to get a PHP account (if you don't already have one).

  2. Request membership to the
    release managers group on GitHub.

  3. 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:

  4. 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!

  5. 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_authenticator file 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."

  6. 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.inc in the web-php
    repository. Add a case for your username to the gpg_key_get() function,
    and paste the output from gpg --fingerprint. You may also need to update
    the $branches array in the gpg_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:

    shell
    gpg --keyserver keys.openpgp.org --send-keys YOURKEYID
    gpg --keyserver keyserver.ubuntu.com --send-keys YOURKEYID

    Add your public key to php-keyring.gpg in web-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 in php-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 to php-keyring.gpg, commit the changes,
    and push.

    shell
    cd /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 push

    web-php-distributions is a submodule of web-php. You'll now have to update
    the commit reference to reflect the change made in web-php-distributions.

    shell
    cd /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 push
  7. Make 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-internals channel)
  • 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 the
re2c 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 the
Bison 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 in
Zend/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 ECHO instruction prints its first operand.

  • The RETURN operand 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 the
TEST_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 executed
  • ext/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 named
bug12345.phpt.

Note: The files generated by tests can be selected by setting the environment variable
TEST_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 `_ of the git repository (``ext/standard/tests/`` is a good place to start looking — though not all the tests currently written are in there). If you want more guidance than that you can always ask the PHP Quality Assurance Team on their mailing list ([email protected]) where they would like you to direct your attentions. How is a phpt test used? When a test is called by the ``run-tests.php`` script it takes various parts of the phpt file to name and create a .php file. That .php file is then executed. The output of the .php file is then compared to a different section of the phpt file. If the output of the script "matches" the output provided in the phpt script — it passes. What should a phpt test do? Basically — it should try and break the PHP function. It should check not only the functions normal parameters, but it should also check edge cases. Intentionally generating an error is allowed and encouraged. ******************** Writing phpt Tests ******************** Naming Conventions ================== Phpt tests follow a very strict naming convention. This is done to easily identify what each phpt test is for. Tests should be named according to the following list: Tests for bugs bug.phpt (bug17123.phpt) Tests for a function's basic behaviour _basic.phpt (dba_open_basic.phpt) Tests for a function's error behaviour _error.phpt (dba_open_error.phpt) Tests for variations in a function's behaviour _variation.phpt (dba_open_variation.phpt) General tests for extensions .phpt (dba_003.phpt) The convention of using _basic, _error and _variation was introduced when we found that writing a single test case for each function resulted in unacceptably large test cases. It's quite hard to debug problems when the test case generates 100s of lines of output. The "basic" test case for a function should just address the single most simple thing that the function is designed to do. For example, if writing a test for the sin() function a basic test would just be to check that sin() returns the correct values for some known angles — eg 30, 90, 180. The "error" tests for a function are test cases which are designed to provoke errors, warnings or notices. There can be more than one error case, if so the convention is to name the test cases mytest_error1.phpt, mytest_error2.phpt and so on. The "variation" tests are any tests that don't fit into "basic" or "error" tests. For example one might use a variation tests to test boundary conditions. How big is a test case? ======================= Small. Really — the smaller the better, a good guide is no more than 10 lines of output. The reason for this is that if we break something in PHP and it breaks your test case we need to be able to find out quite quickly what we broke, going through 1000s of line of test case output is not easy. Having said that it's sometimes just not practical to stay within the 10 line guideline, in this case you can help a lot by commenting the output. You may find plenty of much longer tests in PHP - the small tests message is something that we learnt over time, in fact we are slowly going through and splitting tests up when we need to. Comments ======== Comments help. Not an essay — just a couple of lines on what the objective of the test is. It may seem completely obvious to you as you write it, but it might not be to someone looking at it later on. Basic Format ============ A test must contain the sections TEST, FILE and either EXPECT or EXPECTF at a minimum. The example below illustrates a minimal test. *ext/standard/tests/strings/strtr.phpt* .. code:: php --TEST-- strtr() function — basic test for strtr() --FILE-- "hi", "hi"=>"hello", "a"=>"A", "world"=>"planet"); var_dump(strtr("# hi all, I said hello world! #", $trans)); ?>

--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 of
EXPECT. 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 by
run-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 the
run-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

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 %r will 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:testResponseHello World
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:testResponseHello World
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--


Access Denied

text
<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 and
zend_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
    • fn
    • zend_function*
    • The function pointer to call, usually stored in EG(function_table) for global functions
      or zend_class_entry->function_table for class methods.
    • this_ptr
    • zend_object*
    • Object pointer representing $this.
    • called_scope
    • zend_class_entry*
    • Scope where the method is called, NULL for global functions.
    • retval
    • zval*
    • zval pointer where the return value of the called function will be stored.
    • param_count
    • uint32_t
    • Number of parameters passed to the function via the params argument.
    • params
    • zval*
    • Pointer to a C zval array containing the arguments to the function, may be NULL if
      param_count is 0.
    • named_params

    • HashTable*

    • 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_args
    • uint32_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 callable zval is a PHP callable. Returns true if it is, false otherwise.
      fcc is an out parameter constructing the FCC struct, see below for details. error
      is an optional out parameter holding the error message if callable is not a PHP
      callable. Note: the signature used to be bool 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. object if callable is a
      string, this zend_object* represents the class instance to check if such a method
      exists. check_flags is a bit mask of IS_CALLABLE_CHECK_SYNTAX_ONLY and
      IS_CALLABLE_SUPPRESS_DEPRECATIONS, generally should be left to 0. callable_name is
      an optional out parameter, it will be set to the string representation of the callable
      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. retval may be NULL in which case the
      return value of the function is discarded. If this_ptr is NULL it must be a global
      function or static method. called_scope must not be NULL if fn is 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 additional consumed_args parameter (see
      zend_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 the called_scope is derived from
      this_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 FAILURE if the method_name does 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 optionally object) is a valid PHP
      callable via zend_is_callable_ex() and calls it with the given parameters and return
      value. function_table is ignored and should always be NULL. It is recommended to
      instead use zend_is_callable() to fetch an FCC and call it with
      zend_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 extra named_params argument.


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 the
zend_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 Closure may be a
      ZEND_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_gc object 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, fcc is a pointer to an FCC that may be NULL, in this case the FCC
      will be derived from the FCI function_name zval, including a callability check. This
      function only returns FAILURE if EG(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_function but sets the FCI return value to retval.

    • .. 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 forwarding callable, check_flags, fcc,
      callable_name, and error to zend_is_callable_ex(). As such callable_name
      and error are optional and may be NULL. Returns FAILURE if callable is not
      a PHP callable, SUCCESS otherwise.

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. args must
      be an IS_ARRAY zval. It is recommended to replace calls to this function by
      zend_call_function_with_return_value() and if args is not NULL to set the
      fci.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. args must be an IS_ARRAY zval.
      If args is NULL the FCI arguments are cleared. It is recommended to migrate away
      from this API by setting fci.named_params=Z_ARR_P(args) and calling
      zend_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 if func is not NULL it 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 argc is 0 the 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 argc is 0 the 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 argc is 0 the 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 in
zend_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;

text
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 the
GC_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 an
assert.


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 structure
zend_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 a
zend_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 be
PHP_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 around
zend_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 using
malloc (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". See ZEND_KNOWN_STRINGS in Zend/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_string
    • zval
    • Description
    • ZSTR_LEN
    • Z_STRLEN[_P]
    • Returns the length of the string in bytes.
    • ZSTR_VAL
    • Z_STRVAL[_P]
    • Returns the string data as a char*.
    • ZSTR_HASH
    • Z_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-counting for 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 called
MyClass, 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 that
zend_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 includes
IS_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 of
counted. counted polymorphically refers to any reference counted <todo>__ value, including
strings, arrays, objects, resources and references. null and bool are missing from
zend_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 some
reference 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 given
zval.

.. list-table:: zval macros
:header-rows: 1

    • Macro
    • Description
    • Z_TYPE[_P]
    • Access the zval.u1.v.type part of the type flags, containing the IS_* type.
    • Z_LVAL[_P]
    • Access the underlying int value.
    • Z_DVAL[_P]
    • Access the underlying float value.
    • Z_STR[_P]
    • Access the underlying zend_string pointer.
    • Z_STRVAL[_P]
    • Access the strings raw char * pointer.
    • Z_STRLEN[_P]
    • Access the strings length.
    • ZVAL_COPY_VALUE(t, s)
    • Copy one zval to 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 for
HashTable, 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 corresponding
zend_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 through
zval.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 the
object 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 the
ZVAL_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 using
pip, 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 your
settings.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 your
settings.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 a
compile_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 ---