[presentations] master: php[tek] 23 tutorial
| From: | Derick Rethans | Date: | Fri, 12 May 2023 17:14:13 +0000 |
| Subject: | [presentations] master: php[tek] 23 tutorial | ||
| Groups: | php.pres | ||
| Request: | Send a blank email to pres+get-2423@lists.php.net to get a copy of this message | ||
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>derick@php.net</email>
+<twitter>derickr</twitter>
+<mastodon>@derickr@phpc.social</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[<?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>