# Technical Documentation: php/php-src

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/php/php-src)

### Overview (`README.md`)

<div align="center">
    <a href="https://www.php.net">
        <img
            alt="PHP"
            src="https://www.php.net/images/logos/new-php-logo.svg"
            width="150">
    </a>
</div>

# 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](LICENSE)
(SPDX-License-Identifier: `BSD-3-Clause`).

[](https://github.com/php/php-src/actions/workflows/test.yml)
[](https://issues.oss-fuzz.com/issues?q=project:php)

## Documentation

The PHP manual is available at [php.net/docs](https://www.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](https://windows.php.net). After extracting the archive the
`*.exe` files are ready to use.

For other systems, see the [installation chapter](https://www.php.net/install).

### Building PHP source code

*For Windows, see [Build your own PHP on Windows](https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2).*

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](https://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](https://packagist.org/extensions) list, and installed with
[🥧 PIE, the PHP Installer for Extensions](https://github.com/php/pie) or the
deprecated PHP Extension Community Library - [PECL](https://pecl.php.net).

## Contributing

The PHP source code is located in the Git repository at
[github.com/php/php-src](https://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 internals@lists.php.net.

New features require an RFC and must be accepted by the developers. See
[Request for comments - RFC](https://wiki.php.net/rfc) and
[Voting on PHP features](https://wiki.php.net/rfc/voting) 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](https://bugs.php.net) bug tracker.

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

See [Git workflow](https://wiki.php.net/vcs/gitworkflow) for details on how pull
requests are merged.

### Guidelines for contributors

See further documents in the repository for more information on how to
contribute:

- [Contributing to PHP](/CONTRIBUTING.md)
- [PHP coding standards](/CODING_STANDARDS.md)
- [Internal documentation](https://php.github.io/php-src/)
- [Mailing list rules](/docs/mailinglist-rules.md)
- [PHP release process](/docs/release-process.md)

## Credits

For the list of people who've put work into PHP, please see the
[PHP credits page](https://www.php.net/credits.php).

---

### 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/](https://php.github.io/php-src/). It is in very early
stages, but is intended to become the primary place where new information about
php-src is documented. Over time, it is expected to replace various mediums
like:

* https://www.phpinternalsbook.com/
* https://wiki.php.net/internals
* Blogs from contributors

## How to build

`python` 3 and `pip` are required.

```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``](https://github.com/dzhu/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`)

[](https://travis-ci.org/jedisct1/libsodium-php?branch=master)

libsodium-php
=============

A simple, low-level PHP extension for
[libsodium](https://github.com/jedisct1/libsodium).

Full documentation here:
[Using Libsodium in PHP Projects](https://paragonie.com/book/pecl-libsodium),
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:

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

```
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](https://www.litespeedtech.com/docs/webserver).

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](https://www.litespeedtech.com/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 (philnelson@acm.org) 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](https://www.vagrantup.com) 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

* Davide Mendolia (idaf1er@gmail.com)
* Patrick Allaert (patrick.allaert@gmail.com)
* Côme Bernigaud (mcmic@php.net)

---

### 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](https://github.com/moriyoshi/libmbfl). 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](https://github.com/hirokawa/libmbfl/wiki/Introduction-to-libmbflhttps:--github.com-hirokawa-libmbfl-wiki-Introduction-to-libmbfl-_edit%23)

* 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 <helly@php.net>
Hayk Chamyan <hamshen@gmail.com>
Wez Furlong <wez@thebrainroom.com>
Rui Hirokawa <hirokawa@php.net>
Shigeru Kanemoto <sgk@happysize.co.jp>
U. Kenkichi <kenkichi@axes.co.jp>
Moriyoshi Koizumi <moriyoshi@php.net>
Hironori Sato <satoh@jpnnet.com>
Tsukada Takuya <tsukada@fminn.nagano.nagano.jp>
Tateyama <tateyan@amy.hi-ho.ne.jp>
Den V. Tsopa <tdv@edisoft.ru>
Maksym Veremeyenko <verem@m1stereo.tv>
Haluk AKIN <halukakin@gmail.com>

---

### 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](https://github.com/dstogov/ir)
to produce optimized native code. The necessary part of the IR
Framework is embedded into php-src.

Running tests of the JIT
------------------------

Then, to test the JIT, e.g. with opcache.jit=tracing, an example command
based on what is used to test in CI:

```
make test TESTS="-d opcache.jit_buffer_size=16M -d opcache.enable=1 -d opcache.enable_cli=1 -d opcache.protect_memory=1 -d opcache.jit=tracing --repeat 2 --show-diff -j$(nproc) ext/opcache Zend"
```

- `opcache.jit_buffer_size=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](https://github.com/google/sanitizers/wiki/AddressSanitizer) is often useful.

Some of the time, adding `-m --show-mem` to the `TESTS` configuration is also useful to test with [valgrind](https://valgrind.org/) 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](../../../.github/workflows/push.yml) for examples of
dependencies to install.

If you are running this natively (outside of Docker or a VM):

- Consider running in docker/a VM instead if you are unfamiliar with this.
- Avoid purging packages.
- Avoid `-y` - if the package manager warns you that the dependencies conflict
  then **don't** try to force install them.

#### Prerequisites for 32-bit builds

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

```
sudo dpkg --add-architecture i386
sudo apt-get update -y
# As well as anything else from .github/actions/apt-x32/action.yml that you're testing locally
sudo apt-get install \
    gcc-multilib g++-multilib \
    libxml2-dev:i386 \
    libc6: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.

```
export LDFLAGS=-L/usr/lib/i386-linux-gnu
export CFLAGS='-m32'
export CXXFLAGS='-m32'
export PKG_CONFIG=/usr/bin/i686-linux-gnu-pkg-config
./configure --disable-all --build=i686-pc-linux-gnu
make -j$(nproc)
```

#### Running tests of the JIT on 32-bit builds

See the section "Running tests of the JIT".

### Testing the jit with arm64 on x86 computers

https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
may be useful for local development.

Note that this is slower than compiling and testing natively.

```
# After following steps in https://www.docker.com/blog/faster-multi-platform-builds-dockerfile-cross-compilation-guide/
cp .gitignore .dockerignore
echo .git >> .dockerignore

docker build --network=host -t php-src-arm64-example -f ext/opcache/jit/Dockerfile.arm64.example .
docker run -it --rm php-src-arm64-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:

```
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](https://hub.docker.com/_/microsoft-mssql-server):

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

* SUPER: Required to [create functions if binary logging is enabled](https://dev.mysql.com/doc/refman/8.0/en/stored-programs-logging.html#sa38412929)
* SELECT permissions on performance_schema.session_connect_attrs

---

### Overview (`ext/pgsql/tests/README.md`)

# The pgsql extension tests

Test scripts assumes:

* PostgreSQL server is installed locally
* There is a PostgreSQL account for the users running the test scripts
* There is database named `test`

For instance, if your login name is `testuser`, you should have PostgreSQL user
account named `testuser` and grant that user access to the database `test`.

If you have account and database, type `createdb test` from command prompt to
create the database to execute the test scripts. By executing the above command
as the same user running the tests you ensure that the user is granted access to
the database.

If you find problems in PostgreSQL extension, please report a
[bug](https://github.com/php/php-src/issues).

---

### 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](https://aomedia.googlesource.com/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](https://www.php.net/mailing-lists.php). 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](http://www.faqs.org/rfcs/rfc1855.html).

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](https://wiki.php.net):

- [PHP 8.6](https://wiki.php.net/todo/php86)
- [PHP 8.5](https://wiki.php.net/todo/php85)
- [PHP 8.4](https://wiki.php.net/todo/php84)
- [PHP 8.3](https://wiki.php.net/todo/php83)
- [PHP 8.2](https://wiki.php.net/todo/php82)
- [PHP 8.1](https://wiki.php.net/todo/php81)

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](#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.

   - https://cirrus-ci.com/github/php/php-src
   - https://github.com/php/php-src/actions

   > 💡 **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 release-managers@php.net.

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](#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][gitfaq-mandatory].

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

12. 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
    ```

13. 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
    ```

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

15. 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`.

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

17. Once the release is tagged, contact the release-managers@php.net 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 release-managers@php.net:

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

   * [Announce 8.1.0alpha1](https://github.com/php/web-php/commit/57b9675c8d8550493289fa1fba77427c93cdd472)
   * [Announce 8.1.0alpha2](https://github.com/php/web-php/commit/cec044fc0763f5cfa77d0e79479f8b6279023570)
   * [Announce 8.1.0alpha3](https://github.com/php/web-php/commit/5c480765f444a3fddfd575e01fe0be3fcfdde05b)
   * [Announce 8.1.0beta1](https://github.com/php/web-php/commit/40840e3c3f89d6dd95baa4b8cdf22d6f206f86c2)
   * [Announce 8.1.0beta2](https://github.com/php/web-php/commit/7bf6acdadd4940bd9db711bf3f9d5a4054dc0722)
   * [Announce 8.1.0beta3](https://github.com/php/web-php/commit/38c8a872700fb0c2ebde49e2eae3374257ba6d08)
   * [Announce 8.1.0RC1](https://github.com/php/web-php/commit/6e4bf3d0228ce113728d5f1a769ed42e0d63ca10)
   * [Announce 8.1.0RC2](https://github.com/php/web-php/commit/1ae95f4b686a5d614a94a664a7466ee0e5cd21eb)
   * [Announce 8.1.0RC3](https://github.com/php/web-php/commit/3091246d77a3f445fcc593587597d0abcab8c373)
   * [Announce 8.1.0RC4](https://github.com/php/web-php/commit/fbaeb9403f4e2856115889946d3f63751e183c7b)
   * [Announce 8.1.0RC5](https://github.com/php/web-php/commit/46473ccccfb5f7fedc3f169c55fb7c22a596b55d)
   * [Announce 8.1.0RC6](https://github.com/php/web-php/commit/cacaef9c41352b5dbf3fbbf44702cc6c0cbfb478)

   > ⚠️ **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:

   * `internals@lists.php.net`
   * `php-general@lists.php.net`
   * `php-qa@lists.php.net`

   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.

   * [PHP 8.1.0alpha1 is available for testing](https://news-web.php.net/php.qa/69043)
   * [PHP 8.1.0beta3 available for testing](https://news-web.php.net/php.qa/69079)
   * [PHP 8.1.0RC6 available for testing](https://news-web.php.net/php.qa/69117)
   * [PHP 8.1.7RC1 Ready for testing](https://news-web.php.net/php.qa/69163)

   > 🚨 **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](https://github.com/derickr/toot-together/)
   with highlights from the NEWS file yourself, if you want.

   * [Announce 8.5.0alpha1](https://github.com/derickr/toot-together/pull/42)
   * [Announce 8.5.0alpha2](https://github.com/derickr/toot-together/pull/47)

   We post to [@php@fosstodon.org](https://fosstodon.org/@php).


## Packaging a stable release

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

   ```
   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 git@github.com: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 release-managers@php.net 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 release-managers@php.net:

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

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

5. 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](https://github.com/php/web-php/commit/c966868202caafa880213055f4e3e97c0483119b)

7. 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'
   ```
8. 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.

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

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

   * Make sure the tarballs are available from, e.g.,
     `https://www.php.net/distributions/php-X.Y.Z.tar.gz`
   * Check the "downloads" page to make sure the new version appears:
     https://www.php.net/downloads
   * Does the news entry show up on the home page? https://www.php.net
   * Do the updates to the ChangeLog appear?
     e.g., https://www.php.net/ChangeLog-8.php
   * Is there a release page for the new version?
     e.g., `https://www.php.net/releases/X_Y_Z.php`
   * Does the RC for this version still appear on the Release Candidate Builds page?
     https://www.php.net/release-candidates.php

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

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

12. Send *separate* announcement emails to:

   * `php-announce@lists.php.net`
   * `php-general@lists.php.net`
   * `internals@lists.php.net`

   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:

   * [PHP 8.1.0 Released](https://news-web.php.net/php.announce/321)
   * [PHP 8.1.3 Released](https://news-web.php.net/php.announce/325)
   * [PHP 8.1.6 Released](https://news-web.php.net/php.announce/331)

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

13. Coordinate with the social media team (i.e., Derick) to
    [create a PR request](https://github.com/derickr/toot-together/blob/main/toots/README.md)
    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 php-announce@lists.php.net,
   php-general@lists.php.net and internals@lists.php.net 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
internals@lists.php.net 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.

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

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

7. Update `web-php:git.php` and https://wiki.php.net/vcs/gitworkflow to reflect
   the new branch.

   For example:

   * [Add PHP-8.1 to the Git steps page][]
   * [Changes to the wiki][]

> 💬 **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.

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

   * `.github/matrix.php` (add new branch, update version for `master`)
   [Updating after 8.5 branch cut](https://github.com/php/php-src/commit/197921a8aa7b2f9af103b439bf913e23d6ed1b98)

   * `.github/scripts/windows/find-target-branch.bat` (update version for `master`)
   [Updating after 8.5 branch cut](https://github.com/php/php-src/commit/107075605db6f1b15ce275e345d1c6de04a464b0)

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

   * [Updating after 8.5 branch cut](https://github.com/php/php-src/commit/77af4780ccbbc96a2f85a3102d45a15f5e26f9c9)

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

    * [Updating after 8.5 branch cut](https://github.com/php/php-src/commit/f7e39a91d5a9de53fc4e184f6fce6116ca3388a1)

## Preparing for the initial stable version (PHP X.Y.0)

1. When you release the first pre-GA RC, remind the documentation team
   (phpdoc@lists.php.net) 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
   ```


5. 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](https://www.rfc-editor.org/rfc/rfc9116),
   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](https://github.com/php/web-php)
   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][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 internals@lists.php.net 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](https://www.php.net/git-php.php)
   to get a PHP account (if you don't already have one).

2. Request membership to the
   [release managers group](https://github.com/orgs/php/teams/release-managers) 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:

   * internals@lists.php.net (email internals+subscribe@lists.php.net)
   * php-announce@lists.php.net (email php-announce+subscribe@lists.php.net)
   * php-general@lists.php.net (email php-general+subscribe@lists.php.net)
   * php-qa@lists.php.net (email php-qa+subscribe@lists.php.net)

4. File a [ticket in the infrastructure](https://github.com/php/infrastructure/issues/new?template=request-release-manager-access.yml)
   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](https://github.com/php/infrastructure/blob/main/docs/ServerAccess.rst#logging-into-servers) to set up
   access to downloads.php.net through jump hosts with 2FA.

   Then [create a Google Authenticator file](https://github.com/php/infrastructure/blob/main/docs/ServerAccess.rst#creating-google-authenticator-files),
   and provide the `.google_authenticator` file that this created, as
   attachment to an email to systems@php.net. 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 ramsey@php.net
   pub   rsa4096 2021-04-26 [SC] [expires: 2025-11-24]
   39B6 4134 3D8C 104B 2B14  6DC3 F9C3 9DC0 B969 8544
   uid           [ultimate] Ben Ramsey <ramsey@php.net>
   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:

   * https://github.com/php/php-src
   * https://github.com/php/web-php
   * https://github.com/php/web-php-distributions


[general availability]: https://en.wikipedia.org/wiki/Software_release_life_cycle#General_availability_(GA)
[muscle memory]: https://en.wikipedia.org/wiki/Muscle_memory
[Conditional Includes For Git Config]: https://motowilliams.com/2017-05-11-conditional-includes-for-git-config/
[Update versions for PHP 8.1.0beta3]: https://github.com/php/php-src/commit/3edd1087c70bee2ec21f0fbec1a575d78a500f15
[Update versions for PHP 8.1.6RC1]: https://github.com/php/php-src/commit/40e8ced23898e3069340ca03ea5febc5361015ad
[Update NEWS for PHP 8.1.6RC1]: https://github.com/php/php-src/commit/a4fdeaebe419b88e3b4a1f5aba845c2d4e81fd4e
[Prepare for PHP 8.1.0RC1]: https://github.com/php/php-src/commit/5764414eb8900ae98020a3c20693f4fb793efa99
[Update NEWS for PHP 8.2.0 alpha2]: https://github.com/php/php-src/commit/418f7211f71658d79d934861be20f277db96fe2c
[Update NEWS for PHP 8.2.0RC6]: https://github.com/php/php-src/commit/4ccc414961a70200d638ca281a35f893226d74e2
[PHP 8.3 is now for PHP 8.3.21-dev]: https://github.com/php/php-src/commit/b57f425cfe20a11003253427424cc0517483550b
[GitHub command line tool]: https://cli.github.com
[merging upwards]: https://wiki.php.net/vcs/gitworkflow
[Update versions for PHP 8.1.7]: https://github.com/php/php-src/commit/d35e577a1bd0b35b9386cea97cddc73fd98eed6d
[Update NEWS for PHP 8.1.7]: https://github.com/php/php-src/commit/b241f07f52ca9f87bf52be81817f475e6e727439
[Announce PHP 8.1.6]: https://github.com/php/web-php/commit/9f796a96c65f07e45845ec248933bfb0010b94a9
[feature freeze]: https://en.wikipedia.org/wiki/Freeze_(software_engineering)
[Prepare for PHP 8.2]: https://github.com/php/php-src/commit/1c33ddb5e5598c5385c4c965992c6e031fd00dd6
[Prepare for PHP 8.2 (bis)]: https://github.com/php/php-src/commit/a93e12f8a6dfc23e334339317c97aa35356db821
[Add PHP-8.1 to the Git steps page]: https://github.com/php/web-php/commit/1fcd78c2817cf1fbf1a1de2ddec1350be4e26491
[Changes to the wiki]: https://wiki.php.net/vcs/gitworkflow?do=diff&rev2%5B0%5D=1617123194&rev2%5B1%5D=1654728193&difftype=sidebyside
[migration guide]: https://www.php.net/manual/en/migration81.php
[GPG key]: https://en.wikipedia.org/wiki/GNU_Privacy_Guard
[Generating a new GPG key]: https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key
[sign your GPG key]: https://carouth.com/articles/signing-pgp-keys/
[gitfaq-mandatory]: https://wiki.php.net/vcs/gitfaq#mandatory_git_settings
[Send emails from a different address or alias]: https://support.google.com/mail/answer/22370?hl=en
[security-txt]: https://github.com/php/policies/blob/main/security-policies.rst#making-changes-to-securitytxt

---

### 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=qa-test@domain.com
   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

   <?php
   /** @var string */
   const ANIMAL = "Elephant";
   /** @var float */
   const WEIGHT = 6.8;

   class Atmosphere {
       public function calculateBar(): float {}
   }

   function fahrenheitToCelsius(float $fahrenheitToCelsius): float {}

Any kind of symbol can be declared via stubs. Every type can be used, with the exception of
disjunctive normal form (DNF) types. Additional meta information can be added via PHPDoc blocks or
PHP attributes. Namespaces can also be used by adding a top-level ``namespace`` declaration or by
using namespace blocks:

.. code:: php

   <?php
   namespace {
       /** @var string */
       const ANIMAL = "Elephant";
       /** @var float */
       const WEIGHT_TON = 6.8;

       class Atmosphere {
           public function calculateBar(): float {}
       }
   }

   namespace Algorithms {
       function fahrenheitToCelsius(float $fahrenheit): float {}
   }

The above example declares the global constants ``ANIMAL`` and ``WEIGHT_TON``, and the class
``Atmosphere`` in the top-level namespace. The ``fahrenheitToCelsius()`` function is declared to be
in the ``Algorithms`` namespace.

********************
 Using gen_stub.php
********************

Stub files have the ``.stub.php`` extension by convention.

They are processed by ``build/gen_stub.php``, which uses PHP-Parser_ for parsing. Depending on the
configuration and the supplied arguments, it can generate various artefacts.

The following sections will introduce these capabilities.

.. _php-parser: https://github.com/nikic/PHP-Parser

*******************************
 Generating arginfo Structures
*******************************

The purpose of stubs files is to make it easier to declare arginfo structures, validate parameters
parsing declarations, and maintain documentation.

Previously, you had to manually use the different ``ZEND_BEGIN_ARG_* ... ZEND_END_ARG_INFO()``
macros. This is a tedious and error-prone process. Being able to use pure PHP code on which the C
code can be generated is a huge benefit.

The arginfo file matching our first example looks like:

.. code:: c

   /* This is a generated file, edit the .stub.php file instead.
    * Stub hash: e4ed788d54a20272a92a3f6618b73d48ec848f97 */

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fahrenheitToCelsius, 0, 1, IS_DOUBLE, 0)
       ZEND_ARG_TYPE_INFO(0, fahrenheitToCelsius, IS_DOUBLE, 0)
   ZEND_END_ARG_INFO()

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Atmosphere_calculateBar, 0, 0, IS_DOUBLE, 0)
   ZEND_END_ARG_INFO()

The hash that is included in the file makes sure that stub files are not reprocessed unless the stub
file was modified, or something requires it to be processed (e.g. regeneration was forced by using
the ``-f`` flag).

Another thing to keep in mind is that stub-based type declarations have to be in sync with the
parameter parsing code in the PHP functions through ``ZEND_PARSE_PARAMETERS_*`` macros (``ZPP``).

In release builds, the arginfo structures are only used with Reflection.

In debug builds, PHP will compare arginfo structures against ZPP macros to ensure that the stubs and
actual data matches for both arguments and return types. If they do not, an error is generated.
Therefore it is important that you declare the right types in your stub files.

For documentation purposes, PHPDoc can be used.

Since PHP 8.0, arginfo structures can also contain default values. These can for example be used by
``ReflectionParameter::getDefaultValue()``.

Besides constant literals, default values can also contain compile-time evaluable expressions, and
contain references to constants.

In the example below, we define a function with an optional argument, referencing a constant:

.. code:: php

   <?php
   /** @var string */
   const ANIMAL = "Elephant";

   function formatName(string $defaultName = ANIMAL . " Mc" . ANIMAL . "Face"): string {}

This will result in the following arginfo:

.. code:: c

   /* This is a generated file, edit the .stub.php file instead.
    * Stub hash: a9685164284e73f47b15838122b631ebdfef23d6 */

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_formatName, 0, 0, IS_STRING, 0)
       ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, defaultName, IS_STRING, 0, "ANIMAL . \" Mc\" . ANIMAL . \"Face\"")
   ZEND_END_ARG_INFO()

You can only use constants as long as they are defined in the same stub file.

If this is not possible, then the stub declaring the constant should be included with ``require``:

.. code:: php

   // constants.stub.php
   <?php
   /** @var string */
   const ANIMAL = "Elephant";

.. code:: php

   // example.stub.php
   <?php
   require "constants.stub.php";

   function foo(string $param = ANIMAL): string {}

Sometimes arguments have to be passed by reference, or by using the `ZEND_SEND_PREFER_REF` flag.

To signal parsing by reference, use the usual ``&`` syntax.

To include the ``ZEND_SEND_PREFER_REF`` flag, use the ``@prefer-ref`` PHPDoc tag:

.. code:: php

   <?php
   /**
    * @param array $herd
    * @prefer-ref $elephantName
    */
   function addElephantsToHerd(&$herd, string $elephantName): string {}

This results in the following arginfo file:

.. code:: c

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_addElephantsToHerd, 0, 2, IS_STRING, 0)
       ZEND_ARG_INFO(1, herd)
       ZEND_ARG_TYPE_INFO(ZEND_SEND_PREFER_REF, elephantName, IS_STRING, 0)
   ZEND_END_ARG_INFO()

*****************************
 Generating Function Entries
*****************************

Besides arginfo structures, function entries themselves can also be generated via stubs.

In order to generate these, add the file-level ``@generate-function-entries`` PHPDoc tag:

.. code:: php

   <?php
   /** @generate-function-entries */

   class Atmosphere {
      public function calculateBar(): float {}
   }

   function fahrenheitToCelsius(float $fahrenheit): float {}

Now, the following C code is generated:

.. code:: c

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fahrenheitToCelsius, 0, 1, IS_DOUBLE, 0)
       ZEND_ARG_TYPE_INFO(0, fahrenheit, IS_DOUBLE, 0)
   ZEND_END_ARG_INFO()

   ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Atmosphere_calculateBar, 0, 0, IS_DOUBLE, 0)
   ZEND_END_ARG_INFO()

   ZEND_FUNCTION(fahrenheitToCelsius);
   ZEND_METHOD(Atmosphere, calculateBar);

   static const zend_function_entry ext_functions[] = {
       ZEND_FE(fahrenheitToCelsius, arginfo_fahrenheitToCelsius)
       ZEND_FE_END
   };

   static const zend_function_entry class_Atmosphere_methods[] = {
       ZEND_ME(Atmosphere, calculateBar, arginfo_class_Atmosphere_calculateBar, ZEND_ACC_PUBLIC)
       ZEND_FE_END
   };

The generated ``ext_functions`` variable must be passed as the ``functions`` member of
`zend_module_entry` struct.

The generated ``class_Atmosphere_methods`` must be used when registering the ``Atmosphere`` class:

.. code:: c

   INIT_CLASS_ENTRY(ce, "Atmosphere", class_Atmosphere_methods);

Additional meta information can be attached to functions, with the following PHPDoc tags:

-  ``@deprecated``: Triggers the usual deprecation notice when the function/method is called. As of
   PHP 8.4 the `#[Deprecated]` attribute should be used instead.

-  ``@alias``: If a function/method is an alias of another function/method, then the aliased
   function/method name has to be provided as value. E.g. the function ``sizeof()`` has the ``@alias
   count`` annotation.

-  ``@implementation-alias``: This is very similar to ``@alias`` with some semantic differences.
   These aliases exists purely to avoid duplicating some code, but there is no other connection
   between the alias and the aliased function or method.

   A notable example is ``Error::getCode()``, which has the ``@implementation-alias
   Exception::getCode`` annotation.

   The difference between ``@alias`` and ``@implementation-alias`` is very nuanced and is only
   observable in the manual.

-  ``@tentative-return-type``: By using this annotation, the return type declaration is reclassified
   as a `tentative return type`_.

-  ``@genstubs-expose-comment-block``: By adding this annotation at the beginning of a PHPDoc block,
   the content of the PHPDoc block will be exposed for
   `ReflectionFunctionAbstract::getDocComment()`. This feature was added in PHP 8.4.

.. _tentative return type: https://wiki.php.net/rfc/internal_method_return_types

**************************
 Generating Class Entries
**************************

In order to generate code which is necessary for registering constants, classes, properties, enums,
and traits, use the ``@generate-class-entries`` file-level PHPDoc block.

``@generate-class-entries`` implies ``@generate-function-entries``, so the latter is then
superfluous.

Given the following stub:

.. code:: php

    <?php
    /** @generate-class-entries */

   enum Number: string {
       /** @var string */
       public const ONE = "one";

       case One = Number::ONE;
       case Two = Number::TWO;
   }

   class Elephant extends stdClass {
       /** @cvalue M_PI */
       public const float PI = UNKNOWN;

       public readonly string $name;
   }

The following arginfo file is generated:

.. code:: c

   static const zend_function_entry class_Number_methods[] = {
       ZEND_FE_END
   };

   static const zend_function_entry class_Elephant_methods[] = {
       ZEND_FE_END
   };

   static zend_class_entry *register_class_Number(void)
   {
       zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods);

       ...

       return class_entry;
   }

   static zend_class_entry *register_class_Elephant(zend_class_entry *class_entry_stdClass)
   {
       zend_class_entry ce, *class_entry;

       INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
       class_entry = zend_register_internal_class_ex(&ce, class_entry_stdClass);

       ...

       return class_entry;
   }

The generated ``register_class_*()`` functions must be used to register these classes in the
``PHP_MINIT_FUNCTION`` directly:

.. code:: c

   zend_class_entry *number_ce = register_class_Number();
   zend_class_entry *elephpant_ce = register_class_Elephant(zend_standard_class_def);

Class dependencies, such as the parent class or implemented interface, have to be passed to the
register function. In the example above, we passed the class entry for ``stdClass``
(``zend_standard_class_def``).

Like functions and methods, classes also support meta information passed via PHPDoc tags:

-  ``@deprecated``: triggers a deprecation notice when the class is used

-  ``@strict-properties``: adds the ``ZEND_ACC_NO_DYNAMIC_PROPERTIES`` flag for the class (as of PHP
   8.0), which disallow dynamic properties.

-  ``@not-serializable``: adds the ``ZEND_ACC_NOT_SERIALIZABLE`` flag for the class (as of PHP 8.1),
   which prevents the serialization of the class.

-  ``@genstubs-expose-comment-block``: By adding this tag at the beginning of a PHPDoc block, the
   content of the PHPDoc block will be exposed for `ReflectionClass::getDocComment()`. This feature
   is only available as of PHP 8.4.

This is an example with all the flags:

.. code:: php

   <?php
   /**
    * @generate-class-entries
    */

   /**
    * @deprecated
    * @not-serializable
    * @strict-properties */
   /** @genstubs-expose-comment-block
    * This is a comment
    * @see https://www.php.net */
   class Elephant extends stdClass {
      public readonly string $name;
   }

Resulting in these changes:

.. code:: c

   ...

   static zend_class_entry *register_class_Elephant(zend_class_entry *class_entry_stdClass)
   {
       zend_class_entry ce, *class_entry;

       INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
       class_entry = zend_register_internal_class_ex(&ce, class_entry_stdClass);
       class_entry->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
   <?php
   /** @generate-class-entries */

   /** @var string */
   const ANIMAL = "Elephant";

   /**
   * @var float
   * @cvalue M_PI
   */
   const BAR = UNKNOWN;

   function connect(#[\SensitiveParameter] string $connectionString): string {}

The following C function will be generated in order to register the two global constants and the
attribute. The name of this file is ``example.stub.php``:

.. code:: c

   ...

   static void register_example_symbols(int module_number)
   {
       REGISTER_STRING_CONSTANT("ANIMAL", "Elephant", CONST_PERSISTENT);
       REGISTER_DOUBLE_CONSTANT("BAR", M_PI, CONST_PERSISTENT);


       zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "connect", sizeof("connect") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0);
   }

Similarly to class registration functions, the generated ``register_{$stub_file_name}_symbols()``
functions must be used in ``PHP_MINIT_FUNCTION``, to make the global constants an attributes
available:

.. code:: c

   PHP_MINIT_FUNCTION(example)
   {
       register_example_symbols(module_number);

       return SUCCESS;
   }

Global constants always need to have their type specified with a ``@var`` PHPDoc tag. The type for
class constants is inferred from their type declaration if available, otherwise a ``@var`` PHPDoc
tag is required. A ``@var`` tag is also required if you enable ``generate-legacy-arginfo`` (see
below).

If a constant's value is defined by a 3rd party library, PHP's internals, or a specific type such as
a bitmask, the exact value is not yet known when stubs are used. In these cases, don't duplicate the
value in the stub file, but instead use the ``UNKNOWN`` constant value with the ``@cvalue`` PHPDoc
tag.

In the example below we define the ``BAR`` global constant to ``UNKNOWN``, with the value linked
with ``@cvalue M_PI`` to the C-level constant ``M_PI`` (define by PHP's internals).

Constants can take the following extra meta information passed via PHPDoc tags:

-  ``@deprecated``: Triggers a deprecation notice when the constant is used. As of PHP 8.5 the
   `#[Deprecated]` attribute should be used instead.

-  ``@genstubs-expose-comment-block``: By adding this tag at the beginning of a PHPDoc block, the
   content of the PHPDoc block will be exposed for `ReflectionClass::getDocComment()`. This feature
   is only available as of PHP 8.4.

************************************
 Maintaining Backward Compatibility
************************************

The stubs in the PHP source distribution only need to support the branch they are part of.

Third party extensions often need to support a wider range of PHP versions, with different features
supported, that can be enabled through stubs.

Stubs may get new features which are unavailable in earlier PHP versions, or ABI compatibility
breaks may happen between minor releases. And PHP 7.x versions are substantially different from PHP
8 versions.

It is possible to tell the arginfo generator script ``gen_stub.php`` to create legacy arginfo too,
specifying a minimum supported version.

If your extension still needs to handle PHP 7, then add the ``@generate-legacy-arginfo`` file-level
PHPDoc tag, without any value. In this case, an additional ``_legacy_arginfo.h`` file will be
generated. You can include this file conditionally, such as:

.. code::

   #if (PHP_VERSION_ID >= 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

   <?php
   /**
    * @generate-class-entries
    * @generate-legacy-arginfo 80000
    */

   enum Number: string {
      case One;
   }

   /**
    * @strict-properties
    * @not-serializable */
   class Elephant {
      /**
       * @cvalue M_PI
       * @var float
       */
      public const float PI = UNKNOWN;

      public readonly string $name;
   }

Then notice the ``#if (PHP_VERSION_ID >= ...)`` 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<int, string>
    * @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 (php-qa@lists.php.net) — 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
   <https://github.com/php/php-src>`_ 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 (php-qa@lists.php.net) 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<bugid>.phpt (bug17123.phpt)

Tests for a function's basic behaviour
   <functionname>_basic.phpt (dba_open_basic.phpt)

Tests for a function's error behaviour
   <functionname>_error.phpt (dba_open_error.phpt)

Tests for variations in a function's behaviour
   <functionname>_variation.phpt (dba_open_variation.phpt)

General tests for extensions
   <extname><no>.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--
   <?php
   /* Do not change this test it is a README.TESTING example. */
   $trans = array("hello"=>"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 php-qa@lists.php.net 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 php-qa@lists.php.net. 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 php-qa@lists.php.net 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--
   <?php
   if (PHP_INT_SIZE != 4) die("skip this test is for 32bit platforms only");
   ?>

Tests for 64bit platforms can use:

.. code:: php

   --SKIPIF--
   <?php
   if (PHP_INT_SIZE != 8) die("skip this test is for 64bit platforms only");
   ?>

To run a test only on Windows:

.. code:: php

   --SKIPIF--
   <?php
   if (substr(PHP_OS, 0, 3) != 'WIN') die("skip this test is for Windows platforms only");
   ?>

To run a test only on Linux:

.. code:: php

   --SKIPIF--
   <?php
   if (!stristr(PHP_OS, "Linux")) die("skip this test is Linux platforms only");
   ?>

To skip a test on Mac OS X Darwin:

.. code:: php

   --SKIPIF--
   <?php
   if (!stristr(PHP_OS, "Darwin")) die("skip this test is for Mac OS X platforms only");
   ?>

**********
 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--
   <?php
   /* Do not change this test it is a README.TESTING example. */
   $s = '123';
   var_dump(str_shuffle($s));
   var_dump($s);
   ?>
   --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--
   <?php
   /* Do not change this test it is a README.TESTING example. */
   $s = "alabala nica".chr(0)."turska panica";
   var_dump(strstr($s, "nic"));
   var_dump(strrchr($s," nic"));
   ?>
   --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--
   <?php
   if (!defined('SODIUM_CRYPTO_PWHASH_SALTBYTES')) print "skip libsodium without argon2i";
   ?>
   --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--
   <?php
             $temp_filename = "fred.tmp";
             $fp = fopen($temp_filename, "w");
             fwrite($fp, "Hello Boys!");
             fclose($fp);
   ?>
   --CLEAN--
   <?php
             unlink($temp_filename);
   ?>
   --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--
   <?php
           $temp_filename = __DIR__."/fred.tmp";
           $fp = fopen($temp_filename, "w");
           fwrite ($fp, "Hello Boys!\n");
           fclose($fp);
   ?>
   --CLEAN--
   <?php
           $temp_filename = __DIR__."/fred.tmp";
           unlink($temp_filename);
   ?>
   --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--
   <?php
   $a="23abc";
   $b=-33;
   echo "$a :$b ";
   $res = hypot($a, $b);
   var_dump($res);
   ?>
   ===DONE===
   <?php exit(0); ?>
   --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 zoe@php.net
   # 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--
   <?php if (!extension_loaded("filter")) die("Skipped: filter extension required."); ?>

Example 1 (full): :ref:`sample001.phpt`

Example 2 (snippet):

.. code:: php

   --SKIPIF--
   <?php include('skipif.inc'); ?>

Example 2 (full): :ref:`sample003.phpt`

Example 3 (snippet):

.. code:: php

   --SKIPIF--
   <?php if (getenv('SKIP_ASAN')) die('xfail Startup failure leak'); ?>

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=<p>string</p>&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" />
     </SOAP-ENV:Body>
   </SOAP-ENV:Envelope>

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" />
     </SOAP-ENV:Body>
   </SOAP-ENV:Envelope>

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--
   <?xml version="1.0" encoding="ISO-8859-1"?>
   <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" />
     </SOAP-ENV:Body>
   </SOAP-ENV:Envelope>

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=<b>test</b>&b=http://example.com

Example 1 (full): :ref:`sample001.phpt`

Example 2 (snippet):

.. code:: text

   --GET--
   ar[elm1]=1234&ar[elm2]=0660&a=0234

Example 2 (full): :ref:`sample008.phpt`

``--COOKIE--``
--------------

**Description:** Cookies to be passed to the test script. This section forces the use of the CGI
binary instead of the usual CLI one.

**Required:** No.

Requirements: PHP CGI binary.

**Test Script Support:** ``run-tests.php``

**Format:** A single line of text in a valid HTTP cookie format.

Example 1 (snippet):

.. code::

   --COOKIE--
   hello=World;goodbye=MrChips

Example 1 (full): :ref:`sample002.phpt`

``--STDIN--``
-------------

**Description:** Data to be fed to the test script's standard input.

**Required:** No.

**Test Script Support:** ``run-tests.php``

**Format:** Any text within this section is passed as STDIN to PHP.

Example 1 (snippet):

.. code:: text

   --STDIN--
   fooBar
   use this to input some thing to the php script

Example 1 (full): :ref:`sample009.phpt`

``--INI--``
-----------

**Description:** To be used if you need a specific php.ini setting for the test.

**Required:** No.

**Format:** Key value pairs including automatically replaced tags. One setting per line. Content
that is not a valid ini setting may cause failures.

The following is a list of all tags and what they are used to represent:

-  ``{PWD}``: Represents the directory of the file containing the ``--INI--`` section.
-  ``{TMP}``: Represents the system's temporary directory. Available as of PHP 7.2.19 and 7.3.6.

Example 1 (snippet):

.. code:: text

   --INI--
   precision=14

Example 1 (full): :ref:`sample001.phpt`

Example 2 (snippet):

.. code:: text

   --INI--
   session.use_cookies=0
   session.cache_limiter=
   register_globals=1
   session.serialize_handler=php
   session.save_handler=files

Example 2 (full): :ref:`sample003.phpt`

``--ARGS--``
------------

**Description:** A single line defining the arguments passed to PHP.

**Required:** No.

**Format:** A single line of text that is passed as the argument(s) to the PHP CLI.

Example 1 (snippet):

.. code:: text

   --ARGS--
   --arg value --arg=value -avalue -a=value -a value

Example 1 (full): :ref:`sample010.phpt`

``--ENV--``
-----------

**Description:** Configures environment variables such as those found in the ``$_SERVER`` global
array.

**Required:** No.

**Format:** Key value pairs. One setting per line.

Example 1 (snippet):

.. code:: text

   --ENV--
   SCRIPT_NAME=/frontcontroller10.php
   REQUEST_URI=/frontcontroller10.php/hi
   PATH_INFO=/hi

Example 1 (full): :ref:`sample018.phpt`

``--PHPDBG--``
--------------

**Description:** This section takes arbitrary phpdbg commands and executes the test file according
to them as it would be run in the phpdbg prompt.

**Required:** No.

**Format:** Arbitrary phpdbg commands

Example 1 (snippet):

.. code:: text

   --PHPDBG--
   b
   4
   b
   del
   0
   b
   5
   r
   b
   del
   1
   r
   y
   q

Example 1 (full): :ref:`phpdbg_1.phpt`

``--FILE--``
------------

**Description:** The test source code.

**Required:** One of the ``FILE`` type sections is required.

**Format:** PHP source code enclosed by PHP tags.

Example 1 (snippet):

.. code:: php

   --FILE--
   <?php
   ini_set('html_errors', false);
   var_dump(filter_input(INPUT_GET, "a", FILTER_SANITIZE_STRIPPED));
   var_dump(filter_input(INPUT_GET, "b", FILTER_SANITIZE_URL));
   var_dump(filter_input(INPUT_GET, "a", FILTER_SANITIZE_SPECIAL_CHARS, array(1,2,3,4,5)));
   var_dump(filter_input(INPUT_GET, "b", FILTER_VALIDATE_FLOAT, new stdClass));
   var_dump(filter_input(INPUT_POST, "c", FILTER_SANITIZE_STRIPPED, array(5,6,7,8)));
   var_dump(filter_input(INPUT_POST, "d", FILTER_VALIDATE_FLOAT));
   var_dump(filter_input(INPUT_POST, "c", FILTER_SANITIZE_SPECIAL_CHARS));
   var_dump(filter_input(INPUT_POST, "d", FILTER_VALIDATE_INT));
   var_dump(filter_var(new stdClass, "d"));
   var_dump(filter_input(INPUT_POST, "c", "", ""));
   var_dump(filter_var("", "", "", "", ""));
   var_dump(filter_var(0, 0, 0, 0, 0));
   echo "Done\n";
   ?>

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) "&#60;b&#62;test&#60;/b&#62;"

   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) "&#60;p&#62;string&#60;/p&#62;"
   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--
   <?php
   unlink(__DIR__.'/DomDocument_save_basic.tmp');
   ?>

Example 1 (full): :ref:`sample024.phpt`

Example 2 (snippet):

.. code:: php

   --CLEAN--
   <?php
   require_once('clean.inc');
   ?>

Example 2 (full): :ref:`sample025.phpt`

Example 3 (snippet):

.. code:: php

   --CLEAN--
   <?php
   $key = ftok(__DIR__.'/003.phpt', 'q');
   $s = shm_attach($key);
   shm_remove($s);
   ?>

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--
   <?php
   echo "Hello, world. This is sent to the stdout I/O stream\n";
   fwrite(STDERR, "This is error sent to the stderr I/O stream\n");
   ?>
   --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--
   <?php
   echo "Hello, world. This is sent to the stdout I/O stream\n";
   fwrite(STDERR, "This is error sent to the stderr I/O stream\n");
   ?>
   --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--
   <?php
   echo "Hello, world. This is sent to the stdout I/O stream\n";
   fwrite(STDERR, "This is error sent to the stderr I/O stream\n");
   ?>
   --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

   <?php
   include_once(__DIR__ . '/imap_include.inc');

   $imap_stream = imap_open($default_mailbox, $username, $password);

   // delete all msgs in default mailbox, i.e INBOX
   $check = imap_check($imap_stream);
   for ($i = 1; $i <= $check->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--
   <?php
   var_dump(extension_loaded(&apos;curl&apos;));
   var_dump(extension_loaded(&apos;imagick&apos;));
   var_dump(extension_loaded(&apos;tokenizer&apos;));
   ?>
   --EXPECT--
   bool(true)
   bool(true)
   bool(true)

file012.phpt
------------

.. code:: php

   <?php
     echo "hello world\n";
   ?>

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 <felipe@php.net>
   --INI--
   precision=14
   --SKIPIF--
   <?php if (!extension_loaded("filter")) die("Skipped: filter extension required."); ?>
   --GET--
   a=<b>test</b>&b=https://example.com
   --POST--
   c=<p>string</p>&d=12345.7
   --FILE--
   <?php
   ini_set('html_errors', false);
   var_dump(filter_input(INPUT_GET, "a", FILTER_SANITIZE_STRIPPED));
   var_dump(filter_input(INPUT_GET, "b", FILTER_SANITIZE_URL));
   var_dump(filter_input(INPUT_GET, "a", FILTER_SANITIZE_SPECIAL_CHARS, array(1,2,3,4,5)));
   var_dump(filter_input(INPUT_GET, "b", FILTER_VALIDATE_FLOAT, new stdClass));
   var_dump(filter_input(INPUT_POST, "c", FILTER_SANITIZE_STRIPPED, array(5,6,7,8)));
   var_dump(filter_input(INPUT_POST, "d", FILTER_VALIDATE_FLOAT));
   var_dump(filter_input(INPUT_POST, "c", FILTER_SANITIZE_SPECIAL_CHARS));
   var_dump(filter_input(INPUT_POST, "d", FILTER_VALIDATE_INT));
   var_dump(filter_var(new stdClass, "d"));
   var_dump(filter_input(INPUT_POST, "c", "", ""));
   var_dump(filter_var("", "", "", "", ""));
   var_dump(filter_var(0, 0, 0, 0, 0));
   echo "Done\n";
   ?>
   --EXPECTF--
   string(4) "test"
   string(19) "https://example.com"
   string(27) "&#60;b&#62;test&#60;/b&#62;"

   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) "&#60;p&#62;string&#60;/p&#62;"
   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 zoe@php.net
   # TestFest Munich 2009-05-19
   --COOKIE--
   hello=World;goodbye=MrChips
   --FILE--
   <?php
   var_dump($_COOKIE);
   ?>
   --EXPECT--
   array(2) {
     ["hello"]=>
     string(5) "World"
     ["goodbye"]=>
     string(7) "MrChips"
   }

sample003.phpt
--------------

.. code:: php

   --TEST--
   session object deserialization
   --SKIPIF--
   <?php include('skipif.inc'); ?>
   --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--
   <?php
     if (php_sapi_name()=='cli') echo 'skip';
     require_once('skipif2.inc');
     if (!extension_loaded('zlib')) die('skip zlib extension not available');
   ?>
   --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" />
     </SOAP-ENV:Body>
   </SOAP-ENV:Envelope>
   --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--
   <?xml version="1.0" encoding="UTF-8"?>
   <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:Body><ns1:testResponse><return xsi:type="xsd:string">Hello World</return></ns1:testResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
   ok

sample006.phpt
--------------

.. code:: php

   --TEST--
   is_uploaded_file() function
   --CREDITS--
   Dave Kelsey <d_kelsey@uk.ibm.com>
   --SKIPIF--
   <?php if (php_sapi_name()=='cli') die('skip'); ?>
   --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--
   <?php
     if (php_sapi_name()=='cli') echo 'skip';
     require_once('skipif2.inc');
     if (!extension_loaded('zlib')) die('skip zlib extension not available');
   ?>
   --INI--
   precision=14
   --DEFLATE_POST--
   <?xml version="1.0" encoding="ISO-8859-1"?>
   <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" />
     </SOAP-ENV:Body>
   </SOAP-ENV:Envelope>
   --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--
   <?xml version="1.0" encoding="UTF-8"?>
   <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:Body><ns1:testResponse><return xsi:type="xsd:string">Hello World</return></ns1:testResponse></SOAP-ENV:Body></SOAP-ENV:Envelope>
   ok

sample008.phpt
--------------

.. code:: php

   --TEST--
   GET/POST/REQUEST Test with input_filter
   --SKIPIF--
   <?php if (!extension_loaded("filter")) die("skip"); ?>
   --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--
   <?php
   var_dump(stream_get_contents(STDIN));
   ?>
   --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--
   <?php
     var_dump(getopt("a:", array("arg:")));
   ?>
   --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--
   <?php # vim:ft=php
   if (!extension_loaded('pdo') || !extension_loaded('sqlite')) print 'skip'; ?>
   --REDIRECTTEST--
   return array(
     'ENV' => array(
         'PDOTEST_DSN' => 'sqlite2::memory:'
       ),
     'TESTS' => 'ext/pdo/tests'
     );

sample014.phpt
--------------

.. code:: php

   --TEST--
   MySQL
   --SKIPIF--
   <?php # vim:ft=php
   if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) print 'skip not loaded';
   ?>
   --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--
   <?php
   var_dump($_GET);
   ?>
   --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--
   <?php # vim:ft=php
   if (!extension_loaded('pdo')) die('skip');
   $dir = getenv('REDIR_TEST_DIR');
   if (false == $dir) die('skip no driver');
   require_once $dir . 'pdo_test.inc';
   PDOTest::skip();
   ?>
   --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--
   <?php if (!extension_loaded("phar")) die("skip"); ?>
   --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--
   <html>
    <head>
     <title>Access Denied</title>
    </head>
    <body>
     <h1>403 - File /hi Access Denied</h1>
    </body>
   </html>

sample019.phpt
--------------

.. code:: php

   --TEST--
   bzopen() and invalid parameters
   --SKIPIF--
   <?php if (!extension_loaded("bz2")) print "skip"; ?>
   --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("<element></element>");
   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--
   <?php
   $constants = array(
       "M_E",
       "M_LOG2E",
       "M_LOG10E",
       "M_LN2",
       "M_LN10",
       "M_PI",
       "M_PI_2",
       "M_PI_4",
       "M_1_PI",
       "M_2_PI",
       "M_SQRTPI",
       "M_2_SQRTPI",
       "M_LNPI",
       "M_EULER",
       "M_SQRT2",
       "M_SQRT1_2",
       "M_SQRT3"
   );
   foreach($constants as $constant) {
       printf("%-10s: %s\n", $constant, constant($constant));
   }
   ?>
   --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--
   <?php if (!extension_loaded("sysvshm")) print "skip"; ?>
   --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--
   <?php
   $key = ftok(__DIR__."/003.phpt", 'q');
   $s = shm_attach($key);
   shm_remove($s);
   ?>
   --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--
   <?php
   $a = -12.3456;
   $test = sprintf("%04d", $a);
   var_dump($test, bin2hex($test));
   $test = sprintf("% 13u", $a);
   var_dump($test, bin2hex($test));
   ?>
   --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--
   <?php
   require_once('skipif.inc');
   ?>
   --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--
   <?php
     unlink(__DIR__.'/DomDocument_save_basic.tmp');
   ?>
   --EXPECTF--
   Wrote: 72 bytes

sample025.phpt
--------------

.. code:: php

   --TEST--
   Test imap_append() function : basic functionality
   --SKIPIF--
   <?php
   require_once(__DIR__.'/skipif.inc');
   ?>
   --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: webmaster@something.com\r\n"
                      . "To: info@something.com\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: webmaster@something.com\r\n"
                      . "To: info@something.com\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--
   <?php
   require_once('clean.inc');
   ?>
   --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===
   <?php exit(0); ?>
   --EXPECT--
   1
   21
   221
   222
   231
   3

skipif2.phpt
------------

.. code:: php

   <?php
     if (!extension_loaded('soap')) die('skip soap extension not available');
   ?>

skipif.phpt
-----------

.. code:: php

   <?php
   // This script prints "skip" if condition does not meet.
   if (!extension_loaded("session") && ini_get("enable_dl")) {
     $dlext = (substr(PHP_OS, 0, 3) == "WIN") ? ".dll" : ".so";
     @dl("session$dlext");
   }
   if (!extension_loaded("session")) {
       die("skip Session module not loaded");
   }
   $save_path = ini_get("session.save_path");
   if ($save_path) {
     if (!file_exists($save_path)) {
       die("skip Session save_path doesn't exist");
     }

     if ($save_path && !@is_writable($save_path)) {
       if (($p = strpos($save_path, ';')) !== false) {
         $save_path = substr($save_path, ++$p);
       }
       if (!@is_writable($save_path)) {
         die("skip\n");
       }
     }
   }
   ?>

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--
   <?php
   require_once('skipif.inc');
   if (getenv('SKIP_ASAN')) die('xfail Startup failure leak');
   ?>
   --FILE--
   <?php
   echo "Foobar\n";
   ?>
   --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;

    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 ---
- Files Extracted: 34
- Estimated Token Budget: ~72209 tokens
- Canonical Reference: https://codewiki.google/github.com/php/php-src
