Skip to content
Closed
13 changes: 12 additions & 1 deletion Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "zend_exceptions.h"
#include "zend_closures.h"
#include "zend_inheritance.h"
#include "zend_interfaces.h"

#ifdef HAVE_STDARG_H
#include <stdarg.h>
Expand Down Expand Up @@ -624,6 +625,16 @@ static const char *zend_parse_arg_impl(int arg_num, zval *arg, va_list *va, cons
}
break;

case 't':
{
zval **p = va_arg(*va, zval **);

if (!zend_parse_arg_traversable(arg, p, check_null)) {
return "array or traversable";
}
}
break;

case 'H':
case 'h':
{
Expand Down Expand Up @@ -827,7 +838,7 @@ static int zend_parse_va_args(int num_args, const char *type_spec, va_list *va,
case 'f': case 'A':
case 'H': case 'p':
case 'S': case 'P':
case 'L':
case 'L': case 't':
max_num_args++;
break;

Expand Down
1 change: 0 additions & 1 deletion Zend/zend_API.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include "zend_variables.h"
#include "zend_execute.h"


BEGIN_EXTERN_C()

typedef struct _zend_function_entry {
Expand Down
14 changes: 14 additions & 0 deletions Zend/zend_interfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,20 @@ ZEND_API int zend_class_unserialize_deny(zval *object, zend_class_entry *ce, con

END_EXTERN_C()

static zend_always_inline int zend_parse_arg_traversable(zval *arg, zval **dest, int check_null)
{
if (EXPECTED(Z_TYPE_P(arg) == IS_ARRAY)) {
*dest = arg;
} else if (Z_TYPE_P(arg) == IS_OBJECT && EXPECTED(instanceof_function(Z_OBJCE_P(arg), zend_ce_traversable))) {
*dest = arg;
} else if (check_null && EXPECTED(Z_TYPE_P(arg) == IS_NULL)) {
*dest = NULL;
} else {
return 0;
}
return 1;
}

#endif /* ZEND_INTERFACES_H */

/*
Expand Down
68 changes: 66 additions & 2 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#ifdef HAVE_SPL
#include "ext/spl/spl_array.h"
#endif
#include "php_traversal.h"

/* {{{ defines */
#define EXTR_OVERWRITE 0
Expand Down Expand Up @@ -2773,7 +2774,7 @@ PHP_FUNCTION(array_unshift)
Z_ARRVAL_P(stack)->nNextFreeElement = new_hash.nNextFreeElement;
Z_ARRVAL_P(stack)->arData = new_hash.arData;
Z_ARRVAL_P(stack)->pDestructor = new_hash.pDestructor;

zend_hash_internal_pointer_reset(Z_ARRVAL_P(stack));

/* Clean up and return the number of elements in the stack */
Expand Down Expand Up @@ -4047,7 +4048,7 @@ static void php_array_intersect(INTERNAL_FUNCTION_PARAMETERS, int behavior, int
ZVAL_UNDEF(&list->val);
if (hash->nNumOfElements > 1) {
if (behavior == INTERSECT_NORMAL) {
zend_sort((void *) lists[i], hash->nNumOfElements,
zend_sort((void *) lists[i], hash->nNumOfElements,
sizeof(Bucket), intersect_data_compare_func, (swap_func_t)zend_hash_bucket_swap);
} else if (behavior & INTERSECT_ASSOC) { /* triggered also when INTERSECT_KEY */
zend_sort((void *) lists[i], hash->nNumOfElements,
Expand Down Expand Up @@ -5060,6 +5061,69 @@ PHP_FUNCTION(array_product)
}
/* }}} */

typedef struct _php_traverse_context_until {
PHP_TRAVERSE_CONTEXT_STANDARD_MEMBERS()
int stop_value;
int result;
} php_traverse_context_until;

static zend_bool php_traverse_until(zval *value, zval *key, void *context)
{
zval args[3];
zval retval;
int call_res;
php_traverse_context_until *data = context;

ZVAL_COPY(&args[0], value);
ZVAL_COPY(&args[1], key);
ZVAL_COPY(&args[2], data->traversable);
data->fci.params = args;
data->fci.retval = &retval;

call_res = zend_call_function(&data->fci, &data->fci_cache);
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
zval_ptr_dtor(&args[2]);
data->result = call_res == SUCCESS ? zend_is_true(&retval) : 0;
zval_ptr_dtor(&retval);

return data->result != data->stop_value; // stop condition
}

static void php_array_until(INTERNAL_FUNCTION_PARAMETERS, int stop_value)
{
php_traverse_context_until context;

if (zend_parse_parameters(ZEND_NUM_ARGS(), "tf", &context.traversable, &context.fci, &context.fci_cache) == FAILURE) {
return;
}

context.stop_value = stop_value;
context.result = !stop_value;
context.fci.param_count = 3;
context.fci.no_separation = 0;

php_traverse(context.traversable, php_traverse_until, PHP_TRAVERSE_MODE_KEY_VAL, &context);

RETURN_BOOL(context.result);
}

/* {{{ proto bool array_every(array input, mixed predicate)
Determines whether the predicate holds for all elements in the array. */
PHP_FUNCTION(array_every)
{
php_array_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 0);
}
/* }}} */

/* {{{ proto array array_filter(array input, mixed predicate)
Determines whether the predicate holds for at least one element in the array. */
PHP_FUNCTION(array_any)
{
php_array_until(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
/* }}} */

/* {{{ proto mixed array_reduce(array input, mixed callback [, mixed initial])
Iteratively reduce the array to a single value via the callback. */
PHP_FUNCTION(array_reduce)
Expand Down
12 changes: 12 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,16 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_array_reduce, 0, 0, 2)
ZEND_ARG_INFO(0, initial)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_array_every, 0)
ZEND_ARG_INFO(0, arg)
ZEND_ARG_INFO(0, callback)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO(arginfo_array_any, 0)
ZEND_ARG_INFO(0, arg)
ZEND_ARG_INFO(0, callback)
ZEND_END_ARG_INFO()

ZEND_BEGIN_ARG_INFO_EX(arginfo_array_filter, 0, 0, 1)
ZEND_ARG_INFO(0, arg) /* ARRAY_INFO(0, arg, 0) */
ZEND_ARG_INFO(0, callback)
Expand Down Expand Up @@ -3336,6 +3346,8 @@ const zend_function_entry basic_functions[] = { /* {{{ */
PHP_FE(array_udiff_uassoc, arginfo_array_udiff_uassoc)
PHP_FE(array_sum, arginfo_array_sum)
PHP_FE(array_product, arginfo_array_product)
PHP_FE(array_every, arginfo_array_every)
PHP_FE(array_any, arginfo_array_any)
PHP_FE(array_filter, arginfo_array_filter)
PHP_FE(array_map, arginfo_array_map)
PHP_FE(array_chunk, arginfo_array_chunk)
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -616,7 +616,7 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32.
http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \
var_unserializer.c ftok.c sha1.c user_filters.c uuencode.c \
filters.c proc_open.c streamsfuncs.c http.c password.c \
random.c,,,
random.c php_traversal.c,,,
-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)

PHP_ADD_MAKEFILE_FRAGMENT
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ EXTENSION("standard", "array.c base64.c basic_functions.c browscap.c \
url_scanner_ex.c ftp_fopen_wrapper.c http_fopen_wrapper.c \
php_fopen_wrapper.c credits.c css.c var_unserializer.c ftok.c sha1.c \
user_filters.c uuencode.c filters.c proc_open.c password.c \
streamsfuncs.c http.c flock_compat.c random.c", false /* never shared */,
streamsfuncs.c http.c flock_compat.c random.c php_traversal.c", false /* never shared */,
'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
PHP_INSTALL_HEADERS("", "ext/standard");
if (PHP_MBREGEX != "no") {
Expand Down
2 changes: 2 additions & 0 deletions ext/standard/php_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ PHP_FUNCTION(array_diff_uassoc);
PHP_FUNCTION(array_udiff_uassoc);
PHP_FUNCTION(array_sum);
PHP_FUNCTION(array_product);
PHP_FUNCTION(array_every);
PHP_FUNCTION(array_any);
PHP_FUNCTION(array_filter);
PHP_FUNCTION(array_map);
PHP_FUNCTION(array_key_exists);
Expand Down
78 changes: 78 additions & 0 deletions ext/standard/php_traversal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
+----------------------------------------------------------------------+
| PHP Version 7 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Tjerk Meesters <datibbaw@php.net> |
+----------------------------------------------------------------------+
*/

#include "php.h"
#include "php_traversal.h"

void php_traverse(zval *t, php_traverse_each_t each_func, int traverse_mode, void *context)
{
zend_bool should_continue;
zval *value;
zval key;

if (Z_TYPE_P(t) == IS_ARRAY) {
zend_ulong num_key;
zend_string *string_key;

ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL_P(t), num_key, string_key, value) {
if (traverse_mode == PHP_TRAVERSE_MODE_KEY_VAL) {
if (string_key) {
ZVAL_STR(&key, string_key);
} else {
ZVAL_LONG(&key, num_key);
}
should_continue = each_func(value, &key, context);
} else {
should_continue = each_func(value, NULL, context);
}
if (!should_continue) break;
} ZEND_HASH_FOREACH_END();
} else {
zend_class_entry *ce = Z_OBJCE_P(t);
zend_object_iterator *iter = ce->get_iterator(ce, t, 0);
if (EG(exception)) goto fail;
iter->index = 0;
if (iter->funcs->rewind) {
iter->funcs->rewind(iter);
}
while (!EG(exception) && iter->funcs->valid(iter) == SUCCESS) {
value = iter->funcs->get_current_data(iter);
if (EG(exception) || value == NULL) break;

if (traverse_mode == PHP_TRAVERSE_MODE_KEY_VAL) {
if (iter->funcs->get_current_key) {
iter->funcs->get_current_key(iter, &key);
if (EG(exception)) break;
} else {
ZVAL_NULL(&key);
}

should_continue = each_func(value, &key, context);
zval_ptr_dtor(&key);
} else {
should_continue = each_func(value, NULL, context);
}
if (!should_continue) break;

iter->index++;
iter->funcs->move_forward(iter);
}
fail:
zend_iterator_dtor(iter);
}
}
29 changes: 29 additions & 0 deletions ext/standard/php_traversal.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
+----------------------------------------------------------------------+
| PHP Version 7 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2015 The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Tjerk Meesters <datibbaw@php.net> |
+----------------------------------------------------------------------+
*/

typedef zend_bool (*php_traverse_each_t)(zval *value, zval *key, void *context);

#define PHP_TRAVERSE_MODE_VAL 2
#define PHP_TRAVERSE_MODE_KEY_VAL 3

#define PHP_TRAVERSE_CONTEXT_STANDARD_MEMBERS() \
zval *traversable; \
zend_fcall_info fci; \
zend_fcall_info_cache fci_cache;

PHPAPI void php_traverse(zval *t, php_traverse_each_t each_func, int traverse_mode, void *context);
75 changes: 75 additions & 0 deletions ext/standard/tests/array/array_any.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
--TEST--
Test array_any() function
--FILE--
<?php
/*
Prototype: bool array_any(array $array, mixed $callback);
Description: Iterate array and stop based on return value of callback
*/

function is_int_ex($nr)
{
return is_int($nr);
}

echo "\n*** Testing not enough or wrong arguments ***\n";

var_dump(array_any());
var_dump(array_any(true));
var_dump(array_any([]));

echo "\n*** Testing basic functionality ***\n";

var_dump(array_any(array('hello', 'world'), 'is_int_ex'));
var_dump(array_any(array('hello', 1, 2, 3), 'is_int_ex'));
$iterations = 0;
var_dump(array_any(array('hello', 1, 2, 3), function($item) use (&$iterations) {
++$iterations;
return is_int($item);
}));
var_dump($iterations);

echo "\n*** Testing traversable functionality ***\n";

var_dump(array_any((function() {
yield 'foo' => 'bar';
yield 456;
})(), function($value, $key) {
var_dump($value, $key);
return true;
}));

echo "\n*** Testing edge cases ***\n";

var_dump(array_any(array(), 'is_int_ex'));

echo "\nDone";
?>
--EXPECTF--

*** Testing not enough or wrong arguments ***

Warning: array_any() expects exactly 2 parameters, 0 given in %s on line %d
NULL

Warning: array_any() expects exactly 2 parameters, 1 given in %s on line %d
NULL

Warning: array_any() expects exactly 2 parameters, 1 given in %s on line %d
NULL

*** Testing basic functionality ***
bool(false)
bool(true)
bool(true)
int(2)

*** Testing traversable functionality ***
string(3) "bar"
string(3) "foo"
bool(true)

*** Testing edge cases ***
bool(false)

Done
Loading