2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
17 #include "hphp/runtime/ext/ext_array.h"
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/ext/ext_function.h"
23 #include "hphp/runtime/ext/ext_continuation.h"
24 #include "hphp/runtime/ext/ext_collections.h"
25 #include "hphp/runtime/base/request-local.h"
26 #include "hphp/runtime/base/zend-collator.h"
27 #include "hphp/runtime/base/builtin-functions.h"
28 #include "hphp/runtime/base/sort-flags.h"
29 #include "hphp/runtime/vm/jit/translator.h"
30 #include "hphp/runtime/vm/jit/translator-inline.h"
31 #include "hphp/runtime/base/hphp-array.h"
32 #include "hphp/runtime/base/request-event-handler.h"
33 #include "hphp/util/logger.h"
38 ///////////////////////////////////////////////////////////////////////////////
40 const StaticString
s_count("count");
42 const int64_t k_UCOL_DEFAULT
= UCOL_DEFAULT
;
44 const int64_t k_UCOL_PRIMARY
= UCOL_PRIMARY
;
45 const int64_t k_UCOL_SECONDARY
= UCOL_SECONDARY
;
46 const int64_t k_UCOL_TERTIARY
= UCOL_TERTIARY
;
47 const int64_t k_UCOL_DEFAULT_STRENGTH
= UCOL_DEFAULT_STRENGTH
;
48 const int64_t k_UCOL_QUATERNARY
= UCOL_QUATERNARY
;
49 const int64_t k_UCOL_IDENTICAL
= UCOL_IDENTICAL
;
51 const int64_t k_UCOL_OFF
= UCOL_OFF
;
52 const int64_t k_UCOL_ON
= UCOL_ON
;
54 const int64_t k_UCOL_SHIFTED
= UCOL_SHIFTED
;
55 const int64_t k_UCOL_NON_IGNORABLE
= UCOL_NON_IGNORABLE
;
57 const int64_t k_UCOL_LOWER_FIRST
= UCOL_LOWER_FIRST
;
58 const int64_t k_UCOL_UPPER_FIRST
= UCOL_UPPER_FIRST
;
60 const int64_t k_UCOL_FRENCH_COLLATION
= UCOL_FRENCH_COLLATION
;
61 const int64_t k_UCOL_ALTERNATE_HANDLING
= UCOL_ALTERNATE_HANDLING
;
62 const int64_t k_UCOL_CASE_FIRST
= UCOL_CASE_FIRST
;
63 const int64_t k_UCOL_CASE_LEVEL
= UCOL_CASE_LEVEL
;
64 const int64_t k_UCOL_NORMALIZATION_MODE
= UCOL_NORMALIZATION_MODE
;
65 const int64_t k_UCOL_STRENGTH
= UCOL_STRENGTH
;
66 const int64_t k_UCOL_HIRAGANA_QUATERNARY_MODE
= UCOL_HIRAGANA_QUATERNARY_MODE
;
67 const int64_t k_UCOL_NUMERIC_COLLATION
= UCOL_NUMERIC_COLLATION
;
69 using HPHP::JIT::CallerFrame
;
70 using HPHP::JIT::EagerCallerFrame
;
72 #define getCheckedArrayRet(input, fail) \
73 auto const cell_##input = static_cast<const Variant&>(input).asCell(); \
74 if (UNLIKELY(cell_##input->m_type != KindOfArray)) { \
75 throw_expected_array_exception(); \
78 ArrNR arrNR_##input(cell_##input->m_data.parr); \
79 const Array& arr_##input = arrNR_##input.asArray();
81 #define getCheckedArray(input) getCheckedArrayRet(input, uninit_null())
83 Variant
f_array_change_key_case(const Variant
& input
, int64_t case_
/* = 0 */) {
84 getCheckedArrayRet(input
, false);
85 return ArrayUtil::ChangeKeyCase(arr_input
, !case_
);
88 Variant
f_array_chunk(const Variant
& input
, int chunkSize
,
89 bool preserve_keys
/* = false */) {
91 const auto& cellInput
= *input
.asCell();
92 if (UNLIKELY(!isContainer(cellInput
))) {
93 raise_warning("Invalid operand type was used: %s expects "
94 "an array or collection as argument 1", __FUNCTION__
+2);
99 throw_invalid_argument("size: %d", chunkSize
);
103 Array ret
= Array::Create();
106 for (ArrayIter
iter(cellInput
); iter
; ++iter
) {
108 chunk
.setWithRef(iter
.first(), iter
.secondRefPlus(), true);
110 chunk
.appendWithRef(iter
.secondRefPlus());
112 if ((++current
% chunkSize
) == 0) {
117 if (!chunk
.empty()) {
124 static inline bool array_column_coerce_key(Variant
&key
, const char *name
) {
125 /* NULL has a special meaning for each field */
130 /* Custom coercion rules for key types */
131 if (key
.isInteger() || key
.isDouble()) {
134 } else if (key
.isString() || key
.isObject()) {
135 key
= key
.toString();
138 raise_warning("The %s key should be either a string or an integer", name
);
143 Variant
f_array_column(const Variant
& input
, const Variant
& val_key
,
144 const Variant
& idx_key
/* = null_variant */) {
145 /* Be strict about array type */
146 getCheckedArrayRet(input
, uninit_null());
147 Variant val
= val_key
, idx
= idx_key
;
148 if (!array_column_coerce_key(val
, "column") ||
149 !array_column_coerce_key(idx
, "index")) {
152 Array ret
= Array::Create();
153 for(auto it
= arr_input
.begin(); !it
.end(); it
.next()) {
154 if (!it
.second().isArray()) {
157 Array sub
= it
.second().toArray();
162 } else if (sub
.exists(val
)) {
165 // skip subarray without named element
169 if (idx
.isNull() || !sub
.exists(idx
)) {
171 } else if (sub
[idx
].isObject()) {
172 ret
.set(sub
[idx
].toString(), elem
);
174 ret
.set(sub
[idx
], elem
);
180 Variant
f_array_combine(const Variant
& keys
, const Variant
& values
) {
181 const auto& cell_keys
= *keys
.asCell();
182 const auto& cell_values
= *values
.asCell();
183 if (UNLIKELY(!isContainer(cell_keys
) || !isContainer(cell_values
))) {
184 raise_warning("Invalid operand type was used: array_combine expects "
185 "arrays or collections");
186 return uninit_null();
188 if (UNLIKELY(getContainerSize(cell_keys
) != getContainerSize(cell_values
))) {
189 raise_warning("array_combine(): Both parameters should have an equal "
190 "number of elements");
193 Array ret
= Array::Create();
194 for (ArrayIter
iter1(cell_keys
), iter2(cell_values
);
195 iter1
; ++iter1
, ++iter2
) {
196 const Variant
& key
= iter1
.secondRefPlus();
197 if (key
.isInteger() || key
.isString()) {
198 ret
.setWithRef(key
, iter2
.secondRefPlus());
200 ret
.setWithRef(key
.toString(), iter2
.secondRefPlus());
206 Variant
f_array_count_values(const Variant
& input
) {
207 getCheckedArray(input
);
208 return ArrayUtil::CountValues(arr_input
);
211 Variant
f_array_fill_keys(const Variant
& keys
, const Variant
& value
) {
212 const auto& cell_keys
= *keys
.asCell();
213 if (UNLIKELY(!isContainer(cell_keys
))) {
214 raise_warning("Invalid operand type was used: array_fill_keys expects "
215 "an array or collection");
216 return uninit_null();
219 auto size
= getContainerSize(cell_keys
);
220 if (!size
) return empty_array
;
223 for (ArrayIter
iter(cell_keys
); iter
; ++iter
) {
224 auto& key
= iter
.secondRefPlus();
225 // This is intentionally different to the $foo[$invalid_key] coercion.
226 // See tests/slow/ext_array/array_fill_keys_tostring.php for examples.
227 if (LIKELY(key
.isInteger() || key
.isString())) {
229 } else if (RuntimeOption::EnableHipHopSyntax
) {
230 // @todo (fredemmott): Use the Zend toString() behavior, but retain the
231 // warning/error behind a separate config setting
232 raise_warning("array_fill_keys: keys must be ints or strings");
235 ai
.set(key
.toString(), value
);
241 Variant
f_array_fill(int start_index
, int num
, const Variant
& value
) {
243 throw_invalid_argument("num: [non-positive]");
248 ret
.set(start_index
, value
);
249 for (int i
= num
- 1; i
> 0; i
--) {
255 Variant
f_array_flip(const Variant
& trans
) {
257 auto const& transCell
= *trans
.asCell();
258 if (UNLIKELY(!isContainer(transCell
))) {
259 raise_warning("Invalid operand type was used: %s expects "
260 "an array or collection", __FUNCTION__
+2);
261 return uninit_null();
264 ArrayInit
ret(getContainerSize(transCell
));
265 for (ArrayIter
iter(transCell
); iter
; ++iter
) {
266 const Variant
& value(iter
.secondRefPlus());
267 if (value
.isString() || value
.isInteger()) {
268 ret
.set(value
, iter
.first());
270 raise_warning("Can only flip STRING and INTEGER values!");
273 return ret
.toVariant();
276 bool f_array_key_exists(const Variant
& key
, const Variant
& search
) {
279 auto const searchCell
= search
.asCell();
280 if (LIKELY(searchCell
->m_type
== KindOfArray
)) {
281 ad
= searchCell
->m_data
.parr
;
282 } else if (searchCell
->m_type
== KindOfObject
) {
283 ObjectData
* obj
= searchCell
->m_data
.pobj
;
284 if (obj
->isCollection()) {
285 return collectionContains(obj
, key
);
287 return f_array_key_exists(key
, toArray(search
));
289 throw_bad_type_exception("array_key_exists expects an array or an object; "
294 auto const cell
= key
.asCell();
295 switch (cell
->m_type
) {
297 case KindOfStaticString
: {
299 StringData
*sd
= cell
->m_data
.pstr
;
300 if (sd
->isStrictlyInteger(n
)) {
301 return ad
->exists(n
);
303 return ad
->exists(StrNR(sd
));
306 return ad
->exists(cell
->m_data
.num
);
309 return ad
->exists(empty_string
);
313 raise_warning("Array key should be either a string or an integer");
317 bool f_key_exists(const Variant
& key
, const Variant
& search
) {
318 return f_array_key_exists(key
, search
);
321 Variant
f_array_keys(const Variant
& input
, const Variant
& search_value
/* = null_variant */,
322 bool strict
/* = false */) {
323 const auto& cell_input
= *input
.asCell();
324 if (UNLIKELY(!isContainer(cell_input
))) {
325 raise_warning("array_keys() expects parameter 1 to be an array "
327 return uninit_null();
330 if (LIKELY(!search_value
.isInitialized())) {
331 PackedArrayInit
ai(getContainerSize(cell_input
));
332 for (ArrayIter
iter(cell_input
); iter
; ++iter
) {
333 ai
.append(iter
.first());
337 Array ai
= Array::attach(HphpArray::MakeReserve(0));
338 for (ArrayIter
iter(cell_input
); iter
; ++iter
) {
339 if ((strict
&& HPHP::same(iter
.secondRefPlus(), search_value
)) ||
340 (!strict
&& HPHP::equal(iter
.secondRefPlus(), search_value
))) {
341 ai
.append(iter
.first());
348 Variant
f_array_map(int _argc
, const Variant
& callback
, const Variant
& arr1
, const Array
& _argv
/* = null_array */) {
351 if (!callback
.isNull()) {
353 vm_decode_function(callback
, cf(), false, ctx
);
355 const auto& cell_arr1
= *arr1
.asCell();
356 if (UNLIKELY(!isContainer(cell_arr1
))) {
357 raise_warning("array_map(): Argument #2 should be an array or collection");
358 return uninit_null();
360 if (LIKELY(_argv
.empty())) {
361 // Handle the common case where the caller passed two
362 // params (a callback and a container)
364 if (cell_arr1
.m_type
== KindOfArray
) {
367 return arr1
.toArray();
370 Array ret
= Array::Create();
371 for (ArrayIter
iter(arr1
); iter
; ++iter
) {
373 g_context
->invokeFuncFew((TypedValue
*)&result
, ctx
, 1,
374 iter
.secondRefPlus().asCell());
375 ret
.add(iter
.first(), result
, true);
380 // Handle the uncommon case where the caller passed a callback
381 // and two or more containers
383 (ArrayIter
*)smart_malloc(sizeof(ArrayIter
) * (_argv
.size() + 1));
386 while (numIters
--) iters
[numIters
].~ArrayIter();
389 size_t maxLen
= getContainerSize(cell_arr1
);
390 (void) new (&iters
[numIters
]) ArrayIter(cell_arr1
);
392 for (ArrayIter
it(_argv
); it
; ++it
, ++numIters
) {
393 const auto& c
= *it
.secondRefPlus().asCell();
394 if (UNLIKELY(!isContainer(c
))) {
395 raise_warning("array_map(): Argument #%d should be an array or "
396 "collection", (int)(numIters
+ 2));
397 (void) new (&iters
[numIters
]) ArrayIter(it
.secondRefPlus().toArray());
399 (void) new (&iters
[numIters
]) ArrayIter(c
);
400 size_t len
= getContainerSize(c
);
401 if (len
> maxLen
) maxLen
= len
;
404 Array ret
= Array::Create();
405 for (size_t k
= 0; k
< maxLen
; k
++) {
407 for (size_t i
= 0; i
< numIters
; ++i
) {
409 params
.append(iters
[i
].secondRefPlus());
412 params
.append(init_null_variant
);
417 g_context
->invokeFunc((TypedValue
*)&result
,
418 ctx
.func
, params
, ctx
.this_
,
419 ctx
.cls
, nullptr, ctx
.invName
);
428 static void php_array_merge(Array
&arr1
, const Array
& arr2
) {
432 static void php_array_merge_recursive(PointerSet
&seen
, bool check
,
433 Array
&arr1
, const Array
& arr2
) {
435 if (seen
.find((void*)arr1
.get()) != seen
.end()) {
436 raise_warning("array_merge_recursive(): recursion detected");
439 seen
.insert((void*)arr1
.get());
442 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
443 Variant
key(iter
.first());
444 const Variant
& value(iter
.secondRef());
445 if (key
.isNumeric()) {
446 arr1
.appendWithRef(value
);
447 } else if (arr1
.exists(key
, true)) {
448 // There is no need to do toKey() conversion, for a key that is already
450 Variant
&v
= arr1
.lvalAt(key
, AccessFlags::Key
);
451 Array
subarr1(v
.toArray()->copy());
452 php_array_merge_recursive(seen
, v
.isReferenced(), subarr1
,
454 v
.unset(); // avoid contamination of the value that was strongly bound
457 arr1
.setWithRef(key
, value
, true);
462 seen
.erase((void*)arr1
.get());
466 Variant
f_array_merge(int _argc
, const Variant
& array1
,
467 const Array
& _argv
/* = null_array */) {
468 getCheckedArray(array1
);
469 Array ret
= Array::Create();
470 php_array_merge(ret
, arr_array1
);
471 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
472 Variant v
= iter
.second();
474 throw_expected_array_exception();
475 return uninit_null();
477 const Array
& arr_v
= v
.asCArrRef();
478 php_array_merge(ret
, arr_v
);
483 Variant
f_array_merge_recursive(int _argc
, const Variant
& array1
,
484 const Array
& _argv
/* = null_array */) {
485 getCheckedArray(array1
);
486 Array ret
= Array::Create();
488 php_array_merge_recursive(seen
, false, ret
, arr_array1
);
489 assert(seen
.empty());
490 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
491 Variant v
= iter
.second();
493 throw_expected_array_exception();
494 return uninit_null();
496 const Array
& arr_v
= v
.asCArrRef();
497 php_array_merge_recursive(seen
, false, ret
, arr_v
);
498 assert(seen
.empty());
503 static void php_array_replace(Array
&arr1
, const Array
& arr2
) {
504 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
505 Variant key
= iter
.first();
506 const Variant
& value
= iter
.secondRef();
507 arr1
.setWithRef(key
, value
, true);
511 static void php_array_replace_recursive(PointerSet
&seen
, bool check
,
512 Array
&arr1
, const Array
& arr2
) {
514 if (seen
.find((void*)arr1
.get()) != seen
.end()) {
515 raise_warning("array_replace_recursive(): recursion detected");
518 seen
.insert((void*)arr1
.get());
521 for (ArrayIter
iter(arr2
); iter
; ++iter
) {
522 Variant key
= iter
.first();
523 const Variant
& value
= iter
.secondRef();
524 if (arr1
.exists(key
, true) && value
.isArray()) {
525 Variant
&v
= arr1
.lvalAt(key
, AccessFlags::Key
);
527 Array subarr1
= v
.toArray();
528 const ArrNR
& arr_value
= value
.toArrNR();
529 php_array_replace_recursive(seen
, v
.isReferenced(), subarr1
,
533 arr1
.set(key
, value
, true);
536 arr1
.setWithRef(key
, value
, true);
541 seen
.erase((void*)arr1
.get());
545 Variant
f_array_replace(int _argc
, const Variant
& array1
,
546 const Array
& _argv
/* = null_array */) {
547 getCheckedArray(array1
);
548 Array ret
= Array::Create();
549 php_array_replace(ret
, arr_array1
);
550 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
551 const Variant
& v
= iter
.secondRef();
553 php_array_replace(ret
, arr_v
);
558 Variant
f_array_replace_recursive(int _argc
, const Variant
& array1
,
559 const Array
& _argv
/* = null_array */) {
560 getCheckedArray(array1
);
561 Array ret
= Array::Create();
563 php_array_replace_recursive(seen
, false, ret
, arr_array1
);
564 assert(seen
.empty());
565 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
566 const Variant
& v
= iter
.secondRef();
568 php_array_replace_recursive(seen
, false, ret
, arr_v
);
569 assert(seen
.empty());
574 Variant
f_array_pad(const Variant
& input
, int pad_size
, const Variant
& pad_value
) {
575 getCheckedArray(input
);
577 return ArrayUtil::Pad(arr_input
, pad_value
, pad_size
, true);
579 return ArrayUtil::Pad(arr_input
, pad_value
, -pad_size
, false);
582 Variant
f_array_pop(VRefParam containerRef
) {
583 const auto* container
= containerRef
->asCell();
584 if (UNLIKELY(!isMutableContainer(*container
))) {
586 "%s() expects parameter 1 to be an array or mutable collection",
587 __FUNCTION__
+2 /* remove the "f_" prefix */);
588 return uninit_null();
590 if (!getContainerSize(containerRef
)) {
591 return uninit_null();
593 if (container
->m_type
== KindOfArray
) {
594 return containerRef
.wrapped().toArrRef().pop();
596 assert(container
->m_type
== KindOfObject
);
597 auto* obj
= container
->m_data
.pobj
;
598 assert(obj
->isCollection());
599 switch (obj
->getCollectionType()) {
600 case Collection::VectorType
: return static_cast<c_Vector
*>(obj
)->t_pop();
601 case Collection::MapType
: return static_cast<c_Map
*>(obj
)->pop();
602 case Collection::SetType
: return static_cast<c_Set
*>(obj
)->pop();
606 return uninit_null();
609 Variant
f_array_product(const Variant
& array
) {
610 getCheckedArray(array
);
613 if (ArrayUtil::Product(arr_array
, &i
, &d
) == KindOfInt64
) {
620 Variant
f_array_push(int _argc
, VRefParam container
,
621 const Variant
& var
, const Array
& _argv
/* = null_array */) {
623 if (LIKELY(container
->isArray())) {
624 auto const array_cell
= container
.wrapped().asCell();
625 assert(array_cell
->m_type
== KindOfArray
);
628 * Important note: this *must* cast the parr in the inner cell to
629 * the Array&---we can't copy it to the stack or anything because we
632 Array
& arr_array
= *reinterpret_cast<Array
*>(&array_cell
->m_data
.parr
);
633 arr_array
.append(var
);
634 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
635 arr_array
.append(iter
.second());
637 return arr_array
.size();
640 if (container
.isObject()) {
641 ObjectData
* obj
= container
.getObjectData();
642 auto collection_type
= obj
->getCollectionType();
643 if (collection_type
== Collection::VectorType
) {
644 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
645 vec
->reserve(vec
->size() + _argc
+ 1);
647 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
648 vec
->t_add(iter
.second());
651 } else if (collection_type
== Collection::SetType
) {
652 c_Set
* set
= static_cast<c_Set
*>(obj
);
653 set
->reserve(set
->size() + _argc
+ 1);
655 for (ArrayIter
iter(_argv
); iter
; ++iter
) {
656 set
->t_add(iter
.second());
660 // other collection types are unsupported:
661 // - mapping collections require a key
662 // - frozen collections don't allow insertion
665 throw_expected_array_or_collection_exception();
666 return uninit_null();
669 Variant
f_array_rand(const Variant
& input
, int num_req
/* = 1 */) {
670 getCheckedArray(input
);
671 return ArrayUtil::RandomKeys(arr_input
, num_req
);
674 static Variant
reduce_func(const Variant
& result
, const Variant
& operand
, const void *data
) {
675 CallCtx
* ctx
= (CallCtx
*)data
;
677 TypedValue args
[2] = { *result
.asCell(), *operand
.asCell() };
678 g_context
->invokeFuncFew(ret
.asTypedValue(), *ctx
, 2, args
);
681 Variant
f_array_reduce(const Variant
& input
, const Variant
& callback
,
682 const Variant
& initial
/* = null_variant */) {
683 getCheckedArray(input
);
686 vm_decode_function(callback
, cf(), false, ctx
);
687 if (ctx
.func
== NULL
) {
688 return uninit_null();
690 return ArrayUtil::Reduce(arr_input
, reduce_func
, &ctx
, initial
);
693 Variant
f_array_reverse(const Variant
& input
, bool preserve_keys
/* = false */) {
695 const auto& cell_input
= *input
.asCell();
696 if (UNLIKELY(!isContainer(cell_input
))) {
697 raise_warning("Invalid operand type was used: %s expects "
698 "an array or collection as argument 1",
700 return uninit_null();
703 if (LIKELY(cell_input
.m_type
== KindOfArray
)) {
704 ArrNR
arrNR(cell_input
.m_data
.parr
);
705 const Array
& arr
= arrNR
.asArray();
706 return ArrayUtil::Reverse(arr
, preserve_keys
);
709 // For collections, we convert to their array representation and then
710 // reverse it, rather than building a reversed version of ArrayIter
711 assert(cell_input
.m_type
== KindOfObject
);
712 ObjectData
* obj
= cell_input
.m_data
.pobj
;
713 assert(obj
&& obj
->isCollection());
714 return ArrayUtil::Reverse(obj
->o_toArray(), preserve_keys
);
717 Variant
f_array_shift(VRefParam array
) {
718 const auto* cell_array
= array
->asCell();
719 if (UNLIKELY(!isContainer(*cell_array
))) {
721 "%s() expects parameter 1 to be an array or mutable collection",
722 __FUNCTION__
+2 /* remove the "f_" prefix */);
723 return uninit_null();
725 if (cell_array
->m_type
== KindOfArray
) {
726 return array
.wrapped().toArrRef().dequeue();
728 assert(cell_array
->m_type
== KindOfObject
);
729 auto* obj
= cell_array
->m_data
.pobj
;
730 assert(obj
->isCollection());
731 switch (obj
->getCollectionType()) {
732 case Collection::VectorType
: {
733 auto* vec
= static_cast<c_Vector
*>(obj
);
734 if (!vec
->size()) return uninit_null();
735 return vec
->popFront();
737 case Collection::MapType
: {
738 auto* mp
= static_cast<BaseMap
*>(obj
);
739 if (!mp
->size()) return uninit_null();
740 return mp
->popFront();
742 case Collection::SetType
: {
743 auto* st
= static_cast<c_Set
*>(obj
);
744 if (!st
->size()) return uninit_null();
745 return st
->popFront();
749 "%s() expects parameter 1 to be an array or mutable collection",
750 __FUNCTION__
+2 /* remove the "f_" prefix */);
751 return uninit_null();
756 Variant
f_array_slice(const Variant
& input
, int offset
,
757 const Variant
& length
/* = null_variant */,
758 bool preserve_keys
/* = false */) {
759 const auto& cell_input
= *input
.asCell();
760 if (UNLIKELY(!isContainer(cell_input
))) {
761 raise_warning("Invalid operand type was used: %s expects "
762 "an array or collection as argument 1",
764 return uninit_null();
766 int64_t len
= length
.isNull() ? 0x7FFFFFFF : length
.toInt64();
768 int num_in
= getContainerSize(cell_input
);
769 if (offset
> num_in
) {
771 } else if (offset
< 0 && (offset
= (num_in
+ offset
)) < 0) {
776 len
= num_in
- offset
+ len
;
777 } else if (((unsigned)offset
+ (unsigned)len
) > (unsigned)num_in
) {
778 len
= num_in
- offset
;
785 // PackedArrayInit can't be used because non-numeric keys are preserved
786 // even when preserve_keys is false
787 Array ret
= Array::attach(HphpArray::MakeReserve(len
));
789 ArrayIter
iter(input
);
790 for (; pos
< offset
&& iter
; ++pos
, ++iter
) {}
791 for (; pos
< (offset
+ len
) && iter
; ++pos
, ++iter
) {
792 Variant
key(iter
.first());
793 bool doAppend
= !preserve_keys
&& key
.isNumeric();
794 const Variant
& v
= iter
.secondRefPlus();
796 ret
.appendWithRef(v
);
798 ret
.setWithRef(key
, v
, true);
804 Variant
f_array_splice(VRefParam input
, int offset
,
805 const Variant
& length
/* = null_variant */,
806 const Variant
& replacement
/* = null_variant */) {
807 getCheckedArray(input
);
808 Array
ret(Array::Create());
809 int64_t len
= length
.isNull() ? 0x7FFFFFFF : length
.toInt64();
810 input
= ArrayUtil::Splice(arr_input
, offset
, len
, replacement
, &ret
);
814 Variant
f_array_sum(const Variant
& array
) {
815 getCheckedArray(array
);
818 if (ArrayUtil::Sum(arr_array
, &i
, &d
) == KindOfInt64
) {
825 Variant
f_array_unshift(int _argc
, VRefParam array
, const Variant
& var
, const Array
& _argv
/* = null_array */) {
826 const auto* cell_array
= array
->asCell();
827 if (UNLIKELY(!isContainer(*cell_array
))) {
828 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
829 __FUNCTION__
+2 /* remove the "f_" prefix */);
830 return uninit_null();
832 if (cell_array
->m_type
== KindOfArray
) {
833 if (array
.toArray()->isVectorData()) {
834 if (!_argv
.empty()) {
835 for (ssize_t pos
= _argv
->iter_end(); pos
!= ArrayData::invalid_index
;
836 pos
= _argv
->iter_rewind(pos
)) {
837 array
.wrapped().toArrRef().prepend(_argv
->getValueRef(pos
));
840 array
.wrapped().toArrRef().prepend(var
);
844 newArray
.append(var
);
845 if (!_argv
.empty()) {
846 for (ssize_t pos
= _argv
->iter_begin();
847 pos
!= ArrayData::invalid_index
;
848 pos
= _argv
->iter_advance(pos
)) {
849 newArray
.append(_argv
->getValueRef(pos
));
852 for (ArrayIter
iter(array
.toArray()); iter
; ++iter
) {
853 Variant
key(iter
.first());
854 const Variant
& value(iter
.secondRef());
855 if (key
.isInteger()) {
856 newArray
.appendWithRef(value
);
858 newArray
.setWithRef(key
, value
, true);
863 // Reset the array's internal pointer
864 if (array
.is(KindOfArray
)) {
868 return array
.toArray().size();
870 // Handle collections
871 assert(cell_array
->m_type
== KindOfObject
);
872 auto* obj
= cell_array
->m_data
.pobj
;
873 assert(obj
->isCollection());
874 switch (obj
->getCollectionType()) {
875 case Collection::VectorType
: {
876 auto* vec
= static_cast<c_Vector
*>(obj
);
877 if (!_argv
.empty()) {
878 for (ssize_t pos
= _argv
->iter_end(); pos
!= ArrayData::invalid_index
;
879 pos
= _argv
->iter_rewind(pos
)) {
880 vec
->addFront(cvarToCell(&_argv
->getValueRef(pos
)));
883 vec
->addFront(cvarToCell(&var
));
886 case Collection::SetType
: {
887 auto* st
= static_cast<c_Set
*>(obj
);
888 if (!_argv
.empty()) {
889 for (ssize_t pos
= _argv
->iter_end(); pos
!= ArrayData::invalid_index
;
890 pos
= _argv
->iter_rewind(pos
)) {
891 st
->addFront(cvarToCell(&_argv
->getValueRef(pos
)));
894 st
->addFront(cvarToCell(&var
));
898 raise_warning("%s() expects parameter 1 to be an array, Vector, or Set",
899 __FUNCTION__
+2 /* remove the "f_" prefix */);
900 return uninit_null();
905 Variant
f_array_values(const Variant
& input
) {
906 const auto& cell_input
= *input
.asCell();
907 if (!isContainer(cell_input
)) {
908 raise_warning("array_values() expects parameter 1 to be an array "
910 return uninit_null();
912 PackedArrayInit
ai(getContainerSize(cell_input
));
913 for (ArrayIter
iter(cell_input
); iter
; ++iter
) {
914 ai
.appendWithRef(iter
.secondRefPlus());
919 static void walk_func(VRefParam value
, const Variant
& key
, const Variant
& userdata
,
921 CallCtx
* ctx
= (CallCtx
*)data
;
923 TypedValue args
[3] = { *value
->asRef(), *key
.asCell(), *userdata
.asCell() };
924 g_context
->invokeFuncFew(sink
.asTypedValue(), *ctx
, 3, args
);
927 bool f_array_walk_recursive(VRefParam input
, const Variant
& funcname
,
928 const Variant
& userdata
/* = null_variant */) {
929 if (!input
.isArray()) {
930 throw_expected_array_exception();
935 vm_decode_function(funcname
, cf(), false, ctx
);
936 if (ctx
.func
== NULL
) {
940 ArrayUtil::Walk(input
, walk_func
, &ctx
, true, &seen
, userdata
);
944 bool f_array_walk(VRefParam input
, const Variant
& funcname
,
945 const Variant
& userdata
/* = null_variant */) {
946 if (!input
.isArray()) {
947 throw_expected_array_exception();
952 vm_decode_function(funcname
, cf(), false, ctx
);
953 if (ctx
.func
== NULL
) {
956 ArrayUtil::Walk(input
, walk_func
, &ctx
, false, NULL
, userdata
);
960 static void compact(VarEnv
* v
, Array
&ret
, const Variant
& var
) {
962 for (ArrayIter
iter(var
.getArrayData()); iter
; ++iter
) {
963 compact(v
, ret
, iter
.second());
966 String varname
= var
.toString();
967 if (!varname
.empty() && v
->lookup(varname
.get()) != NULL
) {
968 ret
.set(varname
, *reinterpret_cast<Variant
*>(v
->lookup(varname
.get())));
973 Array
f_compact(int _argc
, const Variant
& varname
, const Array
& _argv
/* = null_array */) {
974 Array ret
= Array::Create();
975 VarEnv
* v
= g_context
->getVarEnv();
977 compact(v
, ret
, varname
);
978 compact(v
, ret
, _argv
);
983 static int php_count_recursive(const Array
& array
) {
984 long cnt
= array
.size();
985 for (ArrayIter
iter(array
); iter
; ++iter
) {
986 Variant value
= iter
.second();
987 if (value
.isArray()) {
988 const Array
& arr_value
= value
.asCArrRef();
989 cnt
+= php_count_recursive(arr_value
);
995 bool f_shuffle(VRefParam array
) {
996 if (!array
.isArray()) {
997 throw_expected_array_exception();
1000 array
= ArrayUtil::Shuffle(array
);
1004 int64_t f_count(const Variant
& var
, int64_t mode
/* = 0 */) {
1005 switch (var
.getType()) {
1011 Object obj
= var
.toObject();
1012 if (obj
->isCollection()) {
1013 return getCollectionSize(obj
.get());
1015 if (obj
.instanceof(SystemLib::s_CountableClass
)) {
1016 return obj
->o_invoke_few_args(s_count
, 0).toInt64();
1022 const Array
& arr_var
= var
.toCArrRef();
1023 return php_count_recursive(arr_var
);
1025 return var
.getArrayData()->size();
1032 int64_t f_sizeof(const Variant
& var
, int64_t mode
/* = 0 */) {
1033 return f_count(var
, mode
);
1038 enum class NoCow
{};
1039 template<class DoCow
= void, class NonArrayRet
, class OpPtr
>
1040 static Variant
iter_op_impl(VRefParam refParam
, OpPtr op
, NonArrayRet nonArray
,
1041 bool(ArrayData::*pred
)() const =
1042 &ArrayData::isInvalid
) {
1043 auto& cell
= *refParam
.wrapped().asCell();
1044 if (cell
.m_type
!= KindOfArray
) {
1045 throw_bad_type_exception("expecting an array");
1046 return Variant(nonArray
);
1049 auto ad
= cell
.m_data
.parr
;
1050 auto constexpr doCow
= !std::is_same
<DoCow
, NoCow
>::value
;
1051 if (doCow
&& ad
->hasMultipleRefs() && !(ad
->*pred
)() &&
1052 !ad
->noCopyOnWrite()) {
1054 cellSet(make_tv
<KindOfArray
>(ad
), cell
);
1061 Variant
f_each(VRefParam refParam
) {
1062 return iter_op_impl(
1069 Variant
f_current(VRefParam refParam
) {
1070 return iter_op_impl
<NoCow
>(
1072 &ArrayData::current
,
1077 Variant
f_pos(VRefParam refParam
) {
1078 return f_current(refParam
);
1081 Variant
f_key(VRefParam refParam
) {
1082 return iter_op_impl
<NoCow
>(
1089 Variant
f_next(VRefParam refParam
) {
1090 return iter_op_impl(
1097 Variant
f_prev(VRefParam refParam
) {
1098 return iter_op_impl(
1105 Variant
f_reset(VRefParam refParam
) {
1106 return iter_op_impl(
1114 Variant
f_end(VRefParam refParam
) {
1115 return iter_op_impl(
1123 bool f_in_array(const Variant
& needle
, const Variant
& haystack
, bool strict
/* = false */) {
1124 const auto& cell_haystack
= *haystack
.asCell();
1125 if (UNLIKELY(!isContainer(cell_haystack
))) {
1126 raise_warning("in_array() expects parameter 2 to be an array "
1131 ArrayIter
iter(cell_haystack
);
1133 for (; iter
; ++iter
) {
1134 if (HPHP::same(iter
.secondRefPlus(), needle
)) {
1139 for (; iter
; ++iter
) {
1140 if (HPHP::equal(iter
.secondRefPlus(), needle
)) {
1148 Variant
f_array_search(const Variant
& needle
, const Variant
& haystack
,
1149 bool strict
/* = false */) {
1150 const auto& cell_haystack
= *haystack
.asCell();
1151 if (UNLIKELY(!isContainer(cell_haystack
))) {
1152 raise_warning("array_search() expects parameter 2 to be an array "
1154 return uninit_null();
1157 ArrayIter
iter(cell_haystack
);
1159 for (; iter
; ++iter
) {
1160 if (HPHP::same(iter
.secondRefPlus(), needle
)) {
1161 return iter
.first();
1165 for (; iter
; ++iter
) {
1166 if (HPHP::equal(iter
.secondRefPlus(), needle
)) {
1167 return iter
.first();
1174 Variant
f_range(const Variant
& low
, const Variant
& high
, const Variant
& step
/* = 1 */) {
1175 bool is_step_double
= false;
1177 if (step
.isDouble()) {
1178 dstep
= step
.toDouble();
1179 is_step_double
= true;
1180 } else if (step
.isString()) {
1183 DataType stype
= step
.toString().get()->isNumericWithVal(sn
, sd
, 0);
1184 if (stype
== KindOfDouble
) {
1185 is_step_double
= true;
1187 } else if (stype
== KindOfInt64
) {
1190 dstep
= step
.toDouble();
1193 dstep
= step
.toDouble();
1195 /* We only want positive step values. */
1196 if (dstep
< 0.0) dstep
*= -1;
1197 if (low
.isString() && high
.isString()) {
1198 String slow
= low
.toString();
1199 String shigh
= high
.toString();
1200 if (slow
.size() >= 1 && shigh
.size() >=1) {
1203 DataType type1
= slow
.get()->isNumericWithVal(n1
, d1
, 0);
1204 DataType type2
= shigh
.get()->isNumericWithVal(n2
, d2
, 0);
1205 if (type1
== KindOfDouble
|| type2
== KindOfDouble
|| is_step_double
) {
1206 if (type1
!= KindOfDouble
) d1
= slow
.toDouble();
1207 if (type2
!= KindOfDouble
) d2
= shigh
.toDouble();
1208 return ArrayUtil::Range(d1
, d2
, dstep
);
1211 int64_t lstep
= (int64_t) dstep
;
1212 if (type1
== KindOfInt64
|| type2
== KindOfInt64
) {
1213 if (type1
!= KindOfInt64
) n1
= slow
.toInt64();
1214 if (type2
!= KindOfInt64
) n2
= shigh
.toInt64();
1215 return ArrayUtil::Range((double)n1
, (double)n2
, lstep
);
1218 return ArrayUtil::Range((unsigned char)slow
.charAt(0),
1219 (unsigned char)shigh
.charAt(0), lstep
);
1223 if (low
.is(KindOfDouble
) || high
.is(KindOfDouble
) || is_step_double
) {
1224 return ArrayUtil::Range(low
.toDouble(), high
.toDouble(), dstep
);
1227 int64_t lstep
= (int64_t) dstep
;
1228 return ArrayUtil::Range(low
.toDouble(), high
.toDouble(), lstep
);
1230 ///////////////////////////////////////////////////////////////////////////////
1231 // diff/intersect helpers
1233 static int cmp_func(const Variant
& v1
, const Variant
& v2
, const void *data
) {
1234 Variant
*callback
= (Variant
*)data
;
1235 return vm_call_user_func(*callback
, make_packed_array(v1
, v2
)).toInt32();
1239 #define diff_intersect_body(type,intersect_params,user_setup) \
1240 getCheckedArray(array1); \
1241 if (!arr_array1.size()) return arr_array1; \
1243 Array ret = arr_array1.type(array2, intersect_params); \
1245 for (ArrayIter iter(_argv); iter; ++iter) { \
1246 ret = ret.type(iter.second(), intersect_params); \
1247 if (!ret.size()) break; \
1252 ///////////////////////////////////////////////////////////////////////////////
1255 static inline void addToSetHelper(c_Set
* st
, const Cell
& c
, TypedValue
* strTv
,
1256 bool convertIntLikeStrs
) {
1257 if (c
.m_type
== KindOfInt64
) {
1258 st
->add(c
.m_data
.num
);
1261 if (LIKELY(IS_STRING_TYPE(c
.m_type
))) {
1264 s
= tvCastToString(&c
);
1265 decRefStr(strTv
->m_data
.pstr
);
1266 strTv
->m_data
.pstr
= s
;
1269 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1277 static inline bool checkSetHelper(c_Set
* st
, const Cell
& c
, TypedValue
* strTv
,
1278 bool convertIntLikeStrs
) {
1279 if (c
.m_type
== KindOfInt64
) {
1280 return st
->contains(c
.m_data
.num
);
1283 if (LIKELY(IS_STRING_TYPE(c
.m_type
))) {
1286 s
= tvCastToString(&c
);
1287 decRefStr(strTv
->m_data
.pstr
);
1288 strTv
->m_data
.pstr
= s
;
1291 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1292 return st
->contains(n
);
1294 return st
->contains(s
);
1297 static void containerValuesToSetHelper(c_Set
* st
, const Variant
& container
) {
1298 Variant
strHolder(empty_string
.get());
1299 TypedValue
* strTv
= strHolder
.asTypedValue();
1300 for (ArrayIter
iter(container
); iter
; ++iter
) {
1301 const auto& c
= *const_cast<TypedValue
*>(iter
.secondRefPlus().asCell());
1302 addToSetHelper(st
, c
, strTv
, true);
1306 static void containerKeysToSetHelper(c_Set
* st
, const Variant
& container
) {
1307 Variant
strHolder(empty_string
.get());
1308 TypedValue
* strTv
= strHolder
.asTypedValue();
1309 bool isKey
= container
.asCell()->m_type
== KindOfArray
;
1310 for (ArrayIter
iter(container
); iter
; ++iter
) {
1311 auto key
= iter
.first();
1312 const auto& c
= *const_cast<TypedValue
*>(key
.asCell());
1313 addToSetHelper(st
, c
, strTv
, !isKey
);
1317 #define ARRAY_DIFF_PRELUDE() \
1318 /* Check to make sure all inputs are containers */ \
1319 const auto& c1 = *container1.asCell(); \
1320 const auto& c2 = *container2.asCell(); \
1321 if (UNLIKELY(!isContainer(c1) || !isContainer(c2))) { \
1322 raise_warning("%s() expects parameter %d to be an array or collection", \
1323 __FUNCTION__+2, /* remove the "f_" prefix */ \
1324 isContainer(c1) ? 2 : 1); \
1325 return uninit_null(); \
1327 bool moreThanTwo = (_argc > 2); \
1328 size_t largestSize = getContainerSize(c2); \
1329 if (UNLIKELY(moreThanTwo)) { \
1331 for (ArrayIter argvIter(_argv); argvIter; ++argvIter, ++pos) { \
1332 const auto& c = *argvIter.secondRef().asCell(); \
1333 if (!isContainer(c)) { \
1334 raise_warning("%s() expects parameter %d to be an array or collection",\
1335 __FUNCTION__+2, /* remove the "f_" prefix */ \
1337 return uninit_null(); \
1339 size_t sz = getContainerSize(c); \
1340 if (sz > largestSize) { \
1345 /* If container1 is empty, we can stop here and return the empty array */ \
1346 if (!getContainerSize(c1)) return empty_array; \
1347 /* If all of the containers (except container1) are empty, we can just \
1348 return container1 (converting it to an array if needed) */ \
1349 if (!largestSize) { \
1350 if (c1.m_type == KindOfArray) { \
1351 return container1; \
1353 return container1.toArray(); \
1356 Array ret = Array::Create();
1358 Variant
f_array_diff(int _argc
, const Variant
& container1
, const Variant
& container2
,
1359 const Array
& _argv
/* = null_array */) {
1360 ARRAY_DIFF_PRELUDE()
1361 // Put all of the values from all the containers (except container1 into a
1362 // Set. All types aside from integer and string will be cast to string, and
1363 // we also convert int-like strings to integers.
1365 Object setObj
= st
= NEWOBJ(c_Set
)();
1366 st
->reserve(largestSize
);
1367 containerValuesToSetHelper(st
, container2
);
1368 if (UNLIKELY(moreThanTwo
)) {
1369 for (ArrayIter
argvIter(_argv
); argvIter
; ++argvIter
) {
1370 const auto& container
= argvIter
.secondRef();
1371 containerValuesToSetHelper(st
, container
);
1374 // Loop over container1, only copying over key/value pairs where the value
1375 // is not present in the Set. When checking if a value is present in the
1376 // Set, any value that is not an integer or string is cast to a string, and
1377 // we convert int-like strings to integers.
1378 Variant
strHolder(empty_string
.get());
1379 TypedValue
* strTv
= strHolder
.asTypedValue();
1380 bool isKey
= c1
.m_type
== KindOfArray
;
1381 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1382 const auto& val
= iter
.secondRefPlus();
1383 const auto& c
= *val
.asCell();
1384 if (checkSetHelper(st
, c
, strTv
, true)) continue;
1385 ret
.setWithRef(iter
.first(), val
, isKey
);
1390 Variant
f_array_diff_key(int _argc
, const Variant
& container1
, const Variant
& container2
,
1391 const Array
& _argv
/* = null_array */) {
1392 ARRAY_DIFF_PRELUDE()
1393 // If we're only dealing with two containers and if they are both arrays,
1394 // we can avoid creating an intermediate Set
1395 if (!moreThanTwo
&& c1
.m_type
== KindOfArray
&& c2
.m_type
== KindOfArray
) {
1396 auto ad2
= c2
.m_data
.parr
;
1397 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1398 auto key
= iter
.first();
1399 const auto& c
= *key
.asCell();
1400 if (c
.m_type
== KindOfInt64
) {
1401 if (ad2
->exists(c
.m_data
.num
)) continue;
1403 assert(IS_STRING_TYPE(c
.m_type
));
1404 if (ad2
->exists(c
.m_data
.pstr
)) continue;
1406 ret
.setWithRef(key
, iter
.secondRefPlus(), true);
1410 // Put all of the keys from all the containers (except container1) into a
1411 // Set. All types aside from integer and string will be cast to string, and
1412 // we also convert int-like strings to integers.
1414 Object setObj
= st
= NEWOBJ(c_Set
)();
1415 st
->reserve(largestSize
);
1416 containerKeysToSetHelper(st
, container2
);
1417 if (UNLIKELY(moreThanTwo
)) {
1418 for (ArrayIter
argvIter(_argv
); argvIter
; ++argvIter
) {
1419 const auto& container
= argvIter
.secondRef();
1420 containerKeysToSetHelper(st
, container
);
1423 // Loop over container1, only copying over key/value pairs where the key is
1424 // not present in the Set. When checking if a key is present in the Set, any
1425 // key that is not an integer or string is cast to a string, and we convert
1426 // int-like strings to integers.
1427 Variant
strHolder(empty_string
.get());
1428 TypedValue
* strTv
= strHolder
.asTypedValue();
1429 bool isKey
= c1
.m_type
== KindOfArray
;
1430 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1431 auto key
= iter
.first();
1432 const auto& c
= *key
.asCell();
1433 if (checkSetHelper(st
, c
, strTv
, !isKey
)) continue;
1434 ret
.setWithRef(key
, iter
.secondRefPlus(), isKey
);
1439 #undef ARRAY_DIFF_PRELUDE
1441 Variant
f_array_udiff(int _argc
, const Variant
& array1
, const Variant
& array2
,
1442 const Variant
& data_compare_func
,
1443 const Array
& _argv
/* = null_array */) {
1444 diff_intersect_body(diff
, false COMMA
true COMMA NULL COMMA NULL
1445 COMMA cmp_func COMMA
&func
,
1446 Variant func
= data_compare_func
;
1447 Array extra
= _argv
;
1448 if (!extra
.empty()) {
1449 extra
.prepend(func
);
1454 Variant
f_array_diff_assoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1455 const Array
& _argv
/* = null_array */) {
1456 diff_intersect_body(diff
, true COMMA
true,);
1459 Variant
f_array_diff_uassoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1460 const Variant
& key_compare_func
,
1461 const Array
& _argv
/* = null_array */) {
1462 diff_intersect_body(diff
, true COMMA
true COMMA cmp_func COMMA
&func
,
1463 Variant func
= key_compare_func
;
1464 Array extra
= _argv
;
1465 if (!extra
.empty()) {
1466 extra
.prepend(func
);
1471 Variant
f_array_udiff_assoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1472 const Variant
& data_compare_func
,
1473 const Array
& _argv
/* = null_array */) {
1474 diff_intersect_body(diff
, true COMMA
true COMMA NULL COMMA NULL
1475 COMMA cmp_func COMMA
&func
,
1476 Variant func
= data_compare_func
;
1477 Array extra
= _argv
;
1478 if (!extra
.empty()) {
1479 extra
.prepend(func
);
1484 Variant
f_array_udiff_uassoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1485 const Variant
& data_compare_func
,
1486 const Variant
& key_compare_func
,
1487 const Array
& _argv
/* = null_array */) {
1488 diff_intersect_body(diff
, true COMMA
true COMMA cmp_func COMMA
&key_func
1489 COMMA cmp_func COMMA
&data_func
,
1490 Variant data_func
= data_compare_func
;
1491 Variant key_func
= key_compare_func
;
1492 Array extra
= _argv
;
1493 if (!extra
.empty()) {
1494 extra
.prepend(key_func
);
1495 extra
.prepend(data_func
);
1496 key_func
= extra
.pop();
1497 data_func
= extra
.pop();
1501 Variant
f_array_diff_ukey(int _argc
, const Variant
& array1
, const Variant
& array2
,
1502 const Variant
& key_compare_func
,
1503 const Array
& _argv
/* = null_array */) {
1504 diff_intersect_body(diff
, true COMMA
false COMMA cmp_func COMMA
&func
,
1505 Variant func
= key_compare_func
;
1506 Array extra
= _argv
;
1507 if (!extra
.empty()) {
1508 extra
.prepend(func
);
1513 ///////////////////////////////////////////////////////////////////////////////
1514 // intersect functions
1516 static inline TypedValue
* makeContainerListHelper(const Variant
& a
,
1520 assert(count
== argv
.size() + 1);
1521 assert(0 <= smallestPos
);
1522 assert(smallestPos
< count
);
1523 // Allocate a TypedValue array and copy 'a' and the contents of 'argv'
1524 TypedValue
* containers
=
1525 (TypedValue
*)smart_malloc(count
* sizeof(TypedValue
));
1526 tvCopy(*a
.asCell(), containers
[0]);
1528 for (ArrayIter
argvIter(argv
); argvIter
; ++argvIter
, ++pos
) {
1529 const auto& c
= *argvIter
.secondRef().asCell();
1530 tvCopy(c
, containers
[pos
]);
1532 // Perform a swap so that the smallest container occurs at the first
1533 // position in the TypedValue array; this helps improve the performance
1534 // of containerValuesIntersectHelper()
1535 if (smallestPos
!= 0) {
1537 tvCopy(containers
[0], tmp
);
1538 tvCopy(containers
[smallestPos
], containers
[0]);
1539 tvCopy(tmp
, containers
[smallestPos
]);
1544 static inline void addToIntersectMapHelper(c_Map
* mp
,
1546 TypedValue
* intOneTv
,
1548 bool convertIntLikeStrs
) {
1549 if (c
.m_type
== KindOfInt64
) {
1550 mp
->set(c
.m_data
.num
, intOneTv
);
1553 if (LIKELY(IS_STRING_TYPE(c
.m_type
))) {
1556 s
= tvCastToString(&c
);
1557 decRefStr(strTv
->m_data
.pstr
);
1558 strTv
->m_data
.pstr
= s
;
1561 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1562 mp
->set(n
, intOneTv
);
1564 mp
->set(s
, intOneTv
);
1569 static inline void updateIntersectMapHelper(c_Map
* mp
,
1573 bool convertIntLikeStrs
) {
1574 if (c
.m_type
== KindOfInt64
) {
1575 auto val
= mp
->get(c
.m_data
.num
);
1576 if (val
&& val
->m_data
.num
== pos
) {
1577 assert(val
->m_type
== KindOfInt64
);
1582 if (LIKELY(IS_STRING_TYPE(c
.m_type
))) {
1585 s
= tvCastToString(&c
);
1586 decRefStr(strTv
->m_data
.pstr
);
1587 strTv
->m_data
.pstr
= s
;
1590 if (convertIntLikeStrs
&& s
->isStrictlyInteger(n
)) {
1591 auto val
= mp
->get(n
);
1592 if (val
&& val
->m_data
.num
== pos
) {
1593 assert(val
->m_type
== KindOfInt64
);
1597 auto val
= mp
->get(s
);
1598 if (val
&& val
->m_data
.num
== pos
) {
1599 assert(val
->m_type
== KindOfInt64
);
1606 static void containerValuesIntersectHelper(c_Set
* st
,
1607 TypedValue
* containers
,
1611 Object mapObj
= mp
= NEWOBJ(c_Map
)();
1612 Variant
strHolder(empty_string
.get());
1613 TypedValue
* strTv
= strHolder
.asTypedValue();
1614 TypedValue intOneTv
= make_tv
<KindOfInt64
>(1);
1615 for (ArrayIter
iter(tvAsCVarRef(&containers
[0])); iter
; ++iter
) {
1616 const auto& c
= *const_cast<TypedValue
*>(iter
.secondRefPlus().asCell());
1617 // For each value v in containers[0], we add the key/value pair (v, 1)
1618 // to the map. If a value (after various conversions) occurs more than
1619 // once in the container, we'll simply overwrite the old entry and that's
1621 addToIntersectMapHelper(mp
, c
, &intOneTv
, strTv
, true);
1623 for (int pos
= 1; pos
< count
; ++pos
) {
1624 for (ArrayIter
iter(tvAsCVarRef(&containers
[pos
])); iter
; ++iter
) {
1625 const auto& c
= *const_cast<TypedValue
*>(iter
.secondRefPlus().asCell());
1626 // We check if the value is present as a key in the map. If an entry
1627 // exists and its value equals pos, we increment it, otherwise we do
1628 // nothing. This is essential so that we don't accidentally double-count
1629 // a key (after various conversions) that occurs in the container more
1631 updateIntersectMapHelper(mp
, c
, pos
, strTv
, true);
1634 for (ArrayIter
iter(mapObj
); iter
; ++iter
) {
1635 // For each key in the map, we copy the key to the set if the
1636 // corresponding value is equal to pos exactly (which means it
1637 // was present in all of the containers).
1638 const auto& val
= *iter
.secondRefPlus().asCell();
1639 assert(val
.m_type
== KindOfInt64
);
1640 if (val
.m_data
.num
== count
) {
1641 st
->add(iter
.first().asCell());
1646 static void containerKeysIntersectHelper(c_Set
* st
,
1647 TypedValue
* containers
,
1651 Object mapObj
= mp
= NEWOBJ(c_Map
)();
1652 Variant
strHolder(empty_string
.get());
1653 TypedValue
* strTv
= strHolder
.asTypedValue();
1654 TypedValue intOneTv
= make_tv
<KindOfInt64
>(1);
1655 bool isKey
= containers
[0].m_type
== KindOfArray
;
1656 for (ArrayIter
iter(tvAsCVarRef(&containers
[0])); iter
; ++iter
) {
1657 auto key
= iter
.first();
1658 const auto& c
= *key
.asCell();
1659 // For each key k in containers[0], we add the key/value pair (k, 1)
1660 // to the map. If a key (after various conversions) occurs more than
1661 // once in the container, we'll simply overwrite the old entry and
1663 addToIntersectMapHelper(mp
, c
, &intOneTv
, strTv
, !isKey
);
1665 for (int pos
= 1; pos
< count
; ++pos
) {
1666 isKey
= containers
[pos
].m_type
== KindOfArray
;
1667 for (ArrayIter
iter(tvAsCVarRef(&containers
[pos
])); iter
; ++iter
) {
1668 auto key
= iter
.first();
1669 const auto& c
= *key
.asCell();
1670 updateIntersectMapHelper(mp
, c
, pos
, strTv
, !isKey
);
1673 for (ArrayIter
iter(mapObj
); iter
; ++iter
) {
1674 // For each key in the map, we copy the key to the set if the
1675 // corresponding value is equal to pos exactly (which means it
1676 // was present in all of the containers).
1677 const auto& val
= *iter
.secondRefPlus().asCell();
1678 assert(val
.m_type
== KindOfInt64
);
1679 if (val
.m_data
.num
== count
) {
1680 st
->add(iter
.first().asCell());
1685 #define ARRAY_INTERSECT_PRELUDE() \
1686 /* Check to make sure all inputs are containers */ \
1687 const auto& c1 = *container1.asCell(); \
1688 const auto& c2 = *container2.asCell(); \
1689 if (!isContainer(c1) || !isContainer(c2)) { \
1690 raise_warning("%s() expects parameter %d to be an array or collection", \
1691 __FUNCTION__+2, /* remove the "f_" prefix */ \
1692 isContainer(c1) ? 2 : 1); \
1693 return uninit_null(); \
1695 bool moreThanTwo = (_argc > 2); \
1696 /* Keep track of which input container was the smallest (excluding \
1698 int smallestPos = 0; \
1699 size_t smallestSize = getContainerSize(c2); \
1700 if (UNLIKELY(moreThanTwo)) { \
1702 for (ArrayIter argvIter(_argv); argvIter; ++argvIter, ++pos) { \
1703 const auto& c = *argvIter.secondRef().asCell(); \
1704 if (!isContainer(c)) { \
1705 raise_warning("%s() expects parameter %d to be an array or collection",\
1706 __FUNCTION__+2, /* remove the "f_" prefix */ \
1708 return uninit_null(); \
1710 size_t sz = getContainerSize(c); \
1711 if (sz < smallestSize) { \
1712 smallestSize = sz; \
1713 smallestPos = pos; \
1717 /* If any of the containers were empty, we can stop here and return the \
1719 if (!getContainerSize(c1) || !smallestSize) return empty_array; \
1720 Array ret = Array::Create();
1722 Variant
f_array_intersect(int _argc
, const Variant
& container1
, const Variant
& container2
,
1723 const Array
& _argv
/* = null_array */) {
1724 ARRAY_INTERSECT_PRELUDE()
1725 // Build up a Set containing the values that are present in all the
1726 // containers (except container1)
1728 Object setObj
= st
= NEWOBJ(c_Set
)();
1729 if (LIKELY(!moreThanTwo
)) {
1730 // There is only one container (not counting container1) so we can
1731 // just call containerValuesToSetHelper() to build the Set.
1732 containerValuesToSetHelper(st
, container2
);
1734 // We're dealing with three or more containers. Copy all of the containers
1735 // (except the first) into a TypedValue array.
1736 int count
= _argv
.size() + 1;
1737 TypedValue
* containers
=
1738 makeContainerListHelper(container2
, _argv
, count
, smallestPos
);
1739 SCOPE_EXIT
{ smart_free(containers
); };
1740 // Build a Set of the values that were present in all of the containers
1741 containerValuesIntersectHelper(st
, containers
, count
);
1743 // Loop over container1, only copying over key/value pairs where the value
1744 // is present in the Set. When checking if a value is present in the Set,
1745 // any value that is not an integer or string is cast to a string, and we
1746 // convert int-like strings to integers.
1747 Variant
strHolder(empty_string
.get());
1748 TypedValue
* strTv
= strHolder
.asTypedValue();
1749 bool isKey
= c1
.m_type
== KindOfArray
;
1750 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1751 const auto& val
= iter
.secondRefPlus();
1752 const auto& c
= *val
.asCell();
1753 if (!checkSetHelper(st
, c
, strTv
, true)) continue;
1754 ret
.setWithRef(iter
.first(), val
, isKey
);
1759 Variant
f_array_intersect_key(int _argc
, const Variant
& container1
, const Variant
& container2
,
1760 const Array
& _argv
/* = null_array */) {
1761 ARRAY_INTERSECT_PRELUDE()
1762 // If we're only dealing with two containers and if they are both arrays,
1763 // we can avoid creating an intermediate Set
1764 if (!moreThanTwo
&& c1
.m_type
== KindOfArray
&& c2
.m_type
== KindOfArray
) {
1765 auto ad2
= c2
.m_data
.parr
;
1766 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1767 auto key
= iter
.first();
1768 const auto& c
= *key
.asCell();
1769 if (c
.m_type
== KindOfInt64
) {
1770 if (!ad2
->exists(c
.m_data
.num
)) continue;
1772 assert(IS_STRING_TYPE(c
.m_type
));
1773 if (!ad2
->exists(c
.m_data
.pstr
)) continue;
1775 ret
.setWithRef(key
, iter
.secondRefPlus(), true);
1779 // Build up a Set containing the keys that are present in all the containers
1780 // (except container1)
1782 Object setObj
= st
= NEWOBJ(c_Set
)();
1783 if (LIKELY(!moreThanTwo
)) {
1784 // There is only one container (not counting container1) so we can just
1785 // call containerKeysToSetHelper() to build the Set.
1786 containerKeysToSetHelper(st
, container2
);
1788 // We're dealing with three or more containers. Copy all of the containers
1789 // (except the first) into a TypedValue array.
1790 int count
= _argv
.size() + 1;
1791 TypedValue
* containers
=
1792 makeContainerListHelper(container2
, _argv
, count
, smallestPos
);
1793 SCOPE_EXIT
{ smart_free(containers
); };
1794 // Build a Set of the keys that were present in all of the containers
1795 containerKeysIntersectHelper(st
, containers
, count
);
1797 // Loop over container1, only copying over key/value pairs where the key
1798 // is present in the Set. When checking if a key is present in the Set,
1799 // any value that is not an integer or string is cast to a string, and we
1800 // convert int-like strings to integers.
1801 Variant
strHolder(empty_string
.get());
1802 TypedValue
* strTv
= strHolder
.asTypedValue();
1803 bool isKey
= c1
.m_type
== KindOfArray
;
1804 for (ArrayIter
iter(container1
); iter
; ++iter
) {
1805 auto key
= iter
.first();
1806 const auto& c
= *key
.asCell();
1807 if (!checkSetHelper(st
, c
, strTv
, !isKey
)) continue;
1808 ret
.setWithRef(key
, iter
.secondRefPlus(), isKey
);
1813 #undef ARRAY_INTERSECT_PRELUDE
1815 Variant
f_array_uintersect(int _argc
, const Variant
& array1
, const Variant
& array2
,
1816 const Variant
& data_compare_func
,
1817 const Array
& _argv
/* = null_array */) {
1818 diff_intersect_body(intersect
, false COMMA
true COMMA NULL COMMA NULL
1819 COMMA cmp_func COMMA
&func
,
1820 Variant func
= data_compare_func
;
1821 Array extra
= _argv
;
1822 if (!extra
.empty()) {
1823 extra
.prepend(func
);
1828 Variant
f_array_intersect_assoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1829 const Array
& _argv
/* = null_array */) {
1830 diff_intersect_body(intersect
, true COMMA
true,);
1833 Variant
f_array_intersect_uassoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1834 const Variant
& key_compare_func
,
1835 const Array
& _argv
/* = null_array */) {
1836 diff_intersect_body(intersect
, true COMMA
true COMMA cmp_func COMMA
&func
,
1837 Variant func
= key_compare_func
;
1838 Array extra
= _argv
;
1839 if (!extra
.empty()) {
1840 extra
.prepend(func
);
1845 Variant
f_array_uintersect_assoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1846 const Variant
& data_compare_func
,
1847 const Array
& _argv
/* = null_array */) {
1848 diff_intersect_body(intersect
, true COMMA
true COMMA NULL COMMA NULL
1849 COMMA cmp_func COMMA
&func
,
1850 Variant func
= data_compare_func
;
1851 Array extra
= _argv
;
1852 if (!extra
.empty()) {
1853 extra
.prepend(func
);
1858 Variant
f_array_uintersect_uassoc(int _argc
, const Variant
& array1
, const Variant
& array2
,
1859 const Variant
& data_compare_func
,
1860 const Variant
& key_compare_func
,
1861 const Array
& _argv
/* = null_array */) {
1862 diff_intersect_body(intersect
, true COMMA
true COMMA cmp_func COMMA
&key_func
1863 COMMA cmp_func COMMA
&data_func
,
1864 Variant data_func
= data_compare_func
;
1865 Variant key_func
= key_compare_func
;
1866 Array extra
= _argv
;
1867 if (!extra
.empty()) {
1868 extra
.prepend(key_func
);
1869 extra
.prepend(data_func
);
1870 key_func
= extra
.pop();
1871 data_func
= extra
.pop();
1875 Variant
f_array_intersect_ukey(int _argc
, const Variant
& array1
, const Variant
& array2
,
1876 const Variant
& key_compare_func
, const Array
& _argv
/* = null_array */) {
1877 diff_intersect_body(intersect
, true COMMA
false COMMA cmp_func COMMA
&func
,
1878 Variant func
= key_compare_func
;
1879 Array extra
= _argv
;
1880 if (!extra
.empty()) {
1881 extra
.prepend(func
);
1886 ///////////////////////////////////////////////////////////////////////////////
1887 // sorting functions
1889 struct Collator final
: RequestEventHandler
{
1890 String
getLocale() {
1893 Intl::IntlError
&getErrorRef() {
1896 bool setLocale(const String
& locale
) {
1897 if (m_locale
.same(locale
)) {
1901 ucol_close(m_ucoll
);
1904 m_errcode
.clearError();
1905 UErrorCode error
= U_ZERO_ERROR
;
1906 m_ucoll
= ucol_open(locale
.data(), &error
);
1907 if (m_ucoll
== NULL
) {
1908 raise_warning("failed to load %s locale from icu data", locale
.data());
1911 if (U_FAILURE(error
)) {
1912 m_errcode
.setError(error
);
1913 ucol_close(m_ucoll
);
1921 UCollator
*getCollator() {
1925 bool setAttribute(int64_t attr
, int64_t val
) {
1927 Logger::Verbose("m_ucoll is NULL");
1930 m_errcode
.clearError();
1931 UErrorCode error
= U_ZERO_ERROR
;
1932 ucol_setAttribute(m_ucoll
, (UColAttribute
)attr
,
1933 (UColAttributeValue
)val
, &error
);
1934 if (U_FAILURE(error
)) {
1935 m_errcode
.setError(error
);
1936 Logger::Verbose("Error setting attribute value");
1942 bool setStrength(int64_t strength
) {
1944 Logger::Verbose("m_ucoll is NULL");
1947 ucol_setStrength(m_ucoll
, (UCollationStrength
)strength
);
1951 Variant
getErrorCode() {
1953 Logger::Verbose("m_ucoll is NULL");
1956 return m_errcode
.getErrorCode();
1959 void requestInit() override
{
1960 m_locale
= String(uloc_getDefault(), CopyString
);
1961 m_errcode
.clearError();
1962 UErrorCode error
= U_ZERO_ERROR
;
1963 m_ucoll
= ucol_open(m_locale
.data(), &error
);
1964 if (U_FAILURE(error
)) {
1965 m_errcode
.setError(error
);
1969 void requestShutdown() override
{
1971 m_errcode
.clearError(false);
1973 ucol_close(m_ucoll
);
1981 Intl::IntlError m_errcode
;
1983 IMPLEMENT_STATIC_REQUEST_LOCAL(Collator
, s_collator
);
1985 static Array::PFUNC_CMP
get_cmp_func(int sort_flags
, bool ascending
) {
1986 switch (sort_flags
) {
1988 return Array::SortNatural
;
1989 case SORT_NATURAL_CASE
:
1990 return Array::SortNaturalCase
;
1993 Array::SortNumericAscending
: Array::SortNumericDescending
;
1996 Array::SortStringAscending
: Array::SortStringDescending
;
1997 case SORT_STRING_CASE
:
1999 Array::SortStringAscendingCase
: Array::SortStringDescendingCase
;
2000 case SORT_LOCALE_STRING
:
2002 Array::SortLocaleStringAscending
: Array::SortLocaleStringDescending
;
2006 Array::SortRegularAscending
: Array::SortRegularDescending
;
2010 class ArraySortTmp
{
2012 explicit ArraySortTmp(Array
& arr
) : m_arr(arr
) {
2013 m_ad
= arr
.get()->escalateForSort();
2014 m_ad
->incRefCount();
2017 if (m_ad
!= m_arr
.get()) {
2019 m_ad
->decRefCount();
2022 ArrayData
* operator->() { return m_ad
; }
2029 php_sort(VRefParam container
, int sort_flags
,
2030 bool ascending
, bool use_collator
) {
2031 if (container
.isArray()) {
2032 Array
& arr_array
= container
.wrapped().toArrRef();
2033 if (use_collator
&& sort_flags
!= SORT_LOCALE_STRING
) {
2034 UCollator
*coll
= s_collator
->getCollator();
2036 Intl::IntlError
&errcode
= s_collator
->getErrorRef();
2037 return collator_sort(container
, sort_flags
, ascending
,
2041 ArraySortTmp
ast(arr_array
);
2042 ast
->sort(sort_flags
, ascending
);
2045 if (container
.isObject()) {
2046 ObjectData
* obj
= container
.getObjectData();
2047 if (obj
->getCollectionType() == Collection::VectorType
) {
2048 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
2049 vec
->sort(sort_flags
, ascending
);
2052 // other collections are not supported:
2053 // - mapping types require associative sort
2054 // - frozen types are not to be modified
2055 // - set types are not ordered
2057 throw_expected_array_or_collection_exception();
2062 php_asort(VRefParam container
, int sort_flags
,
2063 bool ascending
, bool use_collator
) {
2064 if (container
.isArray()) {
2065 Array
& arr_array
= container
.wrapped().toArrRef();
2066 if (use_collator
&& sort_flags
!= SORT_LOCALE_STRING
) {
2067 UCollator
*coll
= s_collator
->getCollator();
2069 Intl::IntlError
&errcode
= s_collator
->getErrorRef();
2070 return collator_asort(container
, sort_flags
, ascending
,
2074 ArraySortTmp
ast(arr_array
);
2075 ast
->asort(sort_flags
, ascending
);
2078 if (container
.isObject()) {
2079 ObjectData
* obj
= container
.getObjectData();
2080 if (obj
->getCollectionType() == Collection::MapType
) {
2081 BaseMap
* mp
= static_cast<BaseMap
*>(obj
);
2082 mp
->asort(sort_flags
, ascending
);
2086 throw_expected_array_or_collection_exception();
2091 php_ksort(VRefParam container
, int sort_flags
, bool ascending
) {
2092 if (container
.isArray()) {
2093 Array
& arr_array
= container
.wrapped().toArrRef();
2094 ArraySortTmp
ast(arr_array
);
2095 ast
->ksort(sort_flags
, ascending
);
2098 if (container
.isObject()) {
2099 ObjectData
* obj
= container
.getObjectData();
2100 if (obj
->getCollectionType() == Collection::MapType
) {
2101 BaseMap
* mp
= static_cast<BaseMap
*>(obj
);
2102 mp
->ksort(sort_flags
, ascending
);
2106 throw_expected_array_or_collection_exception();
2110 bool f_sort(VRefParam array
, int sort_flags
/* = 0 */,
2111 bool use_collator
/* = false */) {
2112 return php_sort(array
, sort_flags
, true, use_collator
);
2115 bool f_rsort(VRefParam array
, int sort_flags
/* = 0 */,
2116 bool use_collator
/* = false */) {
2117 return php_sort(array
, sort_flags
, false, use_collator
);
2120 bool f_asort(VRefParam array
, int sort_flags
/* = 0 */,
2121 bool use_collator
/* = false */) {
2122 return php_asort(array
, sort_flags
, true, use_collator
);
2125 bool f_arsort(VRefParam array
, int sort_flags
/* = 0 */,
2126 bool use_collator
/* = false */) {
2127 return php_asort(array
, sort_flags
, false, use_collator
);
2130 bool f_ksort(VRefParam array
, int sort_flags
/* = 0 */) {
2131 return php_ksort(array
, sort_flags
, true);
2134 bool f_krsort(VRefParam array
, int sort_flags
/* = 0 */) {
2135 return php_ksort(array
, sort_flags
, false);
2138 // NOTE: PHP's implementation of natsort and natcasesort accepts ArrayAccess
2139 // objects as well, which does not make much sense, and which is not supported
2142 Variant
f_natsort(VRefParam array
) {
2143 return php_asort(array
, SORT_NATURAL
, true, false);
2146 Variant
f_natcasesort(VRefParam array
) {
2147 return php_asort(array
, SORT_NATURAL_CASE
, true, false);
2150 bool f_usort(VRefParam container
, const Variant
& cmp_function
) {
2151 if (container
.isArray()) {
2152 Array
& arr_array
= container
.wrapped().toArrRef();
2153 ArraySortTmp
ast(arr_array
);
2154 return ast
->usort(cmp_function
);
2156 if (container
.isObject()) {
2157 ObjectData
* obj
= container
.getObjectData();
2158 if (obj
->getCollectionType() == Collection::VectorType
) {
2159 c_Vector
* vec
= static_cast<c_Vector
*>(obj
);
2160 return vec
->usort(cmp_function
);
2162 // other collections are not supported:
2163 // - mapping types require associative sort
2164 // - frozen types are not to be modified
2165 // - set types are not ordered
2167 throw_expected_array_or_collection_exception();
2171 bool f_uasort(VRefParam container
, const Variant
& cmp_function
) {
2172 if (container
.isArray()) {
2173 Array
& arr_array
= container
.wrapped().toArrRef();
2174 ArraySortTmp
ast(arr_array
);
2175 return ast
->uasort(cmp_function
);
2177 if (container
.isObject()) {
2178 ObjectData
* obj
= container
.getObjectData();
2179 if (obj
->getCollectionType() == Collection::MapType
) {
2180 BaseMap
* mp
= static_cast<BaseMap
*>(obj
);
2181 return mp
->uasort(cmp_function
);
2184 throw_expected_array_or_collection_exception();
2188 bool f_uksort(VRefParam container
, const Variant
& cmp_function
) {
2189 if (container
.isArray()) {
2190 Array
& arr_array
= container
.wrapped().toArrRef();
2191 ArraySortTmp
ast(arr_array
);
2192 return ast
->uksort(cmp_function
);
2194 if (container
.isObject()) {
2195 ObjectData
* obj
= container
.getObjectData();
2196 if (obj
->getCollectionType() == Collection::MapType
) {
2197 BaseMap
* mp
= static_cast<BaseMap
*>(obj
);
2198 return mp
->uksort(cmp_function
);
2201 throw_expected_array_or_collection_exception();
2205 bool f_array_multisort(int _argc
, VRefParam ar1
,
2206 const Array
& _argv
/* = null_array */) {
2207 getCheckedArrayRet(ar1
, false);
2208 std::vector
<Array::SortData
> data
;
2209 std::vector
<Array
> arrays
;
2210 arrays
.reserve(1 + _argv
.size()); // so no resize would happen
2214 arrays
.push_back(arr_ar1
);
2215 sd
.array
= &arrays
.back();
2218 int sort_flags
= SORT_REGULAR
;
2219 bool ascending
= true;
2220 for (int i
= 0; i
< _argv
.size(); i
++) {
2221 Variant
*v
= &((Array
&)_argv
).lvalAt(i
);
2222 auto const cell
= v
->asCell();
2223 if (cell
->m_type
== KindOfArray
) {
2224 sd
.cmp_func
= get_cmp_func(sort_flags
, ascending
);
2227 sort_flags
= SORT_REGULAR
;
2231 arrays
.push_back(Array(cell
->m_data
.parr
));
2232 sd
.array
= &arrays
.back();
2234 int n
= v
->toInt32();
2235 if (n
== SORT_ASC
) {
2237 } else if (n
== SORT_DESC
) {
2245 sd
.cmp_func
= get_cmp_func(sort_flags
, ascending
);
2248 return Array::MultiSort(data
, true);
2251 Variant
f_array_unique(const Variant
& array
, int sort_flags
/* = 2 */) {
2252 // NOTE, PHP array_unique accepts ArrayAccess objects as well,
2253 // which is not supported here.
2254 getCheckedArray(array
);
2255 switch (sort_flags
) {
2257 case SORT_LOCALE_STRING
:
2258 return ArrayUtil::StringUnique(arr_array
);
2260 return ArrayUtil::NumericUnique(arr_array
);
2263 return ArrayUtil::RegularSortUnique(arr_array
);
2267 String
f_i18n_loc_get_default() {
2268 return s_collator
->getLocale();
2271 bool f_i18n_loc_set_default(const String
& locale
) {
2272 return s_collator
->setLocale(locale
);
2275 bool f_i18n_loc_set_attribute(int64_t attr
, int64_t val
) {
2276 return s_collator
->setAttribute(attr
, val
);
2279 bool f_i18n_loc_set_strength(int64_t strength
) {
2280 return s_collator
->setStrength(strength
);
2283 Variant
f_i18n_loc_get_error_code() {
2284 return s_collator
->getErrorCode();
2287 Variant
f_hphp_array_idx(const Variant
& search
, const Variant
& key
, const Variant
& def
) {
2288 if (!key
.isNull()) {
2289 if (LIKELY(search
.isArray())) {
2290 ArrayData
*arr
= search
.getArrayData();
2291 VarNR index
= key
.toKey();
2292 if (!index
.isNull()) {
2293 const Variant
& ret
= arr
->get(index
, false);
2294 return (&ret
!= &null_variant
) ? ret
: def
;
2297 raise_error("hphp_array_idx: search must be an array");
2303 ///////////////////////////////////////////////////////////////////////////////