Skip to content

Commit be7b0bc

Browse files
committed
Implement Generator::throw() method
Generator::throw($exception) throws an exception into the generator. The exception is thrown at the current point of suspension within the generator. It basically behaves as if the current yield statement were replaced with a throw statement and the generator subsequently resumed.
1 parent 24f1ef1 commit be7b0bc

File tree

7 files changed

+159
-5
lines changed

7 files changed

+159
-5
lines changed

NEWS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ PHP NEWS
55
- General improvements:
66
. Fixed bug #63822 (Crash when using closures with ArrayAccess).
77
(Nikita Popov)
8+
. Add Generator::throw() method. (Nikita Popov)
89

910
- cURL:
1011
. Added new functions curl_escape, curl_multi_setopt, curl_multi_strerror
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Generator::throw() on an already closed generator
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
yield;
8+
}
9+
10+
$gen = gen();
11+
$gen->next();
12+
$gen->next();
13+
var_dump($gen->valid());
14+
$gen->throw(new Exception('test'));
15+
16+
?>
17+
--EXPECTF--
18+
bool(false)
19+
20+
Fatal error: Uncaught exception 'Exception' with message 'test' in %s:%d
21+
Stack trace:
22+
#0 {main}
23+
thrown in %s on line %d
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Generator::throw() where the exception is caught in the generator
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
try {
8+
yield;
9+
} catch (RuntimeException $e) {
10+
echo $e, "\n\n";
11+
}
12+
13+
yield 'result';
14+
}
15+
16+
$gen = gen();
17+
var_dump($gen->throw(new RuntimeException('Test')));
18+
19+
?>
20+
--EXPECTF--
21+
exception 'RuntimeException' with message 'Test' in %s:%d
22+
Stack trace:
23+
#0 {main}
24+
25+
string(6) "result"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Generator::throw() with something that's not an exception
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
yield;
8+
}
9+
10+
$gen = gen();
11+
$gen->throw(new stdClass);
12+
13+
?>
14+
--EXPECTF--
15+
Fatal error: Exceptions must be valid objects derived from the Exception base class in %s on line %d
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
--TEST--
2+
Generator::throw() where the generator throws a different exception
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
try {
8+
yield;
9+
} catch (RuntimeException $e) {
10+
echo 'Caught: ', $e, "\n\n";
11+
12+
throw new LogicException('new throw');
13+
}
14+
}
15+
16+
$gen = gen();
17+
var_dump($gen->throw(new RuntimeException('throw')));
18+
19+
?>
20+
--EXPECTF--
21+
Caught: exception 'RuntimeException' with message 'throw' in %s:%d
22+
Stack trace:
23+
#0 {main}
24+
25+
26+
Fatal error: Uncaught exception 'LogicException' with message 'new throw' in %s:%d
27+
Stack trace:
28+
#0 [internal function]: gen()
29+
#1 %s(%d): Generator->throw(Object(RuntimeException))
30+
#2 {main}
31+
thrown in %s on line %d
32+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
--TEST--
2+
Generator::throw() where the exception is not caught in the generator
3+
--FILE--
4+
<?php
5+
6+
function gen() {
7+
yield 'thisThrows';
8+
yield 'notReached';
9+
}
10+
11+
$gen = gen();
12+
var_dump($gen->throw(new RuntimeException('test')));
13+
14+
?>
15+
--EXPECTF--
16+
Fatal error: Uncaught exception 'RuntimeException' with message 'test' in %s:%d
17+
Stack trace:
18+
#0 {main}
19+
thrown in %s on line %d

Zend/zend_generators.c

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -431,10 +431,6 @@ static zend_function *zend_generator_get_constructor(zval *object TSRMLS_DC) /*
431431

432432
ZEND_API void zend_generator_resume(zend_generator *generator TSRMLS_DC) /* {{{ */
433433
{
434-
if (EG(exception)) {
435-
return;
436-
}
437-
438434
/* The generator is already closed, thus can't resume */
439435
if (!generator->execute_data) {
440436
return;
@@ -617,7 +613,7 @@ ZEND_METHOD(Generator, next)
617613
}
618614
/* }}} */
619615

620-
/* {{{ proto mixed Generator::send()
616+
/* {{{ proto mixed Generator::send(mixed $value)
621617
* Sends a value to the generator */
622618
ZEND_METHOD(Generator, send)
623619
{
@@ -648,6 +644,44 @@ ZEND_METHOD(Generator, send)
648644
}
649645
/* }}} */
650646

647+
/* {{{ proto mixed Generator::throw(Exception $exception)
648+
* Throws an exception into the generator */
649+
ZEND_METHOD(Generator, throw)
650+
{
651+
zval *exception, *exception_copy;
652+
zend_generator *generator;
653+
654+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &exception) == FAILURE) {
655+
return;
656+
}
657+
658+
ALLOC_ZVAL(exception_copy);
659+
MAKE_COPY_ZVAL(&exception, exception_copy);
660+
661+
generator = (zend_generator *) zend_object_store_get_object(getThis() TSRMLS_CC);
662+
663+
if (generator->execute_data) {
664+
/* Throw the exception in the context of the generator */
665+
zend_execute_data *current_execute_data = EG(current_execute_data);
666+
EG(current_execute_data) = generator->execute_data;
667+
668+
zend_throw_exception_object(exception_copy TSRMLS_CC);
669+
670+
EG(current_execute_data) = current_execute_data;
671+
672+
zend_generator_resume(generator TSRMLS_CC);
673+
674+
if (generator->value) {
675+
RETURN_ZVAL(generator->value, 1, 0);
676+
}
677+
} else {
678+
/* If the generator is already closed throw the exception in the
679+
* current context */
680+
zend_throw_exception_object(exception_copy TSRMLS_CC);
681+
}
682+
}
683+
/* }}} */
684+
651685
/* {{{ proto void Generator::__wakeup()
652686
* Throws an Exception as generators can't be serialized */
653687
ZEND_METHOD(Generator, __wakeup)
@@ -790,13 +824,18 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_generator_send, 0, 0, 1)
790824
ZEND_ARG_INFO(0, value)
791825
ZEND_END_ARG_INFO()
792826

827+
ZEND_BEGIN_ARG_INFO_EX(arginfo_generator_throw, 0, 0, 1)
828+
ZEND_ARG_INFO(0, exception)
829+
ZEND_END_ARG_INFO()
830+
793831
static const zend_function_entry generator_functions[] = {
794832
ZEND_ME(Generator, rewind, arginfo_generator_void, ZEND_ACC_PUBLIC)
795833
ZEND_ME(Generator, valid, arginfo_generator_void, ZEND_ACC_PUBLIC)
796834
ZEND_ME(Generator, current, arginfo_generator_void, ZEND_ACC_PUBLIC)
797835
ZEND_ME(Generator, key, arginfo_generator_void, ZEND_ACC_PUBLIC)
798836
ZEND_ME(Generator, next, arginfo_generator_void, ZEND_ACC_PUBLIC)
799837
ZEND_ME(Generator, send, arginfo_generator_send, ZEND_ACC_PUBLIC)
838+
ZEND_ME(Generator, throw, arginfo_generator_throw, ZEND_ACC_PUBLIC)
800839
ZEND_ME(Generator, __wakeup, arginfo_generator_void, ZEND_ACC_PUBLIC)
801840
ZEND_FE_END
802841
};

0 commit comments

Comments
 (0)