From 3c168ef332ed66929c404cbf8c02ec04bfd02780 Mon Sep 17 00:00:00 2001 From: Olly Betts Date: Wed, 5 May 2021 12:02:38 +1200 Subject: [PATCH] Map known PHP interfaces to zend_class_entry* Most pre-defined interfaces are accessible via zend_class_entry* variables declared in the PHP C API - we can use these to add an interface at MINIT time (rather than having to wait until RINIT to look up by name) by having a mapping from PHP interface name to them. This will also be a little faster than looking up by name. Closes #2013 --- Lib/php/php.swg | 3 ++ Lib/php/phpinterfaces.i | 62 +++++++++++++++++++++++++++++++++ Lib/php/phprun.swg | 2 ++ Source/Modules/php.cxx | 76 ++++++++++++++++++++++++++++++----------- 4 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 Lib/php/phpinterfaces.i diff --git a/Lib/php/php.swg b/Lib/php/php.swg index 468c7bb55..42985eac7 100644 --- a/Lib/php/php.swg +++ b/Lib/php/php.swg @@ -547,3 +547,6 @@ /* php keywords */ %include + +/* PHP known interfaces */ +%include diff --git a/Lib/php/phpinterfaces.i b/Lib/php/phpinterfaces.i new file mode 100644 index 000000000..dda219d91 --- /dev/null +++ b/Lib/php/phpinterfaces.i @@ -0,0 +1,62 @@ +/* ----------------------------------------------------------------------------- + * phpinterfaces.i + * + * Define "known" PHP interfaces. + * + * These can be added at MINIT time (which is when PHP loads the extension + * module). + * + * Any interface can be added via phpinterfaces, but looking up the + * zend_class_entry by name has to wait until RINIT time, which means it + * happens for every request. + * ----------------------------------------------------------------------------- */ + +// Note: Abstract interfaces such as "Traversable" can't be used in +// "implements" so are not relevant here. + +%insert(header) %{ + +#define SWIG_PHP_INTERFACE_Iterator_CE zend_ce_iterator +#define SWIG_PHP_INTERFACE_Iterator_HEADER "zend_interfaces.h" + +#define SWIG_PHP_INTERFACE_IteratorAggregate_CE zend_ce_aggregate +#define SWIG_PHP_INTERFACE_IteratorAggregate_HEADER "zend_interfaces.h" + +#define SWIG_PHP_INTERFACE_ArrayAccess_CE zend_ce_arrayaccess +#define SWIG_PHP_INTERFACE_ArrayAccess_HEADER "zend_interfaces.h" + +#define SWIG_PHP_INTERFACE_Serializable_CE zend_ce_serializable +#define SWIG_PHP_INTERFACE_Serializable_HEADER "zend_interfaces.h" + +#define SWIG_PHP_INTERFACE_Countable_CE zend_ce_countable +#define SWIG_PHP_INTERFACE_Countable_HEADER "zend_interfaces.h" + +#define SWIG_PHP_INTERFACE_OuterIterator_CE spl_ce_OuterIterator +#define SWIG_PHP_INTERFACE_OuterIterator_HEADER "ext/spl/spl_iterators.h" + +#define SWIG_PHP_INTERFACE_RecursiveIterator_CE spl_ce_RecursiveIterator +#define SWIG_PHP_INTERFACE_RecursiveIterator_HEADER "ext/spl/spl_iterators.h" + +#define SWIG_PHP_INTERFACE_SeekableIterator_CE spl_ce_SeekableIterator +#define SWIG_PHP_INTERFACE_SeekableIterator_HEADER "ext/spl/spl_iterators.h" + +#define SWIG_PHP_INTERFACE_SplObserver_CE spl_ce_SplObserver +#define SWIG_PHP_INTERFACE_SplObserver_HEADER "ext/spl/spl_observer.h" + +#define SWIG_PHP_INTERFACE_SplSubject_CE spl_ce_SplSubject +#define SWIG_PHP_INTERFACE_SplSubject_HEADER "ext/spl/spl_observer.h" + +#define SWIG_PHP_INTERFACE_DateTimeInterface_CE php_date_get_interface_ce() +#define SWIG_PHP_INTERFACE_DateTimeInterface_HEADER "ext/date/php_date.h" + +// The "json" extension needs to be loaded earlier that us for this to work. +#define SWIG_PHP_INTERFACE_JsonSerializable_CE php_json_serializable_ce +#define SWIG_PHP_INTERFACE_JsonSerializable_HEADER "ext/json/php_json.h" + +// New in PHP 8.0. +#if PHP_MAJOR >= 8 +# define SWIG_PHP_INTERFACE_Stringable_CE zend_ce_stringable +# define SWIG_PHP_INTERFACE_Stringable_HEADER "zend_interfaces.h" +#endif + +%} diff --git a/Lib/php/phprun.swg b/Lib/php/phprun.swg index b0376314e..880c98f41 100644 --- a/Lib/php/phprun.swg +++ b/Lib/php/phprun.swg @@ -16,7 +16,9 @@ extern "C" { # error These bindings need PHP 7 or later - to generate PHP5 bindings use: SWIG < 4.0.0 and swig -php5 #endif +#include "zend_inheritance.h" #include "zend_exceptions.h" +#include "zend_inheritance.h" #include /* for abort(), used in generated code. */ diff --git a/Source/Modules/php.cxx b/Source/Modules/php.cxx index ab7aaf375..1ef67d072 100644 --- a/Source/Modules/php.cxx +++ b/Source/Modules/php.cxx @@ -1635,26 +1635,64 @@ public: Setline(node, Getline(n)); String *interfaces = Swig_typemap_lookup("phpinterfaces", node, "", 0); Replaceall(interfaces, " ", ""); - if (interfaces) { - // It seems we need to wait until RINIT time to look up classes. - // The downside is that this then happens for every request. - Printf(r_init, "{\n"); - List *interface_list = Split(interfaces, ',', -1); - int num_interfaces = Len(interface_list); - String *append_interface = NewStringEmpty(); - for(int Iterator = 1; Iterator <= num_interfaces; Iterator++) { - String *interface = Getitem(interface_list, Iterator-1); - String *interface_ce = NewStringEmpty(); - Printf(interface_ce, "php_%s_interface_ce_%d" , class_name , Iterator); - Printf(r_init, " zend_class_entry *%s = zend_lookup_class(zend_string_init(\"%s\", sizeof(\"%s\") - 1, 0));\n", interface_ce, interface, interface); - Append(append_interface, interface_ce); - Append(append_interface, " "); - } - Chop(append_interface); - Replaceall(append_interface, " ", ","); - Printf(r_init, " zend_class_implements(SWIGTYPE_%s_ce, %d, %s);\n", class_name, num_interfaces, append_interface); - Printf(r_init, "}\n"); + if (interfaces && Len(interfaces) > 0) { + // It seems we need to wait until RINIT time to look up class entries + // for interfaces by name. The downside is that this then happens for + // every request. + // + // Most pre-defined interfaces are accessible via zend_class_entry* + // variables declared in the PHP C API - these we can use at MINIT + // time, so we special case them. This will also be a little faster + // than looking up by name. + Printv(s_header, + "#ifdef __cplusplus\n", + "extern \"C\" {\n", + "#endif\n", + NIL); + + String *r_init_prefix = NewStringEmpty(); + + List *interface_list = Split(interfaces, ',', -1); + int num_interfaces = Len(interface_list); + for (int i = 0; i < num_interfaces; ++i) { + String *interface = Getitem(interface_list, i); + // We generate conditional code in both minit and rinit - then we or the user + // just need to define SWIG_PHP_INTERFACE_xxx_CE (and optionally + // SWIG_PHP_INTERFACE_xxx_CE) to handle interface `xxx` at minit-time. + Printv(s_header, + "#ifdef SWIG_PHP_INTERFACE_", interface, "_HEADER\n", + "# include SWIG_PHP_INTERFACE_", interface, "_HEADER\n", + "#endif\n", + NIL); + Printv(s_oinit, + "#ifdef SWIG_PHP_INTERFACE_", interface, "_CE\n", + " zend_do_implement_interface(SWIGTYPE_", class_name, "_ce, SWIG_PHP_INTERFACE_", interface, "_CE);\n", + "#endif\n", + NIL); + Printv(r_init_prefix, + "#ifndef SWIG_PHP_INTERFACE_", interface, "_CE\n", + " {\n", + " zend_class_entry *swig_interface_ce = zend_lookup_class(zend_string_init(\"", interface, "\", sizeof(\"", interface, "\") - 1, 0));\n", + " if (!swig_interface_ce) zend_throw_exception(zend_ce_error, \"Interface \\\"", interface, "\\\" not found\", 0);\n", + " zend_do_implement_interface(SWIGTYPE_", class_name, "_ce, swig_interface_ce);\n", + " }\n", + "#endif\n", + NIL); + } + + // Handle interfaces at the start of rinit so that they're added + // before any potential constant objects, etc which might be created + // later in rinit. + Insert(r_init, 0, r_init_prefix); + Delete(r_init_prefix); + + Printv(s_header, + "#ifdef __cplusplus\n", + "}\n", + "#endif\n", + NIL); } + Delete(interfaces); } Printf(s_oinit, " SWIGTYPE_%s_ce->create_object = %s_object_new;\n", class_name, class_name);