####### Stubs ####### Stub files are pieces of PHP code which only contain declarations. They do not include runnable code, but instead contain empty function and method bodies. A very basic stub looks like this: .. code:: php ce_flags |= ZEND_ACC_DEPRECATED|ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; class_entry->doc_comment = zend_string_init_interned("/**\n * This is a comment\n * @see https://www.php.net */", 55, 1); ... return class_entry; } ******************************************** Generating Global Constants and Attributes ******************************************** Although global constants and function attributes do not relate to classes, they require the ``/** @generate-class-entries */`` file-level PHPDoc block. If a global constant or function attribute are present in the stub file, the generated C-code will include a ``register_{$stub_file_name}_symbols()`` file. Given the following file: .. code:: php // example.stub.php = 80000) # include "example_arginfo.h" #else # include "example_legacy_arginfo.h" #endif When ``@generate-legacy-arginfo`` is passed the minimum PHP version ID that needs to be supported, then only one arginfo file is going to be generated, and ``#if`` prepocessor directives will ensure compatibility with all the required PHP 8 versions. PHP Version IDs are as follows: ``80000`` for PHP 8.0, ``80100`` for PHP PHP 8.1, ``80200`` for PHP 8.2, ``80300`` for PHP 8.3, and ``80400`` for PHP 8.4, In this example we add a PHP 8.0 compatibility requirement to a slightly modified version of a previous example: .. code:: php = ...)`` conditions in the generated arginfo file: .. code:: c ... #if (PHP_VERSION_ID >= 80100) static zend_class_entry *register_class_Number(void) { zend_class_entry *class_entry = zend_register_internal_enum("Number", IS_STRING, class_Number_methods); zend_enum_add_case_cstr(class_entry, "One", NULL); return class_entry; } #endif static zend_class_entry *register_class_Elephant(void) { zend_class_entry ce, *class_entry; INIT_CLASS_ENTRY(ce, "Elephant", class_Elephant_methods); class_entry = zend_register_internal_class_ex(&ce, NULL); #if (PHP_VERSION_ID >= 80100) class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES|ZEND_ACC_NOT_SERIALIZABLE; #elif (PHP_VERSION_ID >= 80000) class_entry->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES; #endif zval const_PI_value; ZVAL_DOUBLE(&const_PI_value, M_PI); zend_string *const_PI_name = zend_string_init_interned("PI", sizeof("PI") - 1, 1); #if (PHP_VERSION_ID >= 80300) zend_declare_typed_class_constant(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_DOUBLE)); #else zend_declare_class_constant_ex(class_entry, const_PI_name, &const_PI_value, ZEND_ACC_PUBLIC, NULL); #endif zend_string_release(const_PI_name); zval property_name_default_value; ZVAL_UNDEF(&property_name_default_value); zend_string *property_name_name = zend_string_init("name", sizeof("name") - 1, 1); #if (PHP_VERSION_ID >= 80100) zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); #elif (PHP_VERSION_ID >= 80000) zend_declare_typed_property(class_entry, property_name_name, &property_name_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING)); #endif zend_string_release(property_name_name); return class_entry; } The preprocessor conditions are necessary because ``enum``s, ``readonly`` properties, and the ``not-serializable`` flag, are PHP 8.1 features and don't exist in PHP 8.0. The registration of ``Number`` is therefore completely omitted, while the ``readonly`` flag is not added for``Elephpant::$name`` for PHP versions before 8.1. Additionally, typed class constants are new in PHP 8.3, and hence a different registration function is used for versions before 8.3. ****************************************** Generating Information for the Optimizer ****************************************** A list of functions is maintained for the optimizer in ``Zend/Optimizer/zend_func_infos.h``. This file contains extra information about the return type and the cardinality of the return value. This can enable more accurate optimizations (i.e. better type inference). Previously, the file was maintained manually, but since PHP 8.1, ``gen_stub.php`` can take care of this with the ``--generate-optimizer-info`` option. This feature is only available for built-in stubs inside php-src, since currently there is no way to provide the function list for the optimizer other than overwriting ``zend_func_infos.h`` directly. A function is added to ``zend_func_infos.h`` if either the ``@return`` or the ``@refcount`` PHPDoc tag supplies more information than what is available based on the return type declaration. By default, scalar return types have a ``refcount`` of ``0``, while non-scalar values are ``N``. If a function can only return newly created non-scalar values, its ``refcount`` can be set to ``1``. An example from the built-in functions: .. code:: php /** * @return array * @refcount 1 */ function get_declared_classes(): array {} Functions can be evaluated at compile-time if their arguments are known in compile-time, and their behavior is free from side-effects and is not affected by the global state. The list of such functions in the optimizer was maintained manually until PHP 8.2. Since PHP 8.2, the ``@compile-time-eval`` PHPDoc tag can be applied to any function which conforms to the above restrictions in order for them to qualify as evaluable at compile-time. The feature internally works by adding the ``ZEND_ACC_COMPILE_TIME_EVAL`` function flag. In PHP 8.4, arity-based frameless functions were introduced. This is another optimization technique, which results in faster internal function calls by eliminating unnecessary checks for the number of passed parameters—if the number of passed arguments is known at compile-time. To take advantage of frameless functions, add the ``@frameless-function`` PHPDoc tag with some configuration. Since only arity-based optimizations are supported, the tag has the form: ``@frameless-function {"arity": NUM}``. ``NUM`` is the number of parameters for which a frameless function is available. The stub of ``in_array()`` is a good example: .. code:: php /** * @compile-time-eval * @frameless-function {"arity": 2} * @frameless-function {"arity": 3} */ function in_array(mixed $needle, array $haystack, bool $strict = false): bool {} Apart from being compile-time evaluable, it has a frameless function counterpart for both the 2 and the 3-parameter signatures: .. code:: c /* The regular in_array() function */ PHP_FUNCTION(in_array) { php_search_array(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0); } /* The frameless version of the in_array() function when 2 arguments are passed */ ZEND_FRAMELESS_FUNCTION(in_array, 2) { zval *value, *array; Z_FLF_PARAM_ZVAL(1, value); Z_FLF_PARAM_ARRAY(2, array); _php_search_array(return_value, value, array, false, 0); flf_clean:; } /* The frameless version of the in_array() function when 3 arguments are passed */ ZEND_FRAMELESS_FUNCTION(in_array, 3) { zval *value, *array; bool strict; Z_FLF_PARAM_ZVAL(1, value); Z_FLF_PARAM_ARRAY(2, array); Z_FLF_PARAM_BOOL(3, strict); _php_search_array(return_value, value, array, strict, 0); flf_clean:; } ************************************** Generating Signatures for the Manual ************************************** The manual should reflect the exact same signatures which are represented by the stubs. This is not exactly the case yet for built-in symbols, but ``gen_stub.php`` has multiple features to automate the process of synchronization. Newly added functions or methods can be documented by providing the ``--generate-methodsynopses`` option. Running ``./build/gen_stub.php --generate-methodsynopses ./ext/mbstring ../doc-en/reference/mbstring`` will create a dedicated page for each ``ext/mbstring`` function which is not yet documented, and saves them into the ``../doc-en/reference/mbstring/functions`` directory. Since these are stub documentation pages, many of the sections are empty. Relevant descriptions have to be added, and irrelevant sections should be removed. Functions or methods that are already available in the manual, the documented signatures can be updated by providing the ``--replace-methodsynopses`` option. Running ``./build/gen_stub.php --replace-methodsynopses ./ ../doc-en/`` will update the function or method signatures in the English documentation whose stub counterpart is found. Class signatures can be updated in the manual by providing the ``--replace-classsynopses`` option. Running ``./build/gen_stub.php --replace-classsynopses ./ ../doc-en/`` will update all the class signatures in the English documentation whose stub counterpart is found. If a symbol is not intended to be documented, the ``@undocumentable`` PHPDoc tag should be added to it. Doing so will prevent any documentation to be created for the given symbol. To avoid a whole stub file to be added to the manual, this PHPDoc tag should be applied to the file itself. These flags are useful for symbols which exist only for testing purposes (e.g. the ones declared for ``ext/zend_test``), or by some other reason documentation is not possible. ************ Validation ************ You can use the ``--verify`` flag to ``gen_stub.php`` to validate whether the alias function/method signatures are correct. An alias function/method should have the exact same signature as its aliased function/method counterpart, apart from the name. In some cases this is not possible. For example. ``bzwrite()`` is an alias of ``fwrite()``, but the name of the first parameter is different because the resource types differ. In order to suppress the error when the check is false positive, the ``@no-verify`` PHPDoc tag should be applied to the alias: .. code:: php /** * @param resource $bz * @implementation-alias fwrite * @no-verify Uses different parameter name */ function bzwrite($bz, string $data, ?int $length = null): int|false {} Besides aliases, the contents of the documentation can also be validated by providing the ``--verify-manual`` option to ``gen_stub.php``. This flag requires the directory with the source stubs, and the target manual directory, as in ``./build/gen_stub.php --verify-manual ./ ../doc-en/``. For this validation, all ``php-src`` stubs and the full English documentation should be available by the specified path. This feature performs the following validations: - Detecting missing global constants - Detecting missing classes - Detecting missing methods - Detecting incorrectly documented alias functions or methods Running it with the stub examples that are used in this guide, the following warnings are shown: .. code:: shell Warning: Missing class synopsis for Number Warning: Missing class synopsis for Elephant Warning: Missing class synopsis for Atmosphere Warning: Missing method synopsis for fahrenheitToCelcius() Warning: Missing method synopsis for Atmosphere::calculateBar() ********************** Parameter Statistics ********************** The ``gen_stub.php`` flag ``--parameter-stats`` counts how many times a parameter name occurs in the codebase. A JSON object is displayed, containing the parameter names and the number of their occurrences in descending order.