]> granicus.if.org Git - php/commitdiff
Add ReflectionGenerator class
authorBob Weinand <bobwei9@hotmail.com>
Thu, 14 May 2015 18:10:35 +0000 (20:10 +0200)
committerBob Weinand <bobwei9@hotmail.com>
Thu, 14 May 2015 18:10:35 +0000 (20:10 +0200)
ext/reflection/php_reflection.c
ext/reflection/tests/ReflectionGenerator_basic.phpt [new file with mode: 0644]
ext/reflection/tests/ReflectionGenerator_in_Generator.phpt [new file with mode: 0644]

index f7dd5531d00c546065d7f1ab52490dd8170fc6f1..65febf4a53ab4c4a8af9f803b6c76de9002d3b75 100644 (file)
@@ -39,7 +39,9 @@
 #include "zend_ini.h"
 #include "zend_interfaces.h"
 #include "zend_closures.h"
+#include "zend_generators.h"
 #include "zend_extensions.h"
+#include "zend_builtin_functions.h"
 
 #define reflection_update_property(object, name, value) do { \
                zval member; \
@@ -55,6 +57,7 @@ PHPAPI zend_class_entry *reflection_exception_ptr;
 PHPAPI zend_class_entry *reflection_ptr;
 PHPAPI zend_class_entry *reflection_function_abstract_ptr;
 PHPAPI zend_class_entry *reflection_function_ptr;
+PHPAPI zend_class_entry *reflection_generator_ptr;
 PHPAPI zend_class_entry *reflection_parameter_ptr;
 PHPAPI zend_class_entry *reflection_class_ptr;
 PHPAPI zend_class_entry *reflection_object_ptr;
@@ -201,6 +204,7 @@ typedef struct _parameter_reference {
 typedef enum {
        REF_TYPE_OTHER,      /* Must be 0 */
        REF_TYPE_FUNCTION,
+       REF_TYPE_GENERATOR,
        REF_TYPE_PARAMETER,
        REF_TYPE_PROPERTY,
        REF_TYPE_DYNAMIC_PROPERTY
@@ -314,6 +318,8 @@ static void reflection_free_objects_storage(zend_object *object) /* {{{ */
                        zend_string_release(prop_reference->prop.name);
                        efree(intern->ptr);
                        break;
+               case REF_TYPE_GENERATOR:
+                       break;
                case REF_TYPE_OTHER:
                        break;
                }
@@ -2108,6 +2114,174 @@ ZEND_METHOD(reflection_function, getExtensionName)
 }
 /* }}} */
 
+/* {{{ proto public void ReflectionGenerator::__construct(Generator) */
+ZEND_METHOD(reflection_generator, __construct)
+{
+       zval *generator, *object;
+       reflection_object *intern;
+       zend_execute_data *ex;
+
+       object = getThis();
+       intern = Z_REFLECTION_P(object);
+       if (intern == NULL) {
+               return;
+       }
+
+       if (zend_parse_parameters_throw(ZEND_NUM_ARGS(), "O", &generator, zend_ce_generator) == FAILURE) {
+               return;
+       }
+
+       ex = ((zend_generator *) Z_OBJ_P(generator))->execute_data;
+       if (!ex) {
+               zend_throw_exception(NULL, "Cannot create ReflectionGenerator based on a terminated Generator", 0);
+               return;
+       }
+
+       intern->ref_type = REF_TYPE_GENERATOR;
+       ZVAL_COPY(&intern->obj, generator);
+       intern->ce = zend_ce_generator;
+}
+/* }}} */
+
+#define REFLECTION_CHECK_VALID_GENERATOR(ex) \
+       if (!ex) { \
+               zend_throw_exception(NULL, "Cannot fetch information from a terminated Generator", 0); \
+               return; \
+       }
+
+/* {{{ proto public array ReflectionGenerator::getTrace($options = DEBUG_BACKTRACE_PROVIDE_OBJECT) */
+ZEND_METHOD(reflection_generator, getTrace)
+{
+       zend_long options = DEBUG_BACKTRACE_PROVIDE_OBJECT;
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_generator *root_generator;
+       zend_execute_data *ex_backup = EG(current_execute_data);
+       zend_execute_data *ex = generator->execute_data;
+       zend_execute_data *root_prev, *cur_prev;
+
+       if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &options) == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       root_generator = zend_generator_get_current(generator);
+
+       cur_prev = generator->execute_data->prev_execute_data;
+       if (generator == root_generator) {
+               generator->execute_data->prev_execute_data = NULL;
+       } else {
+               root_prev = root_generator->execute_data->prev_execute_data;
+               generator->execute_fake.prev_execute_data = NULL;
+               root_generator->execute_data->prev_execute_data = &generator->execute_fake;
+       }
+
+       EG(current_execute_data) = root_generator->execute_data;
+       zend_fetch_debug_backtrace(return_value, 0, options, 0);
+       EG(current_execute_data) = ex_backup;
+
+       root_generator->execute_data->prev_execute_data = root_prev;
+       generator->execute_data->prev_execute_data = cur_prev;
+}
+/* }}} */
+
+/* {{{ proto public int ReflectionGenerator::getExecutingLine() */
+ZEND_METHOD(reflection_generator, getExecutingLine)
+{
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_execute_data *ex = generator->execute_data;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       ZVAL_LONG(return_value, ex->opline->lineno);
+}
+/* }}} */
+
+/* {{{ proto public string ReflectionGenerator::getExecutingFile() */
+ZEND_METHOD(reflection_generator, getExecutingFile)
+{
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_execute_data *ex = generator->execute_data;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       ZVAL_STR_COPY(return_value, ex->func->op_array.filename);
+}
+/* }}} */
+
+/* {{{ proto public ReflectionFunctionAbstract ReflectionGenerator::getFunction() */
+ZEND_METHOD(reflection_generator, getFunction)
+{
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_execute_data *ex = generator->execute_data;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       if (ex->func->common.fn_flags & ZEND_ACC_CLOSURE) {
+               zval closure;
+               ZVAL_OBJ(&closure, (zend_object *) ex->func->common.prototype);
+               reflection_function_factory(ex->func, &closure, return_value);
+       } else if (ex->func->op_array.scope) {
+               reflection_method_factory(ex->func->op_array.scope, ex->func, NULL, return_value);
+       } else {
+               reflection_function_factory(ex->func, NULL, return_value);
+       }
+}
+/* }}} */
+
+/* {{{ proto public object ReflectionGenerator::getThis() */
+ZEND_METHOD(reflection_generator, getThis)
+{
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_execute_data *ex = generator->execute_data;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       if (Z_OBJ(ex->This)) {
+               ZVAL_COPY(return_value, &ex->This);
+       } else {
+               ZVAL_NULL(return_value);
+       }
+}
+/* }}} */
+
+/* {{{ proto public Generator ReflectionGenerator::getExecutingGenerator() */
+ZEND_METHOD(reflection_generator, getExecutingGenerator)
+{
+       zend_generator *generator = (zend_generator *) Z_OBJ(Z_REFLECTION_P(getThis())->obj);
+       zend_execute_data *ex = generator->execute_data;
+       zend_generator *current;
+
+       if (zend_parse_parameters_none() == FAILURE) {
+               return;
+       }
+
+       REFLECTION_CHECK_VALID_GENERATOR(ex)
+
+       current = zend_generator_get_current(generator);
+       ++GC_REFCOUNT(current);
+
+       ZVAL_OBJ(return_value, (zend_object *) current);
+}
+/* }}} */
+
+
 /* {{{ proto public static mixed ReflectionParameter::export(mixed function, mixed parameter [, bool return]) throws ReflectionException
    Exports a reflection object. Returns the output if TRUE is specified for return, printing it otherwise. */
 ZEND_METHOD(reflection_parameter, export)
@@ -5824,6 +5998,25 @@ static const zend_function_entry reflection_function_functions[] = {
        PHP_FE_END
 };
 
+ZEND_BEGIN_ARG_INFO(arginfo_reflection_generator___construct, 0)
+       ZEND_ARG_INFO(0, generator)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_INFO_EX(arginfo_reflection_generator_trace, 0, 0, 0)
+       ZEND_ARG_INFO(0, options)
+ZEND_END_ARG_INFO()
+
+static const zend_function_entry reflection_generator_functions[] = {
+       ZEND_ME(reflection_generator, __construct, arginfo_reflection_generator___construct, 0)
+       ZEND_ME(reflection_generator, getExecutingLine, arginfo_reflection__void, 0)
+       ZEND_ME(reflection_generator, getExecutingFile, arginfo_reflection__void, 0)
+       ZEND_ME(reflection_generator, getTrace, arginfo_reflection_generator_trace, 0)
+       ZEND_ME(reflection_generator, getFunction, arginfo_reflection__void, 0)
+       ZEND_ME(reflection_generator, getThis, arginfo_reflection__void, 0)
+       ZEND_ME(reflection_generator, getExecutingGenerator, arginfo_reflection__void, 0)
+       PHP_FE_END
+};
+
 ZEND_BEGIN_ARG_INFO_EX(arginfo_reflection_method_export, 0, 0, 2)
        ZEND_ARG_INFO(0, class)
        ZEND_ARG_INFO(0, name)
@@ -6204,6 +6397,10 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */
 
        REGISTER_REFLECTION_CLASS_CONST_LONG(function, "IS_DEPRECATED", ZEND_ACC_DEPRECATED);
 
+       INIT_CLASS_ENTRY(_reflection_entry, "ReflectionGenerator", reflection_generator_functions);
+       _reflection_entry.create_object = reflection_objects_new;
+       reflection_generator_ptr = zend_register_internal_class(&_reflection_entry);
+
        INIT_CLASS_ENTRY(_reflection_entry, "ReflectionParameter", reflection_parameter_functions);
        _reflection_entry.create_object = reflection_objects_new;
        reflection_parameter_ptr = zend_register_internal_class(&_reflection_entry);
diff --git a/ext/reflection/tests/ReflectionGenerator_basic.phpt b/ext/reflection/tests/ReflectionGenerator_basic.phpt
new file mode 100644 (file)
index 0000000..528d2d9
--- /dev/null
@@ -0,0 +1,87 @@
+--TEST--
+ReflectionGenerator basic test
+--FILE--
+<?php
+
+function foo() {
+       yield;
+}
+
+$gens = [
+       (new class() {
+               function a() {
+                       yield from foo();
+               }
+       })->a(),
+       (function() {
+               yield;
+       })(),
+       foo(),
+];
+
+foreach ($gens as $gen) {
+       var_dump($gen);
+
+       $gen->valid(); // start Generator
+       $ref = new ReflectionGenerator($gen);
+
+       var_dump($ref->getTrace());
+       var_dump($ref->getExecutingLine());
+       var_dump($ref->getExecutingFile());
+       var_dump($ref->getExecutingGenerator());
+       var_dump($ref->getFunction());
+       var_dump($ref->getThis());
+}
+
+?>
+--EXPECTF--
+object(Generator)#2 (0) {
+}
+array(1) {
+  [0]=>
+  array(2) {
+    ["function"]=>
+    string(3) "foo"
+    ["args"]=>
+    array(0) {
+    }
+  }
+}
+int(%d)
+string(%d) "%sReflectionGenerator_basic.%s"
+object(Generator)#6 (0) {
+}
+object(ReflectionMethod)#8 (2) {
+  ["name"]=>
+  string(1) "a"
+  ["class"]=>
+  string(%d) "class@anonymous%s"
+}
+object(class@anonymous)#1 (0) {
+}
+object(Generator)#4 (0) {
+}
+array(0) {
+}
+int(%d)
+string(%d) "%sReflectionGenerator_basic.%s"
+object(Generator)#4 (0) {
+}
+object(ReflectionFunction)#7 (1) {
+  ["name"]=>
+  string(9) "{closure}"
+}
+NULL
+object(Generator)#5 (0) {
+}
+array(0) {
+}
+int(%d)
+string(%d) "%sReflectionGenerator_basic.%s"
+object(Generator)#5 (0) {
+}
+object(ReflectionFunction)#8 (1) {
+  ["name"]=>
+  string(3) "foo"
+}
+NULL
diff --git a/ext/reflection/tests/ReflectionGenerator_in_Generator.phpt b/ext/reflection/tests/ReflectionGenerator_in_Generator.phpt
new file mode 100644 (file)
index 0000000..00b4a56
--- /dev/null
@@ -0,0 +1,65 @@
+--TEST--
+ReflectionGenerator while being currently executed
+--FILE--
+<?php
+
+function call(ReflectionGenerator $ref, $method, $rec = true) {
+       if ($rec) {
+               call($ref, $method, false);
+               return;
+       }
+       var_dump($ref->$method());
+}
+
+function doCalls(ReflectionGenerator $ref) {
+       call($ref, "getTrace");
+       call($ref, "getExecutingLine");
+       call($ref, "getExecutingFile");
+       call($ref, "getExecutingGenerator");
+       call($ref, "getFunction");
+       call($ref, "getThis");
+}
+
+($gen = (function() use (&$gen) {
+       $ref = new ReflectionGenerator($gen);
+
+       doCalls($ref);
+
+       yield from (function() use ($ref) {
+               doCalls($ref);
+               yield; // Generator !
+       })();
+})())->valid();
+
+?>
+--EXPECTF--
+array(0) {
+}
+int(%d)
+string(%d) "%sReflectionGenerator_in_Generator.%s"
+object(Generator)#2 (0) {
+}
+object(ReflectionFunction)#4 (1) {
+  ["name"]=>
+  string(9) "{closure}"
+}
+NULL
+array(1) {
+  [0]=>
+  array(2) {
+    ["function"]=>
+    string(9) "{closure}"
+    ["args"]=>
+    array(0) {
+    }
+  }
+}
+int(%d)
+string(%d) "%sReflectionGenerator_in_Generator.%s"
+object(Generator)#5 (0) {
+}
+object(ReflectionFunction)#6 (1) {
+  ["name"]=>
+  string(9) "{closure}"
+}
+NULL