Introduction

This book is not intended to be an exhaustive coverage of internal details of PHP, but rather a quick and easy reference for developers that are learning, exploring or even contributing to PHP itself.

For a more comprehensive and detailed write of how PHP works internally, the best available resource (besides the PHP source code itself), is The PHP Internals Book.


Copyright (c) 2022 - Flavio Heleno

Acknowledgements

This work is heavily inspired by the following amazing content (in alphabetical order):


Copyright (c) 2022 - Flavio Heleno

Structure

Before starting, it's important to understand the directory structure and the files that are used to build an extension and the core itself.


Copyright (c) 2022 - Flavio Heleno

Extension

The directory structure for an extension is simple enough, given the command:

php ext_skel.php \
  --ext Hdi \
  --onlyunix

The structure below will be created:

<hdi>
 ├─ tests/          # Code tests directory
 │  ├─ 001.phpt     # Test skeleton
 │  ├─ 002.phpt     # Test skeleton
 │  └─ 003.phpt     # Test skeleton
 ├─ .gitignore      # Patterns to be ignored by git
 ├─ config.m4       # Code that will be imported by the configure script during build
 ├─ hdi.c           # Actual extension code
 ├─ hdi.stub.php    # Stub file that will be used to generate the "hdi_arginfo.h" file
 ├─ hdi_arginfo.h   # Argument information header
 └─ php_hdi.h       # Extension registration header (module entry)

From Zend's chapter "2. Generating a PHP Extension Skeleton":

  • config.m4 is an extension configuration script used by "phpize" or "buildconf" to add extension configuration options into the "configure" command.
  • php_hdi.h is a C header file that contains our common extension definitions. It's not necessary for simple extensions with a single-source C file, but it's useful in case the implementation is spread among few files.
  • hdi.c is the main extension implementation source. It defines all the structures that allow to plug the extension into PHP and make all their internal functions, classes and constants to be available.

hdi.stub.php

This stub file will be parsed by gen_stub.php and as result, the hdi_arginfo.h is generated/updated.

Note that not all PHP constructs are supported by gen_stub.php, in each chapter additional instructions for what to include and what not to include in the stub file will be outlined.

hdi_arginfo.h

This header file is automatically generated/updated by the build process and should not be manually editted.


Copyright (c) 2022 - Flavio Heleno

Constants

This chapter covers how to define a global constant.

Code sample for this chapter, with additional examples, is available here.

Userland PHP Code Snippet

The code below shows how a constant would be declared in userland PHP code.

const HELLO = 'World!';

Internal PHP Code

The following sections show all required code to declare and implement a constant internally in PHP.

PHP Stub (hdi.stub.php)

The stub file should not declare global constants as the stub generator script (gen_stub.php) cannot handle it at this moment.

Argument Information (hdi_arginfo.h)

Constants are not referred to nor registered in the argument information file, so it can be skipped.

Implementation (hdi.c)

The implementation below is very simple and straight forward:

  • declaration (REGISTER_MAIN_STRING_CONSTANT)
    • name string ("HELLO")
    • value string ("World!")
    • flags (CONST_CS and CONST_PERSISTENT)
REGISTER_MAIN_STRING_CONSTANT("HELLO", "World!", CONST_CS | CONST_PERSISTENT);

Macros and Functions:

References:

Note that constants must be registered during the MINIT() stage.


Copyright (c) 2022 - Flavio Heleno

Classes

Userland PHP Code Snippet

The code below shows how a class would be declared in userland PHP code.

class Hdi {
}

Internal PHP Code

The following sections show all required code to declare a class internally in PHP.

PHP Stub (hdi.stub.php)

The stub file is used to declare the class signature:

  • namespace (global)
  • name (Hdi)
class Hdi {}

Argument Information (hdi_arginfo.h)

This file is generated during compilation, based on the stub file contents.

Header File (hdi.h)

In this file, a pointer to a zend_class_entry must be declared for the defined class:

  extern zend_class_entry *zceHdi;

Implementation (hdi.c)

The implementation below is elaborated and each part has its own purpose, as shown below:


Copyright (c) 2022 - Flavio Heleno

Class Constants

This chapter covers how to define a class constant.

Note that more in dept details about constants are covered in the constants chapter.

Code sample for this chapter, with additional examples, is available here.

Userland PHP Code Snippet

The code below shows how a class constant would be declared in userland PHP code.

class Hdi {
  public const HELLO = 'World!';
}

Internal PHP Code

The following sections show all required code to declare and implement a class constant internally in PHP.

PHP Stub (hdi.stub.php)

The stub file should not declare class constants as the stub generator script (gen_stub.php) cannot handle it at this moment.

Argument Information (hdi_arginfo.h)

Class constants are not referred to nor registered in the argument information file, so it can be skipped.

Implementation (hdi.c)

The implementation below is a little bit more elaborate than what is seen in global scope constant, but it can be broken into:

  • name definition (zend_string_init)
    • name string ("HELLO")
    • name size (sizeof)
    • persistent string (0 ie. not persistent1)
  • value definition (ZVAL_NEW_STR and zend_string_init)
    • value string ("World!")
    • value size (sizeof)
    • persistent string (depends if classEntry->type is a ZEND_INTERNAL_CLASS or not)
  • declaration (zend_declare_class_constant_ex)
    • class entry (classEntry)
    • visibility (ZEND_ACC_PRIVATE)
zend_string *name = zend_string_init(
  "HELLO",
  sizeof("HELLO") - 1,
  0
);

zval value;
ZVAL_NEW_STR(
  &value,
  zend_string_init(
    "World!",
    sizeof("World!") - 1,
    classEntry->type & ZEND_INTERNAL_CLASS
  )
);

zend_declare_class_constant_ex(
  classEntry,
  name,
  &value,
  ZEND_ACC_PRIVATE,
  NULL
);
zend_string_release(name);

Alternatively, for public class constants, it can be simplified as:

zend_declare_class_constant_string(
  classEntry,
  "HELLO",
  sizeof("HELLO") - 1,
  "World!"
);

Macros and Functions:

Visibility options:

Notes:

1

More about persistent objects in the Zend Memory Manager chapter of the PHP Internals Book.


Copyright (c) 2022 - Flavio Heleno

Class Methods

This chapter covers how to define a class method.

Note that details such as method arguments and method return values are covered in the functions chapter.

Code sample for this chapter, with additional examples, is available here.

Userland PHP Code Snippet

The code below shows how a class method would be declared and implemented in userland PHP code.

class Hdi {
  public function sayHello(): string {
    return 'Hello World!';
  }
}

Internal PHP Code

The following sections show all required code to declare and implement a class method internally in PHP.

PHP Stub (hdi.stub.php)

The stub file is used to declare the method signature:

  • visibility (public)
  • name (sayHello)
  • argument list (())
  • return type (string)
class Hdi {
  public function sayHello(): string {}
}

Argument Information (hdi_arginfo.h)

This file is generated during compilation, based on the stub file contents.

The argument information header is the standard/default file where all class methods details are defined:

  • declaration (ZEND_METHOD)
    • class name (Hdi)
    • method name (sayHello)
  • class method list (class_Hdi_methods[] variable)
    • entry registration (ZEND_ME)
      • class name (Hdi)
      • method name (sayHello)
      • argument information (arginfo_class_Hdi_sayHello)
      • visibility (ZEND_ACC_PUBLIC)
  • argument declaration (ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX)
    • argument information name (arginfo_class_Hdi_sayHello)
    • return reference (0 ie. return by value)
    • required arguments (0 ie. no required arguments)
    • return type (IS_STRING)
    • allow null (0 ie. not allowed)
/* This is a generated file, edit the .stub.php file instead. */
/* NOTE: Only relevant code is shown below! */
ZEND_METHOD(Hdi, sayHello);

static const zend_function_entry class_Hdi_methods[] = {
  ZEND_ME(Hdi, sayHello, arginfo_class_Hdi_sayHello, ZEND_ACC_PUBLIC)
  ZEND_FE_END
};

ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Hdi_sayHello, 0, 0, IS_STRING, 0)
ZEND_END_ARG_INFO()

Macros and Functions:

Visibility options:

Type options:

Implementation (hdi.c)

The implementation below is quite simple and straight forward:

  • declaration (PHP_METHOD)
    • class name (Hdi)
    • method name (sayHello)
  • parameter parsing (ZEND_PARSE_PARAMETERS_NONE)
  • return (RETURN_STR)
    • value string ("Hello World!")
/* {{{ Hdi: function sayHello(): string */
PHP_METHOD(Hdi, sayHello) {
  ZEND_PARSE_PARAMETERS_NONE();

  RETURN_STR("Hello World!");
}
/* }}} */

Macros and Functions:


Copyright (c) 2022 - Flavio Heleno

Class Properties

This chapter covers how to define a class property.

Code sample for this chapter, with additional examples, is available here.

Userland PHP Code Snippet

The code below shows how a class property would be declared in userland PHP code.

class Hdi {
  public int $value;
}

Internal PHP Code

The following sections show all required code to declare and implement a class property internally in PHP.

PHP Stub (hdi.stub.php)

The stub file may or may not declare class properties, but the stub generator script (gen_stub.php) will not produce any output related to them as they are implementation specific.

Argument Information (hdi_arginfo.h)

Class properties are not referred to nor registered in the argument information file, so it can be skipped.

Implementation (hdi.c)

zend_string *propName = zend_string_init("value", sizeof("value") - 1, false);
zval propDefaultValue;
/* default property value (undefined) */
ZVAL_UNDEF(&propDefaultValue);

zend_declare_typed_property(
  classEntry,
  propName,
  &defaultValue,
  ZEND_ACC_PRIVATE,
  NULL,
  (zend_type)ZEND_TYPE_INIT_MASK(MAY_BE_LONG)
);
zend_string_release(propName);

Macros and Functions:

Visibility options:

Type options:

Manipulating Property Values

Update/Set a Property

Updating a property value is straight forward:

  • update function (zend_update_property_long)
    • class entry (classEntry)
    • object reference (Z_OBJ_P(ZEND_THIS) ie. $this)
    • property name string ("value")
    • property name size (sizeof)
    • value (42)
zend_update_property_long(
  classEntry,
  Z_OBJ_P(ZEND_THIS),
  "value",
  sizeof("value") - 1,
  42
);

Note that for static properties, zend_update_property_long must be replaced by its static version (zend_update_static_property_long), that does not have an object argument.

Macros and Functions:

Read a Property

Reading a property value requires a bit more leg work:

  • read function (zend_read_property)
    • class entry (classEntry)
    • object reference (Z_OBJ_P(ZEND_THIS) ie. $this)
    • property name string ("value")
    • property name size (sizeof)
    • ???
    • ???
zval rv;
zval *value = zend_read_property(
  classEntry,
  Z_OBJ_P(ZEND_THIS),
  "value",
  sizeof("value") - 1,
  true,
  &rv
);

Note that for static properties, zend_read_property must be replaced by its static version (zend_read_static_property), that does not have an object argument.

Macros and Functions:


Copyright (c) 2022 - Flavio Heleno