[presentations] master: php[tek] 23 tutorial

[email protected] (Derick Rethans) Fri, 12 May 2023 17:14:13 +0000
Newsgroups php.pres
Message-ID <[email protected]>
Author: Derick Rethans (derickr)
Date: 2023-05-12T18:00:43+01:00

Commit: https://github.com/php/presentations/commit/6f8db6a3964e2bc41a794ed6dd5ce10f164b7add
Raw diff: https://github.com/php/presentations/commit/6f8db6a3964e2bc41a794ed6dd5ce10f164b7add.diff

php[tek] 23 tutorial

Changed paths:
  A  php-tek23.xml
  A  slides/internals/collection-add-add-handler-implementation.xml
  A  slides/internals/collection-add-add-handler-implementation2.xml
  A  slides/internals/collection-add-add-handler.xml
  A  slides/internals/collection-add-ast-clean-up.xml
  A  slides/internals/collection-add-ast-type-check.xml
  A  slides/internals/collection-add-ast.xml
  A  slides/internals/collection-add-has-handler.xml
  A  slides/internals/collection-add-init-and-property.xml
  A  slides/internals/collection-add-interface-handlers.xml
  A  slides/internals/collection-add-interface.xml
  A  slides/internals/collection-add-parser-rules.xml
  A  slides/internals/collection-add-read-handler.xml
  A  slides/internals/collection-add-read-handler2.xml
  A  slides/internals/collection-add-token.xml
  A  slides/internals/collection-add-unset-handler.xml
  A  slides/internals/collection-hook-init-and-property.xml
  A  slides/internals/collection-make-check-type-public.xml
  A  slides/internals/collection-setup-interface-handlers.xml
  A  slides/internals/collection-syntax-example.xml
  M  slides/internals/recap-content.xml
  M  slides/mongodb/me.xml


Diff:

diff --git a/php-tek23.xml b/php-tek23.xml
new file mode 100644
index 00000000..9a92e0f2
--- /dev/null
+++ b/php-tek23.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8"?>
+<presentation css="derick.css">
+<topic>PHP</topic>
+<title>PHP Internals Deep Dive</title>
+<event>php[tek] 2023</event>
+<location>Chicago, IL, USA</location>
+<date>May 16h, 2023</date>
+<speaker>Derick Rethans</speaker>
+<email>[email protected]</email>
+<twitter>derickr</twitter>
+<mastodon>@[email protected]</mastodon>
+<url>https://derickrethans.nl/talks/php-tek23</url>
+<joindin>https://joind.in/29096</joindin>
+
+<slide>slides/xdebug/title.xml</slide>
+<slide>slides/mongodb/me.xml</slide>
+
+<slide>slides/internals/title-agenda.xml</slide>
+
+<slide>slides/internals/title-stages.xml</slide>
+<!-- INTRODUCTION
+Compile stages:
+Parse script
+Create AST
+Convert to opcodes
+Run the code
+Opcache for caching
+-->
+<slide>slides/internals/stages.xml</slide>
+
+<!-- STAGES
+Parse
+- Show parser rules
+- Show "compiler" rules
+-->
+
+<slide>slides/internals/collection-syntax-example.xml</slide>
+
+<slide>slides/internals/title-parsing.xml</slide>
+<slide>slides/internals/parse-state-machine.xml</slide>
+<slide>slides/internals/parse-tokenization.xml</slide>
+
+<slide>slides/internals/collection-add-token.xml</slide>
+
+<slide>slides/internals/scanner.xml</slide>
+<slide>slides/internals/scanner-rules.xml</slide>
+
+<slide>slides/internals/collection-add-parser-rules.xml</slide>
+
+<slide>slides/internals/ast.xml</slide>
+<slide>slides/internals/ast1.xml</slide>
+<slide>slides/internals/scanner-ast.xml</slide>
+<slide>slides/internals/scanner-ast2.xml</slide>
+
+<slide>slides/internals/collection-add-ast.xml</slide>
+<slide>slides/internals/collection-add-ast-type-check.xml</slide>
+<slide>slides/internals/collection-add-ast-clean-up.xml</slide>
+
+<slide>slides/internals/collection-add-init-and-property.xml</slide>
+<slide>slides/internals/collection-hook-init-and-property.xml</slide>
+<slide>slides/internals/collection-add-interface.xml</slide>
+<slide>slides/internals/collection-add-interface-handlers.xml</slide>
+<slide>slides/internals/collection-setup-interface-handlers.xml</slide>
+<slide>slides/internals/collection-add-add-handler.xml</slide>
+<slide>slides/internals/collection-add-add-handler-implementation.xml</slide>
+<slide>slides/internals/collection-add-add-handler-implementation2.xml</slide>
+<slide>slides/internals/collection-make-check-type-public.xml</slide>
+<slide>slides/internals/collection-add-read-handler.xml</slide>
+<slide>slides/internals/collection-add-read-handler2.xml</slide>
+<slide>slides/internals/collection-add-has-handler.xml</slide>
+<slide>slides/internals/collection-add-unset-handler.xml</slide>
+
+<slide>slides/internals/bytecode0.xml</slide>
+<slide>slides/internals/bytecode½.xml</slide>
+<slide>slides/internals/bytecode1.xml</slide>
+
+<!--
+AST
+- Show AST for various control structures
+
+Opcodes
+- Show AST for the same control structures
+-->
+
+<slide>slides/internals/jumps-for.xml</slide>
+<slide>slides/internals/jumps-for-rewritten.xml</slide>
+<slide>slides/internals/jumps-complex-dot.xml</slide>
+
+<!-- Conclusion
+
+Code -> Tokens -> AST -> opcode
+
+Transformation of control structures
+
+Useful implementations 
+
+-->
+<slide>slides/internals/recap.xml</slide>
+<slide>slides/internals/recap-content.xml</slide>
+
+
+
+<slide>slides/mongodb/questions.xml</slide>
+<slide>slides/xdebug/phpinternalsnews.xml</slide>
+
+<slide>slides/mongodb/resources.xml</slide>
+
+</presentation>
diff --git a/slides/internals/collection-add-add-handler-implementation.xml b/slides/internals/collection-add-add-handler-implementation.xml
new file mode 100644
index 00000000..18635226
--- /dev/null
+++ b/slides/internals/collection-add-add-handler-implementation.xml
@@ -0,0 +1,46 @@
+<slide>
+<title>Collection: Add Write Handler Implementation [1]</title>
+
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+void zend_collection_add_item(zend_object *object, zval *offset, zval *value)
+{
+       zend_class_entry *ce = object->ce;
+       zval rv;
+       zval *value_prop;
+       ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION);
+
+       if (!zend_check_type(&ce->collection_item_type, value, NULL, ce, 0, false)) {
+               zend_string *type_str = zend_type_to_string(ce->collection_item_type);
+               zend_type_error(
+                       "Value type %s does not match collection item type %s",
+                       zend_zval_type_name(value), ZSTR_VAL(type_str));
+               zend_string_release(type_str);
+               return;
+       }
+
+       if (!offset && ce->collection_key_type == IS_LONG) {
+               create_array_if_needed(ce, object);
+               value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+               Z_ADDREF_P(value);
+               add_next_index_zval(value_prop, value);
+       } else if (offset && ce->collection_key_type == IS_LONG && Z_TYPE_P(offset) == IS_LONG) {
+               create_array_if_needed(ce, object);
+               value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+               Z_ADDREF_P(value);
+               add_index_zval(value_prop, Z_LVAL_P(offset), value);
+       } else if (offset && ce->collection_key_type == IS_STRING && Z_TYPE_P(offset) == IS_STRING) {
+               create_array_if_needed(ce, object);
+               value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+               Z_ADDREF_P(value);
+               add_assoc_zval_ex(value_prop, Z_STRVAL_P(offset), Z_STRLEN_P(offset), value);
+       } else {
+               zend_type_error(
+                       "Key type %s of element does not match collection key type %s",
+                       offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL),
+                       zend_get_type_by_const(ce->collection_key_type));
+       }
+}
+]]></example>
+
+</slide>
diff --git a/slides/internals/collection-add-add-handler-implementation2.xml b/slides/internals/collection-add-add-handler-implementation2.xml
new file mode 100644
index 00000000..dc17c226
--- /dev/null
+++ b/slides/internals/collection-add-add-handler-implementation2.xml
@@ -0,0 +1,21 @@
+<slide>
+<title>Collection: Add Write Handler Implementation [2]</title>
+
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+static void create_array_if_needed(zend_class_entry *ce, zend_object *object)
+{
+       zval *value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, NULL);
+
+       if (Z_TYPE_P(value_prop) == IS_ARRAY) {
+               return;
+       }
+
+       zval new_array;
+       array_init(&new_array);
+       zend_update_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), &new_array);
+       zval_ptr_dtor(&new_array);
+}
+]]></example>
+
+</slide>
diff --git a/slides/internals/collection-add-add-handler.xml b/slides/internals/collection-add-add-handler.xml
new file mode 100644
index 00000000..37326824
--- /dev/null
+++ b/slides/internals/collection-add-add-handler.xml
@@ -0,0 +1,37 @@
+<slide>
+<title>Collection: Add Write Handler</title>
+
+<blurb>%Zend/zend_object_handlers.c%</blurb>
+<example class="small"><![CDATA[
+@@ -26,6 +26,7 @@
+ #include "zend_objects_API.h"
+ #include "zend_object_handlers.h"
+ #include "zend_interfaces.h"
++#include "zend_collection.h"
+ #include "zend_exceptions.h"
+…
+
+@@ -1042,6 +1045,8 @@ ZEND_API void zend_std_write_dimension(zend_object *object, zval *offset, zval *
+                zend_call_known_instance_method_with_2_params(funcs->zf_offsetset, object, NULL, &tmp_offset, value);
+                OBJ_RELEASE(object);
+                zval_ptr_dtor(&tmp_offset);
++       } else if (zend_class_implements_interface(ce, zend_ce_collection)) {
++               zend_collection_add_item(object, offset, value);
+        } else {
+                zend_bad_array_access(ce);
+        }
+]]></example>
+
+<div effect="fade-in">
+<break/>
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+@@ -34,6 +34,11 @@ void zend_collection_add_interfaces(zend_class_entry *ce);
+ void zend_collection_register_handlers(zend_class_entry *ce);
+ void zend_collection_register_props(zend_class_entry *ce);
+
++void zend_collection_add_item(zend_object *object, zval *offset, zval *value);
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-ast-clean-up.xml b/slides/internals/collection-add-ast-clean-up.xml
new file mode 100644
index 00000000..40c24451
--- /dev/null
+++ b/slides/internals/collection-add-ast-clean-up.xml
@@ -0,0 +1,19 @@
+<slide>
+<title>Collection: Clean Up Class Entry</title>
+
+<blurb>%Zend/zend_opcode.c%</blurb>
+<example class="small"><![CDATA[
+--- Zend/zend_opcode.c
++++ Zend/zend_opcode.c
+@@ -430,6 +430,9 @@ ZEND_API void destroy_zend_class(zval *zv)
+                        if (ce->backed_enum_table) {
+                                zend_hash_release(ce->backed_enum_table);
+                        }
++                       if (ZEND_TYPE_IS_SET(ce->collection_item_type)) {
++                               zend_type_release(ce->collection_item_type, 0);
++                       }
+                        if (ce->default_properties_table) {
+                                zval *p = ce->default_properties_table;
+                                zval *end = p + ce->default_properties_count;
+]]></example>
+</slide>
diff --git a/slides/internals/collection-add-ast-type-check.xml b/slides/internals/collection-add-ast-type-check.xml
new file mode 100644
index 00000000..87d68914
--- /dev/null
+++ b/slides/internals/collection-add-ast-type-check.xml
@@ -0,0 +1,45 @@
+<slide>
+<title>Collection: Add AST Code</title>
+
+<blurb>%Zend/zend_compile.c%</blurb>
+<example class="small"><![CDATA[
++static void zend_compile_collection_key_type(zend_class_entry *ce, zend_ast *collection_key_type_ast)
++{
++       ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION);
++       zend_type type = zend_compile_typename(collection_key_type_ast, 0);
++       uint32_t type_mask = ZEND_TYPE_PURE_MASK(type);
++       if (ZEND_TYPE_IS_COMPLEX(type) || (type_mask != MAY_BE_LONG && type_mask != MAY_BE_STRING)) {
++               zend_string *type_string = zend_type_to_string(type);
++               zend_error_noreturn(E_COMPILE_ERROR,
++                       "Collection key type must be int or string, %s given",
++                       ZSTR_VAL(type_string));
++       }
++       if (type_mask == MAY_BE_LONG) {
++               ce->collection_key_type = IS_LONG;
++       } else {
++               ZEND_ASSERT(type_mask == MAY_BE_STRING);
++               ce->collection_key_type = IS_STRING;
++       }
++       zend_type_release(type, 0);
++}]]></example>
+
+<div effect="fade-in">
+<example class="small"><![CDATA[
++static void zend_compile_collection_item_type(zend_class_entry *ce, zend_ast *collection_item_type_ast)
++{
++       ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION);
++       zend_type type = zend_compile_typename(collection_item_type_ast, 0);
++
++       if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_VOID|MAY_BE_NEVER|MAY_BE_CALLABLE)) {
++               zend_string *str = zend_type_to_string(type);
++               zend_error_noreturn(E_COMPILE_ERROR,
++                       "Collection item type cannot have type %s", ZSTR_VAL(str));
++       }
++
++       ce->collection_item_type = type;
++}
++
+ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
+]]></example>
+</div>
+</slide>
diff --git a/slides/internals/collection-add-ast.xml b/slides/internals/collection-add-ast.xml
new file mode 100644
index 00000000..1199f9d3
--- /dev/null
+++ b/slides/internals/collection-add-ast.xml
@@ -0,0 +1,44 @@
+<slide>
+<title>Collection: Add AST Code</title>
+
+<blurb>%Zend/zend.h%</blurb>
+<example class="small"><![CDATA[
+        HashTable *backed_enum_table;
+
++       uint32_t collection_key_type;
++       zend_type collection_item_type;
+]]></example>
+
+<blurb>%Zend/zend_compile.c%</blurb>
+<example class="small"><![CDATA[
+ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
+ {
+        zend_ast_decl *decl = (zend_ast_decl *) ast;
+        zend_ast *extends_ast = decl->child[0];
+        zend_ast *implements_ast = decl->child[1];
+        zend_ast *stmt_ast = decl->child[2];
++       zend_ast *collection_key_type_ast = decl->child[3];
++       zend_ast *collection_item_type_ast = decl->child[4];
+        zend_ast *enum_backing_type_ast = decl->child[4];
+        zend_string *name, *lcname;
+        zend_class_entry *ce = zend_arena_alloc(&CG(arena), sizeof(zend_class_entry));
+@@ -8003,7 +8039,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
+
+        CG(active_class_entry) = ce;
+
+-       if (decl->child[3]) {
++       if (decl->child[3] && !(ce->ce_flags & ZEND_ACC_COLLECTION)) {
+                zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0);
+        }
+
+@@ -8019,6 +8055,13 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
+                zend_enum_register_props(ce);
+        }
+
++       if (ce->ce_flags & ZEND_ACC_COLLECTION) {
++               zend_compile_collection_key_type(ce, collection_key_type_ast);
++               zend_compile_collection_item_type(ce, collection_item_type_ast);
++       }
++
+]]></example>
+</slide>
diff --git a/slides/internals/collection-add-has-handler.xml b/slides/internals/collection-add-has-handler.xml
new file mode 100644
index 00000000..c2b5c5df
--- /dev/null
+++ b/slides/internals/collection-add-has-handler.xml
@@ -0,0 +1,50 @@
+<slide>
+<title>Collection: Add Has Handler</title>
+
+<blurb>%Zend/zend_object_handlers.c%</blurb>
+<example class="small"><![CDATA[
+@@ -1068,6 +1073,8 @@ ZEND_API int zend_std_has_dimension(zend_object *object, zval *offset, int check
+                }
+                OBJ_RELEASE(object);
+                zval_ptr_dtor(&tmp_offset);
++       } else if (zend_class_implements_interface(ce, zend_ce_collection)) {
++               return zend_collection_has_item(object, offset);
+        } else {
+                zend_bad_array_access(ce);
+                return 0;
+]]></example>
+
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+ void zend_collection_add_item(zend_object *object, zval *offset, zval *value);
++int zend_collection_has_item(zend_object *object, zval *offset);
+]]></example>
+</div>
+
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+int zend_collection_has_item(zend_object *object, zval *offset)
+{
+       zval rv;
+       zval *value_prop;
+       zend_class_entry *ce = object->ce;
+
+       if (!key_type_allowed(ce, offset)) {
+               return false;
+       }
+
+       value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+       if (Z_TYPE_P(offset) == IS_STRING) {
+               return zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset)) != NULL;
+       } else {
+               return zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset)) != NULL;
+       }
+
+       return false;
+}
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-init-and-property.xml b/slides/internals/collection-add-init-and-property.xml
new file mode 100644
index 00000000..5688deda
--- /dev/null
+++ b/slides/internals/collection-add-init-and-property.xml
@@ -0,0 +1,60 @@
+<slide>
+<title>Collection: Init and Storage</title>
+<!--
+<blurb>%zend_compile.c%</blurb>
+<example class="small"><![CDATA[
+                zend_compile_collection_key_type(ce, collection_key_type_ast);
+                zend_compile_collection_item_type(ce, collection_item_type_ast);
++               zend_collection_register_handlers(ce);
++               zend_collection_register_props(ce);
+        }
+
+]]></example>
+-->
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+#ifndef ZEND_COLLECTION_H
+#define ZEND_COLLECTION_H
+
+#include "zend.h"
+#include <stdint.h>
+
+BEGIN_EXTERN_C()
+
+extern ZEND_API zend_object_handlers zend_collection_object_handlers;
+
+void zend_collection_register_handlers(zend_class_entry *ce);
+void zend_collection_register_props(zend_class_entry *ce);
+
+END_EXTERN_C()
+#endif /* ZEND_COLLECTION_H */
+]]></example>
+</div>
+
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+#include "zend.h"
+#include "zend_API.h"
+
+void zend_collection_register_handlers(zend_class_entry *ce)
+{
+       memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+       zend_collection_object_handlers.clone_obj = NULL;
+       zend_collection_object_handlers.compare = zend_objects_not_comparable;
+       ce->default_object_handlers = &zend_collection_object_handlers;
+}
+
+void zend_collection_register_props(zend_class_entry *ce)
+{
+       zval name_default_value;
+       ZVAL_UNDEF(&name_default_value);
+       zend_type name_type = ZEND_TYPE_INIT_CODE(IS_ARRAY, 0, 0);
+       zend_declare_typed_property(ce, ZSTR_KNOWN(ZEND_STR_VALUE), &name_default_value, ZEND_ACC_PUBLIC | ZEND_ACC_R
+       ce->ce_flags |= ZEND_ACC_NO_DYNAMIC_PROPERTIES;
+}
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-interface-handlers.xml b/slides/internals/collection-add-interface-handlers.xml
new file mode 100644
index 00000000..4c20df07
--- /dev/null
+++ b/slides/internals/collection-add-interface-handlers.xml
@@ -0,0 +1,57 @@
+<slide>
+<title>Collection: Add Interface Handlers</title>
+
+<blurb>%Zend/zend_collection.c%</blurb>
+
+<div effect="fade-in">
+<example class="small"><![CDATA[
+static int zend_implement_collection(zend_class_entry *interface, zend_class_entry *class_type)
+{
+       if (class_type->ce_flags & ZEND_ACC_COLLECTION) {
+               return SUCCESS;
+       }
+
+       zend_error_noreturn(E_ERROR, "Non-collection class %s cannot implement interface %s",
+               ZSTR_VAL(class_type->name),
+               ZSTR_VAL(interface->name));
+
+       return FAILURE;
+}
+]]></example>
+</div>
+
+<div effect="fade-in">
+<example class="small"><![CDATA[
+void zend_register_collection_ce(void)
+{
+       zend_ce_collection = register_class_Collection();
+       zend_ce_collection->interface_gets_implemented = zend_implement_collection;
+
+       memcpy(&zend_collection_object_handlers, &std_object_handlers, sizeof(zend_object_handlers));
+       zend_collection_object_handlers.clone_obj = NULL;
+       zend_collection_object_handlers.compare = zend_objects_not_comparable;
+}
+]]></example>
+</div>
+
+<div effect="fade-in">
+<example class="small"><![CDATA[
+void zend_collection_add_interfaces(zend_class_entry *ce)
+{
+       uint32_t num_interfaces_before = ce->num_interfaces;
+
+       ce->num_interfaces++;
+
+       ZEND_ASSERT(!(ce->ce_flags & ZEND_ACC_RESOLVED_INTERFACES));
+
+       ce->interface_names = erealloc(ce->interface_names, sizeof(zend_class_name) * ce->num_interfaces);
+
+       ce->interface_names[num_interfaces_before].name = zend_string_copy(zend_ce_collection->name);
+       ce->interface_names[num_interfaces_before].lc_name = ZSTR_INIT_LITERAL("collection", 0);
+
+       ce->default_object_handlers = &zend_collection_object_handlers;
+}
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-interface.xml b/slides/internals/collection-add-interface.xml
new file mode 100644
index 00000000..f5155ef0
--- /dev/null
+++ b/slides/internals/collection-add-interface.xml
@@ -0,0 +1,34 @@
+<slide>
+<title>Collection: Add Interface</title>
+
+<blurb>%Zend/zend_collection.stub.php%</blurb>
+<example class="small"><![CDATA[
+<?php
+/** @generate-class-entries */
+interface Collection
+{
+}
+]]></example>
+
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+ extern ZEND_API zend_object_handlers zend_collection_object_handlers;
++extern ZEND_API zend_class_entry *zend_ce_collection;
++
++void zend_register_collection_ce(void);
++void zend_collection_add_interfaces(zend_class_entry *ce);
+]]></example>
+
+<div effect="fade-in">
+<break/>
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
++#include "zend_collection_arginfo.h"
++#include "zend_execute.h"
+
++ZEND_API zend_class_entry *zend_ce_collection;
+ ZEND_API zend_object_handlers zend_collection_object_handlers;
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-parser-rules.xml b/slides/internals/collection-add-parser-rules.xml
new file mode 100644
index 00000000..ed499fd4
--- /dev/null
+++ b/slides/internals/collection-add-parser-rules.xml
@@ -0,0 +1,43 @@
+<slide>
+<title>Collection: Add Parser Rules</title>
+
+<blurb>%Zend/zend_language_parser.y%</blurb>
+<example class="small"><![CDATA[
+%type <ast> enum_declaration_statement enum_backing_type enum_case enum_case_expr
+…
+
+enum_declaration_statement:
+  T_ENUM { $<num>$ = CG(zend_lineno); }
+  T_STRING enum_backing_type implements_list backup_doc_comment '{' class_statement_list '}'
+    { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_ENUM|ZEND_ACC_FINAL, $<num>2, $6, zend_ast_get_str($3), NULL, $5, $8, NULL, $4); }
+;]]></example>
+
+<blurb>Add:</blurb>
+<example class="small"><![CDATA[
+%type <ast> collection_declaration_statement collection_type
+…
+
+collection_declaration_statement:
+  T_COLLECTION { $<num>$ = CG(zend_lineno); }
+  T_STRING '(' collection_type T_DOUBLE_ARROW collection_type ')' backup_doc_comment '{' class_statement_list '}'
+    { $$ = zend_ast_create_decl(ZEND_AST_CLASS, ZEND_ACC_COLLECTION|ZEND_ACC_FINAL, $<num>2, $9, zend_ast_get_str($3), NULL, NULL, $11, $5, $7); }
+;
+
+collection_type:
+  type_expr { $$ = $1; }
+…
+
+    |       enum_declaration_statement          { $$ = $1; }
+    |       collection_declaration_statement    { $$ = $1; }
+]]></example>
+
+
+<blurb>%Zend/zend_compile.h%</blurb>
+
+<example class="small"><![CDATA[
+/* Special class types                                    |     |     |     */
+…
+#define ZEND_ACC_ENUM                    (1 << 28) /*  X  |     |     |     */
+#define ZEND_ACC_COLLECTION              (1 << 30) /*  X  |     |     |     */
+]]></example>
+</slide>
diff --git a/slides/internals/collection-add-read-handler.xml b/slides/internals/collection-add-read-handler.xml
new file mode 100644
index 00000000..0f7e9f46
--- /dev/null
+++ b/slides/internals/collection-add-read-handler.xml
@@ -0,0 +1,26 @@
+<slide>
+<title>Collection: Add Read Handler [1]</title>
+
+<blurb>%Zend/zend_object_handlers.c%</blurb>
+<example class="small"><![CDATA[
+@@ -1019,6 +1020,8 @@ ZEND_API zval *zend_std_read_dimension(zend_object *object, zval *offset, int ty
+                        return NULL;
+                }
+                return rv;
++       } else if (zend_class_implements_interface(ce, zend_ce_collection)) {
++               return zend_collection_read_item(object, offset);
+        } else {
+                zend_bad_array_access(ce);
+                return NULL;
+]]></example>
+
+<div effect="fade-in">
+<break/>
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+ void zend_collection_add_item(zend_object *object, zval *offset, zval *value);
++zval *zend_collection_read_item(zend_object *object, zval *offset);
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-add-read-handler2.xml b/slides/internals/collection-add-read-handler2.xml
new file mode 100644
index 00000000..d5020709
--- /dev/null
+++ b/slides/internals/collection-add-read-handler2.xml
@@ -0,0 +1,44 @@
+<slide>
+<title>Collection: Add Read Handler [2]</title>
+
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+static int key_type_allowed(zend_class_entry *ce, zval *offset)
+{
+       ZEND_ASSERT(ce->ce_flags & ZEND_ACC_COLLECTION);
+
+       if (ce->collection_key_type != Z_TYPE_P(offset)) {
+               zend_type_error(
+                       "Key type %s of element does not match collection key type %s",
+                       offset ? zend_zval_type_name(offset) : zend_get_type_by_const(IS_NULL),
+                       zend_get_type_by_const(ce->collection_key_type)
+               );
+               return false;
+       }
+
+       return true;
+}
+
+zval *zend_collection_read_item(zend_object *object, zval *offset)
+{
+       zval rv;
+       zval *value_prop, *value;
+       zend_class_entry *ce = object->ce;
+
+       if (!key_type_allowed(ce, offset)) {
+               return NULL;
+       }
+
+       value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+
+       if (Z_TYPE_P(offset) == IS_STRING) {
+               value = zend_hash_find(HASH_OF(value_prop), Z_STR_P(offset));
+       } else {
+               value = zend_hash_index_find(HASH_OF(value_prop), Z_LVAL_P(offset));
+       }
+
+       return value;
+}
+]]></example>
+
+</slide>
diff --git a/slides/internals/collection-add-token.xml b/slides/internals/collection-add-token.xml
new file mode 100644
index 00000000..4179d812
--- /dev/null
+++ b/slides/internals/collection-add-token.xml
@@ -0,0 +1,31 @@
+<slide>
+<title>Collection: Add Token</title>
+
+<blurb>%Zend/zend_language_parser.y%</blurb>
+<example inline="2"><![CDATA[
+ %token <ident> T_ENUM          "'enum'"
+*+%token <ident> T_COLLECTION    "'collection'"*
+ %token <ident> T_EXTENDS       "'extends'"
+]]></example>
+
+<blurb>%Zend/zend_language_scanner.l%</blurb>
+
+<example><![CDATA[
+        RETURN_TOKEN_WITH_IDENT(T_ENUM);
+ }
+
++/*
++ * The collection keyword must be followed by whitespace and another identifier.
++ * This avoids the BC break of using collection in classes, namespaces, functions and constants.
++ */
++<ST_IN_SCRIPTING>"collection"{WHITESPACE_OR_COMMENTS}("extends"|"implements") {
++       yyless(10);
++       RETURN_TOKEN_WITH_STR(T_STRING, 0);
++}
++<ST_IN_SCRIPTING>"collection"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] {
++       yyless(10);
++       RETURN_TOKEN_WITH_IDENT(T_COLLECTION);
++}
++
+]]></example>
+</slide>
diff --git a/slides/internals/collection-add-unset-handler.xml b/slides/internals/collection-add-unset-handler.xml
new file mode 100644
index 00000000..5ff71f0c
--- /dev/null
+++ b/slides/internals/collection-add-unset-handler.xml
@@ -0,0 +1,49 @@
+<slide>
+<title>Collection: Add Unset Handler</title>
+
+<blurb>%Zend/zend_object_handlers.c%</blurb>
+<example class="small"><![CDATA[
+@@ -1251,6 +1258,8 @@ ZEND_API void zend_std_unset_dimension(zend_object *object, zval *offset) /* {{{
+                zend_call_known_instance_method_with_1_params(funcs->zf_offsetunset, object, NULL, &tmp_offset);
+                OBJ_RELEASE(object);
+                zval_ptr_dtor(&tmp_offset);
++       } else if (zend_class_implements_interface(ce, zend_ce_collection)) {
++               zend_collection_unset_item(object, offset);
+        } else {
+                zend_bad_array_access(ce);
+        }
+]]></example>
+
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.h%</blurb>
+<example class="small"><![CDATA[
+ zval *zend_collection_read_item(zend_object *object, zval *offset);
++void zend_collection_unset_item(zend_object *object, zval *offset);
+]]></example>
+</div>
+
+<div effect="fade-in">
+<blurb>%Zend/zend_collection.c%</blurb>
+<example class="small"><![CDATA[
+void zend_collection_unset_item(zend_object *object, zval *offset)
+{
+       zval rv;
+       zval *value_prop;
+       zend_class_entry *ce = object->ce;
+
+       if (!key_type_allowed(ce, offset)) {
+               return;
+       }
+
+       value_prop = zend_read_property_ex(ce, object, ZSTR_KNOWN(ZEND_STR_VALUE), true, &rv);
+
+       if (Z_TYPE_P(offset) == IS_STRING) {
+               zend_hash_del(HASH_OF(value_prop), Z_STR_P(offset));
+       } else {
+               zend_hash_index_del(HASH_OF(value_prop), Z_LVAL_P(offset));
+       }
+}
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-hook-init-and-property.xml b/slides/internals/collection-hook-init-and-property.xml
new file mode 100644
index 00000000..15dbaffa
--- /dev/null
+++ b/slides/internals/collection-hook-init-and-property.xml
@@ -0,0 +1,41 @@
+<slide>
+<title>Collection: Hook-Up Init and Storage</title>
+
+<blurb>%zend_compile.c%</blurb>
+<example class="small"><![CDATA[
+                zend_compile_collection_key_type(ce, collection_key_type_ast);
+                zend_compile_collection_item_type(ce, collection_item_type_ast);
++               zend_collection_register_handlers(ce);
++               zend_collection_register_props(ce);
+        }
+
+]]></example>
+
+<div effect="fade-in">
+<break/>
+<blurb>%configure.ac%</blurb>
+<example class="small"><![CDATA[
+@@ -1722,7 +1722,7 @@ PHP_ADD_SOURCES(Zend, \
+     zend_closures.c zend_weakrefs.c zend_float.c zend_string.c zend_signal.c zend_generators.c \
+     zend_virtual_cwd.c zend_ast.c zend_objects.c zend_object_handlers.c zend_objects_API.c \
+     zend_default_classes.c zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_gdb.c \
+-    zend_observer.c zend_system_id.c zend_enum.c zend_fibers.c zend_atomic.c \
++    zend_observer.c zend_system_id.c zend_enum.c zend_collection.c zend_fibers.c zend_atomic.c \
+     zend_max_execution_timer.c \
+]]></example>
+</div>
+
+<div effect="fade-in">
+<break/>
+<blurb>%win32/build/config.w32%</blurb>
+<example class="small"><![CDATA[
+@@ -240,7 +240,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \
+        zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \
+        zend_float.c zend_string.c zend_generators.c zend_virtual_cwd.c zend_ast.c \
+        zend_inheritance.c zend_smart_str.c zend_cpuinfo.c zend_observer.c zend_system_id.c \
+-       zend_enum.c zend_fibers.c zend_atomic.c");
++       zend_enum.c zend_collection.c zend_fibers.c zend_atomic.c");
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-make-check-type-public.xml b/slides/internals/collection-make-check-type-public.xml
new file mode 100644
index 00000000..ef2f0a5c
--- /dev/null
+++ b/slides/internals/collection-make-check-type-public.xml
@@ -0,0 +1,30 @@
+<slide>
+<title>Collection: Make Check Type Public</title>
+
+<blurb>%Zend/zend_execute.h%</blurb>
+<example class="small"><![CDATA[
+@@ -89,6 +89,7 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_invalid_class_constant_type_error(uin
+ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_object_released_while_assigning_to_property_error(const zend_property_inf
+
+ ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg);
++ZEND_API bool zend_check_type(zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope, bool is_return_type, bool is_internal);
+ ZEND_API ZEND_COLD void zend_verify_arg_error(
+                const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value);
+ ZEND_API ZEND_COLD void zend_verify_return_error(
+]]></example>
+
+
+<blurb>%Zend/zend_execute.c%</blurb>
+<example class="small"><![CDATA[
+@@ -1182,7 +1182,7 @@ static zend_always_inline bool zend_check_type_slow(
+         * because this case is already checked at compile-time. */
+ }
+
+-static zend_always_inline bool zend_check_type(
++zend_always_inline bool zend_check_type(
+                zend_type *type, zval *arg, void **cache_slot, zend_class_entry *scope,
+                bool is_return_type, bool is_internal)
+ {
+]]></example>
+
+</slide>
diff --git a/slides/internals/collection-setup-interface-handlers.xml b/slides/internals/collection-setup-interface-handlers.xml
new file mode 100644
index 00000000..cec7b773
--- /dev/null
+++ b/slides/internals/collection-setup-interface-handlers.xml
@@ -0,0 +1,29 @@
+<slide>
+<title>Collection: Add Interface Handlers</title>
+
+<blurb>%Zend/zend_compile.c%</blurb>
+<example class="small"><![CDATA[
+@@ -8059,6 +8059,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
+        if (ce->ce_flags & ZEND_ACC_COLLECTION) {
+                zend_compile_collection_key_type(ce, collection_key_type_ast);
+                zend_compile_collection_item_type(ce, collection_item_type_ast);
++               zend_collection_add_interfaces(ce);
+                zend_collection_register_handlers(ce);
+                zend_collection_register_props(ce);
+        }
+]]></example>
+
+<div effect="fade-in">
+<break/>
+<blurb>%Zend/zend_default_classes.c%</blurb>
+<example class="small"><![CDATA[
+@@ -40,4 +40,5 @@ ZEND_API void zend_register_default_classes(void)
+        zend_register_attribute_ce();
+        zend_register_enum_ce();
+        zend_register_fiber_ce();
++       zend_register_collection_ce();
+ }
+]]></example>
+</div>
+
+</slide>
diff --git a/slides/internals/collection-syntax-example.xml b/slides/internals/collection-syntax-example.xml
new file mode 100644
index 00000000..8b8fa9b3
--- /dev/null
+++ b/slides/internals/collection-syntax-example.xml
@@ -0,0 +1,78 @@
+<slide>
+<title>Collection: Syntax</title>
+
+<div effect="fade-out">
+<example inline="2"><![CDATA[&lt;?php
+*collection* Articles(*int|string* => *Class*)
+{
+}
+?>]]></example>
+</div>
+
+<div effect="fade-in-out">
+<blurb>Example Usage:</blurb>
+<example><![CDATA[<?php
+collection Articles(string => Article)
+{
+}
+
+class Article
+{
+    function __construct(public string $title)
+    {
+    }
+}
+
+$c = new Articles;
+
+// OK:
+$c["nine"] = new Article("The Ninth Planet: Pluto");
+
+// Error:
+$c["six"] = "Henry VIII";
+?>]]></example>
+
+</div>
+
+<div effect="fade-in">
+<blurb>%Zend/tests/collection/collection_3.phpt%</blurb>
+<example class="small"><![CDATA[--TESTS--
+Collections: Syntax
+--FILE--
+<?php
+collection Articles(string => Article) { }
+
+class Article
+{
+    function __construct(public string $title) { }
+}
+
+$c = new Articles;
+
+// OK:
+$c["nine"] = new Article("The Ninth Planet: Pluto");
+print_r($c);
+
+// Error:
+try {
+    $c["six"] = "Henry VIII";
+} catch (Error $e) {
+    echo get_class($e), ': ', $e->getMessage(), "\n";
+}
+?>
+--EXPECT--
+Articles Object
+(
+    [value] => Array
+        (
+            [nine] => Article Object
+                (
+                    [title] => The Ninth Planet: Pluto
+                )
+        )
+)
+TypeError: Value type string does not match collection item type Article
+]]></example>
+
+</div>
+</slide>
diff --git a/slides/internals/recap-content.xml b/slides/internals/recap-content.xml
index 4f72b290..e440c71d 100644
--- a/slides/internals/recap-content.xml
+++ b/slides/internals/recap-content.xml
@@ -5,6 +5,7 @@
 	<bullet>Stages:<br/>Code → Tokens → Parsing → AST → Byte Code</bullet>
 	<bullet>All looping structures are jumps</bullet>
 	<bullet>Code analysis for Fun and Profit</bullet>
+	<bullet>Collections branch with each individual step:<br/>%https://github.com/derickr/php-src/tree/collections%</bullet>
 </list>
 
 <break/>
diff --git a/slides/mongodb/me.xml b/slides/mongodb/me.xml
index 2be5addc..bb6531f2 100644
--- a/slides/mongodb/me.xml
+++ b/slides/mongodb/me.xml
@@ -13,6 +13,6 @@
 <!--		<bullet>Host of *https://phpinternals.news* podcast</bullet> -->
 		<bullet>I ♥🌍 maps, I ♥🍺 beer, I ♥🥃  whisky</bullet>
 		<bullet>mastodon: :-:mastodon:-:</bullet>
-		<bullet>|777777|twitter: @:-:twitter:-:|</bullet>
+		<bullet>|999999|twitter: @:-:twitter:-:|</bullet>
 	</list>
 </slide>