5Stub files are pieces of PHP code which only contain declarations. They do not include runnable
6code, but instead contain empty function and method bodies. A very basic stub looks like this:
12 const ANIMAL = "Elephant";
17 public function calculateBar(): float {}
20 function fahrenheitToCelcius(float $fahrenheitToCelcius): float {}
22Any kind of symbol can be declared via stubs. Every type can be used, with the exception of
23disjunctive normal form (DNF) types. Additional meta information can be added via PHPDoc blocks or
24PHP attributes. Namespaces can also be used by adding a top-level ``namespace`` declaration or by
25using namespace blocks:
32 const ANIMAL = "Elephant";
34 const WEIGHT_TON = 6.8;
37 public function calculateBar(): float {}
41 namespace Algorithms {
42 function fahrenheitToCelcius(float $fahrenheit): float {}
45The above example declares the global constants ``ANIMAL`` and ``WEIGHT_TON``, and the class
46``Atmopshere`` in the top-level namespace. The ``fahrenheitToCelcius()`` function is declared to be
47in the ``Algorithms`` namespace.
53Stub files have the ``.stub.php`` extension by convention.
55They are processed by ``build/gen_stub.php``, which uses PHP-Parser_ for parsing. Depending on the
56configuration and the supplied arguments, it can generate various artefacts.
58The following sections will introduce these capabilities.
60.. _php-parser: https://github.com/nikic/PHP-Parser
62*******************************
63 Generating arginfo Structures
64*******************************
66The purpose of stubs files is to make it easier to declare arginfo structures, validate parameters
67parsing declarations, and maintain documentation.
69Previously, you had to manually use the different ``ZEND_BEGIN_ARG_* ... ZEND_END_ARG_INFO()``
70macros. This is a tedious and error-prone process. Being able to use pure PHP code on which the C
71code can be generated is a huge benefit.
73The arginfo file matching our first example looks like:
77 /* This is a generated file, edit the .stub.php file instead.
78 * Stub hash: e4ed788d54a20272a92a3f6618b73d48ec848f97 */
80 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fahrenheitToCelcius, 0, 1, IS_DOUBLE, 0)
81 ZEND_ARG_TYPE_INFO(0, fahrenheitToCelcius, IS_DOUBLE, 0)
84 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Atmopshere_calculateBar, 0, 0, IS_DOUBLE, 0)
87The hash that is included in the file makes sure that stub files are not reprocessed unless the stub
88file was modified, or something requires it to be processed (e.g. regeneration was forced by using
91Another thing to keep in mind is that stub-based type declarations have to be in sync with the
92parameter parsing code in the PHP functions through ``ZEND_PARSE_PARAMETERS_*`` macros (``ZPP``).
94In release builds, the arginfo structures are only used with Reflection.
96In debug builds, PHP will compare arginfo structures against ZPP macros to ensure that the stubs and
97actual data matches for both arguments and return types. If they do not, an error is generated.
98Therefore it is important that you declare the right types in your stub files.
100For documentation purposes, PHPDoc can be used.
102Since PHP 8.0, arginfo structures can also contain default values. These can for example be used by
103``ReflectionParameter::getDefaultValue()``.
105Besides constant literals, default values can also contain compile-time evaluable expressions, and
106contain references to constants.
108In the example below, we define a function with an optional argument, referencing a constant:
114 const ANIMAL = "Elephant";
116 function formatName(string $defaultName = ANIMAL . " Mc" . ANIMAL . "Face"): string {}
118This will result in the following arginfo:
122 /* This is a generated file, edit the .stub.php file instead.
123 * Stub hash: a9685164284e73f47b15838122b631ebdfef23d6 */
125 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_formatName, 0, 0, IS_STRING, 0)
126 ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, defaultName, IS_STRING, 0, "ANIMAL . \" Mc\" . ANIMAL . \"Face\"")
129You can only use constants as long as they are defined in the same stub file.
131If this is not possible, then the stub declaring the constant should be included with ``require``:
135 // constants.stub.php
138 const ANIMAL = "Elephant";
144 require "constants.stub.php";
146 function foo(string $param = ANIMAL): string {}
148Sometimes arguments have to be passed by reference, or by using the `ZEND_SEND_PREFER_REF` flag.
150To signal parsing by reference, use the usual ``&`` syntax.
152To include the ``ZEND_SEND_PREFER_REF`` flag, use the ``@prefer-ref`` PHPDoc tag:
159 * @prefer-ref $elephantName
161 function addElephantsToHerd(&$herd, string $elephantName): string {}
163This results in the following arginfo file:
167 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_addElephantsToHerd, 0, 2, IS_STRING, 0)
168 ZEND_ARG_INFO(1, herd)
169 ZEND_ARG_TYPE_INFO(ZEND_SEND_PREFER_REF, elephantName, IS_STRING, 0)
172*****************************
173 Generating Function Entries
174*****************************
176Besides arginfo structures, function entries themselves can also be generated via stubs.
178In order to generate these, add the file-level ``@generate-function-entries`` PHPDoc tag:
183 /** @generate-function-entries */
186 public function calculateBar(): float {}
189 function fahrenheitToCelcius(float $fahrenheit): float {}
191Now, the following C code is generated:
195 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_fahrenheitToCelcius, 0, 1, IS_DOUBLE, 0)
196 ZEND_ARG_TYPE_INFO(0, fahrenheit, IS_DOUBLE, 0)
199 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Atmosphere_calculateBar, 0, 0, IS_DOUBLE, 0)
202 ZEND_FUNCTION(fahrenheitToCelcius);
203 ZEND_METHOD(Atmosphere, calculateBar);
205 static const zend_function_entry ext_functions[] = {
206 ZEND_FE(fahrenheitToCelcius, arginfo_fahrenheitToCelcius)
210 static const zend_function_entry class_Atmosphere_methods[] = {
211 ZEND_ME(Atmosphere, calculateBar, arginfo_class_Atmosphere_calculateBar, ZEND_ACC_PUBLIC)
215The generated ``ext_functions`` variable must be passed as the ``functions`` member of
216`zend_module_entry` struct.
218The generated ``class_Atmosphere_methods`` must be used when registering the ``Atmosphere`` class:
222 INIT_CLASS_ENTRY(ce, "Atmosphere", class_Atmosphere_methods);
224Additional meta information can be attached to functions, with the following PHPDoc tags:
226- ``@deprecated``: Triggers the usual deprecation notice when the function/method is called.
228- ``@alias``: If a function/method is an alias of another function/method, then the aliased
229 function/method name has to be provided as value. E.g. the function ``sizeof()` has the ``@alias
232- ``@implementation-alias``: This is very similar to ``@alias`` with some semantic differences.
233 These aliases exists purely to avoid duplicating some code, but there is no other connection
234 between the alias and the aliased function or method.
236 A notable example is ``Error::getCode()``, which has the ``@implementation-alias
237 Exception::getCode`` annotation.
239 The difference between ``@alias`` and ``@implementation-alias`` is very nuanced and is only
240 observable in the manual.
242- ``@tentative-return-type``: By using this annotation, the return type declaration is reclassified
243 as a `tentative return type`_.
245- ``@genstubs-expose-comment-block``: By adding this annotation at the beginning of a PHPDoc block,
246 the content of the PHPDoc block will be exposed for
247 `ReflectionFunctionAbstract::getDocComment()`. This feature was added in PHP 8.4.0.
249.. _tentative return type: https://wiki.php.net/rfc/internal_method_return_types
251**************************
252 Generating Class Entries
253**************************
255In order to generate code which is necessary for registering constants, classes, properties, enums,
256and traits, use the ``@generate-class-entries`` file-level PHPDoc block.
258``@generate-class-entries`` implies ``@generate-function-entries```, so the latter is then
261Given the following stub:
266 /** @generate-class-entries */
268 enum Number: string {
270 public const ONE = "one";
272 case One = Number::ONE;
273 case Two = Number::TWO;
276 class Elephant extends stdClass {
278 public const float PI = UNKNOWN;
280 public readonly string $name;
283The following arginfo file is generated:
287 static const zend_function_entry class_Number_methods[] = {
291 static const zend_function_entry class_Elephant_methods[] = {
295 static zend_class_entry *register_class_Number(void)
297 zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods);
304 static zend_class_entry *register_class_Elephant(zend_class_entry *class_entry_stdClass)
306 zend_class_entry ce, *class_entry;
308 INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
309 class_entry = zend_register_internal_class_ex(&ce, class_entry_stdClass);
316The generated ``register_class_*()`` functions must be used to register these classes in the
317``PHP_MINIT_FUNCTION`` directly:
321 zend_class_entry *number_ce = register_class_Number();
322 zend_class_entry *elephpant_ce = register_class_Elephant(zend_standard_class_def);
324Class dependencies, such as the parent class or implemented interface, have to be passed to the
325register function. In the example above, we passed the class entry for ``stdClass``
326(``zend_standard_class_def``).
328Like functions and methods, classes also support meta information passed via PHPDoc tags:
330- ``@deprecated``: triggers a deprecation notice when the class is used
332- ``@strict-properties``: adds the ``ZEND_ACC_NO_DYNAMIC_PROPERTIES`` flag for the class (as of PHP
333 8.0), which disallow dynamic properties.
335- ``@not-serializable``: adds the ``ZEND_ACC_NOT_SERIALIZABLE`` flag for the class (as of PHP 8.1),
336 which prevents the serialization of the class.
338- ``@genstubs-expose-comment-block``: By adding this tag at the beginning of a PHPDoc block, the
339 content of the PHPDoc block will be exposed for `ReflectionClass::getDocComment()`. This feature
340 is only available as of PHP 8.4.0.
342This is an example with all the flags:
348 * @generate-class-entries
354 * @strict-properties */
355 /** @genstubs-expose-comment-block
357 * @see https://www.php.net */
358 class Elephant extends stdClass {
359 public readonly string $name;
362Resulting in these changes:
368 static zend_class_entry *register_class_Elephant(zend_class_entry *class_entry_stdClass)
370 zend_class_entry ce, *class_entry;
372 INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
373 class_entry = zend_register_internal_class_ex(&ce, class_entry_stdClass);
374 class_entry->ce_flags |= ZEND_ACC_DEPRECATED|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
375 class_entry->doc_comment = zend_string_init_interned("/**\n * This is a comment\n * @see https://www.php.net */", 55, 1);
382********************************************
383 Generating Global Constants and Attributes
384********************************************
386Although global constants and function attributes do not relate to classes, they require the ``/**
387@generate-class-entries */`` file-level PHPDoc block.
389If a global constant or function attribute are present in the stub file, the generated C-code will
390include a ``register_{$stub_file_name}_symbols()`` file.
392Given the following file:
398 /** @generate-class-entries */
401 const ANIMAL = "Elephant";
409 function connect(#[\SensitiveParameter] string $connectionString): string {}
411The following C function will be generated in order to register the two global constants and the
412attribute. The name of this file is ``example.stub.php``:
418 static void register_example_symbols(int module_number)
420 REGISTER_STRING_CONSTANT("ANIMAL", "Elephant", CONST_PERSISTENT);
421 REGISTER_DOUBLE_CONSTANT("BAR", M_PI, CONST_PERSISTENT);
424 zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "connect", sizeof("connect") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0);
427Similarly to class registration functions, the generated ``register_{$stub_file_name}_symbols()``
428functions must be used in ``PHP_MINIT_FUNCTION``, to make the global constants an attributes
433 PHP_MINIT_FUNCTION(example)
435 register_example_symbols(module_number);
440Global constants always need to have their type specified with a ``@var`` PHPDoc tag. The type for
441class constants is inferred from their type declaration if available, otherwise a ``@var`` PHPDoc
442tag is required. A ``@var`` tag is also required if you enable ``generate-legacy-arginfo`` (see
445If a constant's value is defined by a 3rd party library, PHP's internals, or a specific type such as
446a bitmask, the exact value is not yet known when stubs are used. In these cases, don't duplicate the
447value in the stub file, but instead use the ``UNKNOWN`` constant value with the ``@cvalue`` PHPDoc
450In the example below we define the ``BAR`` global constant to ``UNKNOWN``, with the value linked
451with ``@cvalue M_PI`` to the C-level constant ``M_PI`` (define by PHP's internals).
453Constants can take the following extra meta information passed via PHPDoc tags:
455- ``@deprecated``: Triggers a deprecation notice when the constant is used.
457- ``@genstubs-expose-comment-block``: By adding this tag at the beginning of a PHPDoc block, the
458 content of the PHPDoc block will be exposed for `ReflectionClass::getDocComment()`. This feature
459 is only available as of PHP 8.4.0.
461************************************
462 Maintaining Backward Compatibility
463************************************
465The stubs in the PHP source distribution only need to support the branch they are part of.
467Third party extensions often need to support a wider range of PHP versions, with different features
468supported, that can be enabled through stubs.
470Stubs may get new features which are unavailable in earlier PHP versions, or ABI compatibility
471breaks may happen between minor releases. And PHP 7.x versions are substantially different from PHP
474It is possible to tell the arginfo generator script ``gen_stub.php`` to create legacy arginfo too,
475specifying a minimum supported version.
477If your extension still needs to handle PHP 7, then add the ``@generate-legacy-arginfo`` file-level
478PHPDoc tag, without any value. In this case, an additional ``_legacy_arginfo.h`` file will be
479generated. You can include this file conditionally, such as:
483 #if (PHP_VERSION_ID >= 80000)
484 # include "example_arginfo.h"
486 # include "example_legacy_arginfo.h"
489When ``@generate-legacy-arginfo`` is passed the minimum PHP version ID that needs to be supported,
490then only one arginfo file is going to be generated, and ``#if`` prepocessor directives will ensure
491compatibility with all the required PHP 8 versions.
493PHP Version IDs are as follows: ``80000`` for PHP 8.0, ``80100`` for PHP PHP 8.1, ``80200`` for PHP
4948.2, ``80300`` for PHP 8.3, and ``80400`` for PHP 8.4,
496In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a
503 * @generate-class-entries
504 * @generate-legacy-arginfo 80000
507 enum Number: string {
513 * @not-serializable */
519 public const float PI = UNKNOWN;
521 public readonly string $name;
524Then notice the ``#if (PHP_VERSION_ID >= ...)`` conditions in the generated arginfo file:
530 #if (PHP_VERSION_ID >= 80100)
531 static zend_class_entry *register_class_Number(void)
533 zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods);
535 zend_enum_add_case_cstr(class_entry, "One", NULL);
541 static zend_class_entry *register_class_Elephant(void)
543 zend_class_entry ce, *class_entry;
545 INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods);
546 class_entry = zend_register_internal_class_ex(&ce, NULL);
547 #if (PHP_VERSION_ID >= 80100)
548 class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE;
549 #elif (PHP_VERSION_ID >= 80000)
550 class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES;
554 ZVAL_DOUBLE(&const_PI_value, M_PI);
555 zend_string *const_PI_name = zend_string_init_interned("PI", sizeof("PI") - 1, 1);
556 #if (PHP_VERSION_ID >= 80300)
557 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));
559 zend_declare_class_constant_ex(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL);
561 zend_string_release(const_PI_name);
563 zval property_name_default_value;
564 ZVAL_UNDEF(&property_name_default_value);
565 zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1);
566 #if (PHP_VERSION_ID >= 80100)
567 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));
568 #elif (PHP_VERSION_ID >= 80000)
569 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));
571 zend_string_release(property_name_name);
576The preprocessor conditions are necessary because ``enum``s, ``readonly`` properties, and the
577``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0.
579The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not
580added for``Elephpant::$name`` for PHP versions before 8.1.
582Additionally, typed class constants are new in PHP 8.3, and hence a different registration function
583is used for versions before 8.3.
585******************************************
586 Generating Information for the Optimizer
587******************************************
589A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This
590file contains extra information about the return type and the cardinality of the return value. This
591can enable more accurate optimizations (i.e. better type inference).
593Previously, the file was maintained manually, but since PHP 8.1, ``gen_stub.php`` can take care of
594this with the ``--generate-optimizer-info`` option.
596This feature is only available for built-in stubs inside php-src, since currently there is no way to
597provide the function list for the optimizer other than overwriting ``zend_func_infos.h`` directly.
599A function is added to ``zend_func_infos.h`` if either the ``@return`` or the ``@refcount`` PHPDoc
600tag supplies more information than what is available based on the return type declaration. By
601default, scalar return types have a ``refcount`` of ``0``, while non-scalar values are ``N``. If a
602function can only return newly created non-scalar values, its ``refcount`` can be set to ``1``.
604An example from the built-in functions:
609 * @return array<int, string>
612 function get_declared_classes(): array {}
614Functions can be evaluated at compile-time if their arguments are known in compile-time, and their
615behavior is free from side-effects and is not affected by the global state.
617The list of such functions in the optimizer was maintained manually until PHP 8.2.
619Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms
620to the above restrictions in order for them to qualify as evaluable at compile-time. The feature
621internally works by adding the ``ZEND_ACC_COMPILE_TIME_EVAL`` function flag.
623In PHP 8.4, arity-based frameless functions were introduced. This is another optimization technique,
624which results in faster internal function calls by eliminating unnecessary checks for the number of
625passed parameters—if the number of passed arguments is known at compile-time.
627To take advantage of frameless functions, add the ``@frameless-function`` PHPDoc tag with some
630Since only arity-based optimizations are supported, the tag has the form: ``@frameless-function
631{"arity": NUM}``. ``NUM`` is the number of parameters for which a frameless function is available.
633The stub of ``in_array()`` is a good example:
639 * @frameless-function {"arity": 2}
640 * @frameless-function {"arity": 3}
642 function in_array(mixed $needle, array $haystack, bool $strict = false): bool {}
644Apart from being compile-time evaluable, it has a frameless function counterpart for both the 2 and
645the 3-parameter signatures:
649 /* The regular in_array() function */
650 PHP_FUNCTION(in_array)
652 php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
655 /* The frameless version of the in_array() function when 2 arguments are passed */
656 ZEND_FRAMELESS_FUNCTION(in_array, 2)
660 Z_FLF_PARAM_ZVAL(1, value);
661 Z_FLF_PARAM_ARRAY(2, array);
663 _php_search_array(return_value, value, array, false, 0);
668 /* The frameless version of the in_array() function when 3 arguments are passed */
669 ZEND_FRAMELESS_FUNCTION(in_array, 3)
674 Z_FLF_PARAM_ZVAL(1, value);
675 Z_FLF_PARAM_ARRAY(2, array);
676 Z_FLF_PARAM_BOOL(3, strict);
678 _php_search_array(return_value, value, array, strict, 0);
683**************************************
684 Generating Signatures for the Manual
685**************************************
687The manual should reflect the exact same signatures which are represented by the stubs. This is not
688exactly the case yet for built-in symbols, but ``gen_stub.php`` has multiple features to automate
689the process of synchronization.
691Newly added functions or methods can be documented by providing the ``--generate-methodsynopses``
694Running ``./build/gen_stub.php --generate-methodsynopses ./ext/mbstring
695../doc-en/reference/mbstring`` will create a dedicated page for each ``ext/mbstring`` function which
696is not yet documented, and saves them into the ``../doc-en/reference/mbstring/functions`` directory.
698Since these are stub documentation pages, many of the sections are empty. Relevant descriptions have
699to be added, and irrelevant sections should be removed.
701Functions or methods that are already available in the manual, the documented signatures can be
702updated by providing the ``--replace-methodsynopses`` option.
704Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or
705method signatures in the English documentation whose stub counterpart is found.
707Class signatures can be updated in the manual by providing the ``--replace-classsynopses`` option.
709Running ``./build/gen_stub.php --replace-classsynopses ./ ../doc-en/`` will update all the class
710signatures in the English documentation whose stub counterpart is found.
712If a symbol is not intended to be documented, the ``@undocumentable`` PHPDoc tag should be added to
713it. Doing so will prevent any documentation to be created for the given symbol. To avoid a whole
714stub file to be added to the manual, this PHPDoc tag should be applied to the file itself.
716These flags are useful for symbols which exist only for testing purposes (e.g. the ones declared for
717``ext/zend_test``), or by some other reason documentation is not possible.
723You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method
724signatures are correct.
726An alias function/method should have the exact same signature as its aliased function/method
727counterpart, apart from the name. In some cases this is not possible. For example. ``bzwrite()`` is
728an alias of ``fwrite()``, but the name of the first parameter is different because the resource
731In order to suppress the error when the check is false positive, the ``@no-verify`` PHPDoc tag
732should be applied to the alias:
737 * @param resource $bz
738 * @implementation-alias fwrite
739 * @no-verify Uses different parameter name
741 function bzwrite($bz, string $data, ?int $length = null): int|false {}
743Besides aliases, the contents of the documentation can also be validated by providing the
744``--verify-manual`` option to ``gen_stub.php``. This flag requires the directory with the source
745stubs, and the target manual directory, as in ``./build/gen_stub.php --verify-manual ./
748For this validation, all ``php-src`` stubs and the full English documentation should be available by
751This feature performs the following validations:
753- Detecting missing global constants
754- Detecting missing classes
755- Detecting missing methods
756- Detecting incorrectly documented alias functions or methods
758Running it with the stub examples that are used in this guide, the following warnings are shown:
762 Warning: Missing class synopsis for Number
763 Warning: Missing class synopsis for Elephant
764 Warning: Missing class synopsis for Atmosphere
765 Warning: Missing method synopsis for fahrenheitToCelcius()
766 Warning: Missing method synopsis for Atmosphere::calculateBar()
768**********************
770**********************
772The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the
775A JSON object is displayed, containing the parameter names and the number of their occurrences in