From 749731b754c557b0c071dd1b78102c43763ca242 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 11:15:11 +0700
Subject: [PATCH 01/15] docs: enabled markdown support
---
docs/requirements.txt | 1 +
docs/source/conf.py | 8 ++++++++
2 files changed, 9 insertions(+)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index ca19fe15c2e0..aa90a21be9a1 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,4 +1,5 @@
Sphinx
+myst-parser>=5.1
sphinx-design
sphinxawesome-theme
rstfmt
diff --git a/docs/source/conf.py b/docs/source/conf.py
index f28102206a9f..a2bc42e6f677 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -15,9 +15,17 @@
project = 'php-src docs'
author = 'The PHP Group'
extensions = [
+ 'myst_parser',
'sphinx_design',
'sphinx.ext.autosectionlabel',
]
+myst_enable_extensions = [
+ 'alert',
+ 'gfm_autolink',
+ 'strikethrough',
+ 'tasklist',
+]
+myst_heading_anchors = 6
templates_path = ['_templates']
html_theme = 'sphinxawesome_theme'
html_static_path = ['_static']
From 1ae6a768c5d27baf2e1328f7d443b8100d7b73e4 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 12:07:29 +0700
Subject: [PATCH 02/15] docs: converted to markdown syntax
---
.github/workflows/docs.yml | 2 +
docs-old/output-api.md | 236 +-
docs-old/parameter-parsing-api.md | 22 +-
docs-old/self-contained-extensions.md | 18 +-
docs-old/streams.md | 70 +-
docs-old/unix-build-system.md | 30 +-
docs/Makefile | 22 +-
docs/README.md | 11 +-
docs/requirements.txt | 3 +-
docs/source/conf.py | 1 +
docs/source/core/data-structures/index.rst | 20 +-
.../data-structures/reference-counting.rst | 258 +-
.../core/data-structures/zend_constant.rst | 86 +-
.../core/data-structures/zend_string.rst | 243 +-
docs/source/core/data-structures/zval.rst | 347 +-
docs/source/index.rst | 78 +-
.../introduction/high-level-overview.rst | 194 +-
docs/source/introduction/ides/index.rst | 14 +-
.../introduction/ides/visual-studio-code.rst | 202 +-
docs/source/miscellaneous/running-tests.rst | 233 +-
docs/source/miscellaneous/stubs.rst | 954 ++--
docs/source/miscellaneous/writing-tests.rst | 4111 ++++++++---------
22 files changed, 3416 insertions(+), 3739 deletions(-)
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index ffb45c9a20cd..e8e615fd5288 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -22,6 +22,8 @@ jobs:
run: pip install -r docs/requirements.txt
- name: Check formatting
run: make -C docs check-formatting
+ - name: Build
+ run: make -C docs html
- name: Publish
if: github.event_name == 'push'
uses: sphinx-notes/pages@v3
diff --git a/docs-old/output-api.md b/docs-old/output-api.md
index 67bdfa3668dd..a09cc0184959 100644
--- a/docs-old/output-api.md
+++ b/docs-old/output-api.md
@@ -3,108 +3,110 @@
Everything now resides beneath the php_output namespace, and there's an API call
for every output handler op.
- Checking output control layers status:
- // Using OG()
- php_output_get_status();
-
- Starting the default output handler:
- // php_start_ob_buffer(NULL, 0, 1);
- php_output_start_default();
-
- Starting an user handler by zval:
- // php_start_ob_buffer(zhandler, chunk_size, erase);
- php_output_start_user(zhandler, chunk_size, flags);
-
- Starting an internal handler without context:
- // php_ob_set_internal_handler(my_php_output_handler_func_t, buffer_size, "output handler name", erase);
- php_output_start_internal(handler_name, handler_name_len, my_php_output_handler_func_t, chunk_size, flags);
-
- Starting an internal handler with context:
- // not possible with old API
- php_output_handler *h;
- h = php_output_handler_create_internal(handler_name, handler_name_len, my_php_output_handler_context_func_t, chunk_size, flags);
- php_output_handler_set_context(h, my_context, my_context_dtor);
- php_output_handler_start(h);
-
- Testing whether a certain output handler has already been started:
- // php_ob_handler_used("output handler name");
- php_output_handler_started(handler_name, handler_name_len);
-
- Flushing one output buffer:
- // php_end_ob_buffer(1, 1);
- php_output_flush();
-
- Flushing all output buffers:
- // not possible with old API
- php_output_flush_all();
-
- Cleaning one output buffer:
- // php_ob_end_buffer(0, 1);
- php_output_clean();
-
- Cleaning all output buffers:
- // not possible with old API
- php_output_clean_all();
-
- Discarding one output buffer:
- // php_ob_end_buffer(0, 0);
- php_output_discard();
-
- Discarding all output buffers:
- // php_ob_end_buffers(0);
- php_output_discard_all();
-
- Stopping (and dropping) one output buffer:
- // php_ob_end_buffer(1, 0)
- php_output_end();
-
- Stopping (and dropping) all output buffers:
- // php_ob_end_buffers(1, 0);
- php_output_end_all();
-
- Retrieving output buffers contents:
- // php_ob_get_buffer(zstring);
- php_output_get_contents(zstring);
-
- Retrieving output buffers length:
- // php_ob_get_length(zlength);
- php_output_get_length(zlength);
-
- Retrieving output buffering level:
- // OG(nesting_level);
- php_output_get_level();
-
- Issue a warning because of an output handler conflict:
- // php_ob_init_conflict("to be started handler name", "to be tested if already started handler name");
- php_output_handler_conflict(new_handler_name, new_handler_name_len, set_handler_name, set_handler_name_len);
-
- Registering a conflict checking function, which will be checked prior starting the handler:
- // not possible with old API, unless hardcoding into output.c
- php_output_handler_conflict_register(handler_name, handler_name_len, my_php_output_handler_conflict_check_t);
-
- Registering a reverse conflict checking function, which will be checked prior starting the specified foreign handler:
- // not possible with old API
- php_output_handler_reverse_conflict_register(foreign_handler_name, foreign_handler_name_len, my_php_output_handler_conflict_check_t);
-
- Facilitating a context from within an output handler callable with ob_start():
- // not possible with old API
- php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ, (void *) &custom_ctx_ptr_ptr);
-
- Disabling of the output handler by itself:
- //not possible with old API
- php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_DISABLE, NULL);
-
- Marking an output handler immutable by itself because of irreversibility of its operation:
- // not possible with old API
- php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE, NULL);
-
- Restarting the output handler because of a CLEAN operation:
- // not possible with old API
- if (flags & PHP_OUTPUT_HANDLER_CLEAN) { ... }
-
- Recognizing by the output handler itself if it gets discarded:
- // not possible with old API
- if ((flags & PHP_OUTPUT_HANDLER_CLEAN) && (flags & PHP_OUTPUT_HANDLER_FINAL)) { ... }
+```
+Checking output control layers status:
+ // Using OG()
+ php_output_get_status();
+
+Starting the default output handler:
+ // php_start_ob_buffer(NULL, 0, 1);
+ php_output_start_default();
+
+Starting an user handler by zval:
+ // php_start_ob_buffer(zhandler, chunk_size, erase);
+ php_output_start_user(zhandler, chunk_size, flags);
+
+Starting an internal handler without context:
+ // php_ob_set_internal_handler(my_php_output_handler_func_t, buffer_size, "output handler name", erase);
+ php_output_start_internal(handler_name, handler_name_len, my_php_output_handler_func_t, chunk_size, flags);
+
+Starting an internal handler with context:
+ // not possible with old API
+ php_output_handler *h;
+ h = php_output_handler_create_internal(handler_name, handler_name_len, my_php_output_handler_context_func_t, chunk_size, flags);
+ php_output_handler_set_context(h, my_context, my_context_dtor);
+ php_output_handler_start(h);
+
+Testing whether a certain output handler has already been started:
+ // php_ob_handler_used("output handler name");
+ php_output_handler_started(handler_name, handler_name_len);
+
+Flushing one output buffer:
+ // php_end_ob_buffer(1, 1);
+ php_output_flush();
+
+Flushing all output buffers:
+ // not possible with old API
+ php_output_flush_all();
+
+Cleaning one output buffer:
+ // php_ob_end_buffer(0, 1);
+ php_output_clean();
+
+Cleaning all output buffers:
+ // not possible with old API
+ php_output_clean_all();
+
+Discarding one output buffer:
+ // php_ob_end_buffer(0, 0);
+ php_output_discard();
+
+Discarding all output buffers:
+ // php_ob_end_buffers(0);
+ php_output_discard_all();
+
+Stopping (and dropping) one output buffer:
+ // php_ob_end_buffer(1, 0)
+ php_output_end();
+
+Stopping (and dropping) all output buffers:
+ // php_ob_end_buffers(1, 0);
+ php_output_end_all();
+
+Retrieving output buffers contents:
+ // php_ob_get_buffer(zstring);
+ php_output_get_contents(zstring);
+
+Retrieving output buffers length:
+ // php_ob_get_length(zlength);
+ php_output_get_length(zlength);
+
+Retrieving output buffering level:
+ // OG(nesting_level);
+ php_output_get_level();
+
+Issue a warning because of an output handler conflict:
+ // php_ob_init_conflict("to be started handler name", "to be tested if already started handler name");
+ php_output_handler_conflict(new_handler_name, new_handler_name_len, set_handler_name, set_handler_name_len);
+
+Registering a conflict checking function, which will be checked prior starting the handler:
+ // not possible with old API, unless hardcoding into output.c
+ php_output_handler_conflict_register(handler_name, handler_name_len, my_php_output_handler_conflict_check_t);
+
+Registering a reverse conflict checking function, which will be checked prior starting the specified foreign handler:
+ // not possible with old API
+ php_output_handler_reverse_conflict_register(foreign_handler_name, foreign_handler_name_len, my_php_output_handler_conflict_check_t);
+
+Facilitating a context from within an output handler callable with ob_start():
+ // not possible with old API
+ php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ, (void *) &custom_ctx_ptr_ptr);
+
+Disabling of the output handler by itself:
+ //not possible with old API
+ php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_DISABLE, NULL);
+
+Marking an output handler immutable by itself because of irreversibility of its operation:
+ // not possible with old API
+ php_output_handler_hook(PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE, NULL);
+
+Restarting the output handler because of a CLEAN operation:
+ // not possible with old API
+ if (flags & PHP_OUTPUT_HANDLER_CLEAN) { ... }
+
+Recognizing by the output handler itself if it gets discarded:
+ // not possible with old API
+ if ((flags & PHP_OUTPUT_HANDLER_CLEAN) && (flags & PHP_OUTPUT_HANDLER_FINAL)) { ... }
+```
## Output handler hooks
@@ -113,23 +115,25 @@ remove the CLEANABLE and REMOVABLE bits when the first output has passed through
or handlers implemented in C to be used with ob_start() can contain a non-global
context:
- PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ
- pass a void*** pointer as second arg to receive the address of a pointer
- pointer to the opaque field of the output handler context
- PHP_OUTPUT_HANDLER_HOOK_GET_FLAGS
- pass a int* pointer as second arg to receive the flags set for the output handler
- PHP_OUTPUT_HANDLER_HOOK_GET_LEVEL
- pass a int* pointer as second arg to receive the level of this output handler
- (starts with 0)
- PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE
- the second arg is ignored; marks the output handler to be neither cleanable
- nor removable
- PHP_OUTPUT_HANDLER_HOOK_DISABLE
- the second arg is ignored; marks the output handler as disabled
+```
+PHP_OUTPUT_HANDLER_HOOK_GET_OPAQ
+ pass a void*** pointer as second arg to receive the address of a pointer
+ pointer to the opaque field of the output handler context
+PHP_OUTPUT_HANDLER_HOOK_GET_FLAGS
+ pass a int* pointer as second arg to receive the flags set for the output handler
+PHP_OUTPUT_HANDLER_HOOK_GET_LEVEL
+ pass a int* pointer as second arg to receive the level of this output handler
+ (starts with 0)
+PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE
+ the second arg is ignored; marks the output handler to be neither cleanable
+ nor removable
+PHP_OUTPUT_HANDLER_HOOK_DISABLE
+ the second arg is ignored; marks the output handler as disabled
+```
## Open questions
-* Should the userland API be adjusted and unified?
+- Should the userland API be adjusted and unified?
Many bits of the manual (and very first implementation) do not comply with the
behaviour of the current (to be obsoleted) code, thus should the manual or the
diff --git a/docs-old/parameter-parsing-api.md b/docs-old/parameter-parsing-api.md
index fae10f2fec8a..04c939ab40fc 100644
--- a/docs-old/parameter-parsing-api.md
+++ b/docs-old/parameter-parsing-api.md
@@ -24,7 +24,7 @@ int zend_parse_parameters_ex(int flags, int num_args, char *type_spec, ...);
The `zend_parse_parameters()` function takes the number of parameters passed to
the extension function, the type specifier string, and the list of pointers to
-variables to store the results in. The _ex() version also takes 'flags' argument
+variables to store the results in. The \_ex() version also takes 'flags' argument
-- current only `ZEND_PARSE_PARAMS_QUIET` can be used as 'flags' to specify that
the function should operate quietly and not output any error messages.
@@ -61,7 +61,7 @@ See also
The following list shows the type specifier, its meaning, and the parameter types
that need to be passed by address. All passed parameters are set if the PHP
parameter is non-optional and untouched if optional and the parameter is not
-present. The only exception is O where the zend_class_entry* has to be provided
+present. The only exception is O where the zend_class_entry\* has to be provided
on input and is used to verify the PHP parameter is an instance of that class.
```txt
@@ -96,18 +96,18 @@ z - the actual zval (zval*)
The following characters also have a meaning in the specifier string:
-* `|` - indicates that the remaining parameters are optional, they should be
+- `|` - indicates that the remaining parameters are optional, they should be
initialized to default values by the extension since they will not be touched
by the parsing function if they are not passed to it.
-* `/` - use SEPARATE_ZVAL() on the parameter it follows
-* `!` - the parameter it follows can be of specified type or NULL. If NULL is
+- `/` - use SEPARATE_ZVAL() on the parameter it follows
+- `!` - the parameter it follows can be of specified type or NULL. If NULL is
passed, and the output for such type is a pointer, then the output pointer is
set to a native NULL pointer. For 'b', 'l' and 'd', an extra argument of type
- bool* must be passed after the corresponding bool*, zend_long* or
- double* arguments, respectively. A non-zero value will be written to the
+ bool\* must be passed after the corresponding bool\*, zend_long\* or
+ double\* arguments, respectively. A non-zero value will be written to the
bool if a PHP NULL is passed.
- For `f` use the ``ZEND_FCI_INITIALIZED(fci)`` macro to check if a callable
- has been provided and ``!ZEND_FCI_INITIALIZED(fci)`` to check if a PHP NULL
+ For `f` use the `ZEND_FCI_INITIALIZED(fci)` macro to check if a callable
+ has been provided and `!ZEND_FCI_INITIALIZED(fci)` to check if a PHP NULL
is passed.
## Note on 64bit compatibility
@@ -119,7 +119,7 @@ and `size_t` to strings length (i.e. for "s" you need to pass char `*` and
Both mistakes might cause memory corruptions and segfaults:
-* 1
+- 1
```c
char *str;
@@ -127,7 +127,7 @@ long str_len; /* XXX THIS IS WRONG!! Use size_t instead. */
zend_parse_parameters(ZEND_NUM_ARGS(), "s", &str, &str_len)
```
-* 2
+- 2
```c
int num; /* XXX THIS IS WRONG!! Use zend_long instead. */
diff --git a/docs-old/self-contained-extensions.md b/docs-old/self-contained-extensions.md
index 522716c37089..ff0441befb12 100644
--- a/docs-old/self-contained-extensions.md
+++ b/docs-old/self-contained-extensions.md
@@ -3,8 +3,8 @@
A self-contained extension can be distributed independently of the PHP source.
To create such an extension, two things are required:
-* Configuration file (config.m4)
-* Source code for your module
+- Configuration file (config.m4)
+- Source code for your module
We will describe now how to create these and how to put things together.
@@ -12,12 +12,14 @@ We will describe now how to create these and how to put things together.
While the result will run on any system, a developer's setup needs these tools:
-* GNU autoconf
-* GNU m4
+- GNU autoconf
+- GNU m4
All of these are available from
- ftp://ftp.gnu.org/pub/gnu/
+```
+ftp://ftp.gnu.org/pub/gnu/
+```
## Converting an existing extension
@@ -144,10 +146,10 @@ an existing module called `foo`.
automatically be able to use `--with-foo=shared[,..]` or
`--enable-foo=shared[,..]`.
-2. In `config.m4`, use `PHP_NEW_EXTENSION([foo],.., [$ext_shared])` to enable
+1. In `config.m4`, use `PHP_NEW_EXTENSION([foo],.., [$ext_shared])` to enable
building the extension.
-3. Add the following lines to your C source file:
+1. Add the following lines to your C source file:
```c
#ifdef COMPILE_DL_FOO
@@ -162,7 +164,7 @@ points to be regarded.
1. Add `LICENSE` or `COPYING` to the `package.xml`
-2. The following should be defined in one of the extension header files
+1. The following should be defined in one of the extension header files
```c
#define PHP_FOO_VERSION "1.2.3"
diff --git a/docs-old/streams.md b/docs-old/streams.md
index 8220f9db78fc..1f05dee90e1a 100644
--- a/docs-old/streams.md
+++ b/docs-old/streams.md
@@ -1,6 +1,7 @@
# An overview of the PHP streams abstraction
-WARNING: some prototypes in this file are out of date.
+> [!WARNING]
+> Some prototypes in this file are out of date.
## Why streams?
@@ -51,21 +52,21 @@ PHPAPI php_stream *php_stream_open_wrapper(const char *path, const char *mode,
Where:
-* `path` is the file or resource to open.
-* `mode` is the stdio compatible mode eg: "wb", "rb" etc.
-* `options` is a combination of the following values:
- * `IGNORE_PATH` (default) - don't use include path to search for the file
- * `USE_PATH` - use include path to search for the file
- * `IGNORE_URL` - do not use plugin wrappers
- * `REPORT_ERRORS` - show errors in a standard format if something goes wrong.
- * `STREAM_MUST_SEEK` - If you really need to be able to seek the stream and
+- `path` is the file or resource to open.
+- `mode` is the stdio compatible mode eg: "wb", "rb" etc.
+- `options` is a combination of the following values:
+ - `IGNORE_PATH` (default) - don't use include path to search for the file
+ - `USE_PATH` - use include path to search for the file
+ - `IGNORE_URL` - do not use plugin wrappers
+ - `REPORT_ERRORS` - show errors in a standard format if something goes wrong.
+ - `STREAM_MUST_SEEK` - If you really need to be able to seek the stream and
don't need to be able to write to the original file/URL, use this option to
arrange for the stream to be copied (if needed) into a stream that can be
seek()ed.
-* `opened_path` is used to return the path of the actual file opened, but if you
+- `opened_path` is used to return the path of the actual file opened, but if you
used `STREAM_MUST_SEEK`, may not be valid. You are responsible for
`efree()ing` `opened_path`.
-* `opened_path` may be (and usually is) `NULL`.
+- `opened_path` may be (and usually is) `NULL`.
If you need to open a specific stream, or convert standard resources into
streams there are a range of functions to do this defined in `php_streams.h`. A
@@ -147,22 +148,27 @@ It returns one of the following values:
`make_seekable` will always set newstream to be the stream that is valid if the
function succeeds. When you have finished, remember to close the stream.
-NOTE: If you only need to seek forward, there is no need to call this function,
-as the `php_stream_seek` can emulate forward seeking when the whence parameter
-is `SEEK_CUR`.
+> [!NOTE]
+> If you only need to seek forward, there is no need to call this function, as
+> `php_stream_seek` can emulate forward seeking when the whence parameter is
+> `SEEK_CUR`.
-NOTE: Writing to the stream may not affect the original source, so it only makes
-sense to use this for read-only use.
+> [!NOTE]
+> Writing to the stream may not affect the original source, so it only makes
+> sense to use this for read-only use.
-NOTE: If the origstream is network based, this function will block until the
-whole contents have been downloaded.
+> [!NOTE]
+> If the origstream is network based, this function will block until the whole
+> contents have been downloaded.
-NOTE: Never call this function with an origstream that is referenced as a
-resource! It will close the origstream on success, and this can lead to a crash
-when the resource is later used/released.
+> [!NOTE]
+> Never call this function with an origstream that is referenced as a resource!
+> It will close the origstream on success, and this can lead to a crash when the
+> resource is later used/released.
-NOTE: If you are opening a stream and need it to be seekable, use the
-`STREAM_MUST_SEEK` option to php_stream_open_wrapper();
+> [!NOTE]
+> If you are opening a stream and need it to be seekable, use the
+> `STREAM_MUST_SEEK` option to `php_stream_open_wrapper()`.
```c
PHPAPI int php_stream_supports_lock(php_stream * stream);
@@ -205,7 +211,7 @@ PHP_STREAM_AS_SOCKETD - a socket descriptor
If you ask a socket stream for a `FILE*`, the abstraction will use fdopen to
create it for you. Be warned that doing so may cause buffered data to be lost
-if you mix ANSI stdio calls on the FILE* with php stream calls on the stream.
+if you mix ANSI stdio calls on the FILE\* with php stream calls on the stream.
If your system has the fopencookie function, php streams can synthesize a
`FILE*` on top of any stream, which is useful for SSL sockets, memory based
@@ -291,17 +297,17 @@ PHPAPI php_stream * php_stream_alloc(php_stream_ops * ops, void * abstract,
size_t bufsize, int persistent, const char * mode)
```
-* `ops` is a pointer to the implementation,
-* `abstract` holds implementation specific data that is relevant to this
+- `ops` is a pointer to the implementation,
+- `abstract` holds implementation specific data that is relevant to this
instance of the stream,
-* `bufsize` is the size of the buffer to use - if 0, then buffering at the
+- `bufsize` is the size of the buffer to use - if 0, then buffering at the
stream
-* `level` will be disabled (recommended for underlying sources that implement
+- `level` will be disabled (recommended for underlying sources that implement
their own buffering - such a `FILE*`)
-* `persistent` controls how the memory is to be allocated - persistently so that
+- `persistent` controls how the memory is to be allocated - persistently so that
it lasts across requests, or non-persistently so that it is freed at the end
of a request (it uses pemalloc),
-* `mode` is the stdio-like mode of operation - php streams places no real
+- `mode` is the stdio-like mode of operation - php streams places no real
meaning in the mode parameter, except that it checks for a `w` in the string
when attempting to write (this may change).
@@ -310,14 +316,14 @@ into a `FILE*`, so it should be compatible with the mode parameter of `fopen()`.
## Writing your own stream implementation
-* **RULE #1**: when writing your own streams: make sure you have configured PHP
+- **RULE #1**: when writing your own streams: make sure you have configured PHP
with `--enable-debug`.
Some great great pains have been taken to hook into the Zend memory manager to
help track down allocation problems. It will also help you spot incorrect use
of the STREAMS_DC, STREAMS_CC and the semi-private STREAMS_REL_CC macros for
function definitions.
-* RULE #2: Please use the stdio stream as a reference; it will help you
+- RULE #2: Please use the stdio stream as a reference; it will help you
understand the semantics of the stream operations, and it will always be more
up to date than these docs :-)
diff --git a/docs-old/unix-build-system.md b/docs-old/unix-build-system.md
index 01bb8e4e51cb..9ee603b69591 100644
--- a/docs-old/unix-build-system.md
+++ b/docs-old/unix-build-system.md
@@ -1,22 +1,22 @@
# PHP build system V5 overview
-* supports Makefile.ins during transition phase
-* not-really-portable Makefile includes have been eliminated
-* supports separate build directories without VPATH by using explicit rules only
-* does not waste disk-space/CPU-time for building temporary libraries =>
+- supports Makefile.ins during transition phase
+- not-really-portable Makefile includes have been eliminated
+- supports separate build directories without VPATH by using explicit rules only
+- does not waste disk-space/CPU-time for building temporary libraries =>
especially noticeable on slower systems
-* slow recursive make replaced with one global Makefile
-* eases integration of proper dependencies
-* abandoning the "one library per directory" concept
-* improved integration of the CLI
-* several new targets:
- * `build-modules`: builds and copies dynamic modules into `modules/`
- * `install-cli`: installs the CLI only, so that the install-sapi target does
+- slow recursive make replaced with one global Makefile
+- eases integration of proper dependencies
+- abandoning the "one library per directory" concept
+- improved integration of the CLI
+- several new targets:
+ - `build-modules`: builds and copies dynamic modules into `modules/`
+ - `install-cli`: installs the CLI only, so that the install-sapi target does
only what its name says
-* finally abandoned automake
-* changed some configure-time constructs to run at buildconf-time
-* upgraded shtool to 1.5.4
-* removed `$(moduledir)` (use `EXTENSION_DIR`)
+- finally abandoned automake
+- changed some configure-time constructs to run at buildconf-time
+- upgraded shtool to 1.5.4
+- removed `$(moduledir)` (use `EXTENSION_DIR`)
## The reason for a new system
diff --git a/docs/Makefile b/docs/Makefile
index 40f8dbfb5ed2..0824dc428092 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -4,33 +4,29 @@
# If people set these on the make command line, use 'em
SPHINXBUILD ?= sphinx-build
+MDFORMAT ?= mdformat
SOURCEDIR = source
BUILDDIR = build
-RSTFMT = rstfmt
-RSTFMTFLAGS = -w 100
rwildcard = $(foreach d,$(wildcard $(1:=/*)),$(call rwildcard,$d,$2) $(filter $(subst *,%,$2),$d))
-FILES = $(call rwildcard,$(SOURCEDIR),*.rst)
+MARKDOWN_FILES = $(call rwildcard,$(SOURCEDIR),*.md) $(call rwildcard,$(SOURCEDIR),*.rst)
all : html
-.PHONY : check-formatting clean html preflight
+.PHONY : check-formatting clean format html
.SUFFIXES : # Disable legacy behavior
check-formatting :
- $(RSTFMT) $(RSTFMTFLAGS) --check $(SOURCEDIR)
+ $(MDFORMAT) --check $(MARKDOWN_FILES)
clean :
- rm -rf -- $(wildcard $(SOURCEDIR)/.~ $(BUILDDIR))
+ rm -rf -- $(BUILDDIR)
-html : preflight
+format :
+ $(MDFORMAT) $(MARKDOWN_FILES)
+
+html :
$(SPHINXBUILD) -M $@ $(SOURCEDIR) $(BUILDDIR)
@printf 'Browse the \e]8;;%s\e\\%s\e]8;;\e\\.\n' \
"file://$(abspath $(BUILDDIR))/$@/index.$@" "php-src html docs locally"
-
-preflight : $(SOURCEDIR)/.~
-
-$(SOURCEDIR)/.~ : $(FILES)
- $(RSTFMT) $(RSTFMTFLAGS) $?
- touch $@
diff --git a/docs/README.md b/docs/README.md
index 3c0d7ddd2bcc..2d4328c95b67 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -27,12 +27,11 @@ your browser.
## Formatting
-The files in this documentation are formatted using the
-[``rstfmt``](https://github.com/dzhu/rstfmt) tool.
+The files in this documentation are formatted using
+[mdformat](https://mdformat.readthedocs.io/) with its
+[MyST plugin](https://github.com/executablebooks/mdformat-myst).
```bash
-rstfmt -w 100 source
+make format
+make check-formatting
```
-
-This tool is not perfect. It breaks on custom directives, so we might switch to
-either a fork or something else in the future.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index aa90a21be9a1..026f279a0705 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,5 +1,6 @@
Sphinx
+mdformat==1.0.0
+mdformat-myst==0.3.0
myst-parser>=5.1
sphinx-design
sphinxawesome-theme
-rstfmt
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a2bc42e6f677..432c15576da9 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -26,6 +26,7 @@
'tasklist',
]
myst_heading_anchors = 6
+source_suffix = {'.rst': 'markdown'}
templates_path = ['_templates']
html_theme = 'sphinxawesome_theme'
html_static_path = ['_static']
diff --git a/docs/source/core/data-structures/index.rst b/docs/source/core/data-structures/index.rst
index 8fcb860f686b..520ce1962163 100644
--- a/docs/source/core/data-structures/index.rst
+++ b/docs/source/core/data-structures/index.rst
@@ -1,13 +1,13 @@
-#################
- Data structures
-#################
+# Data structures
-.. toctree::
- :hidden:
-
- zval
- reference-counting
- zend_string
- zend_constant
+```{toctree}
+---
+hidden: true
+---
+zval
+reference-counting
+zend_string
+zend_constant
+```
This section provides an overview of the core data structures used in php-src.
diff --git a/docs/source/core/data-structures/reference-counting.rst b/docs/source/core/data-structures/reference-counting.rst
index 6895d5d682a8..312335e0d652 100644
--- a/docs/source/core/data-structures/reference-counting.rst
+++ b/docs/source/core/data-structures/reference-counting.rst
@@ -1,9 +1,7 @@
-####################
- Reference counting
-####################
+# 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.
+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
@@ -17,119 +15,78 @@ used by another party. When the party no longer needs the value, it is responsib
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
+```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
+- 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``).
+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
+```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](#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,
+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.
+**`zval` macros**
-.. [#non-rc]
+| 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. |
- 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.
+**`zend_refcounted_h` macros**
-.. 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. |
- - - 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
-************
+## 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
@@ -143,17 +100,15 @@ the value is not observable from other places. Modifying a value with RC 1 is un
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]
+```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
-***********************************
+## 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
@@ -161,68 +116,69 @@ or threads to reduce memory consumption. As you may know, sharing memory between
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.
+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
+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
+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
-*****************
+## 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);
+```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
+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 `_ chapter.
+the `gc_collect_cycles()` function. The cycle collectors design is described in the Cycle collector chapter.
-**********
- GC flags
-**********
+## GC flags
-.. code:: c
+```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 */
+```
- /* 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.
+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
+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`_.
+`GC_IMMUTABLE` has been discussed in [Immutable reference counted types](#immutable-reference-counted-types).
-The ``GC_PERSISTENT`` flag indicates that the value was allocated using ``malloc``, instead of PHPs
+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 `_ chapter for more
+being freed at the end of the request. See the Zend allocator chapter for more
information.
-The ``GC_PERSISTENT_LOCAL`` flag indicates that a ``GC_PERSISTENT`` value is only accessible in one
+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``.
+`assert`.
+
+[^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.
+
+[^immutable]: Whether the macro works with immutable types, described under [Immutable reference counted types](#immutable-reference-counted-types).
diff --git a/docs/source/core/data-structures/zend_constant.rst b/docs/source/core/data-structures/zend_constant.rst
index a5e85dc78638..fedcad44c359 100644
--- a/docs/source/core/data-structures/zend_constant.rst
+++ b/docs/source/core/data-structures/zend_constant.rst
@@ -1,75 +1,63 @@
-###############
- zend_constant
-###############
+# 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.
+`zend_constant`, which holds both the value of the constant and details for using it.
-************
- definition
-************
+## definition
-.. code:: c
+```c
+typedef struct _zend_constant {
+ zval value;
+ zend_string *name;
+ zend_string *filename;
+ HashTable *attributes;
+} zend_constant;
+```
- 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``
+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
-*******
+## value
-The value of the constant is stored in the :doc:`./zval` ``value``. However, since the ``zval``
+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``.
+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 */
+```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.
+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.
+`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
-******
+## name
-The ``name`` holds a :doc:`zend_string` with the name of the constant, to allow searching for
+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
-**********
+## 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()``.
+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
-************
+## attributes
-The ``attributes`` holds a ``HashTable`` (essentially an array) with the details of the 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 );``.
+compile time via `const`, e.g. `const EXAMPLE = 123`, not those declared at runtime, e.g.
+`define( 'EXAMPLE', 123 );`.
diff --git a/docs/source/core/data-structures/zend_string.rst b/docs/source/core/data-structures/zend_string.rst
index d6b20a49a74c..007f123a07de 100644
--- a/docs/source/core/data-structures/zend_string.rst
+++ b/docs/source/core/data-structures/zend_string.rst
@@ -1,189 +1,126 @@
-#############
- zend_string
-#############
+# 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
+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.
+- 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
+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 `__ 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
+```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 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](https://www.geeksforgeeks.org/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``:
+Here's a basic example of how to use `zend_string`:
-.. code:: c
+```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);
+```
- // 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 (`persistent == false`) that is emptied after each request.
-``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 `__ (``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
+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
-*****
+## API
-The string API is defined in ``Zend/zend_string.h``. It provides a number of functions for creating
+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
+**`zend_string` creation**
+
+| 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. |
+
+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.
+**Accessor macros**
-.. list-table:: Reference counting 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. |
- - - Macro
- - Description
+**Reference counting macros**
- - - ``zend_string_copy(s)``
- - Increases the reference count and returns the same string. The reference count is not
- increased if the string is interned.
+| 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. |
- - - ``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
+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
-******************
+## 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
+`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 `__ where existing interned
+string interning. Essentially, this is just a simple HashTable 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));
+```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));
+// 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);
+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.
@@ -192,5 +129,7 @@ With opcache, this goes one step further by sharing strings across different pro
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.
+`zend_new_interned_string` may not actually return an interned string if opcache is enabled.
Usually you don't have to worry about this.
+
+[^persistent]: `s` = `zend_string`, `l` = `length`, `p` = `persistent`.
diff --git a/docs/source/core/data-structures/zval.rst b/docs/source/core/data-structures/zval.rst
index 512abfdf2195..29b0b08c2586 100644
--- a/docs/source/core/data-structures/zval.rst
+++ b/docs/source/core/data-structures/zval.rst
@@ -1,225 +1,202 @@
-######
- zval
-######
+# 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
+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
+## zval types
+
+```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.
+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
+`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;
+## zend_value
+
+```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``
+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.
+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 `__ value, including
-strings, arrays, objects, resources and references. ``null`` and ``bool`` are missing from
-``zend_value`` because their types are self-contained.
+The top members of `zend_value` mostly mirror the `IS_*` constants, with the exception of
+`counted`. `counted` polymorphically refers to any reference counted 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
-******
+## zval
-Together, the value and the tag make up the ``zval``, along with some other fields. It may look
+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
+```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 `__ flags, and ``v.u.extra`` is pretty much unused.
+into 3 parts. `v.type` stores the actual variable type, `v.type_flags` is used for some reference counting 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
+`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``,
+## 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`.
+
+**`zval` macros**
+
+| 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. |
+
+
+
+## Other zval types
+
+`zval`s are sometimes used internally with types that don't exist in userland.
+
+```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 `__ chapter for more information
-on ASTs. When this flag is set, the ``zval.value.ast`` union member is set accordingly.
+expression rather than the concrete values. Check the parser 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
+`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)``
+`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
+`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.
+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 `__. It is described in more
+`_IS_ERROR` is used as an error value for some object handlers. 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 `__ 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.
+```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 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.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 21e2526f47f6..90859ea9d3b8 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,38 +1,40 @@
-##############
- 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
+# php-src docs
+
+```{toctree}
+---
+caption: Introduction
+hidden: true
+---
+introduction/high-level-overview
+introduction/ides/index
+```
+
+```{toctree}
+---
+caption: Core
+hidden: true
+---
+core/data-structures/index
+```
+
+```{toctree}
+---
+caption: Miscellaneous
+hidden: true
+---
+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
- `__ file for a
- list of technical resources.
+> [!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
@@ -43,21 +45,17 @@ This documentation is not intended to be comprehensive, but is meant to explain
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?
-******************
+## 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 `__ (``#php-internals`` channel)
-- `R11 on StackOverflow `__
+- [Discord](https://phpc.chat) (`#php-internals` channel)
+- [R11 on StackOverflow](https://chat.stackoverflow.com/rooms/11/php)
-***************
- Prerequisites
-***************
+## 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
diff --git a/docs/source/introduction/high-level-overview.rst b/docs/source/introduction/high-level-overview.rst
index 1240bed4c0e6..17c09f072e08 100644
--- a/docs/source/introduction/high-level-overview.rst
+++ b/docs/source/introduction/high-level-overview.rst
@@ -1,6 +1,4 @@
-#####################
- High-level overview
-#####################
+# 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
@@ -9,110 +7,98 @@ prototyping, as it skips a lengthy compilation phase. However, it also poses som
to performance, which is one of the primary reasons interpreters can be complex. php-src borrows
many concepts from other compilers and interpreters.
-**********
- Pipeline
-**********
+## 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.
+- 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
+```haskell
+source_code
+ |> tokenizer -- tokens
+ |> parser -- ast
+ |> compiler -- opcodes
+ |> interpreter
+```
Let's go into each phase in a bit more detail.
-**************
- Tokenization
-**************
+## 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
+```php
+if ($cond) {
+ echo "Cond is true\n";
+}
+```
+
+```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/
+stream of characters. The definition for PHP lives in `Zend/zend_language_scanner.l`. Check the
+[re2c documentation](https://re2c.org/) for details.
-*********
- Parsing
-*********
+## 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
+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" },
- },
- },
- },
- }
+```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.
+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](https://www.gnu.org/software/bison/manual/) 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.
-Parsing is described in more detail in its `dedicated chapter `__.
-
-*************
- Compilation
-*************
+## 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
@@ -130,65 +116,55 @@ in an actual CPU instruction set (e.g. adding two numbers), while others are muc
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
+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)
+```text
+0000 JMPZ CV0($cond) 0002
+0001 ECHO string("Cond is true\n")
+0002 RETURN int(1)
+```
-****************
- Interpretation
-****************
+## Interpretation
-Finally, the opcodes are read and executed by the interpreter. PHPs uses `three-address code`_ for
+Finally, the opcodes are read and executed by the interpreter. PHPs uses [three-address code](https://en.wikipedia.org/wiki/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
+operands. Most modern CPUs also use this format. Both result and operands in PHP are {doc}`zvals <../core/data-structures/zval>`.
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``.
+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.
+- 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.
+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 `__ chapter.
+is quite complex, and will be discussed separately in the virtual machine chapter.
-*********
- Opcache
-*********
+## 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.
+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``.
+possible to improve performance during execution. The optimizer lives in `Zend/Optimizer`.
-JIT
-===
+### 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``.
+barely scratch the surface of how it works. It lives in `ext/opcache/jit`.
diff --git a/docs/source/introduction/ides/index.rst b/docs/source/introduction/ides/index.rst
index e12e0d5c7ccd..047e320a43b8 100644
--- a/docs/source/introduction/ides/index.rst
+++ b/docs/source/introduction/ides/index.rst
@@ -1,10 +1,10 @@
-######
- IDEs
-######
+# IDEs
-.. toctree::
- :hidden:
-
- visual-studio-code
+```{toctree}
+---
+hidden: true
+---
+visual-studio-code
+```
Here you can find instructions on how to effectively use common IDEs for php-src development.
diff --git a/docs/source/introduction/ides/visual-studio-code.rst b/docs/source/introduction/ides/visual-studio-code.rst
index 3493c00e83aa..963b25f4555f 100644
--- a/docs/source/introduction/ides/visual-studio-code.rst
+++ b/docs/source/introduction/ides/visual-studio-code.rst
@@ -1,132 +1,114 @@
-####################
- Visual Studio Code
-####################
+# Visual Studio Code
-.. note::
-
- These instructions have been tested on Linux. macOS should mostly work the same. For Windows,
- ymmv.
+> [!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 `__ for
+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.
+> [!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
-*****************
+## 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``
+The [C/C++ extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) 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
+To generate the `compile_commands.json` file, you can use the [compiledb](https://github.com/nickdiego/compiledb) tool. Install it using
+`pip`, and then prefix your `make` command accordingly:
-.. code:: bash
+```bash
+# Install compiledb
+pip install compiledb
+# Compile php-src and generate compile_commands.json
+compiledb make -j8
+```
- # 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:
-To tell the C/C++ extension to use the ``compile_commands.json`` file, add the following to your
-``settings.json`` file:
+```json
+{
+ "C_Cpp.default.compileCommands": "${workspaceFolder}/compile_commands.json"
+}
+```
-.. code:: json
+## clangd
- {
- "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
+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
-`__, 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``
+`settings.json` file:
+
+```json
+{
+ "C_Cpp.intelliSenseEngine": "disabled"
+}
+```
+
+Follow the [official installation instructions for clangd](https://clangd.llvm.org/installation.html), and then install the [clangd extension](https://marketplace.visualstudio.com/items?itemName=llvm-vs-code-extensions.vscode-clangd).
+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:
+
+```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
+```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.
+
diff --git a/docs/source/miscellaneous/running-tests.rst b/docs/source/miscellaneous/running-tests.rst
index bb2c56dcecd2..f9ca124bf81f 100644
--- a/docs/source/miscellaneous/running-tests.rst
+++ b/docs/source/miscellaneous/running-tests.rst
@@ -1,164 +1,151 @@
-###############
- Running Tests
-###############
+# 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
+`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
+```shell
+sapi/cli/php [-c /path/to/php.ini] run-tests.php [ext/foo/tests/GLOB]
+```
- sapi/cli/php [-c /path/to/php.ini] run-tests.php [ext/foo/tests/GLOB]
+## Which php executable does make test use?
-******************************************
- 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
+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``).
+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
+`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.
+> [!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?
-************************
+## 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,
+`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?
-**********************************
+## Which test scripts are executed?
-The ``run-tests.php`` (``make test``), without any arguments executes all test scripts by extracting
+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.
+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
-*********************
+```shell
+./sapi/cli/php run-tests.php ext/mbstring/*
+./sapi/cli/php run-tests.php ext/mbstring/020.phpt
+```
-The ``run-tests.php`` test runner has many options. You can see these options by using the ``-h``
-option with ``run-tests.php``.
+## Test Runner Options
-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:
+The `run-tests.php` test runner has many options. You can see these options by using the `-h`
+option with `run-tests.php`.
-.. code:: shell
+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:
- php run-tests.php -j24
- # or
- TEST_PHP_ARGS="-j24" make test
+```shell
+php run-tests.php -j24
+# or
+TEST_PHP_ARGS="-j24" make test
+```
-Running Tests in Parallel
-=========================
+### Running Tests in Parallel
-The test runner can run tests in parallel, by using the ``-j`` option:
+The test runner can run tests in parallel, by using the `-j` option:
-.. code:: shell
+```shell
+php run-tests.php -j24 ext/date/*.phpt
+```
- php run-tests.php -j24 ext/date/*.phpt
+## Test results
-**************
- Test results
-**************
-
-Test results are printed to standard output. If there is a failed test, the ``run-tests.php`` script
+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:
+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)
+- `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
+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``.
+`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]
+> 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.
+> [!NOTE]
+> You can set environment variable `TEST_PHP_DETAILED` to enable detailed test information.
-*******************
- Automated testing
-*******************
+## 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.
+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.
+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
+```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.
@@ -166,20 +153,20 @@ 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:
+```shell
+make test TESTS=ext/standard.
+```
-.. code:: shell
+Where `TESTS=` points to a directory containing .phpt files or a single .phpt file like:
- make test TESTS=tests/basic/001.phpt.
+```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``.
+(`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.
+*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.
diff --git a/docs/source/miscellaneous/stubs.rst b/docs/source/miscellaneous/stubs.rst
index 395034afe8d5..ff9b4e633a56 100644
--- a/docs/source/miscellaneous/stubs.rst
+++ b/docs/source/miscellaneous/stubs.rst
@@ -1,95 +1,87 @@
-#######
- Stubs
-#######
+# 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
+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);
+ 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;
- }
+ return class_entry;
+}
+```
-********************************************
- Generating Global Constants and Attributes
-********************************************
+## 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.
+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.
+include a `register_{$stub_file_name}_symbols()` file.
Given the following file:
-.. code:: php
+```php
+// example.stub.php
+= 80000)
- # include "example_arginfo.h"
- #else
- # include "example_legacy_arginfo.h"
- #endif
+```
+#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
+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,
+PHP Version IDs are as follows: `80000` for PHP 8.0, `80100` for PHP PHP 8.1, `80200` for PHP
+8.2, `80300` for PHP 8.3, and `80400` for PHP 8.4,
In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a
previous example:
-.. code:: php
-
- = ...)`` conditions in the generated arginfo file:
-
-.. code:: c
-
- ...
-
- #if (PHP_VERSION_ID >= 80100)
- static zend_class_entry *register_class_Number(void)
- {
- zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods);
-
- zend_enum_add_case_cstr(class_entry, "One", NULL);
-
- return class_entry;
- }
- #endif
-
- static zend_class_entry *register_class_Elephant(void)
- {
- zend_class_entry ce, *class_entry;
-
- INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
- class_entry = zend_register_internal_class_ex(&ce, NULL);
- #if (PHP_VERSION_ID >= 80100)
- class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
- #elif (PHP_VERSION_ID >= 80000)
- class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES;
- #endif
-
- zval const_PI_value;
- ZVAL_DOUBLE(&const_PI_value, M_PI);
- zend_string *const_PI_name = zend_string_init_interned("PI", sizeof("PI") - 1, 1);
- #if (PHP_VERSION_ID >= 80300)
- zend_declare_typed_class_constant(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE));
- #else
- zend_declare_class_constant_ex(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL);
- #endif
- zend_string_release(const_PI_name);
-
- zval property_name_default_value;
- ZVAL_UNDEF(&property_name_default_value);
- zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);
- #if (PHP_VERSION_ID >= 80100)
- zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
- #elif (PHP_VERSION_ID >= 80000)
- zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING));
- #endif
- zend_string_release(property_name_name);
-
- return class_entry;
- }
-
-The preprocessor conditions are necessary because enumerations (``enum``), ``readonly`` properties,
-and the ``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0.
-
-The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not
-added for``Elephpant::$name`` for PHP versions before 8.1.
+ public const float PI = UNKNOWN;
+
+ public readonly string $name;
+}
+```
+
+Then notice the `#if (PHP_VERSION_ID >= ...)` conditions in the generated arginfo file:
+
+```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
-******************************************
+## Generating Information for the Optimizer
-A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This
+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.
+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.
+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
+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``.
+default, scalar return types have a `refcount` of `0`, while non-scalar values are `N`. If a
+function can only return newly created non-scalar values, its `refcount` can be set to `1`.
An example from the built-in functions:
-.. code:: php
-
- /**
- * @return array
- * @refcount 1
- */
- function get_declared_classes(): array {}
+```php
+/**
+ * @return array
+ * @refcount 1
+ */
+function get_declared_classes(): array {}
+```
Functions can be evaluated at compile-time if their arguments are known in compile-time, and their
behavior is free from side-effects and is not affected by the global state.
The list of such functions in the optimizer was maintained manually until PHP 8.2.
-Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms
+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.
+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
+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:
+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.
-.. code:: php
+The stub of `in_array()` is a good example:
- /**
- * @compile-time-eval
- * @frameless-function {"arity": 2}
- * @frameless-function {"arity": 3}
- */
- function in_array(mixed $needle, array $haystack, bool $strict = false): bool {}
+```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);
- }
+```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;
+/* 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);
+ Z_FLF_PARAM_ZVAL(1, value);
+ Z_FLF_PARAM_ARRAY(2, array);
- _php_search_array(return_value, value, array, false, 0);
+ _php_search_array(return_value, value, array, false, 0);
- flf_clean:;
- }
+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;
+/* 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);
+ 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);
+ _php_search_array(return_value, value, array, strict, 0);
- flf_clean:;
- }
+flf_clean:;
+}
+```
-**************************************
- Generating Signatures for the Manual
-**************************************
+## 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
+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``
+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.
+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.
+updated by providing the `--replace-methodsynopses` option.
-Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or
+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.
+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
+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
+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.
+`ext/zend_test`), or by some other reason documentation is not possible.
-************
- Validation
-************
+## Validation
-You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method
+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
+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
+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 {}
+```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/``.
+`--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
+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
+- 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()
+```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
-**********************
+## Parameter Statistics
-The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the
+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
diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.rst
index 40e273b6fd71..cee965a6ec9a 100644
--- a/docs/source/miscellaneous/writing-tests.rst
+++ b/docs/source/miscellaneous/writing-tests.rst
@@ -1,83 +1,71 @@
-###############
- Writing Tests
-###############
+# Writing Tests
-******************
- phpt Test Basics
-******************
+## 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!
+scared about writing to ([php-qa@lists.php.net](mailto: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.
+> 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.
+> All that is really needed to write a phpt test is a basic understanding of the PHP language, a
+> text editor, and a way to get the results of your code. That is it. So if you have been writing
+> and running PHP scripts already — you have everything you need.
What do you write phpt tests on?
- Basically you can write a phpt test on one of the various php functions available. You can write
- a test on a basic language function (a string function or an array function) , or a function
- provided by one of PHP's numerous extensions (a mysql function or a image function or a mcrypt
- function).
-
- You can find out what functions already have phpt tests by looking in the `html version
- `_ of the git repository (``ext/standard/tests/`` is a good place
- to start looking — though not all the tests currently written are in there).
-
- If you want more guidance than that you can always ask the PHP Quality Assurance Team on their
- mailing list (php-qa@lists.php.net) where they would like you to direct your attentions.
+> 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](mailto: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.
+> 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.
+> 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
-********************
+## Writing phpt Tests
-Naming Conventions
-==================
+### Naming Conventions
Phpt tests follow a very strict naming convention. This is done to easily identify what each phpt
test is for. Tests should be named according to the following list:
-Tests for bugs
- bug.phpt (bug17123.phpt)
-
-Tests for a function's basic behaviour
- _basic.phpt (dba_open_basic.phpt)
-
-Tests for a function's error behaviour
- _error.phpt (dba_open_error.phpt)
-
-Tests for variations in a function's behaviour
- _variation.phpt (dba_open_variation.phpt)
-
-General tests for extensions
- .phpt (dba_003.phpt)
-
-The convention of using _basic, _error and _variation was introduced when we found that writing a
+- Tests for bugs
+ - `bug.phpt` (`bug17123.phpt`)
+- Tests for a function's basic behaviour
+ - `_basic.phpt` (`dba_open_basic.phpt`)
+- Tests for a function's error behaviour
+ - `_error.phpt` (`dba_open_error.phpt`)
+- Tests for variations in a function's behaviour
+ - `_variation.phpt` (`dba_open_variation.phpt`)
+- General tests for extensions
+ - `.phpt` (`dba_003.phpt`)
+
+The convention of using \_basic, \_error and \_variation was introduced when we found that writing a
single test case for each function resulted in unacceptably large test cases. It's quite hard to
debug problems when the test case generates 100s of lines of output.
@@ -92,8 +80,7 @@ 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?
-=======================
+### 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
@@ -103,33 +90,31 @@ case you can help a lot by commenting the output. You may find plenty of much lo
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
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
-============
+### Basic Format
A test must contain the sections TEST, FILE and either EXPECT or EXPECTF at a minimum. The example
below illustrates a minimal test.
*ext/standard/tests/strings/strtr.phpt*
-.. code:: php
-
- --TEST--
- strtr() function — basic test for strtr()
- --FILE--
- "hi", "hi"=>"hello", "a"=>"A", "world"=>"planet");
- var_dump(strtr("# hi all, I said hello world! #", $trans));
- ?>
- --EXPECT--
- string(32) "# hello All, I sAid hi planet! #"
+```php
+--TEST--
+strtr() function — basic test for strtr()
+--FILE--
+"hi", "hi"=>"hello", "a"=>"A", "world"=>"planet");
+var_dump(strtr("# hi all, I said hello world! #", $trans));
+?>
+--EXPECT--
+string(32) "# hello All, I sAid hi planet! #"
+```
As you can see the file is divided into several sections. The TEST section holds a one line title of
the phpt test, this should be a simple description and shouldn't ever exceed one line, if you need
@@ -138,14 +123,12 @@ when generating a .php file. The FILE section is used as the body of the .php fi
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
-======================
+### 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
-=======================
+### 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
@@ -154,66 +137,62 @@ 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.
+> A diff file between the expected output (be it in EXPECT, EXPECTF or another option) and the
+> actual output.
foo.exp
- The expected output.
+> The expected output.
foo.log
- A log containing expected output, actual output and results. Most likely very similar to info in
- the other files.
+> 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.
+> The actual output of your .phpt test part.
foo.php
- The php code that was executed for this test.
+> 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.
+> An executable file that executes the test for you as it was executed during failure.
-Testing your test cases
-=======================
+### 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
+worry, if you have karma, commit the test but watch [php-qa@lists.php.net](mailto: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>`_.
+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.
+`--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
-`_.
+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?
-===================================================================
+### 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 `_ 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
+to [php-qa@lists.php.net](mailto: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](mailto: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 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
@@ -230,229 +209,221 @@ 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');
+```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
+```
+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
+--SKIPIF--
+
+```
Tests for 64bit platforms can use:
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
To run a test only on Windows:
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
To run a test only on Linux:
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
To skip a test on Mac OS X Darwin:
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
-**********
- Examples
-**********
+## Examples
-EXPECTF
-=======
+### 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
+`/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
-``'/'``).
+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.
+See also [EXPECTF](#expectf) details.
*/ext/standard/tests/strings/str_shuffle.phpt*
-.. code:: php
-
- --TEST--
- Testing str_shuffle.
- --FILE--
-
- --EXPECTF--
- string(3) "%s"
- string(3) "123"
-
-EXPECTREGEX
-===========
-
-``/ext/standard/tests/strings/strings001.phpt`` is a good example for using ``EXPECTREGEX`` instead
-of ``EXPECT``. This test also shows that in ``EXPECTREGEX`` some characters need to be escaped since
+```php
+--TEST--
+Testing str_shuffle.
+--FILE--
+
+--EXPECTF--
+string(3) "%s"
+string(3) "123"
+```
+
+### EXPECTREGEX
+
+`/ext/standard/tests/strings/strings001.phpt` is a good example for using `EXPECTREGEX` instead
+of `EXPECT`. This test also shows that in `EXPECTREGEX` some characters need to be escaped since
otherwise they would be interpreted as a regular expression.
*/ext/standard/tests/strings/strings001.phpt*
-.. code:: php
-
- --TEST--
- Test whether strstr() and strrchr() are binary safe.
- --FILE--
-
- --EXPECTREGEX--
- string\(18\) \"nica\x00turska panica\"
- string\(19\) \" nica\x00turska panica\"
-
-EXTENSIONS
-==========
+```php
+--TEST--
+Test whether strstr() and strrchr() are binary safe.
+--FILE--
+
+--EXPECTREGEX--
+string\(18\) \"nica\x00turska panica\"
+string\(19\) \" nica\x00turska panica\"
+```
+
+### EXTENSIONS
Some tests depend on PHP extensions that may be unavailable. These extensions should be listed in
-the ``EXTENSIONS`` section. If an extension is missing, PHP will try to find it in a shared module
+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
+```php
+--TEST--
+Check for libsodium scalarmult
+--EXTENSIONS--
+sodium
+--FILE--
+
- --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
+```php
+--TEST--
+Check for libsodium argon2i
+--EXTENSIONS--
+sodium
+--SKIPIF--
+
+--FILE--
+[snip]
+```
+
+Test script and `SKIPIF` code should be directly written into `\*.phpt`. However, it is
+recommended to use include files when more test scripts depend on the same `SKIPIF` code or when
certain test files need the same values for some input.
-Note: no file used by any test should have one of the following extensions: ".php", ".log", ".mem",
-".exp", ".out" or ".diff". When you use an include file for the ``SKIPIF`` section it should be
-named "skipif.inc" and an include file used in the ``FILE`` section of many tests should be named
-"test.inc".
+> [!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
-*************
+## Final Notes
-Cleaning up after running a test
-================================
+### 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.
+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--``
+The PHP code in the `--CLEAN--` section is executed separately from the code in the `--FILE--`
section. For example, this code:
-.. code:: php
-
- --TEST--
- Will fail to clean up
- --FILE--
-
- --CLEAN--
-
- --EXPECT--
-
-will not remove the temporary file because the variable $temp_filename is not defined in the
-``--CLEAN--`` section.
+```php
+--TEST--
+Will fail to clean up
+--FILE--
+
+--CLEAN--
+
+--EXPECT--
+```
+
+will not remove the temporary file because the variable \$temp_filename is not defined in the
+`--CLEAN--` section.
Here is a better way to write the code:
-.. code:: php
-
- --TEST--
- This will remove temporary files
- --FILE--
-
- --CLEAN--
-
- --EXPECT--
-
-Note the use of the ``__DIR__`` construct which will ensure that the temporary file is created in
+```php
+--TEST--
+This will remove temporary files
+--FILE--
+
+--CLEAN--
+
+--EXPECT--
+```
+
+Note the use of the `__DIR__` construct which will ensure that the temporary file is created in
the same directory as the phpt test script.
When creating temporary files it is a good idea to use an extension that indicates the use of the
@@ -462,23 +433,22 @@ related to the test case. For example, mytest.phpt should create mytest.tmp (or
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
+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
+`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
-=================
+### Redirecting tests
-Using ``--REDIRECTTEST--`` it is possible to redirect from one test to a bunch of other tests. That
+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.
@@ -488,71 +458,65 @@ directory where the test scripts are located and should be relative. Optionally
'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.
+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
-========================
+### 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.
+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
+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 ``\``.
+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
-========
+### 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
+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 ````. 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===``.
+block. The workaround is to use terminate the `--FILE--` section with the two lines `===DONE===`
+and ``. When doing so `run-tests.php` does not execute the line containing the
+exit call as that would suppress leak messages. Actually `run-tests.php` ignores any part after a
+line consisting only of `===DONE===`.
Here is an example:
-.. code:: php
-
- --TEST--
- Test hypot() — dealing with mixed number/character input
- --INI--
- precision=14
- --FILE--
-
- ===DONE===
-
- --EXPECTF--
- 23abc :-33 float(40.224370722238)
- ===DONE===
-
-If executed as PHP script the output will stop after the code on the ``--FILE--`` section has been
+```php
+--TEST--
+Test hypot() — dealing with mixed number/character input
+--INI--
+precision=14
+--FILE--
+
+===DONE===
+
+--EXPECTF--
+23abc :-33 float(40.224370722238)
+===DONE===
+```
+
+If executed as PHP script the output will stop after the code on the `--FILE--` section has been
run.
-***********
- Reference
-***********
+## Reference
-PHPT Sections
-=============
+### PHPT Sections
-``--TEST--``
-------------
+#### `--TEST--`
**Description:** Title of test as a single line short description.
@@ -562,15 +526,14 @@ PHPT Sections
Example 1 (snippet):
-.. code:: text
-
- --TEST--
- Test filter_input() with GET and POST data.
+```text
+--TEST--
+Test filter_input() with GET and POST data.
+```
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
-``--DESCRIPTION--``
--------------------
+#### `--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
@@ -582,15 +545,14 @@ information, this section is completely ignored by the test binary.
Example 1 (snippet):
-.. code:: text
+```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.
+```
- --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`
-Example 1 (full): :ref:`sample001.phpt`
-
-``--CREDITS--``
----------------
+#### `--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
@@ -605,32 +567,31 @@ of a bug or a contributor who is not credited via `Co-authored-by` tag.
Example 1 (snippet):
-.. code:: text
-
- --CREDITS--
- Felipe Pena
+```text
+--CREDITS--
+Felipe Pena
+```
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: text
+```text
+--CREDITS--
+Zoe Slattery zoe@php.net
+# TestFest Munich 2009-05-19
+```
- --CREDITS--
- Zoe Slattery zoe@php.net
- # TestFest Munich 2009-05-19
+Example 2 (full): {ref}`sample002.phpt`
-Example 2 (full): :ref:`sample002.phpt`
-
-``--SKIPIF--``
---------------
+#### `--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.
+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
+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.
@@ -642,52 +603,51 @@ of PHP 7.2.0. The "flaky" convention is supported as of PHP 8.2.25 and PHP 8.3.1
Example 1 (snippet):
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: php
+```php
+--SKIPIF--
+
+```
- --SKIPIF--
-
-
-Example 2 (full): :ref:`sample003.phpt`
+Example 2 (full): {ref}`sample003.phpt`
Example 3 (snippet):
-.. code:: php
-
- --SKIPIF--
-
+```php
+--SKIPIF--
+
+```
-Example 3 (full): :ref:`xfailif.phpt`
+Example 3 (full): {ref}`xfailif.phpt`
Example 4 (snippet):
-.. code:: php
+```php
+--SKIPIF--
+string
&d=12345.7
+```
- --POST--
- c=
string
&d=12345.7
-
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: xml
-
- --POST--
-
-
-
-
-
+```xml
+--POST--
+
+
+
+
+
+```
-Example 2 (full): :ref:`sample005.phpt`
+Example 2 (full): {ref}`sample005.phpt`
-``--POST_RAW--``
-----------------
+#### `--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
@@ -829,31 +784,30 @@ within the section. This section forces the use of the CGI binary instead of the
Requirements: PHP CGI binary.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
**Format:** Follows the HTTP post data format.
Example 1 (snippet):
-.. code:: text
+```text
+--POST_RAW--
+Content-type: multipart/form-data, boundary=AaB03x
- --POST_RAW--
- Content-type: multipart/form-data, boundary=AaB03x
+--AaB03x content-disposition: form-data; name="field1"
- --AaB03x content-disposition: form-data; name="field1"
+Joe Blow
+--AaB03x
+content-disposition: form-data; name="pics"; filename="file1.txt"
+Content-Type: text/plain
- Joe Blow
- --AaB03x
- content-disposition: form-data; name="pics"; filename="file1.txt"
- Content-Type: text/plain
+abcdef123456789
+--AaB03x--
+```
- abcdef123456789
- --AaB03x--
+Example 1 (full): {ref}`sample006.phpt`
-Example 1 (full): :ref:`sample006.phpt`
-
-``--PUT--``
------------
+#### `--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.
@@ -862,51 +816,49 @@ section forces the use of the CGI binary instead of the usual CLI one.
Requirements: PHP CGI binary.
-**Test Script Support:** ``run-tests.php``
+**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
+```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}
+{"name":"default output handler","type":0,"flags":112,"level":0,"chunk_size":0,"buffer_size":16384,"buffer_used":3}
+```
-``--GZIP_POST--``
------------------
+#### `--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``
+**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--
-
-
-
-
-
+```xml
+--GZIP_POST--
+
+
+
+
+
+```
-Example 1 (full): :ref:`sample005.phpt`
+Example 1 (full): {ref}`sample005.phpt`
-``--DEFLATE_POST--``
---------------------
+#### `--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.
@@ -915,31 +867,30 @@ forces the use of the CGI binary instead of the usual CLI one.
Requirements:
-**Test Script Support:** ``run-tests.php``
+**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--
-
-
-
-
-
-
-
-Example 1 (full): :ref:`sample007.phpt`
-
-``--GET--``
------------
+```xml
+--DEFLATE_POST--
+
+
+
+
+
+
+```
+
+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.
@@ -952,24 +903,23 @@ Requirements: PHP CGI binary.
Example 1 (snippet):
-.. code:: text
-
- --GET--
- a=test&b=http://example.com
+```text
+--GET--
+a=test&b=http://example.com
+```
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: text
+```text
+--GET--
+ar[elm1]=1234&ar[elm2]=0660&a=0234
+```
- --GET--
- ar[elm1]=1234&ar[elm2]=0660&a=0234
+Example 2 (full): {ref}`sample008.phpt`
-Example 2 (full): :ref:`sample008.phpt`
-
-``--COOKIE--``
---------------
+#### `--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.
@@ -978,42 +928,40 @@ binary instead of the usual CLI one.
Requirements: PHP CGI binary.
-**Test Script Support:** ``run-tests.php``
+**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
+```
+--COOKIE--
+hello=World;goodbye=MrChips
+```
-Example 1 (full): :ref:`sample002.phpt`
+Example 1 (full): {ref}`sample002.phpt`
-``--STDIN--``
--------------
+#### `--STDIN--`
**Description:** Data to be fed to the test script's standard input.
**Required:** No.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
**Format:** Any text within this section is passed as STDIN to PHP.
Example 1 (snippet):
-.. code:: text
+```text
+--STDIN--
+fooBar
+use this to input some thing to the php script
+```
- --STDIN--
- fooBar
- use this to input some thing to the php script
+Example 1 (full): {ref}`sample009.phpt`
-Example 1 (full): :ref:`sample009.phpt`
-
-``--INI--``
------------
+#### `--INI--`
**Description:** To be used if you need a specific php.ini setting for the test.
@@ -1024,33 +972,32 @@ 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.
+- `{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
+```text
+--INI--
+precision=14
+```
-Example 1 (full): :ref:`sample001.phpt`
+Example 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: text
+```text
+--INI--
+session.use_cookies=0
+session.cache_limiter=
+register_globals=1
+session.serialize_handler=php
+session.save_handler=files
+```
- --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`
-Example 2 (full): :ref:`sample003.phpt`
-
-``--ARGS--``
-------------
+#### `--ARGS--`
**Description:** A single line defining the arguments passed to PHP.
@@ -1060,17 +1007,16 @@ Example 2 (full): :ref:`sample003.phpt`
Example 1 (snippet):
-.. code:: text
-
- --ARGS--
- --arg value --arg=value -avalue -a=value -a value
+```text
+--ARGS--
+--arg value --arg=value -avalue -a=value -a value
+```
-Example 1 (full): :ref:`sample010.phpt`
+Example 1 (full): {ref}`sample010.phpt`
-``--ENV--``
------------
+#### `--ENV--`
-**Description:** Configures environment variables such as those found in the ``$_SERVER`` global
+**Description:** Configures environment variables such as those found in the `$_SERVER` global
array.
**Required:** No.
@@ -1079,17 +1025,16 @@ array.
Example 1 (snippet):
-.. code:: text
+```text
+--ENV--
+SCRIPT_NAME=/frontcontroller10.php
+REQUEST_URI=/frontcontroller10.php/hi
+PATH_INFO=/hi
+```
- --ENV--
- SCRIPT_NAME=/frontcontroller10.php
- REQUEST_URI=/frontcontroller10.php/hi
- PATH_INFO=/hi
+Example 1 (full): {ref}`sample018.phpt`
-Example 1 (full): :ref:`sample018.phpt`
-
-``--PHPDBG--``
---------------
+#### `--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.
@@ -1100,121 +1045,117 @@ to them as it would be run in the phpdbg prompt.
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--``
-------------
+```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.
+**Required:** One of the `FILE` type sections is required.
**Format:** PHP source code enclosed by PHP tags.
Example 1 (snippet):
-.. code:: php
-
- --FILE--
-
-
-Example 1 (full): :ref:`sample001.phpt`
-
-``--FILEEOF--``
----------------
-
-**Description:** An alternative to ``--FILE--`` where any trailing line breaks (\n || \r || \r\n
+```php
+--FILE--
+
+```
+
+Example 1 (full): {ref}`sample001.phpt`
+
+#### `--FILEEOF--`
+
+**Description:** An alternative to `--FILE--` where any trailing line breaks (n || r || rn
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.
+**Required:** One of the `FILE` type sections is required.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
**Format:** PHP source code enclosed by PHP tags.
Example 1 (snippet):
-.. code:: php
-
- --FILEEOF--
- array(
+ 'PDOTEST_DSN' => 'sqlite2::memory:'
+ ),
+ 'TESTS' => 'ext/pdo/tests'
+ );
+```
- --REDIRECTTEST--
- return array(
- 'ENV' => array(
- 'PDOTEST_DSN' => 'sqlite2::memory:'
- ),
- 'TESTS' => 'ext/pdo/tests'
- );
+Example 1 (full): {ref}`sample013.phpt`
-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.
+> [!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
+```php
+--REDIRECTTEST--
+# magic auto-configuration
- $config = array(
- 'TESTS' => 'ext/pdo/tests'
- );
+$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'] = '';
- }
+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;
+return $config;
+```
-Example 2 (full): :ref:`sample014.phpt`
+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.
+> [!NOTE]
+> The destination tests for this example are not included. See the PDO extension tests for
+> reference to live tests using this section.
-``--CGI--``
------------
+#### `--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.
+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.
+**Format:** No value, just the `--CGI--` statement.
Example 1 (snippet):
-.. code:: text
+```text
+--CGI--
+```
- --CGI--
+Example 1 (full): {ref}`sample016.phpt`
-Example 1 (full): :ref:`sample016.phpt`
-
-``--XFAIL--``
--------------
+#### `--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
@@ -1295,26 +1238,25 @@ include tests that are written before the functionality they are testing is impl
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
+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``
+**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
+```text
+--XFAIL--
+This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64
+```
-Example 1 (full): :ref:`sample017.phpt`
+Example 1 (full): {ref}`sample017.phpt`
-``--FLAKY--``
--------------
+#### `--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
@@ -1322,26 +1264,25 @@ should include a brief description of why the test is flaky. Reasons for this in
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
+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``
+**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
+```
+--FLAKY--
+This test frequently fails in CI
+```
Example 1 (full): flaky.phpt
-``--EXPECTHEADERS--``
----------------------
+#### `--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
@@ -1357,78 +1298,76 @@ Example 1 (snippet):
Example 1 (snippet):
-.. code:: text
-
- --EXPECTHEADERS--
- Content-type: text/html; charset=UTF-8
- Status: 403 Access Denied
+```text
+--EXPECTHEADERS--
+Content-type: text/html; charset=UTF-8
+Status: 403 Access Denied
+```
-Example 1 (full): :ref:`sample018.phpt`
+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.
+> [!NOTE]
+> The destination tests for this example are not included. See the phar extension tests for
+> reference to live tests using this section.
-``--EXPECT--``
---------------
+#### `--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.
+**Required:** One of the `EXPECT` type sections is required.
**Format:** Plain text. Multiple lines of text are allowed.
Example 1 (snippet):
-.. code:: text
+```text
+--EXPECT--
+array(2) {
+ ["hello"]=>
+ string(5) "World"
+ ["goodbye"]=>
+ string(7) "MrChips"
+}
+```
- --EXPECT--
- array(2) {
- ["hello"]=>
- string(5) "World"
- ["goodbye"]=>
- string(7) "MrChips"
- }
+Example 1 (full): {ref}`sample002.phpt`
-Example 1 (full): :ref:`sample002.phpt`
+#### `--EXPECT_EXTERNAL--`
-``--EXPECT_EXTERNAL--``
------------------------
-
-**Description:** Similar to ``--EXPECT--`` section, but just stating a filename where to load the
+**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.
+**Required:** One of the `EXPECT` type sections is required.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
Example 1 (snippet):
-.. code:: text
-
- --EXPECT_EXTERNAL--
- test001.expected.txt
+```text
+--EXPECT_EXTERNAL--
+test001.expected.txt
+```
*test001.expected.txt*
-.. code:: php
+```php
+array(2) {
+ ["hello"]=>
+ string(5) "World"
+ ["goodbye"]=>
+ string(7) "MrChips"
+}
+```
- array(2) {
- ["hello"]=>
- string(5) "World"
- ["goodbye"]=>
- string(7) "MrChips"
- }
+#### `--EXPECTF--`
-``--EXPECTF--``
----------------
-
-**Description:** An alternative of ``--EXPECT--``. Where it differs from ``--EXPECT--`` is that it
+**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.
+**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
@@ -1436,1439 +1375,1399 @@ 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.
+> - `%e`: Represents a directory separator, for example / on Linux.
+> - `%s`: One or more of anything (character or white space) except the end of line character.
+> - `%S`: Zero or more of anything (character or white space) except the end of line character.
+> - `%a`: One or more of anything (character or white space) including the end of line
+> character.
+> - `%A`: Zero or more of anything (character or white space) including the end of line
+> character.
+> - `%w`: Zero or more white space characters.
+> - `%i`: A signed integer value, for example +3142, -3142, 3142.
+> - `%d`: An unsigned integer value, for example 123456.
+> - `%x`: One or more hexadecimal character. That is, characters in the range 0-9, a-f, A-F.
+> - `%f`: A floating point number, for example: 3.142, -3.142, 3.142E-10, 3.142e+10.
+> - `%c`: A single character of any sort (.).
+> - `%r...%r`: Any string (...) enclosed between two `%r` will be treated as a regular
+> expression.
Example 1 (snippet):
-.. code:: text
-
- --EXPECTF--
- string(4) "test"
- string(18) "http://example.com"
- string(27) "<b>test</b>"
+```text
+--EXPECTF--
+string(4) "test"
+string(18) "http://example.com"
+string(27) "<b>test</b>"
- Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
- bool(false)
- string(6) "string"
- float(12345.7)
- string(29) "<p>string</p>"
- bool(false)
+Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
+bool(false)
+string(6) "string"
+float(12345.7)
+string(29) "<p>string</p>"
+bool(false)
- Warning: filter_var() expects parameter 2 to be long, string given in %s011.php on line %d
- NULL
+Warning: filter_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_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
- Warning: filter_var() expects at most 3 parameters, 5 given in %s011.php on line %d
- NULL
- Done
+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 1 (full): {ref}`sample001.phpt`
Example 2 (snippet):
-.. code:: text
+```text
+--EXPECTF--
+Warning: bzopen() expects exactly 2 parameters, 0 given in %s on line %d NULL
- --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(): '' 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(): 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(): '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(): '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
+```
- 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 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
+```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.
+**Required:** One of the `EXPECT` type sections is required.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
-``--EXPECTREGEX--``
--------------------
+#### `--EXPECTREGEX--`
-**Description:** An alternative of ``--EXPECT--``. This form allows the tester to specify the result
+**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.
+**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`
+```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`
+```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\")
+```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`
+Example 3 (full): {ref}`sample023.phpt`
-``--EXPECTREGEX_EXTERNAL--``
-----------------------------
+#### `--EXPECTREGEX_EXTERNAL--`
-**Description:** Similar to ``--EXPECTREGEX--`` section, but like the ``--EXPECT_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.
+**Required:** One of the `EXPECT` type sections is required.
-**Test Script Support:** ``run-tests.php``
+**Test Script Support:** `run-tests.php`
-``--CLEAN--``
--------------
+#### `--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.
+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.
+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.
+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``
+**Test Script Support:** `run-tests.php`
**Format:** PHP source code enclosed by PHP tags.
Example 1 (snippet):
-.. code:: php
+```php
+--CLEAN--
+
+```
- --CLEAN--
-
-
-Example 1 (full): :ref:`sample024.phpt`
+Example 1 (full): {ref}`sample024.phpt`
Example 2 (snippet):
-.. code:: php
-
- --CLEAN--
-
+```php
+--CLEAN--
+
+```
-Example 2 (full): :ref:`sample025.phpt`
+Example 2 (full): {ref}`sample025.phpt`
Example 3 (snippet):
-.. code:: php
-
- --CLEAN--
-
-
-Example 3 (full): :ref:`sample022.phpt`
-
-Samples
-=======
-
-capture_stdio_1.phpt
---------------------
-
-.. code:: php
-
- --TEST--
- Test covering the I/O stdin and stdout streams.
- --DESCRIPTION--
- This tests checks if the output of stdin and stdout I/O streams match the
- expected content.
- --CAPTURE_STDIO--
- STDIN STDERR
- --FILE--
-
- --EXPECT--
- This is error sent to the stderr I/O stream
-
-capture_stdio_2.phpt
---------------------
-
-.. code:: php
-
- --TEST--
- Test covering the I/O stdin and stderr streams.
- --DESCRIPTION--
- This tests checks if the output of stdin and stderr I/O streams match the
- expected content.
- --CAPTURE_STDIO--
- STDIN STDOUT
- --FILE--
-
- --EXPECT--
- Hello, world. This is sent to the stdout I/O stream
-
-capture_stdio_3.phpt
---------------------
-
-.. code:: php
-
- --TEST--
- Test covering the all standard I/O streams.
- --DESCRIPTION--
- This tests checks if the output of stdin, stdout and stderr I/O streams match
- the expected content.
- --CAPTURE_STDIO--
- STDIN STDOUT STDERR
- --FILE--
-
- --EXPECT--
- Hello, world. This is sent to the stdout I/O stream
- This is error sent to the stderr I/O stream
-
-clean.php
----------
-
-.. code:: php
-
- Nmsgs; $i++) {
- imap_delete($imap_stream, $i);
- }
-
- $mailboxes = imap_getmailboxes($imap_stream, $server, '*');
-
- foreach($mailboxes as $value) {
- // Only delete mailboxes with our prefix
- if (preg_match('/\{.*?\}INBOX\.(.+)/', $value->name, $match) == 1) {
- if (strlen($match[1]) >= strlen($mailbox_prefix)
- && substr_compare($match[1], $mailbox_prefix, 0, strlen($mailbox_prefix)) == 0) {
- imap_deletemailbox($imap_stream, $value->name);
- }
- }
- }
-
- imap_close($imap_stream, CL_EXPUNGE);
- ?>
-
-conflicts_1.phpt
-----------------
-
-.. code:: php
-
- --TEST--
- Test get_headers() function : test with context
- --CONFLICTS--
- server
- --FILE--
- array(
- 'method' => 'HEAD'
- )
- );
-
- $context = stream_context_create($opts);
- $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1, $context);
- echo $headers["X-Request-Method"]."\n";
-
- stream_context_set_default($opts);
- $headers = get_headers("http://".PHP_CLI_SERVER_ADDRESS, 1);
- echo $headers["X-Request-Method"]."\n";
-
- echo "Done";
- ?>
- --EXPECT--
- HEAD
- HEAD
- Done
-
-extensions.phpt
----------------
-
-.. code:: php
-
- --TEST--
- phpt EXTENSIONS directive with shared extensions
- --DESCRIPTION--
- This test covers the presence of some loaded extensions with a list of additional
- extensions to be loaded when running test.
- --EXTENSIONS--
- curl
- imagick
- tokenizer
- --FILE--
-
- --EXPECT--
- bool(true)
- bool(true)
- bool(true)
-
-file012.phpt
-------------
-
-.. code:: php
-
-
-
-phpdbg_1.phpt
--------------
-
-.. code:: php
-
- --TEST--
- Test deleting breakpoints
- --PHPDBG--
- b 4
- b del 0
- b 5
- r
- b del 1
- r
- y
- q
- --EXPECTF--
- [Successful compilation of %s]
- prompt> [Breakpoint #0 added at %s:4]
- prompt> [Deleted breakpoint #0]
- prompt> [Breakpoint #1 added at %s:5]
- prompt> 12
- [Breakpoint #1 at %s:5, hits: 1]
- >00005: echo $i++;
- 00006: echo $i++;
- 00007:
- prompt> [Deleted breakpoint #1]
- prompt> Do you really want to restart execution? (type y or n): 1234
- [Script ended normally]
- prompt>
- --FILE--
-
- --INI--
- precision=14
- --SKIPIF--
-
- --GET--
- a=test&b=https://example.com
- --POST--
- c=
string
&d=12345.7
- --FILE--
-
- --EXPECTF--
- string(4) "test"
- string(19) "https://example.com"
- string(27) "<b>test</b>"
-
- Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
- bool(false)
- string(6) "string"
- float(12345.7)
- string(29) "<p>string</p>"
- bool(false)
-
- Warning: filter_var() expects parameter 2 to be long, string given in %ssample001.php on line %d
- NULL
-
- Warning: filter_input() expects parameter 3 to be long, string given in %ssample001.php on line %d
- NULL
-
- Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
- NULL
-
- Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
- NULL
- Done
-
-sample002.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Test receipt of cookie data.
- --CREDITS--
- Zoe Slattery zoe@php.net
- # TestFest Munich 2009-05-19
- --COOKIE--
- hello=World;goodbye=MrChips
- --FILE--
-
- --EXPECT--
- array(2) {
- ["hello"]=>
- string(5) "World"
- ["goodbye"]=>
- string(7) "MrChips"
- }
-
-sample003.phpt
---------------
-
-.. code:: php
-
- --TEST--
- session object deserialization
- --SKIPIF--
-
- --INI--
- session.use_cookies=0
- session.cache_limiter=
- register_globals=1
- session.serialize_handler=php
- session.save_handler=files
- --FILE--
- yes++; }
- }
-
- session_id("abtest");
- session_start();
- session_decode('baz|O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}arr|a:1:{i:3;O:3:"foo":2:{s:3:"bar";s:2:"ok";s:3:"yes";i:1;}}');
-
- $baz->method();
- $arr[3]->method();
-
- var_dump($baz);
- var_dump($arr);
- session_destroy();
- --EXPECT--
- object(foo)#1 (2) {
- ["bar"]=>
- string(2) "ok"
- ["yes"]=>
- int(2)
- }
- array(1) {
- [3]=>
- object(foo)#2 (2) {
- ["bar"]=>
- string(2) "ok"
- ["yes"]=>
- int(2)
- }
- }
-
-sample005.phpt
---------------
-
-.. code:: php
-
- --TEST--
- SOAP Server 19: compressed request (gzip)
- --SKIPIF--
-
- --INI--
- precision=14
- --GZIP_POST--
-
-
-
-
-
- --FILE--
- "http://testuri.org"));
- $server->addfunction("test");
- $server->handle();
- echo "ok\n";
- ?>
- --EXPECT--
-
- Hello World
- ok
-
-sample006.phpt
---------------
-
-.. code:: php
-
- --TEST--
- is_uploaded_file() function
- --CREDITS--
- Dave Kelsey
- --SKIPIF--
-
- --POST_RAW--
- Content-type: multipart/form-data, boundary=AaB03x
-
- --AaB03x
- content-disposition: form-data; name="field1"
-
- Joe Blow
- --AaB03x
- content-disposition: form-data; name="pics"; filename="file1.txt"
- Content-Type: text/plain
-
- abcdef123456789
- --AaB03x--
- --FILE--
-
- --EXPECTF--
- bool(true)
- bool(false)
- bool(false)
- bool(false)
-
- Warning: is_uploaded_file() expects exactly 1 parameter, 0 given in %s on line %d
- NULL
-
- Warning: is_uploaded_file() expects exactly 1 parameter, 2 given in %s on line %d
- NULL
-
-sample007.phpt
---------------
-
-.. code:: php
-
- --TEST--
- SOAP Server 20: compressed request (deflate)
- --SKIPIF--
-
- --INI--
- precision=14
- --DEFLATE_POST--
-
-
-
-
-
-
- --FILE--
- "http://testuri.org"));
- $server->addfunction("test");
- $server->handle();
- echo "ok\n";
- ?>
- --EXPECT--
-
- Hello World
- ok
-
-sample008.phpt
---------------
-
-.. code:: php
-
- --TEST--
- GET/POST/REQUEST Test with input_filter
- --SKIPIF--
-
- --POST--
- d=379
- --GET--
- ar[elm1]=1234&ar[elm2]=0660&a=0234
- --FILE--
- FILTER_FLAG_ALLOW_OCTAL));
- var_dump($ret);
-
- $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_REQUIRE_ARRAY));
- var_dump($ret);
-
- $ret = filter_input(INPUT_GET, 'ar', FILTER_VALIDATE_INT, array('flags'=>FILTER_FLAG_ALLOW_OCTAL|FILTER_REQUIRE_ARRAY));
- var_dump($ret);
-
- ?>
- --EXPECT--
- bool(false)
- int(156)
- array(2) {
- ["elm1"]=>
- int(1234)
- ["elm2"]=>
- bool(false)
- }
- array(2) {
- ["elm1"]=>
- int(1234)
- ["elm2"]=>
- int(432)
- }
-
-sample009.phpt
---------------
-
-.. code:: php
-
- --TEST--
- STDIN input
- --FILE--
-
- --STDIN--
- fooBar
- use this to input some thing to the php script
- --EXPECT--
- string(54) "fooBar
- use this to input some thing to the php script
- "
-
-sample010.phpt
---------------
-
-.. code:: php
-
- --TEST--
- getopt#005 (Required values)
- --ARGS--
- --arg value --arg=value -avalue -a=value -a value
- --INI--
- register_argc_argv=On
- variables_order=GPS
- --FILE--
-
- --EXPECT--
- array(2) {
- ["arg"]=>
- array(2) {
- [0]=>
- string(5) "value"
- [1]=>
- string(5) "value"
- }
- ["a"]=>
- array(3) {
- [0]=>
- string(5) "value"
- [1]=>
- string(5) "value"
- [2]=>
- string(5) "value"
- }
- }
-
-sample011.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Bug #35382 (Comment in end of file produces fatal error)
- --FILEEOF--
-
- --REDIRECTTEST--
- return array(
- 'ENV' => array(
- 'PDOTEST_DSN' => 'sqlite2::memory:'
- ),
- 'TESTS' => 'ext/pdo/tests'
- );
-
-sample014.phpt
---------------
-
-.. code:: php
-
- --TEST--
- MySQL
- --SKIPIF--
-
- --REDIRECTTEST--
- # magic auto-configuration
-
- $config = array(
- 'TESTS' => 'ext/pdo/tests'
- );
-
- if (false !== getenv('PDO_MYSQL_TEST_DSN')) {
- # user set them from their shell
- $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');
- $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
- $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
- if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
- $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
- }
- } else {
- $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
- $config['ENV']['PDOTEST_USER'] = 'root';
- $config['ENV']['PDOTEST_PASS'] = '';
- }
-
- return $config;
-
-sample016.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Test get variables with CGI binary
- --GET--
- hello=World&goodbye=MrChips
- --CGI--
- --FILE--
-
- --EXPECT--
- array(2) {
- ["hello"]=>
- string(5) "World"
- ["goodbye"]=>
- string(7) "MrChips"
- }
-
-sample017.phpt
---------------
-
-.. code:: php
-
- --TEST--
- PDO Common: Bug #34630 (inserting streams as LOBs)
- --SKIPIF--
-
- --FILE--
- getAttribute(PDO::ATTR_DRIVER_NAME);
- $is_oci = $driver == 'oci';
-
- if ($is_oci) {
- $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val BLOB)');
- } else {
- $db->exec('CREATE TABLE test (id int NOT NULL PRIMARY KEY, val VARCHAR(256))');
- }
- $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
-
- $fp = tmpfile();
- fwrite($fp, "I am the LOB data");
- rewind($fp);
-
- if ($is_oci) {
- /* oracle is a bit different; you need to initiate a transaction otherwise
- * the empty blob will be committed implicitly when the statement is
- * executed */
- $db->beginTransaction();
- $insert = $db->prepare("insert into test (id, val) values (1, EMPTY_BLOB()) RETURNING val INTO :blob");
- } else {
- $insert = $db->prepare("insert into test (id, val) values (1, :blob)");
- }
- $insert->bindValue(':blob', $fp, PDO::PARAM_LOB);
- $insert->execute();
- $insert = null;
-
- $db->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, true);
- var_dump($db->query("SELECT * from test")->fetchAll(PDO::FETCH_ASSOC));
-
- ?>
- --XFAIL--
- This bug might be still open on aix5.2-ppc64 and hpux11.23-ia64
- --EXPECT--
- array(1) {
- [0]=>
- array(2) {
- ["id"]=>
- string(1) "1"
- ["val"]=>
- string(17) "I am the LOB data"
- }
- }
-
-sample018.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Phar front controller rewrite access denied [cache_list]
- --INI--
- default_charset=UTF-8
- phar.cache_list={PWD}/frontcontroller10.php
- --SKIPIF--
-
- --ENV--
- SCRIPT_NAME=/frontcontroller10.php
- REQUEST_URI=/frontcontroller10.php/hi
- PATH_INFO=/hi
- --FILE_EXTERNAL--
- files/frontcontroller4.phar
- --EXPECTHEADERS--
- Content-type: text/html; charset=UTF-8
- Status: 403 Access Denied
- --EXPECT--
-
-
- Access Denied
-
-
-
403 - File /hi Access Denied
-
-
-
-sample019.phpt
---------------
-
-.. code:: php
-
- --TEST--
- bzopen() and invalid parameters
- --SKIPIF--
-
- --FILE--
-
- --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--
- query('*');
- var_dump($nodes);
- var_dump($nodes->length);
- $length = $nodes->length;
- var_dump(empty($nodes->length), empty($length));
-
- $doc->loadXML("");
- var_dump($doc->firstChild->nodeValue, empty($doc->firstChild->nodeValue), isset($doc->firstChild->nodeValue));
- var_dump(empty($doc->nodeType), empty($doc->firstChild->nodeType))
- ?>
- --EXPECTF--
- object(DOMNodeList)#%d (0) {
- }
- int(0)
- bool(true)
- bool(true)
- string(0) ""
- bool(true)
- bool(true)
- bool(false)
- bool(false)
-
-sample021.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Math constants
- --INI--
- precision=14
- --FILE--
-
- --EXPECTREGEX--
- M_E : 2.718281[0-9]*
- M_LOG2E : 1.442695[0-9]*
- M_LOG10E : 0.434294[0-9]*
- M_LN2 : 0.693147[0-9]*
- M_LN10 : 2.302585[0-9]*
- M_PI : 3.141592[0-9]*
- M_PI_2 : 1.570796[0-9]*
- M_PI_4 : 0.785398[0-9]*
- M_1_PI : 0.318309[0-9]*
- M_2_PI : 0.636619[0-9]*
- M_SQRTPI : 1.772453[0-9]*
- M_2_SQRTPI: 1.128379[0-9]*
- M_LNPI : 1.144729[0-9]*
- M_EULER : 0.577215[0-9]*
- M_SQRT2 : 1.414213[0-9]*
- M_SQRT1_2 : 0.707106[0-9]*
- M_SQRT3 : 1.732050[0-9]*
-
-sample022.phpt
---------------
-
-.. code:: php
-
- --TEST--
- shm_detach() tests
- --SKIPIF--
-
- --FILE--
-
- --CLEAN--
-
- --EXPECTF--
- Warning: shm_detach() expects exactly 1 parameter, 0 given in %ssample022.php on line %d
- NULL
-
- Warning: shm_detach() expects exactly 1 parameter, 2 given in %ssample022.php on line %d
- NULL
- bool(true)
-
- Warning: shm_detach(): %d is not a valid sysvshm resource in %ssample022.php on line %d
- bool(false)
-
- Warning: shm_remove(): %d is not a valid sysvshm resource in %ssample022.php on line %d
-
- Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
- NULL
-
- Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
- NULL
-
- Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
- NULL
- Done
-
-sample023.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Bug #23894 (sprintf() decimal specifiers problem)
- --FILE--
-
- --EXPECTREGEX--
- string\(4\) \"-012\"
- string\(8\) \"2d303132\"
- (string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\")
- (string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\")
-
-sample024.phpt
---------------
-
-.. code:: php
-
- --TEST--
- DOMDocument::save Test basic function of save method
- --SKIPIF--
-
- --FILE--
- formatOutput = true;
-
- $root = $doc->createElement('book');
-
- $root = $doc->appendChild($root);
-
- $title = $doc->createElement('title');
- $title = $root->appendChild($title);
-
- $text = $doc->createTextNode('This is the title');
- $text = $title->appendChild($text);
-
- $temp_filename = __DIR__.'/DomDocument_save_basic.tmp';
-
- echo 'Wrote: ' . $doc->save($temp_filename) . ' bytes'; // Wrote: 72 bytes
- ?>
- --CLEAN--
-
- --EXPECTF--
- Wrote: 72 bytes
-
-sample025.phpt
---------------
-
-.. code:: php
-
- --TEST--
- Test imap_append() function : basic functionality
- --SKIPIF--
-
- --FILE--
- 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--
-
- --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--
- array(21, 22 => array(221, 222), 23 => array(231)), 3);
-
- $dir = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY);
-
- foreach ($dir as $file) {
- print "$file\n";
- }
-
- ?>
- ===DONE===
-
- --EXPECT--
- 1
- 21
- 221
- 222
- 231
- 3
-
-skipif2.phpt
-------------
-
-.. code:: php
-
-
-
-skipif.phpt
------------
-
-.. code:: php
-
-
-
-xfailif.phpt
-------------
-
-.. code:: php
-
- --TEST--
- Handling of errors during linking
- --INI--
- opcache.enable=1
- opcache.enable_cli=1
- opcache.optimization_level=-1
- opcache.preload={PWD}/preload_inheritance_error_ind.inc
- --SKIPIF--
-
- --FILE--
-
- --EXPECTF--
- Fatal error: Declaration of B::foo($bar) must be compatible with A::foo() in %spreload_inheritance_error.inc on line 8
+```php
+--CLEAN--
+
+```
+
+Example 3 (full): {ref}`sample022.phpt`
+
+### Samples
+
+#### capture_stdio_1.phpt
+
+```php
+--TEST--
+Test covering the I/O stdin and stdout streams.
+--DESCRIPTION--
+This tests checks if the output of stdin and stdout I/O streams match the
+expected content.
+--CAPTURE_STDIO--
+STDIN STDERR
+--FILE--
+
+--EXPECT--
+This is error sent to the stderr I/O stream
+```
+
+#### capture_stdio_2.phpt
+
+```php
+--TEST--
+Test covering the I/O stdin and stderr streams.
+--DESCRIPTION--
+This tests checks if the output of stdin and stderr I/O streams match the
+expected content.
+--CAPTURE_STDIO--
+STDIN STDOUT
+--FILE--
+
+--EXPECT--
+Hello, world. This is sent to the stdout I/O stream
+```
+
+#### capture_stdio_3.phpt
+
+```php
+--TEST--
+Test covering the all standard I/O streams.
+--DESCRIPTION--
+This tests checks if the output of stdin, stdout and stderr I/O streams match
+the expected content.
+--CAPTURE_STDIO--
+STDIN STDOUT STDERR
+--FILE--
+
+--EXPECT--
+Hello, world. This is sent to the stdout I/O stream
+This is error sent to the stderr I/O stream
+```
+
+#### clean.php
+
+```php
+Nmsgs; $i++) {
+ imap_delete($imap_stream, $i);
+}
+
+$mailboxes = imap_getmailboxes($imap_stream, $server, '*');
+
+foreach($mailboxes as $value) {
+ // Only delete mailboxes with our prefix
+ if (preg_match('/\{.*?\}INBOX\.(.+)/', $value->name, $match) == 1) {
+ if (strlen($match[1]) >= strlen($mailbox_prefix)
+ && substr_compare($match[1], $mailbox_prefix, 0, strlen($mailbox_prefix)) == 0) {
+ imap_deletemailbox($imap_stream, $value->name);
+ }
+ }
+}
+
+imap_close($imap_stream, CL_EXPUNGE);
+?>
+```
+
+#### conflicts_1.phpt
+
+```php
+--TEST--
+Test get_headers() function : test with context
+--CONFLICTS--
+server
+--FILE--
+ 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
+
+```php
+--TEST--
+phpt EXTENSIONS directive with shared extensions
+--DESCRIPTION--
+This test covers the presence of some loaded extensions with a list of additional
+extensions to be loaded when running test.
+--EXTENSIONS--
+curl
+imagick
+tokenizer
+--FILE--
+
+--EXPECT--
+bool(true)
+bool(true)
+bool(true)
+```
+
+#### file012.phpt
+
+```php
+
+```
+
+#### phpdbg_1.phpt
+
+```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--
+
+--INI--
+precision=14
+--SKIPIF--
+
+--GET--
+a=test&b=https://example.com
+--POST--
+c=
string
&d=12345.7
+--FILE--
+
+--EXPECTF--
+string(4) "test"
+string(19) "https://example.com"
+string(27) "<b>test</b>"
+
+Notice: Object of class stdClass could not be converted to int in %ssample001.php on line %d
+bool(false)
+string(6) "string"
+float(12345.7)
+string(29) "<p>string</p>"
+bool(false)
+
+Warning: filter_var() expects parameter 2 to be long, string given in %ssample001.php on line %d
+NULL
+
+Warning: filter_input() expects parameter 3 to be long, string given in %ssample001.php on line %d
+NULL
+
+Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
+NULL
+
+Warning: filter_var() expects at most 3 parameters, 5 given in %ssample001.php on line %d
+NULL
+Done
+```
+
+#### sample002.phpt
+
+```php
+--TEST--
+Test receipt of cookie data.
+--CREDITS--
+Zoe Slattery zoe@php.net
+# TestFest Munich 2009-05-19
+--COOKIE--
+hello=World;goodbye=MrChips
+--FILE--
+
+--EXPECT--
+array(2) {
+ ["hello"]=>
+ string(5) "World"
+ ["goodbye"]=>
+ string(7) "MrChips"
+}
+```
+
+#### sample003.phpt
+
+```php
+--TEST--
+session object deserialization
+--SKIPIF--
+
+--INI--
+session.use_cookies=0
+session.cache_limiter=
+register_globals=1
+session.serialize_handler=php
+session.save_handler=files
+--FILE--
+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
+
+```php
+--TEST--
+SOAP Server 19: compressed request (gzip)
+--SKIPIF--
+
+--INI--
+precision=14
+--GZIP_POST--
+
+
+
+
+
+--FILE--
+"http://testuri.org"));
+$server->addfunction("test");
+$server->handle();
+echo "ok\n";
+?>
+--EXPECT--
+
+Hello World
+ok
+```
+
+#### sample006.phpt
+
+```php
+--TEST--
+is_uploaded_file() function
+--CREDITS--
+Dave Kelsey
+--SKIPIF--
+
+--POST_RAW--
+Content-type: multipart/form-data, boundary=AaB03x
+
+--AaB03x
+content-disposition: form-data; name="field1"
+
+Joe Blow
+--AaB03x
+content-disposition: form-data; name="pics"; filename="file1.txt"
+Content-Type: text/plain
+
+abcdef123456789
+--AaB03x--
+--FILE--
+
+--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
+
+```php
+--TEST--
+SOAP Server 20: compressed request (deflate)
+--SKIPIF--
+
+--INI--
+precision=14
+--DEFLATE_POST--
+
+
+
+
+
+
+--FILE--
+"http://testuri.org"));
+$server->addfunction("test");
+$server->handle();
+echo "ok\n";
+?>
+--EXPECT--
+
+Hello World
+ok
+```
+
+#### sample008.phpt
+
+```php
+--TEST--
+GET/POST/REQUEST Test with input_filter
+--SKIPIF--
+
+--POST--
+d=379
+--GET--
+ar[elm1]=1234&ar[elm2]=0660&a=0234
+--FILE--
+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
+
+```php
+--TEST--
+STDIN input
+--FILE--
+
+--STDIN--
+fooBar
+use this to input some thing to the php script
+--EXPECT--
+string(54) "fooBar
+use this to input some thing to the php script
+"
+```
+
+#### sample010.phpt
+
+```php
+--TEST--
+getopt#005 (Required values)
+--ARGS--
+--arg value --arg=value -avalue -a=value -a value
+--INI--
+register_argc_argv=On
+variables_order=GPS
+--FILE--
+
+--EXPECT--
+array(2) {
+ ["arg"]=>
+ array(2) {
+ [0]=>
+ string(5) "value"
+ [1]=>
+ string(5) "value"
+ }
+ ["a"]=>
+ array(3) {
+ [0]=>
+ string(5) "value"
+ [1]=>
+ string(5) "value"
+ [2]=>
+ string(5) "value"
+ }
+}
+```
+
+#### sample011.phpt
+
+```php
+--TEST--
+Bug #35382 (Comment in end of file produces fatal error)
+--FILEEOF--
+
+--REDIRECTTEST--
+return array(
+ 'ENV' => array(
+ 'PDOTEST_DSN' => 'sqlite2::memory:'
+ ),
+ 'TESTS' => 'ext/pdo/tests'
+ );
+```
+
+#### sample014.phpt
+
+```php
+--TEST--
+MySQL
+--SKIPIF--
+
+--REDIRECTTEST--
+# magic auto-configuration
+
+$config = array(
+ 'TESTS' => 'ext/pdo/tests'
+);
+
+if (false !== getenv('PDO_MYSQL_TEST_DSN')) {
+ # user set them from their shell
+ $config['ENV']['PDOTEST_DSN'] = getenv('PDO_MYSQL_TEST_DSN');
+ $config['ENV']['PDOTEST_USER'] = getenv('PDO_MYSQL_TEST_USER');
+ $config['ENV']['PDOTEST_PASS'] = getenv('PDO_MYSQL_TEST_PASS');
+ if (false !== getenv('PDO_MYSQL_TEST_ATTR')) {
+ $config['ENV']['PDOTEST_ATTR'] = getenv('PDO_MYSQL_TEST_ATTR');
+ }
+} else {
+ $config['ENV']['PDOTEST_DSN'] = 'mysql:host=localhost;dbname=test';
+ $config['ENV']['PDOTEST_USER'] = 'root';
+ $config['ENV']['PDOTEST_PASS'] = '';
+}
+
+return $config;
+```
+
+#### sample016.phpt
+
+```php
+--TEST--
+Test get variables with CGI binary
+--GET--
+hello=World&goodbye=MrChips
+--CGI--
+--FILE--
+
+--EXPECT--
+array(2) {
+ ["hello"]=>
+ string(5) "World"
+ ["goodbye"]=>
+ string(7) "MrChips"
+}
+```
+
+#### sample017.phpt
+
+```php
+--TEST--
+PDO Common: Bug #34630 (inserting streams as LOBs)
+--SKIPIF--
+
+--FILE--
+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
+
+```php
+--TEST--
+Phar front controller rewrite access denied [cache_list]
+--INI--
+default_charset=UTF-8
+phar.cache_list={PWD}/frontcontroller10.php
+--SKIPIF--
+
+--ENV--
+SCRIPT_NAME=/frontcontroller10.php
+REQUEST_URI=/frontcontroller10.php/hi
+PATH_INFO=/hi
+--FILE_EXTERNAL--
+files/frontcontroller4.phar
+--EXPECTHEADERS--
+Content-type: text/html; charset=UTF-8
+Status: 403 Access Denied
+--EXPECT--
+
+
+ Access Denied
+
+
+
403 - File /hi Access Denied
+
+
+```
+
+#### sample019.phpt
+
+```php
+--TEST--
+bzopen() and invalid parameters
+--SKIPIF--
+
+--FILE--
+
+--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
+
+```php
+--TEST--
+Bug #42082 (NodeList length zero should be empty)
+--FILE--
+query('*');
+var_dump($nodes);
+var_dump($nodes->length);
+$length = $nodes->length;
+var_dump(empty($nodes->length), empty($length));
+
+$doc->loadXML("");
+var_dump($doc->firstChild->nodeValue, empty($doc->firstChild->nodeValue), isset($doc->firstChild->nodeValue));
+var_dump(empty($doc->nodeType), empty($doc->firstChild->nodeType))
+?>
+--EXPECTF--
+object(DOMNodeList)#%d (0) {
+}
+int(0)
+bool(true)
+bool(true)
+string(0) ""
+bool(true)
+bool(true)
+bool(false)
+bool(false)
+```
+
+#### sample021.phpt
+
+```php
+--TEST--
+Math constants
+--INI--
+precision=14
+--FILE--
+
+--EXPECTREGEX--
+M_E : 2.718281[0-9]*
+M_LOG2E : 1.442695[0-9]*
+M_LOG10E : 0.434294[0-9]*
+M_LN2 : 0.693147[0-9]*
+M_LN10 : 2.302585[0-9]*
+M_PI : 3.141592[0-9]*
+M_PI_2 : 1.570796[0-9]*
+M_PI_4 : 0.785398[0-9]*
+M_1_PI : 0.318309[0-9]*
+M_2_PI : 0.636619[0-9]*
+M_SQRTPI : 1.772453[0-9]*
+M_2_SQRTPI: 1.128379[0-9]*
+M_LNPI : 1.144729[0-9]*
+M_EULER : 0.577215[0-9]*
+M_SQRT2 : 1.414213[0-9]*
+M_SQRT1_2 : 0.707106[0-9]*
+M_SQRT3 : 1.732050[0-9]*
+```
+
+#### sample022.phpt
+
+```php
+--TEST--
+shm_detach() tests
+--SKIPIF--
+
+--FILE--
+
+--CLEAN--
+
+--EXPECTF--
+Warning: shm_detach() expects exactly 1 parameter, 0 given in %ssample022.php on line %d
+NULL
+
+Warning: shm_detach() expects exactly 1 parameter, 2 given in %ssample022.php on line %d
+NULL
+bool(true)
+
+Warning: shm_detach(): %d is not a valid sysvshm resource in %ssample022.php on line %d
+bool(false)
+
+Warning: shm_remove(): %d is not a valid sysvshm resource in %ssample022.php on line %d
+
+Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
+NULL
+
+Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
+NULL
+
+Warning: shm_detach() expects parameter 1 to be resource, integer given in %ssample022.php on line %d
+NULL
+Done
+```
+
+#### sample023.phpt
+
+```php
+--TEST--
+Bug #23894 (sprintf() decimal specifiers problem)
+--FILE--
+
+--EXPECTREGEX--
+string\(4\) \"-012\"
+string\(8\) \"2d303132\"
+(string\(13\) \" 4294967284\"|string\(20\) \"18446744073709551604\")
+(string\(26\) \"20202034323934393637323834\"|string\(40\) \"3138343436373434303733373039353531363034\")
+```
+
+#### sample024.phpt
+
+```php
+--TEST--
+DOMDocument::save Test basic function of save method
+--SKIPIF--
+
+--FILE--
+formatOutput = true;
+
+$root = $doc->createElement('book');
+
+$root = $doc->appendChild($root);
+
+$title = $doc->createElement('title');
+$title = $root->appendChild($title);
+
+$text = $doc->createTextNode('This is the title');
+$text = $title->appendChild($text);
+
+$temp_filename = __DIR__.'/DomDocument_save_basic.tmp';
+
+echo 'Wrote: ' . $doc->save($temp_filename) . ' bytes'; // Wrote: 72 bytes
+?>
+--CLEAN--
+
+--EXPECTF--
+Wrote: 72 bytes
+```
+
+#### sample025.phpt
+
+```php
+--TEST--
+Test imap_append() function : basic functionality
+--SKIPIF--
+
+--FILE--
+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--
+
+--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
+
+```php
+--TEST--
+SPL: ArrayIterator implementing RecursiveIterator
+--FILE--
+ array(21, 22 => array(221, 222), 23 => array(231)), 3);
+
+$dir = new RecursiveIteratorIterator(new RecursiveArrayIterator($array), RecursiveIteratorIterator::LEAVES_ONLY);
+
+foreach ($dir as $file) {
+ print "$file\n";
+}
+
+?>
+===DONE===
+
+--EXPECT--
+1
+21
+221
+222
+231
+3
+```
+
+#### skipif2.phpt
+
+```php
+
+```
+
+#### skipif.phpt
+
+```php
+
+```
+
+#### xfailif.phpt
+
+```php
+--TEST--
+Handling of errors during linking
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/preload_inheritance_error_ind.inc
+--SKIPIF--
+
+--FILE--
+
+--EXPECTF--
+Fatal error: Declaration of B::foo($bar) must be compatible with A::foo() in %spreload_inheritance_error.inc on line 8
+```
From 5826ae9b5896a26402887d1b31f0d0c5f1846ebb Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 12:08:06 +0700
Subject: [PATCH 03/15] docs: flipped to markdown extensions
---
docs/source/conf.py | 1 -
docs/source/core/data-structures/{index.rst => index.md} | 0
.../{reference-counting.rst => reference-counting.md} | 0
.../core/data-structures/{zend_constant.rst => zend_constant.md} | 0
.../core/data-structures/{zend_string.rst => zend_string.md} | 0
docs/source/core/data-structures/{zval.rst => zval.md} | 0
docs/source/{index.rst => index.md} | 0
.../{high-level-overview.rst => high-level-overview.md} | 0
docs/source/introduction/ides/{index.rst => index.md} | 0
.../ides/{visual-studio-code.rst => visual-studio-code.md} | 0
.../source/miscellaneous/{running-tests.rst => running-tests.md} | 0
docs/source/miscellaneous/{stubs.rst => stubs.md} | 0
.../source/miscellaneous/{writing-tests.rst => writing-tests.md} | 0
13 files changed, 1 deletion(-)
rename docs/source/core/data-structures/{index.rst => index.md} (100%)
rename docs/source/core/data-structures/{reference-counting.rst => reference-counting.md} (100%)
rename docs/source/core/data-structures/{zend_constant.rst => zend_constant.md} (100%)
rename docs/source/core/data-structures/{zend_string.rst => zend_string.md} (100%)
rename docs/source/core/data-structures/{zval.rst => zval.md} (100%)
rename docs/source/{index.rst => index.md} (100%)
rename docs/source/introduction/{high-level-overview.rst => high-level-overview.md} (100%)
rename docs/source/introduction/ides/{index.rst => index.md} (100%)
rename docs/source/introduction/ides/{visual-studio-code.rst => visual-studio-code.md} (100%)
rename docs/source/miscellaneous/{running-tests.rst => running-tests.md} (100%)
rename docs/source/miscellaneous/{stubs.rst => stubs.md} (100%)
rename docs/source/miscellaneous/{writing-tests.rst => writing-tests.md} (100%)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 432c15576da9..a2bc42e6f677 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -26,7 +26,6 @@
'tasklist',
]
myst_heading_anchors = 6
-source_suffix = {'.rst': 'markdown'}
templates_path = ['_templates']
html_theme = 'sphinxawesome_theme'
html_static_path = ['_static']
diff --git a/docs/source/core/data-structures/index.rst b/docs/source/core/data-structures/index.md
similarity index 100%
rename from docs/source/core/data-structures/index.rst
rename to docs/source/core/data-structures/index.md
diff --git a/docs/source/core/data-structures/reference-counting.rst b/docs/source/core/data-structures/reference-counting.md
similarity index 100%
rename from docs/source/core/data-structures/reference-counting.rst
rename to docs/source/core/data-structures/reference-counting.md
diff --git a/docs/source/core/data-structures/zend_constant.rst b/docs/source/core/data-structures/zend_constant.md
similarity index 100%
rename from docs/source/core/data-structures/zend_constant.rst
rename to docs/source/core/data-structures/zend_constant.md
diff --git a/docs/source/core/data-structures/zend_string.rst b/docs/source/core/data-structures/zend_string.md
similarity index 100%
rename from docs/source/core/data-structures/zend_string.rst
rename to docs/source/core/data-structures/zend_string.md
diff --git a/docs/source/core/data-structures/zval.rst b/docs/source/core/data-structures/zval.md
similarity index 100%
rename from docs/source/core/data-structures/zval.rst
rename to docs/source/core/data-structures/zval.md
diff --git a/docs/source/index.rst b/docs/source/index.md
similarity index 100%
rename from docs/source/index.rst
rename to docs/source/index.md
diff --git a/docs/source/introduction/high-level-overview.rst b/docs/source/introduction/high-level-overview.md
similarity index 100%
rename from docs/source/introduction/high-level-overview.rst
rename to docs/source/introduction/high-level-overview.md
diff --git a/docs/source/introduction/ides/index.rst b/docs/source/introduction/ides/index.md
similarity index 100%
rename from docs/source/introduction/ides/index.rst
rename to docs/source/introduction/ides/index.md
diff --git a/docs/source/introduction/ides/visual-studio-code.rst b/docs/source/introduction/ides/visual-studio-code.md
similarity index 100%
rename from docs/source/introduction/ides/visual-studio-code.rst
rename to docs/source/introduction/ides/visual-studio-code.md
diff --git a/docs/source/miscellaneous/running-tests.rst b/docs/source/miscellaneous/running-tests.md
similarity index 100%
rename from docs/source/miscellaneous/running-tests.rst
rename to docs/source/miscellaneous/running-tests.md
diff --git a/docs/source/miscellaneous/stubs.rst b/docs/source/miscellaneous/stubs.md
similarity index 100%
rename from docs/source/miscellaneous/stubs.rst
rename to docs/source/miscellaneous/stubs.md
diff --git a/docs/source/miscellaneous/writing-tests.rst b/docs/source/miscellaneous/writing-tests.md
similarity index 100%
rename from docs/source/miscellaneous/writing-tests.rst
rename to docs/source/miscellaneous/writing-tests.md
From 6ff0b84a0822404470913c27b16b18281a783a03 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 12:09:05 +0700
Subject: [PATCH 04/15] docs: centralised documentation todos
---
docs-old/output-api.md | 8 -------
docs/source/conf.py | 1 +
docs/source/core/TODO.md | 8 +++++++
docs/source/core/data-structures/TODO.md | 4 ++++
.../data-structures/reference-counting.md | 6 ++----
.../core/data-structures/zend_string.md | 12 +++++------
docs/source/core/data-structures/zval.md | 21 +++++++------------
docs/source/core/memory-management/TODO.md | 12 +++++++++++
docs/source/core/output-buffering-TODO.md | 7 +++++++
.../introduction/high-level-overview.md | 4 +---
docs/source/introduction/ides/TODO.md | 3 +++
.../introduction/ides/visual-studio-code.md | 2 --
12 files changed, 52 insertions(+), 36 deletions(-)
create mode 100644 docs/source/core/TODO.md
create mode 100644 docs/source/core/data-structures/TODO.md
create mode 100644 docs/source/core/memory-management/TODO.md
create mode 100644 docs/source/core/output-buffering-TODO.md
create mode 100644 docs/source/introduction/ides/TODO.md
diff --git a/docs-old/output-api.md b/docs-old/output-api.md
index a09cc0184959..28028ea8f3c6 100644
--- a/docs-old/output-api.md
+++ b/docs-old/output-api.md
@@ -130,11 +130,3 @@ PHP_OUTPUT_HANDLER_HOOK_IMMUTABLE
PHP_OUTPUT_HANDLER_HOOK_DISABLE
the second arg is ignored; marks the output handler as disabled
```
-
-## Open questions
-
-- Should the userland API be adjusted and unified?
-
-Many bits of the manual (and very first implementation) do not comply with the
-behaviour of the current (to be obsoleted) code, thus should the manual or the
-behaviour be adjusted?
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a2bc42e6f677..fcb0d929bc2a 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -19,6 +19,7 @@
'sphinx_design',
'sphinx.ext.autosectionlabel',
]
+exclude_patterns = ['**/*TODO.md']
myst_enable_extensions = [
'alert',
'gfm_autolink',
diff --git a/docs/source/core/TODO.md b/docs/source/core/TODO.md
new file mode 100644
index 000000000000..9cf1af025b26
--- /dev/null
+++ b/docs/source/core/TODO.md
@@ -0,0 +1,8 @@
+Core TODO
+
+Pages to add:
+
+- Parser and AST: grammar generation, AST representation, compilation boundaries,
+ and important extension points.
+- Virtual Machine: opcode execution, operands, call frames, and VM variants.
+- Object Handlers: handler contracts, object storage, and common ownership traps.
diff --git a/docs/source/core/data-structures/TODO.md b/docs/source/core/data-structures/TODO.md
new file mode 100644
index 000000000000..b2ee1dfc2153
--- /dev/null
+++ b/docs/source/core/data-structures/TODO.md
@@ -0,0 +1,4 @@
+Data Structures TODO
+
+- Add a HashTable page covering ownership, iteration, mutation, and common APIs.
+- Expand the zval macro table and document the remaining internal zval types.
diff --git a/docs/source/core/data-structures/reference-counting.md b/docs/source/core/data-structures/reference-counting.md
index 312335e0d652..be4f3baf1794 100644
--- a/docs/source/core/data-structures/reference-counting.md
+++ b/docs/source/core/data-structures/reference-counting.md
@@ -144,8 +144,7 @@ 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 chapter.
+the `gc_collect_cycles()` function.
## GC flags
@@ -171,8 +170,7 @@ again.
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 chapter for more
-information.
+being freed at the end of the request.
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
diff --git a/docs/source/core/data-structures/zend_string.md b/docs/source/core/data-structures/zend_string.md
index 007f123a07de..2ae814fe7387 100644
--- a/docs/source/core/data-structures/zend_string.md
+++ b/docs/source/core/data-structures/zend_string.md
@@ -21,9 +21,9 @@ struct _zend_string {
};
```
-The `gc` field is used for {doc}`./reference-counting`. The `h` field contains a hash value,
-which is used for hash table lookups. The `len` field stores the length of the string
-in bytes, and the `val` field contains the actual string data.
+The `gc` field is used for {doc}`./reference-counting`. The `h` field contains a hash value, which is
+used for hash table 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](https://www.geeksforgeeks.org/struct-hack/) in C. It is used to create structs with a flexible size, namely by allowing the last element
@@ -45,7 +45,7 @@ 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 (`persistent == false`) that is emptied after each request.
+`malloc` (`persistent == true`) or `emalloc`, PHP's custom allocator (`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
@@ -104,8 +104,8 @@ use.
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 where existing interned
-strings are stored. When creating a new interned string, php-src first checks the interned string
+string interning. Essentially, this is just a simple `HashTable` 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.
diff --git a/docs/source/core/data-structures/zval.md b/docs/source/core/data-structures/zval.md
index 29b0b08c2586..062864fa8339 100644
--- a/docs/source/core/data-structures/zval.md
+++ b/docs/source/core/data-structures/zval.md
@@ -62,8 +62,8 @@ member, but never both at the same time. However, it doesn't know which member i
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 value, including
-strings, arrays, objects, resources and references. `null` and `bool` are missing from
+`counted`. `counted` polymorphically refers to any [reference-counted](reference-counting.md) 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.
@@ -108,8 +108,8 @@ struct _zval_struct {
`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 flags, and `v.u.extra` is pretty much unused.
+into 3 parts. `v.type` stores the actual variable type, `v.type_flags` is used for some
+[reference-counting](reference-counting.md) 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
@@ -135,8 +135,6 @@ there's a `_P`-suffixed variant that performs the same operation on a pointer to
| `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. |
-
-
## Other zval types
`zval`s are sometimes used internally with types that don't exist in userland.
@@ -153,16 +151,14 @@ there's a `_P`-suffixed variant that performs the same operation on a pointer to
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 chapter for more information
-on ASTs. When this flag is set, the `zval.value.ast` union member is set accordingly.
+expression rather than the concrete values. 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.
-
-
`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
@@ -173,8 +169,7 @@ Otherwise, it is essentially the same as `IS_PTR`. Arbitrary data is accessed th
`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. It is described in more
-detail in its own chapter.
+`_IS_ERROR` is used as an error value for some object handlers.
```c
/* Fake types used only for type hinting.
@@ -192,7 +187,7 @@ detail in its own chapter.
```
These flags are never actually stored in `zval.u1`. They are used for type hinting and in the
-object handler API.
+object handler 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
diff --git a/docs/source/core/memory-management/TODO.md b/docs/source/core/memory-management/TODO.md
new file mode 100644
index 000000000000..869060e09a88
--- /dev/null
+++ b/docs/source/core/memory-management/TODO.md
@@ -0,0 +1,12 @@
+Memory Management TODO
+
+The Reference Counting page contained TODOs for dedicated Cycle Collector and
+Zend Allocator pages; grouping them under Memory Management makes sense. Pages
+to be added:
+
+- Cycle Collector:
+ candidate buffering, collection phases, collectable types, and correct use of
+ GC flags and macros.
+- Zend Allocator:
+ allocator pairing, overflow-safe allocation, request/persistent lifetimes, and
+ relevant arena cleanup.
diff --git a/docs/source/core/output-buffering-TODO.md b/docs/source/core/output-buffering-TODO.md
new file mode 100644
index 000000000000..4d262e775279
--- /dev/null
+++ b/docs/source/core/output-buffering-TODO.md
@@ -0,0 +1,7 @@
+## Open Questions
+
+- Should the userland API be adjusted and unified?
+
+Many bits of the manual (and very first implementation) do not comply with the
+behaviour of the current (to be obsoleted) code, thus should the manual or the
+behaviour be adjusted?
diff --git a/docs/source/introduction/high-level-overview.md b/docs/source/introduction/high-level-overview.md
index 17c09f072e08..08cc3510415b 100644
--- a/docs/source/introduction/high-level-overview.md
+++ b/docs/source/introduction/high-level-overview.md
@@ -96,8 +96,6 @@ Like with tokenization, we use a tool called `Bison` to generate the parser impl
grammar specification. The grammar lives in the `Zend/zend_language_parser.y` file. Check the
[Bison documentation](https://www.gnu.org/software/bison/manual/) for details. Luckily, the syntax is quite approachable.
-Parsing is described in more detail in its dedicated chapter.
-
## Compilation
Computers don't understand human language, or even programming languages. They only understand
@@ -149,7 +147,7 @@ With these simple rules, we can see that the interpreter will `echo` only when `
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 chapter.
+is quite complex.
## Opcache
diff --git a/docs/source/introduction/ides/TODO.md b/docs/source/introduction/ides/TODO.md
new file mode 100644
index 000000000000..510dbaa955f0
--- /dev/null
+++ b/docs/source/introduction/ides/TODO.md
@@ -0,0 +1,3 @@
+IDEs TODO
+
+- Add LLDB setup and debugging instructions, particularly for macOS.
diff --git a/docs/source/introduction/ides/visual-studio-code.md b/docs/source/introduction/ides/visual-studio-code.md
index 963b25f4555f..6e966150bd2a 100644
--- a/docs/source/introduction/ides/visual-studio-code.md
+++ b/docs/source/introduction/ides/visual-studio-code.md
@@ -110,5 +110,3 @@ file:
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.
-
-
From bd010f5a3551011c80c94e8fe85aa46dc2072d29 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 12:23:41 +0700
Subject: [PATCH 05/15] docs: moved output buffering
---
docs-old/output-api.md => docs/source/core/output-buffering.md | 0
docs/source/index.md | 1 +
2 files changed, 1 insertion(+)
rename docs-old/output-api.md => docs/source/core/output-buffering.md (100%)
diff --git a/docs-old/output-api.md b/docs/source/core/output-buffering.md
similarity index 100%
rename from docs-old/output-api.md
rename to docs/source/core/output-buffering.md
diff --git a/docs/source/index.md b/docs/source/index.md
index 90859ea9d3b8..d623c4ef1f75 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -15,6 +15,7 @@ caption: Core
hidden: true
---
core/data-structures/index
+core/output-buffering
```
```{toctree}
From 0864a473a92f2047bd5d0bd92ff13867af14ec58 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 12:45:56 +0700
Subject: [PATCH 06/15] docs: moved parameter parsing
---
.../source/extensions/parameter-parsing.md | 0
docs/source/index.md | 8 ++++++++
2 files changed, 8 insertions(+)
rename docs-old/parameter-parsing-api.md => docs/source/extensions/parameter-parsing.md (100%)
diff --git a/docs-old/parameter-parsing-api.md b/docs/source/extensions/parameter-parsing.md
similarity index 100%
rename from docs-old/parameter-parsing-api.md
rename to docs/source/extensions/parameter-parsing.md
diff --git a/docs/source/index.md b/docs/source/index.md
index d623c4ef1f75..08bab4e0b335 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -18,6 +18,14 @@ core/data-structures/index
core/output-buffering
```
+```{toctree}
+---
+caption: Extensions
+hidden: true
+---
+extensions/parameter-parsing
+```
+
```{toctree}
---
caption: Miscellaneous
From 738a86445c410be5aa2c030af454682026ea95dc Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 13:11:20 +0700
Subject: [PATCH 07/15] docs: moved self contained extensions
---
.../source/extensions/unbundled-extensions.md | 0
docs/source/index.md | 1 +
2 files changed, 1 insertion(+)
rename docs-old/self-contained-extensions.md => docs/source/extensions/unbundled-extensions.md (100%)
diff --git a/docs-old/self-contained-extensions.md b/docs/source/extensions/unbundled-extensions.md
similarity index 100%
rename from docs-old/self-contained-extensions.md
rename to docs/source/extensions/unbundled-extensions.md
diff --git a/docs/source/index.md b/docs/source/index.md
index 08bab4e0b335..ffda8187e664 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -24,6 +24,7 @@ caption: Extensions
hidden: true
---
extensions/parameter-parsing
+extensions/unbundled-extensions
```
```{toctree}
From 10b185015618e7e3e12b6bb00000ac8d0418c3bd Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 14:10:04 +0700
Subject: [PATCH 08/15] docs: moved unix build system
---
.../source/extensions/bundled-extensions.md | 0
docs/source/index.md | 1 +
2 files changed, 1 insertion(+)
rename docs-old/unix-build-system.md => docs/source/extensions/bundled-extensions.md (100%)
diff --git a/docs-old/unix-build-system.md b/docs/source/extensions/bundled-extensions.md
similarity index 100%
rename from docs-old/unix-build-system.md
rename to docs/source/extensions/bundled-extensions.md
diff --git a/docs/source/index.md b/docs/source/index.md
index ffda8187e664..7e4ea3f7aef2 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -24,6 +24,7 @@ caption: Extensions
hidden: true
---
extensions/parameter-parsing
+extensions/bundled-extensions
extensions/unbundled-extensions
```
From f9d5dcb1115ac74958bdd0b222a44a2b405951e2 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 14:27:53 +0700
Subject: [PATCH 09/15] docs: moved reference counting to memory management
---
docs/source/_templates/redirect.html | 15 +++++++++++++++
docs/source/conf.py | 14 ++++++++++++++
docs/source/core/data-structures/index.md | 3 +--
docs/source/core/data-structures/zend_string.md | 14 +++++++-------
docs/source/core/data-structures/zval.md | 9 +++++----
docs/source/core/memory-management/index.md | 10 ++++++++++
.../reference-counting.md | 2 +-
docs/source/index.md | 1 +
8 files changed, 54 insertions(+), 14 deletions(-)
create mode 100644 docs/source/_templates/redirect.html
create mode 100644 docs/source/core/memory-management/index.md
rename docs/source/core/{data-structures => memory-management}/reference-counting.md (99%)
diff --git a/docs/source/_templates/redirect.html b/docs/source/_templates/redirect.html
new file mode 100644
index 000000000000..b7a37e55b720
--- /dev/null
+++ b/docs/source/_templates/redirect.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ Redirecting…
+
+
+
+
+
+
diff --git a/docs/source/conf.py b/docs/source/conf.py
index fcb0d929bc2a..28ee3caee85e 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -67,3 +67,17 @@
)
html_theme_options = asdict(theme_options)
pygments_style = 'sphinx'
+
+
+redirects = {
+ 'core/data-structures/reference-counting': '../memory-management/reference-counting.html',
+}
+
+
+def generate_redirects(_app):
+ for source, target in redirects.items():
+ yield source, {'redirect_url': target}, 'redirect.html'
+
+
+def setup(app):
+ app.connect('html-collect-pages', generate_redirects)
diff --git a/docs/source/core/data-structures/index.md b/docs/source/core/data-structures/index.md
index 520ce1962163..08dd9e4adf65 100644
--- a/docs/source/core/data-structures/index.md
+++ b/docs/source/core/data-structures/index.md
@@ -1,11 +1,10 @@
-# Data structures
+# Data Structures
```{toctree}
---
hidden: true
---
zval
-reference-counting
zend_string
zend_constant
```
diff --git a/docs/source/core/data-structures/zend_string.md b/docs/source/core/data-structures/zend_string.md
index 2ae814fe7387..e5dae0bd931c 100644
--- a/docs/source/core/data-structures/zend_string.md
+++ b/docs/source/core/data-structures/zend_string.md
@@ -21,9 +21,9 @@ struct _zend_string {
};
```
-The `gc` field is used for {doc}`./reference-counting`. The `h` field contains a hash value, which is
-used for hash table lookups. The `len` field stores the length of the string in bytes, and the `val`
-field contains the actual string data.
+The `gc` field is used for {doc}`../memory-management/reference-counting`. The `h` field contains a hash value,
+which is used for hash table 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](https://www.geeksforgeeks.org/struct-hack/) in C. It is used to create structs with a flexible size, namely by allowing the last element
@@ -90,7 +90,7 @@ strings.
| `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_separate(s)` | Duplicates the string if the reference count is greater than 1. See {doc}`../memory-management/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
@@ -105,9 +105,9 @@ Programs use some strings many times. For example, if your program declares a cl
`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` 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.
+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.
```c
zend_string *str1 = zend_new_interned_string(
diff --git a/docs/source/core/data-structures/zval.md b/docs/source/core/data-structures/zval.md
index 062864fa8339..ad482cd180d2 100644
--- a/docs/source/core/data-structures/zval.md
+++ b/docs/source/core/data-structures/zval.md
@@ -62,9 +62,9 @@ member, but never both at the same time. However, it doesn't know which member i
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](reference-counting.md) value,
-including strings, arrays, objects, resources and references. `null` and `bool` are missing from
-`zend_value` because their types are self-contained.
+`counted`. `counted` polymorphically refers to any [reference-counted](../memory-management/reference-counting.md)
+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.
@@ -109,7 +109,8 @@ struct _zval_struct {
`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](reference-counting.md) flags, and `v.u.extra` is pretty much unused.
+[reference-counting](../memory-management/reference-counting.md) 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
diff --git a/docs/source/core/memory-management/index.md b/docs/source/core/memory-management/index.md
new file mode 100644
index 000000000000..e818837dfef1
--- /dev/null
+++ b/docs/source/core/memory-management/index.md
@@ -0,0 +1,10 @@
+# Memory Management
+
+```{toctree}
+---
+hidden: true
+---
+reference-counting
+```
+
+This section describes how php-src manages the lifetime of allocated data.
diff --git a/docs/source/core/data-structures/reference-counting.md b/docs/source/core/memory-management/reference-counting.md
similarity index 99%
rename from docs/source/core/data-structures/reference-counting.md
rename to docs/source/core/memory-management/reference-counting.md
index be4f3baf1794..8e7aac0e7c71 100644
--- a/docs/source/core/data-structures/reference-counting.md
+++ b/docs/source/core/memory-management/reference-counting.md
@@ -1,4 +1,4 @@
-# Reference counting
+# 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.
diff --git a/docs/source/index.md b/docs/source/index.md
index 7e4ea3f7aef2..441a9e38cb8b 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -15,6 +15,7 @@ caption: Core
hidden: true
---
core/data-structures/index
+core/memory-management/index
core/output-buffering
```
From faea5f427d4f0882d4a1bffd6d7f853a35214fb3 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Fri, 14 Aug 2026 15:11:44 +0700
Subject: [PATCH 10/15] docs: moved sapi input filtering
---
docs/source/extensions/bundled-extensions.md | 7 +++++++
.../source/extensions/bundled-extensions/filter.md | 2 +-
2 files changed, 8 insertions(+), 1 deletion(-)
rename docs-old/input-filter.md => docs/source/extensions/bundled-extensions/filter.md (99%)
diff --git a/docs/source/extensions/bundled-extensions.md b/docs/source/extensions/bundled-extensions.md
index 9ee603b69591..dff0b854bbaf 100644
--- a/docs/source/extensions/bundled-extensions.md
+++ b/docs/source/extensions/bundled-extensions.md
@@ -1,5 +1,12 @@
# PHP build system V5 overview
+```{toctree}
+---
+hidden: true
+---
+bundled-extensions/filter
+```
+
- supports Makefile.ins during transition phase
- not-really-portable Makefile includes have been eliminated
- supports separate build directories without VPATH by using explicit rules only
diff --git a/docs-old/input-filter.md b/docs/source/extensions/bundled-extensions/filter.md
similarity index 99%
rename from docs-old/input-filter.md
rename to docs/source/extensions/bundled-extensions/filter.md
index b4df9a6e77df..01b71d38994d 100644
--- a/docs-old/input-filter.md
+++ b/docs/source/extensions/bundled-extensions/filter.md
@@ -1,4 +1,4 @@
-# Input filter support in PHP
+# ext/filter
XSS (Cross Site Scripting) hacks are becoming more and more prevalent, and can
be quite difficult to prevent. Whenever you accept user data and somehow display
From c4ff5e494a5c21e9a7274398a948eb6b3309821f Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Tue, 18 Aug 2026 16:03:16 +0700
Subject: [PATCH 11/15] docs: moved streams
---
docs-old/streams.md => docs/source/core/streams/index.md | 0
docs/source/index.md | 1 +
2 files changed, 1 insertion(+)
rename docs-old/streams.md => docs/source/core/streams/index.md (100%)
diff --git a/docs-old/streams.md b/docs/source/core/streams/index.md
similarity index 100%
rename from docs-old/streams.md
rename to docs/source/core/streams/index.md
diff --git a/docs/source/index.md b/docs/source/index.md
index 441a9e38cb8b..d1a8c272b363 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -17,6 +17,7 @@ hidden: true
core/data-structures/index
core/memory-management/index
core/output-buffering
+core/streams/index
```
```{toctree}
From e7b034c8e2d1d5213ba9c9ffc80366e1452dd907 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Wed, 19 Aug 2026 00:54:29 +0700
Subject: [PATCH 12/15] docs: moved testing
---
docs/source/index.md | 11 +++++++++--
.../running-tests/index.md} | 0
.../writing-tests/index.md} | 0
3 files changed, 9 insertions(+), 2 deletions(-)
rename docs/source/{miscellaneous/running-tests.md => testing/running-tests/index.md} (100%)
rename docs/source/{miscellaneous/writing-tests.md => testing/writing-tests/index.md} (100%)
diff --git a/docs/source/index.md b/docs/source/index.md
index d1a8c272b363..d6d73ca5c43f 100644
--- a/docs/source/index.md
+++ b/docs/source/index.md
@@ -30,14 +30,21 @@ extensions/bundled-extensions
extensions/unbundled-extensions
```
+```{toctree}
+---
+caption: Testing
+hidden: true
+---
+testing/running-tests/index
+testing/writing-tests/index
+```
+
```{toctree}
---
caption: Miscellaneous
hidden: true
---
miscellaneous/stubs
-miscellaneous/writing-tests
-miscellaneous/running-tests
```
Welcome to the php-src documentation!
diff --git a/docs/source/miscellaneous/running-tests.md b/docs/source/testing/running-tests/index.md
similarity index 100%
rename from docs/source/miscellaneous/running-tests.md
rename to docs/source/testing/running-tests/index.md
diff --git a/docs/source/miscellaneous/writing-tests.md b/docs/source/testing/writing-tests/index.md
similarity index 100%
rename from docs/source/miscellaneous/writing-tests.md
rename to docs/source/testing/writing-tests/index.md
From 9b20e300dbe4b2df8360adc90522753c793e9672 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Wed, 19 Aug 2026 01:07:28 +0700
Subject: [PATCH 13/15] docs: redirected moved testing pages
---
docs/source/conf.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 28ee3caee85e..1fec64c317d6 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -71,6 +71,8 @@
redirects = {
'core/data-structures/reference-counting': '../memory-management/reference-counting.html',
+ 'miscellaneous/running-tests': '../testing/running-tests/index.html',
+ 'miscellaneous/writing-tests': '../testing/writing-tests/index.html',
}
From 8e0fbae4fb7799f4a761ff54a25c1b16630da2c6 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Wed, 19 Aug 2026 01:08:42 +0700
Subject: [PATCH 14/15] docs: migrated old output buffering
---
docs/source/core/output-buffering.md | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/docs/source/core/output-buffering.md b/docs/source/core/output-buffering.md
index 28028ea8f3c6..ff0b2d2b12b1 100644
--- a/docs/source/core/output-buffering.md
+++ b/docs/source/core/output-buffering.md
@@ -1,6 +1,6 @@
-# API adjustment to the old output control code
+# Output Buffering
-Everything now resides beneath the php_output namespace, and there's an API call
+Everything resides beneath the `php_output` namespace, and there's an API call
for every output handler op.
```
@@ -55,11 +55,11 @@ Discarding all output buffers:
// php_ob_end_buffers(0);
php_output_discard_all();
-Stopping (and dropping) one output buffer:
+Finalising and removing one output handler:
// php_ob_end_buffer(1, 0)
php_output_end();
-Stopping (and dropping) all output buffers:
+Finalising and removing all output handlers:
// php_ob_end_buffers(1, 0);
php_output_end_all();
@@ -108,7 +108,7 @@ Recognizing by the output handler itself if it gets discarded:
if ((flags & PHP_OUTPUT_HANDLER_CLEAN) && (flags & PHP_OUTPUT_HANDLER_FINAL)) { ... }
```
-## Output handler hooks
+## Output Handler Hooks
The output handler can change its abilities at runtime. For example, the gz handler can
remove the CLEANABLE and REMOVABLE bits when the first output has passed through it;
From 01c62e29a2125b5badc111ae97f22314222769c2 Mon Sep 17 00:00:00 2001
From: NickSdot
Date: Wed, 19 Aug 2026 01:09:51 +0700
Subject: [PATCH 15/15] docs: migrated streams
- replaced obsolete prototypes and external MySQL example with current stream contracts
---
docs/source/core/streams/index.md | 503 +++++++++---------------------
1 file changed, 156 insertions(+), 347 deletions(-)
diff --git a/docs/source/core/streams/index.md b/docs/source/core/streams/index.md
index 1f05dee90e1a..b07b2b754969 100644
--- a/docs/source/core/streams/index.md
+++ b/docs/source/core/streams/index.md
@@ -1,411 +1,220 @@
-# An overview of the PHP streams abstraction
+# Streams
-> [!WARNING]
-> Some prototypes in this file are out of date.
-
-## Why streams?
-
-You may have noticed a shed-load of issock parameters flying around the PHP
-code; we don't want them - they are ugly and cumbersome and force you to special
-case sockets and files every time you need to work with a "user-level" PHP file
-pointer.
-
-Streams take care of that and present the PHP extension coder with an ANSI
-stdio-alike API that looks much nicer and can be extended to support non file
-based data sources.
+PHP streams provide one byte-stream abstraction for files, sockets, memory and
+wrapper-backed sources. Core code can therefore avoid source-specific I/O
+paths. The stream layer also coordinates buffering, filters, contexts and
+resource lifetime.
-## Using streams
-
-Streams use a `php_stream*` parameter just as ANSI stdio (fread etc.) use a
-`FILE*` parameter.
-
-The main functions are:
-
-```c
-PHPAPI size_t php_stream_read(php_stream * stream, char * buf, size_t count);
-PHPAPI size_t php_stream_write(php_stream * stream, const char * buf, size_t
- count);
-PHPAPI size_t php_stream_printf(php_stream * stream,
- const char * fmt, ...);
-PHPAPI int php_stream_eof(php_stream * stream);
-PHPAPI int php_stream_getc(php_stream * stream);
-PHPAPI char *php_stream_gets(php_stream * stream, char *buf, size_t maxlen);
-PHPAPI int php_stream_close(php_stream * stream);
-PHPAPI int php_stream_flush(php_stream * stream);
-PHPAPI int php_stream_seek(php_stream * stream, off_t offset, int whence);
-PHPAPI off_t php_stream_tell(php_stream * stream);
-PHPAPI int php_stream_lock(php_stream * stream, int mode);
-```
-
-These (should) behave in the same way as the ANSI stdio functions with similar
-names: fread, fwrite, fprintf, feof, fgetc, fgets, fclose, fflush, fseek, ftell,
-flock.
-
-## Opening streams
-
-In most cases, you should use this API:
-
-```c
-PHPAPI php_stream *php_stream_open_wrapper(const char *path, const char *mode,
- int options, char **opened_path);
-```
+## Basic Operations
-Where:
-
-- `path` is the file or resource to open.
-- `mode` is the stdio compatible mode eg: "wb", "rb" etc.
-- `options` is a combination of the following values:
- - `IGNORE_PATH` (default) - don't use include path to search for the file
- - `USE_PATH` - use include path to search for the file
- - `IGNORE_URL` - do not use plugin wrappers
- - `REPORT_ERRORS` - show errors in a standard format if something goes wrong.
- - `STREAM_MUST_SEEK` - If you really need to be able to seek the stream and
- don't need to be able to write to the original file/URL, use this option to
- arrange for the stream to be copied (if needed) into a stream that can be
- seek()ed.
-- `opened_path` is used to return the path of the actual file opened, but if you
- used `STREAM_MUST_SEEK`, may not be valid. You are responsible for
- `efree()ing` `opened_path`.
-- `opened_path` may be (and usually is) `NULL`.
-
-If you need to open a specific stream, or convert standard resources into
-streams there are a range of functions to do this defined in `php_streams.h`. A
-brief list of the most commonly used functions:
+A `php_stream *` has a similar role to a `FILE *`. Normal callers use the API
+in `main/php_streams.h` rather than accessing its fields. The main operations
+are:
```c
-PHPAPI php_stream *php_stream_fopen_from_file(FILE *file, const char *mode);
- /* Convert a FILE * into a stream. */
-
-PHPAPI php_stream *php_stream_fopen_tmpfile(void);
- /* Open a FILE * with tmpfile() and convert into a stream. */
-
-PHPAPI php_stream *php_stream_fopen_temporary_file(const char *dir,
- const char *pfx, char **opened_path);
- /* Generate a temporary file name and open it. */
+PHPAPI ssize_t php_stream_read(
+ php_stream *stream, char *buf, size_t count
+);
+PHPAPI ssize_t php_stream_write(
+ php_stream *stream, const char *buf, size_t count
+);
+PHPAPI ssize_t php_stream_printf(
+ php_stream *stream, const char *fmt, ...
+);
+PHPAPI bool php_stream_eof(php_stream *stream);
+PHPAPI int php_stream_getc(php_stream *stream);
+PHPAPI char *php_stream_get_line(
+ php_stream *stream, char *buf, size_t maxlen, size_t *returned_len
+);
+PHPAPI int php_stream_flush(php_stream *stream);
+PHPAPI int php_stream_seek(
+ php_stream *stream, zend_off_t offset, int whence
+);
+PHPAPI zend_off_t php_stream_tell(const php_stream *stream);
+#define php_stream_close(stream) \
+ php_stream_free((stream), PHP_STREAM_FREE_CLOSE)
```
-There are some network enabled relatives in `php_network.h`:
-
-```c
-PHPAPI php_stream *php_stream_sock_open_from_socket(int socket, int persistent);
- /* Convert a socket into a stream. */
+These mostly follow their stdio equivalents. Reads and writes return `ssize_t`,
+so a negative result can report failure. Positions and offsets use
+`zend_off_t`.
-PHPAPI php_stream *php_stream_sock_open_host(const char *host, unsigned short port,
- int socktype, int timeout, int persistent);
- /* Open a connection to a host and return a stream. */
+Use these functions rather than calling `stream->ops` directly. The stream
+layer maintains buffering, filters and its logical position around the
+underlying operations.
-PHPAPI php_stream *php_stream_sock_open_unix(const char *path, int persistent,
- struct timeval *timeout);
- /* Open a UNIX domain socket. */
-```
+Use `php_stream_supports_lock()` before `php_stream_lock()` when locking is
+required. Both delegate to the implementation's `set_option` callback.
-## Stream utilities
+## Opening Streams
-If you need to copy some data from one stream to another, you will be please to
-know that the streams API provides a standard way to do this:
+Use `php_stream_open_wrapper()` for paths handled by stream wrappers:
```c
-PHPAPI size_t php_stream_copy_to_stream(php_stream *src,
- php_stream *dest, size_t maxlen);
+zend_string *opened_path = NULL;
+php_stream *stream = php_stream_open_wrapper(
+ path, mode, options, &opened_path
+);
```
-If you want to copy all remaining data from the src stream, pass
-`PHP_STREAM_COPY_ALL` as the maxlen parameter, otherwise maxlen indicates the
-number of bytes to copy. This function will try to use mmap where available to
-make the copying more efficient.
+`options` is a bitmask. Common values are:
-If you want to read the contents of a stream into an allocated memory buffer,
-you should use:
+- `USE_PATH`: search `PG(include_path)`.
+- `IGNORE_URL`: disallow URL wrappers.
+- `REPORT_ERRORS`: report failures through the stream error API.
+- `STREAM_MUST_SEEK`: return a seekable stream or fail.
+- `STREAM_WILL_CAST`: prepare a wrapper stream for a later cast.
+- `STREAM_OPEN_PERSISTENT`: require a persistent stream.
-```c
-PHPAPI size_t php_stream_copy_to_mem(php_stream *src, char **buf,
- size_t maxlen, int persistent);
-```
+Pass `NULL` instead of `&opened_path` when the resolved path is not needed.
+Otherwise, release a returned path with `zend_string_release()`.
+`php_stream_open_wrapper_ex()` additionally accepts a `php_stream_context *`.
-This function will set buf to the address of the buffer that it allocated, which
-will be maxlen bytes in length, or will be the entire length of the data
-remaining on the stream if you set maxlen to `PHP_STREAM_COPY_ALL`. The buffer
-is allocated using `pemalloc()`. You need to call `pefree()` to release the
-memory when you are done. As with `copy_to_stream`, this function will try use
-mmap where it can.
+Helpers for plain files, descriptors, pipes and temporary files are declared
+in `main/streams/php_stream_plain_wrapper.h`. Socket helpers are declared in
+`main/php_network.h`.
-If you have an existing stream and need to be able to `seek()` it, you can use
-this function to copy the contents into a new stream that can be `seek()ed`:
+## Copying Streams
-```c
-PHPAPI int php_stream_make_seekable(php_stream *origstream, php_stream **newstream);
-```
-
-It returns one of the following values:
+Use the following interfaces to copy between streams or into memory:
```c
-#define PHP_STREAM_UNCHANGED 0 /* orig stream was seekable anyway */
-#define PHP_STREAM_RELEASED 1 /* newstream should be used; origstream is no longer valid */
-#define PHP_STREAM_FAILED 2 /* an error occurred while attempting conversion */
-#define PHP_STREAM_CRITICAL 3 /* an error occurred; origstream is in an unknown state; you should close origstream */
-```
-
-`make_seekable` will always set newstream to be the stream that is valid if the
-function succeeds. When you have finished, remember to close the stream.
-
-> [!NOTE]
-> If you only need to seek forward, there is no need to call this function, as
-> `php_stream_seek` can emulate forward seeking when the whence parameter is
-> `SEEK_CUR`.
-
-> [!NOTE]
-> Writing to the stream may not affect the original source, so it only makes
-> sense to use this for read-only use.
-
-> [!NOTE]
-> If the origstream is network based, this function will block until the whole
-> contents have been downloaded.
+zend_result php_stream_copy_to_stream_ex(
+ php_stream *src,
+ php_stream *dest,
+ size_t maxlen,
+ size_t *copied
+);
-> [!NOTE]
-> Never call this function with an origstream that is referenced as a resource!
-> It will close the origstream on success, and this can lead to a crash when the
-> resource is later used/released.
-
-> [!NOTE]
-> If you are opening a stream and need it to be seekable, use the
-> `STREAM_MUST_SEEK` option to `php_stream_open_wrapper()`.
-
-```c
-PHPAPI int php_stream_supports_lock(php_stream * stream);
+zend_string *php_stream_copy_to_mem(
+ php_stream *src,
+ size_t maxlen,
+ bool persistent
+);
```
-This function will return either 1 (success) or 0 (failure) indicating whether
-or not a lock can be set on this stream. Typically, you can only set locks on
-stdio streams.
+Pass `PHP_STREAM_COPY_ALL` to copy until EOF. `copied` may be `NULL` when the
+length is not needed. `php_stream_copy_to_stream()` is deprecated; use the
+`_ex` form so failure is distinguishable from the byte count.
-## Casting streams
+Release a non-`NULL` string returned by `php_stream_copy_to_mem()` with
+`zend_string_release()`. Its `persistent` argument controls the string's
+allocation.
-What if your extension needs to access the `FILE*` of a user level file pointer?
-You need to "cast" the stream into a `FILE*`, and this is how you do it:
+## Seeking
-```c
-FILE * fp;
-php_stream * stream; /* already opened */
+`php_stream_seek()` accounts for buffered data and can emulate a forward
+`SEEK_CUR` by reading. Arbitrary seeks require the stream implementation to
+provide a `seek` operation.
-if (php_stream_cast(stream, PHP_STREAM_AS_STDIO, (void*)&fp, REPORT_ERRORS) == FAILURE) {
- RETURN_FALSE;
-}
-```
+`STREAM_MUST_SEEK` makes `php_stream_open_wrapper()` copy a non-seekable source
+to a temporary stream. When this occurs, opening may block until the source is
+exhausted, and writes to the temporary stream do not affect the source.
-The prototype is:
+`php_stream_make_seekable()` performs the same conversion explicitly:
```c
-PHPAPI int php_stream_cast(php_stream * stream, int castas, void ** ret, int show_err);
+php_stream *seekable;
+php_stream_make_seekable_status status = php_stream_make_seekable(
+ stream, &seekable, PHP_STREAM_NO_PREFERENCE
+);
```
-The `show_err` parameter, if non-zero, will cause the function to display an
-appropriate error message of type `E_WARNING` if the cast fails.
-
-`castas` can be one of the following values:
-
-```txt
-PHP_STREAM_AS_STDIO - a stdio FILE*
-PHP_STREAM_AS_FD - a generic file descriptor
-PHP_STREAM_AS_SOCKETD - a socket descriptor
-```
+Use `PHP_STREAM_PREFER_STDIO` to prefer a file-backed replacement, or
+`PHP_STREAM_FORCE_CONVERSION` to replace an already seekable stream.
-If you ask a socket stream for a `FILE*`, the abstraction will use fdopen to
-create it for you. Be warned that doing so may cause buffered data to be lost
-if you mix ANSI stdio calls on the FILE\* with php stream calls on the stream.
+- `PHP_STREAM_UNCHANGED`: the returned stream is the original stream.
+- `PHP_STREAM_RELEASED`: the returned stream replaces the closed original.
+- `PHP_STREAM_FAILED`: conversion failed and the original remains valid.
+- `PHP_STREAM_CRITICAL`: conversion failed; close the original stream.
-If your system has the fopencookie function, php streams can synthesize a
-`FILE*` on top of any stream, which is useful for SSL sockets, memory based
-streams, database streams etc. etc.
+After either success result, use the returned stream.
-In situations where this is not desirable, you should query the stream to see if
-it naturally supports `FILE *`. You can use this code snippet for this purpose:
-
-```c
-if (php_stream_is(stream, PHP_STREAM_IS_STDIO)) {
- /* can safely cast to FILE* with no adverse side effects */
-}
-```
-
-You can use:
+> [!WARNING]
+> Never call `php_stream_make_seekable()` for a stream referenced by a resource.
+> It may close the original while the resource still points to it.
-```c
-PHPAPI int php_stream_can_cast(php_stream * stream, int castas)
-```
+## Casting Streams
-to find out if a stream can be cast, without actually performing the cast, so to
-check if a stream is a socket you might use:
+`php_stream_cast()` exposes a compatible underlying handle:
```c
-if (php_stream_can_cast(stream, PHP_STREAM_AS_SOCKETD) == SUCCESS) {
- /* it can be a socket */
-}
+PHPAPI zend_result php_stream_cast(
+ php_stream *stream,
+ int castas,
+ void **result,
+ int show_err
+);
```
-Please note the difference between `php_stream_is` and `php_stream_can_cast`;
-`stream_is` tells you if the stream is a particular type of stream, whereas
-`can_cast` tells you if the stream can be forced into the form you request. The
-former doesn't change anything, while the later *might* change some state in the
-stream.
+The base cast types are:
-## Stream internals
+- `PHP_STREAM_AS_STDIO`: a `FILE *`.
+- `PHP_STREAM_AS_FD`: a file descriptor.
+- `PHP_STREAM_AS_SOCKETD`: a socket descriptor.
+- `PHP_STREAM_AS_FD_FOR_SELECT`: a descriptor for `select()`.
+- `PHP_STREAM_AS_FD_FOR_COPY`: a `php_io_fd` for internal copying.
-There are two main structures associated with a stream - the `php_stream`
-itself, which holds some state information (and possibly a buffer) and a
-`php_stream_ops` structure, which holds the "virtual method table" for the
-underlying implementation.
+A non-zero `show_err` reports a warning when casting fails.
+`php_stream_can_cast()` queries support by passing a `NULL` result, while
+`php_stream_is()` only compares the operations table.
-The `php_streams` ops struct consists of pointers to methods that implement
-read, write, close, flush, seek, gets and cast operations. Of these, an
-implementation need only implement write, read, close and flush. The gets method
-is intended to be used for streams if there is an underlying method that can
-efficiently behave as fgets. The ops struct also contains a label for the
-implementation that will be used when printing error messages - the stdio
-implementation has a label of `STDIO` for example.
+Avoid `PHP_STREAM_CAST_TRY_HARD` unless consuming the source into a temporary
+stream is acceptable. `PHP_STREAM_CAST_RELEASE` invalidates the stream after a
+successful cast.
-The idea is that a stream implementation defines a `php_stream_ops` struct, and
-associates it with a `php_stream` using `php_stream_alloc`.
+Where supported, a stdio cast may create a `FILE *` with `fopencookie()` rather
+than expose an existing handle.
-As an example, the `php_stream_fopen()` function looks like this:
+Do not interleave access through a cast handle with `php_stream_*()` calls.
+Their separate buffering can desynchronise positions or lose buffered data.
-```c
-PHPAPI php_stream * php_stream_fopen(const char * filename, const char * mode)
-{
- FILE * fp = fopen(filename, mode);
- php_stream * ret;
-
- if (fp) {
- ret = php_stream_alloc(&php_stream_stdio_ops, fp, 0, 0, mode);
- if (ret)
- return ret;
-
- fclose(fp);
- }
- return NULL;
-}
-```
-
-`php_stream_stdio_ops` is a `php_stream_ops` structure that can be used to
-handle `FILE*` based streams.
-
-A socket based stream would use code similar to that above to create a stream to
-be passed back to fopen_wrapper (or it's yet to be implemented successor).
+## Stream Implementations
-The prototype for php_stream_alloc is this:
+A `php_stream` holds common state and a `php_stream_ops` table. Implementations
+store their own state in `stream->abstract` and allocate the stream with
+`php_stream_alloc()`.
-```c
-PHPAPI php_stream * php_stream_alloc(php_stream_ops * ops, void * abstract,
- size_t bufsize, int persistent, const char * mode)
-```
-
-- `ops` is a pointer to the implementation,
-- `abstract` holds implementation specific data that is relevant to this
- instance of the stream,
-- `bufsize` is the size of the buffer to use - if 0, then buffering at the
- stream
-- `level` will be disabled (recommended for underlying sources that implement
- their own buffering - such a `FILE*`)
-- `persistent` controls how the memory is to be allocated - persistently so that
- it lasts across requests, or non-persistently so that it is freed at the end
- of a request (it uses pemalloc),
-- `mode` is the stdio-like mode of operation - php streams places no real
- meaning in the mode parameter, except that it checks for a `w` in the string
- when attempting to write (this may change).
-
-The mode parameter is passed on to `fdopen/fopencookie` when the stream is cast
-into a `FILE*`, so it should be compatible with the mode parameter of `fopen()`.
-
-## Writing your own stream implementation
-
-- **RULE #1**: when writing your own streams: make sure you have configured PHP
- with `--enable-debug`.
- Some great great pains have been taken to hook into the Zend memory manager to
- help track down allocation problems. It will also help you spot incorrect use
- of the STREAMS_DC, STREAMS_CC and the semi-private STREAMS_REL_CC macros for
- function definitions.
-
-- RULE #2: Please use the stdio stream as a reference; it will help you
- understand the semantics of the stream operations, and it will always be more
- up to date than these docs :-)
-
-First, you need to figure out what data you need to associate with the
-`php_stream`. For example, you might need a pointer to some memory for memory
-based streams, or if you were making a stream to read data from an RDBMS like
-MySQL, you might want to store the connection and rowset handles.
-
-The stream has a field called abstract that you can use to hold this data. If
-you need to store more than a single field of data, define a structure to hold
-it, allocate it (use pemalloc with the persistent flag set appropriately), and
-use the abstract pointer to refer to it.
-
-For structured state you might have this:
+For a normal data stream, `write`, `read`, `close` and `flush` are mandatory.
+`seek`, `cast`, `stat` and `set_option` are optional:
```c
-struct my_state {
- MYSQL conn;
- MYSQL_RES * result;
+static const php_stream_ops my_ops = {
+ my_write,
+ my_read,
+ my_close,
+ my_flush,
+ "my stream",
+ my_seek,
+ NULL, /* cast */
+ NULL, /* stat */
+ NULL, /* set_option */
};
-
-struct my_state * state = pemalloc(sizeof(struct my_state), persistent);
-
-/* initialize the connection, and run a query, using the fields in state to
- * hold the results */
-
-state->result = mysql_use_result(&state->conn);
-
-/* now allocate the stream itself */
-stream = php_stream_alloc(&my_ops, state, 0, persistent, "r");
-
-/* now stream->abstract == state */
```
-Once you have that part figured out, you can write your implementation and
-define your own php_stream_ops struct (we called it my_ops in the above
-example).
+The callbacks have several important contracts:
-For example, for reading from this weird MySQL stream:
-
-```c
-static size_t php_mysqlop_read(php_stream * stream, char * buf, size_t count)
-{
- struct my_state * state = (struct my_state*)stream->abstract;
-
- if (buf == NULL && count == 0) {
- /* in this special case, php_streams is asking if we have reached the
- * end of file */
- if (... at end of file ...)
- return EOF;
- else
- return 0;
- }
-
- /* pull out some data from the stream and put it in buf */
- ... mysql_fetch_row(state->result) ...
- /* we could do something strange, like format the data as XML here,
- and place that in the buf, but that brings in some complexities,
- such as coping with a buffer size too small to hold the data,
- so I won't even go in to how to do that here */
-}
-```
+- `read` and `write` return a byte count or a negative value on failure.
+- `read` sets `stream->eof` when the source reaches its final EOF.
+- `seek` writes the new position and returns zero on success.
+- `close` releases owned state in `stream->abstract` and honours `close_handle`
+ for any underlying handle.
-Implement the other operations - remember that write, read, close and flush are
-all mandatory. The rest are optional. Declare your stream ops struct:
+Allocate a non-persistent stream as follows:
```c
-php_stream_ops my_ops = {
- php_mysqlop_write, php_mysqlop_read, php_mysqlop_close,
- php_mysqlop_flush, NULL, NULL, NULL,
- "Strange MySQL example"
-}
+php_stream *stream = php_stream_alloc(&my_ops, state, NULL, mode);
```
-That's it!
+The third argument is a persistent identifier, not a Boolean flag. When it is
+non-`NULL`, owned implementation state must use matching persistent allocation.
+Use `php_stream_is_persistent()` when freeing that state. Supply a valid
+fopen-style `mode`; streams retain it for casts and other stream operations.
-Take a look at the STDIO implementation in streams.c for more information about
-how these operations work.
+Use `php_stream_to_zval()` when returning a stream as a PHP resource. Once
+exposed, its resource controls the stream's lifetime.
-The main thing to remember is that in your close operation you need to release
-and free the resources you allocated for the abstract field. In the case of the
-example above, you need to use mysql_free_result on the rowset, close the
-connection and then use pefree to dispose of the struct you allocated. You may
-read the stream->persistent field to determine if your struct was allocated in
-persistent mode or not.
+Build PHP with `--enable-debug` while developing an implementation. The
+`STREAMS_*` call-site macros then help diagnose allocation and lifetime errors.
+Current examples are available in `main/streams/plain_wrapper.c` and bundled
+extensions such as `ext/bz2/bz2.c`.