Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion Zend/zend_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "zend_closures.h"
#include "zend_inheritance.h"
#include "zend_ini.h"
#include "zend_interfaces.h"

#include <stdarg.h>

Expand Down Expand Up @@ -614,6 +615,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 @@ -808,7 +819,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 'n':
case 'L': case 'n': 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 @@ -29,7 +29,6 @@
#include "zend_execute.h"
#include "zend_type_info.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,4 +80,18 @@ 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 */
64 changes: 64 additions & 0 deletions ext/standard/array.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "zend_bitset.h"
#include "zend_exceptions.h"
#include "ext/spl/spl_array.h"
#include "php_traversal.h"

/* {{{ defines */
#define EXTR_OVERWRITE 0
Expand Down Expand Up @@ -5942,6 +5943,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
2 changes: 2 additions & 0 deletions ext/standard/basic_functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -779,6 +779,8 @@ static 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
10 changes: 10 additions & 0 deletions ext/standard/basic_functions_arginfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,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_WITH_RETURN_TYPE_INFO_EX(arginfo_array_filter, 0, 1, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, arg, IS_ARRAY, 0)
ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 0)
Expand Down
2 changes: 1 addition & 1 deletion ext/standard/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,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 net.c hrtime.c,,,
random.c net.c hrtime.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 @@ -35,7 +35,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 hrtime.c", false /* never shared */,
streamsfuncs.c http.c flock_compat.c random.c hrtime.c php_traversal.c", false /* never shared */,
'/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1');
ADD_MAKEFILE_FRAGMENT();
PHP_INSTALL_HEADERS("", "ext/standard");
2 changes: 2 additions & 0 deletions ext/standard/php_array.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,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);
80 changes: 80 additions & 0 deletions ext/standard/tests/array/array_any.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
--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";

try {
var_dump(array_any());
} catch (Throwable $e) {
echo "Exception: " . $e->getMessage() . "\n";
}
try {
var_dump(array_any(true));
} catch (Throwable $e) {
echo "Exception: " . $e->getMessage() . "\n";
}
try {
var_dump(array_any([]));
} catch (Throwable $e) {
echo "Exception: " . $e->getMessage() . "\n";
}

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 ***
Exception: array_any() expects exactly 2 parameters, 0 given
Exception: array_any() expects exactly 2 parameters, 1 given
Exception: array_any() expects exactly 2 parameters, 1 given

*** 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