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
diff --git a/Lib/php/php.swg b/Lib/php/php.swg
index 468c7bb..42985ea 100644
--- a/Lib/php/php.swg
+++ b/Lib/php/php.swg
@@ -547,3 +547,6 @@
 
 /* php keywords */
 %include <phpkw.swg>
+
+/* PHP known interfaces */
+%include <phpinterfaces.i>
diff --git a/Lib/php/phpinterfaces.i b/Lib/php/phpinterfaces.i
new file mode 100644
index 0000000..dda219d
--- /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 b037631..880c98f 100644
--- a/Lib/php/phprun.swg
+++ b/Lib/php/phprun.swg
@@ -16,7 +16,9 @@
 # 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 <stdlib.h> /* for abort(), used in generated code. */
 
diff --git a/Source/Modules/php.cxx b/Source/Modules/php.cxx
index ab7aaf3..1ef67d0 100644
--- a/Source/Modules/php.cxx
+++ b/Source/Modules/php.cxx
@@ -1635,26 +1635,64 @@
       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);