15 KiB
C++ Namespace, Prefix, and Symbol ABI Rules
This document is the internal C++ naming convention for TypePHP, PHPX, and TypePHP-generated code. It solves the following problems:
- Distinguishing TypePHP runtime logic, PHPX ZendAPI wrappers, project-private implementations, and user PHP symbols;
- Preventing framework helpers from generating the same C++ symbols as user-defined PHP functions or class methods;
- Clarifying which names are part of the stable ABI and which names are only for internal use within a single generated project;
- Providing a unified naming decision for adding helpers, caches, entry functions, and generated symbols.
1. Overall Rules
| Naming Domain | Meaning | Typical Form | Visibility Scope | ABI Property |
|---|---|---|---|---|
typephp_ |
TypePHP-specific runtime or compiled-artifact support logic | typephp_call_parent_constructor() |
TypePHP/PHPX runtime | Internal or explicitly exported ABI |
php:: |
C++ wrappers for PHP runtime capabilities such as ZendAPI, zval, HashTable, and call frames | php::deindirect() |
PHPX C++ API | PHPX API |
typephp_<project> |
The private C++ namespace of a single compiled project | namespace typephp_tpc |
Current generated project | Non-public ABI |
php_ |
C++ callable symbols mapped from user PHP functions and class methods | php_app__user__save() |
Visible to the linker | TypePHP/stub callable ABI |
Core constraints:
- Do not add new global framework
php_*helpers. - Capabilities that are unrelated to TypePHP and only wrap ZendAPI must be placed in
namespace php. - Logic that is unique to TypePHP and needs to be called across generated files uses the
typephp_prefix. - Data and functions that serve only one compiled project go into the
typephp_<project>namespace. - Global
php_*callable names are reserved for the compiled ABI of user PHP declarations.
2. typephp_: TypePHP-specific Logic
typephp_ indicates that the API's semantics are defined by TypePHP and are not a general-purpose C++ wrapper of ZendAPI. Common scenarios include:
- TypePHP property read/write rules;
- TypePHP construction, cloning, and parent method call chains;
- Runtime support for TypePHP compile-time Attributes;
- TypePHP-specific runtime logic such as Native Class and Property Hook;
- Initialization and shutdown entry points of the TypePHP embed runtime.
Examples:
typephp_call_parent_constructor(object, constructor, args);
typephp_call_parent_clone(object, clone_method);
typephp_install_property_handlers(class_entry, handlers);
typephp_write_property_scoped(object, member, value, scope);
TYPEPHP_RUNTIME_INIT(project)(argc, argv);
2.1 Usage Boundaries
- This prefix is the TypePHP internal C/C++ name space and does not represent PHP user functions.
- When adding an API, use a complete, recognizable snake_case name; do not use overly broad names such as
typephp_call(). - Functions used in only one
.ccfile should additionally be markedstaticor placed in an anonymous namespace. - When crossing dynamic library boundaries, use the corresponding export macro; helpers that do not need to be exported should not widen symbol visibility.
- Do not use
typephp_merely because the code is intypephp_helper.h; the criterion is whether the semantics are TypePHP-specific.
2.2 Positive and Negative Examples
// Correct: the constructor chain semantics are TypePHP-specific.
typephp_call_parent_constructor(object, constructor, args);
// Incorrect: this only materializes an INDIRECT zval into a plain value and is not TypePHP-specific.
typephp_deindirect(value);
// Correct: generic Zend value wrapping belongs to PHPX.
php::deindirect(value);
3. php::: C++ Wrappers for ZendAPI
namespace php is provided by PHPX to wrap Zend's C API, macros, raw pointers, and manual resource management into a type-safe, RAII-friendly C++ API.
This naming domain contains two categories of capabilities:
- PHP values and runtime objects, such as
php::Var,php::Str,php::Array, andphp::Object; - Safe wrappers of ZendAPI, such as symbol lookup, scope management, value conversion, object creation, and invocation.
Examples:
php::Var value;
php::Array arguments;
auto plain = php::deindirect(value);
auto called_ce = php::getCalledCe(this_);
auto scope = php::getCallableScope(function, this_);
auto create_object = php::getCreateObjectFn(class_entry);
auto globals = php::globalsArray();
3.1 When to Use php::
Place into namespace php when the following conditions are met:
- The API is meaningful to any PHPX C++ caller;
- The API's behavior can be fully explained by Zend/PHP runtime semantics;
- The API does not depend on TypePHP AST, compile-time Attributes, or TypePHP-specific language rules;
- The API's main purpose is to hide Zend macros, raw
zval *, reference counting, or exception checking.
3.2 Forbidding Global php_* Helpers
The following legacy forms are forbidden:
php::Var php_deindirect(const php::Var &value);
php::Str php_get_called_class(php::Object &this_);
zend_class_entry *php_get_called_ce(php::Object &this_);
auto php_get_create_object_fn(zend_class_entry *ce);
They must be written as:
namespace php {
Var deindirect(const Var &value);
Str getCalledClass(Object &this_);
zend_class_entry *getCalledCe(Object &this_);
auto getCreateObjectFn(zend_class_entry *ce);
} // namespace php
The reason is that users can legitimately declare:
function deindirect(mixed $value): mixed {}
function get_called_ce(): string {}
function get_create_object_fn(): string {}
These PHP functions generate php_deindirect, php_get_called_ce, and
php_get_create_object_fn. If PHPX also defines same-named helpers globally, conflicts may occur at the declaration, overload resolution, or linking stage.
3.3 Naming Style
The PHPX C++ API uses the existing camelCase style:
php::getCalledClass();
php::getClassEntrySafe();
php::getPersistentCache();
php::stdCreateObject();
Do not mechanically preserve Zend's snake_case names as global C++ names. Lower-level calls can continue to use the original Zend API, such as zend_objects_new(), but the wrapper layer exposed to generated code should use php::.
4. typephp_<project>: Project-private Namespace
Each TypePHP compiled project has an independent C++ namespace:
typephp_<target-name>
For example, if the project name is tpc:
namespace typephp_tpc {
// Project-private generated state and helpers.
}
The - and * in the project name are converted to _, and the remaining characters must satisfy the compiler's target identifier validation. Because of the fixed typephp_ prefix, the final C++ namespace is a valid identifier even if the project name starts with a digit.
4.1 Content That Should Go into This Namespace
- The literal string table and
get_str(); - The class/function/property cache tables and their accessor functions;
- Global variable storage of the current project;
- Class entries, object handlers, and default property templates;
- Module entry and MINIT/RINIT/RSHUTDOWN auxiliary state;
- Functions such as
module_init()andmodule_clean()that are called only inside the generated extension file; - Project-level generated state such as the Python module cache.
Illustration:
namespace typephp_demo {
static php::Str literal_strings[] = {
php::Str{"hello"},
};
php::Str &get_str(uint32_t index) {
return literal_strings[index];
}
static THREAD_LOCAL zend_class_entry *class_map[8];
zend_class_entry *get_class(int id, const php::Str &name) {
// Resolve and cache a symbol owned by this project.
}
static void module_init() {
// Initialize this project's generated state.
}
} // namespace typephp_demo
4.2 Visibility and ABI
- Names inside
typephp_<project>are implementation details, not library stub ABI. - Objects and functions that can be limited to
staticshould continue to be markedstatic. - Generated headers may declare project-internal accessors that must be used across translation units, but must not expose underlying arrays or cache tables.
- External handwritten C++ code must not depend on literal indexes, cache indexes, or project-internal storage names.
- Different TypePHP projects can be linked into the same process, because the same internal short names reside in different project namespaces.
4.3 Scope Takes Priority over Name Spelling
Historical generated names may still appear in the project namespace, for example:
typephp_demo::php_class_entry_App_User
Although the member name starts with php_, the full symbol resides in typephp_demo, so it is a project-private implementation rather than the global user callable ABI described in Section 5. New project-internal helpers should prefer short names without php_, such as get_class(), get_func(), and get_str().
5. php_: The C++ ABI of User PHP Callables
The global php_ prefix is used by TypePHP to map user-declared PHP functions and class methods into C++ callable symbols. This naming is used by generated code, library stubs, and external C++ implementations alike, so it cannot be changed arbitrarily.
Example:
namespace App;
function greet(string $name): string {}
class User
{
public function save(): bool {}
}
The conceptual C++ symbols are:
php::Str php_app__greet(php::Str name);
php::Bool php_app__user__save(php::Object &this_);
The rules include:
- Use
php_to mark "mapped from a PHP declaration"; - PHP namespace, class, and method/function names are combined after normalization;
__is the existing ABI combination separator;- The first parameter of an instance method is the object
this_; - Stubs, libraries, and consumers must use exactly the same mapping rules.
5.1 Why Internal Helpers Cannot Use php_
The php_ mapping is not an independent reserved keyword space, but a mechanical ABI of user PHP names. The following user declaration:
function deindirect(mixed $value): mixed {}
naturally generates:
php::Var php_deindirect(php::Var value);
Therefore, if the framework defines a global php_deindirect(), it encroaches on the user symbol space. The correct approach is php::deindirect().
5.2 Combination Collisions
Because the current ABI uses __ to combine PHP namespace, class, and callable names, the following two PHP declarations may map to the same C++ symbol:
function App\user__test(): void {}
namespace App;
class User
{
public function test(): void {}
}
The compiler must detect this situation during the preprocessing stage and throw a FatalError; it must not be handled through overriding, link order, or added runtime dispatch. Changing the mapping separator rules would break existing stubs/ABI, so collisions must be resolved by the user through renaming.
5.3 Entry Symbol Exceptions
A small number of C ABI/embed entry points are fixed by the generator and do not belong to ordinary user callables. For example:
php_<project>_embed_get_module();
typephp_<project>_runtime_init(argc, argv);
typephp_<project>_runtime_shutdown();
These are the connection points between the binary/library embed runtime and the current project's module entry. Definitions and references are uniformly generated through
TYPEPHP_EMBED_GET_MODULE_FUNCTION(), TYPEPHP_RUNTIME_INIT_FUNCTION(),
TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(), and the corresponding symbol macros, in a style consistent with Zend's
PHP_MINIT_FUNCTION()/PHP_MINIT(). The final symbols contain the project name and must not be used as a general helper naming template.
5.4 The Shared Runtime in Multi-extension Processes
TypePHP extensions must not separately compile or statically link a PHPX implementation containing process-level Zend state. The Reflection handler,
FiberGenerator class entry, scope, and Property Hook runtime are all provided solely by the shared libphpx:
- Host-mode extensions/libraries must link
libphpx.so,libphpx.dylib, orphpx.dll, and must not fall back tolibphpx.a; - Unix PHP extensions do not link the Embed
libphp.so; Zend/PHP symbols are provided by the SAPI that loads them; - macOS extensions use
-undefined dynamic_lookupto resolve host symbols; - Binaries and standalone WASI programs can still link statically, because each process or Wasm instance has only one copy of the runtime.
src/core/typephp_*.cc only carries the TypePHP-specific typephp_* runtime; php:: ZendAPI wrappers should be placed in core source files without the
typephp_ prefix, such as src/core/scope.cc.
6. Name Selection Flow
When adding a C++ API, judge in the following order:
- Is it the compiled body of a user PHP function or class method?
- Yes: use the existing
php_callable ABI generator; do not handwrite another mapping.
- Yes: use the existing
- Does it serve only one current TypePHP project?
- Yes: place it in
typephp_<project>, and usestaticor private accessors where possible.
- Yes: place it in
- Does it implement TypePHP-specific semantics?
- Yes: use the
typephp_prefix.
- Yes: use the
- Is it only a C++ wrapper of Zend/PHP runtime capabilities?
- Yes: place it in
namespace php, using the PHPX camelCase style.
- Yes: place it in
- None of the above?
- It should not be arbitrarily added to
typephp_helper.h; reconfirm its owning module and public API boundary.
- It should not be arbitrarily added to
7. Code Review Checklist
When adding or modifying generated helpers, check:
- No new global
php_*helpers intypephp_helper.h; - ZendAPI wrappers are in
namespace php; - TypePHP-specific logic uses
typephp_; - Project caches and storage are in
typephp_<project>; - Project-private tables are not exposed directly via
externthrough generated headers; - User callables still use the unified
php_ABI generator; - New names do not collide with user-declarable PHP functions or methods;
- bin, lib, ext, and WASM builds use the same project name derivation rule;
- Stub and existing ABI are evaluated together when modifying the public callable mapping;
- At least one compilation regression test is added for a user function with the same name.
The current related regression test is:
tests/compiler/basic/helper-symbol-collision.phpt
8. Main Implementation Locations
| Responsibility | File |
|---|---|
php_ callable prefix and combination separator |
src/CompilerBase.php |
| Callable combination collision detection | src/Preprocessor.php |
typephp_<project> generation and project-private tables |
src/Translator.php |
| TypePHP extension prefix constants | src/Metadata/Constants.php |
| PHPX/TypePHP helper classification | vendor/swoole/phpx/include/typephp_helper.h |
| Embed module accessor concatenation | vendor/swoole/phpx/src/misc/typephp_main.cc |