Compare commits

...

14 Commits

Author SHA1 Message Date
CismonX 9a7a72dd29 Update LICENSE 2020-03-30 13:21:48 +08:00
CismonX 66934839e8 Update README.md 2020-03-30 13:08:27 +08:00
CismonX 96a7c69edd Add codecov.yml. Bumping version. 2020-03-30 12:43:03 +08:00
CismonX f4b5e60ba9 Update readme and travis config. 2020-03-30 12:28:12 +08:00
CismonX 9cd6222d39 integrating Codecov 2020-03-24 11:07:16 +08:00
CismonX d7f2835fc0 fix travis configuration 2020-03-20 11:34:55 +08:00
CismonX eeb0b4e809 update .travis.yml 2020-03-20 11:20:08 +08:00
CismonX 368a49271a fix bug. refactor code. 2020-03-20 11:15:54 +08:00
CismonX 1baf525cd7 prepare for 0.1.1 2019-12-10 04:52:49 +08:00
CismonX 426d80e5b1 compatible to PHP 7.4 2019-12-10 04:50:01 +08:00
CismonX 8d9f4d773b small refactor && update documentation 2019-07-25 00:09:02 +08:00
CismonX 394fdbf5f0 Merge pull request #4 from CismonX/dev
merge changes
2019-06-12 11:27:48 +08:00
CismonX da855f8844 refactor 2019-06-12 11:20:37 +08:00
CismonX 5f3f4f1d96 update 2019-06-08 00:18:42 +08:00
9 changed files with 132 additions and 88 deletions

4
.gitattributes vendored
View File

@ -1 +1,3 @@
*.h linguist-language=C
*.h linguist-language=C
stubs/* linguist-documentation
config.m4 linguist-detectable=false

15
.gitignore vendored
View File

@ -28,11 +28,14 @@ missing
mkinstalldirs
modules
run-tests.php
tests/*/*.diff
tests/*/*.out
tests/*/*.php
tests/*/*.exp
tests/*/*.log
tests/*/*.sh
tests/*.diff
tests/*.out
tests/*.php
tests/*.exp
tests/*.log
tests/*.sh
.idea/
.vscode/
*.gcov
*.gcda
*.gcno

View File

@ -1,16 +1,19 @@
dist: xenial
group: edge
language: php
dist: bionic
php:
- 7.1
- 7.2
- 7.3
- nightly
- 7.4
script:
- phpize
- ./configure
- make
- make EXTRA_CFLAGS='-coverage' EXTRA_LDFLAGS='-fprofile-arcs'
- make test
- gcov --object-directory=src/.libs src/collections_methods.c
after_success:
- bash <(curl -s https://codecov.io/bash)

View File

@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2018 CismonX
Copyright (c) 2018-2020 CismonX<admin@cismon.net>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
THE SOFTWARE.

101
README.md
View File

@ -1,57 +1,33 @@
# ext-collections
[![Travis-CI](https://travis-ci.org/CismonX/ext-collections.svg?branch=master)](https://travis-ci.org/CismonX/ext-collections)
[![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Travis-CI](https://travis-ci.com/CismonX/ext-collections.svg?branch=master)](https://travis-ci.com/CismonX/ext-collections)
[![Codecov](https://codecov.io/gh/CismonX/ext-collections/branch/master/graphs/badge.svg)](https://codecov.io/gh/CismonX/ext-collections)
[![MIT license](https://img.shields.io/badge/licence-MIT-blue.svg)](LICENSE)
## 1. Introduction
This PHP extension provides a set of useful functional-style operations on PHP arrays, which makes array manipulation simple and scalable.
This PHP extension provides a set of useful and convenient operations on PHP arrays, which makes working with arrays simple and scalable.
Method names and functionalities are inspired by [Kotlin.Collections](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/).
Method names and functionalities are inspired by [Kotlin.Collections](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/), having a slightly different style than that of [Laravel Collections](https://laravel.com/docs/5.8/collections).
### 1.1 Notes
Requires PHP version >= 7.1 and < 8.0 (master branch).
* Requires PHP 7.1 and above.
* Thread safety:
* Distinct objects: **safe**.
* Shared objects: **unsafe**.
### 1.1 Example
## 2. Documentation
### 2.1 Functionalities
See [stubs](stubs/) directory for signature of all classes and methods of this extension, with PHPDoc. They can also serve as IDE helper.
### 2.2 PHP-style access
The `Collection` class implements `ArrayAccess` and `Countable` interface internally, you can treat an instance of `Collection` as an `ArrayObject`.
* The `isset()`, `unset()` keywords can be used on elements of `Collection`.
* Elements can be accessed via property and bracket expression.
* `empty()`, `count()` can be used on instance of `Collection`.
* Elements can be traversed via `foreach()` keyword.
### 2.4 Notes
* The `Collection::xxxTo()` methods will preserve the original key-value pairs of destination `Collection` when keys collide.
* Some methods of `Collection` involves comparing two of its elements, which accepts `$flags` as one of its arguments. When these methods are being invoked, make sure all elements are of the same type (numeric/string/others), otherwise you're likely to get a segfault.
## 3. Example
Here is a simple example for how to work with arrays gracefully using this extension.
Here is a simple example on how to work with arrays gracefully using this extension.
```php
$employees = [
['name' => 'Alice', 'sex' => 'female', 'age' => 35],
['name' => 'Bob', 'sex' => 'male', 'age' => 29],
['name' => 'David', 'sex' => 'male', 'age' => 40],
['name' => 'Benjamin', 'sex' => 'male', 'age' => 32]
['name' => 'Alice', 'gender' => 'female', 'age' => 35],
['name' => 'Bob', 'gender' => 'male', 'age' => 29],
['name' => 'David', 'gender' => 'male', 'age' => 40],
['name' => 'Benjamin', 'gender' => 'male', 'age' => 32]
];
// Trying to get an array of names of male employees,
// sorted by the descending order of their age.
$names = Collection::init($employees)
->filter(function ($value) {
return $value['sex'] == 'male';
return $value['gender'] == 'male';
})
->sortedByDescending(function ($value) {
return $value['age'];
@ -62,3 +38,54 @@ $names = Collection::init($employees)
->toArray();
// You got $names == ['David', 'Benjamin', 'Bob'].
```
## 2. Getting Started
### 2.1 Installation
Like other PHP extensions, ext-collections can be built and installed with a few commands:
```bash
phpize
./configure
make
sudo make install
```
Include it in your PHP configuration file to enable this extension:
```php.ini
extension=collections.so
```
Building on Windows is not as convenient, however, pre-built binaries for Windows are provided in the [releases](https://github.com/CismonX/ext-collections/releases). If you want to build it yourself, follow the [official PHP wiki](https://wiki.php.net/internals/windows/stepbystepbuild_sdk_2).
### 2.2 API Reference
See [stubs](stubs/) directory for signatures of all classes and methods of this extension, with PHPDoc. They can also serve as IDE helper.
### 2.3 PHP-style Access
The `Collection` class implements `ArrayAccess` and `Countable` interface internally, you can treat an instance of `Collection` as an `ArrayObject`.
* The `isset()`, `unset()` keywords can be used on elements of `Collection`.
* Elements can be accessed via property or bracket expression.
* `empty()`, `count()` can be used on instance of `Collection`.
* Elements can be traversed via `foreach()` keyword.
## 3. Notes
* The `Collection::xxxTo()` methods will preserve the original key-value pairs of destination `Collection` when keys collide.
* Some methods of `Collection` involves comparing two of its elements, which accepts `$flags` as one of its arguments. When these methods are being invoked, make sure all elements are of the same type (numeric/string/others), otherwise you're likely to get a segfault.
### 3.1 Copy-on-write Mechanism
Class `Collection` does not introduce new data structures internally. Instead, it only holds a pointer to a `zend_array`, and all its methods works directly on top of `zend_array`. Which means conversion between `Collection` and `array` does not involve copying, until write operation is performed on one of the duplicates.
```php
$foo = ['a', 'b']; // arr0: refcount = 1
$bar = Collection::init($foo); // arr0: refcount = 2, no copying of either `zend_array` or its elements
echo $bar->containsValue('a'); // arr0: refcount = 2, read operation, no copying
$bar->shuffle(); // arr0: refcount = 1, arr1: refcount = 1, write operation, `zend_array` is separated
$baz = $bar->toArray(); // arr0: refcount = 1, arr1: refcount = 2, no copying
```

2
codecov.yml Normal file
View File

@ -0,0 +1,2 @@
ignore:
- "src/collections.c"

View File

@ -6,12 +6,6 @@
#include "php_collections_me.h"
// ZEND_ACC_CTOR is removed in PHP 7.4
// Removing the flag does not affect the constructor from being recognized.
#if PHP_VERSION_ID >= 70400
#define ZEND_ACC_CTOR 0
#endif
ZEND_BEGIN_ARG_INFO(action_arginfo, 0)
ZEND_ARG_CALLABLE_INFO(0, action, 0)
ZEND_END_ARG_INFO()

View File

@ -15,36 +15,38 @@
#define GC_DELREF(p) --GC_REFCOUNT(p)
#endif
// Thread safty assured global variables.
#define FCI_G COLLECTIONS_G(fci)
#define FCC_G COLLECTIONS_G(fcc)
#define REF_G COLLECTIONS_G(ref)
#define CMP_G COLLECTIONS_G(cmp)
#define Z_COLLECTION_P(val) (Z_OBJ_P(val)->properties)
#define THIS_COLLECTION Z_COLLECTION_P(getThis())
#define THIS_COLLECTION Z_COLLECTION_P(&EX(This))
#define PAIR_FIRST(obj) OBJ_PROP_NUM(obj, 0)
#define PAIR_SECOND(obj) OBJ_PROP_NUM(obj, 1)
#define IS_COLLECTION(val) \
Z_TYPE(val) == IS_OBJECT && Z_OBJCE(val) == collections_collection_ce
(Z_TYPE(val) == IS_OBJECT && Z_OBJCE(val) == collections_collection_ce)
#define IS_PAIR(val) \
Z_TYPE(val) == IS_OBJECT && Z_OBJCE(val) == collections_pair_ce
(Z_TYPE(val) == IS_OBJECT && Z_OBJCE(val) == collections_pair_ce)
// Separation on the zend_array held by the collection.
#define SEPARATE_COLLECTION(ht, obj) \
if (GC_REFCOUNT(ht) > 1) { \
GC_DELREF(ht); \
ht = Z_OBJ_P(obj)->properties = zend_array_dup(ht); \
}
#define SEPARATE_CURRENT_COLLECTION(ht) \
SEPARATE_COLLECTION(ht, getThis())
SEPARATE_COLLECTION(ht, &EX(This))
#define INIT_FCI(fci, num_args) \
zval params[num_args], retval; \
(fci)->size = sizeof(zend_fcall_info); \
(fci)->param_count = (num_args); \
(fci)->retval = &retval; \
(fci)->params = params
// Invoke callback function, passing bucket value as 1st arg, key as 2nd.
#define CALLBACK_KEYVAL_INVOKE(params, bucket) \
ZVAL_COPY_VALUE(&(params)[0], &(bucket)->val); \
if ((bucket)->key) { \
@ -54,6 +56,7 @@
} \
zend_call_function(&fci, &fcc)
// Several E_WARNING level error messages. Could use exceptions instead.
#define PHP_COLLECTIONS_ERROR(type, msg) \
php_error_docref(NULL, type, msg)
#define ERR_BAD_ARGUMENT_TYPE() \
@ -74,6 +77,7 @@
PHP_COLLECTIONS_ERROR(E_WARNING, "The array should be packed")
#define ERR_SILENCED()
// Validate and extract zend_array from collection.
#define ELEMENTS_VALIDATE(elements, err, err_then) \
zend_array* elements##_arr; \
if (IS_COLLECTION(*elements)) { \
@ -86,13 +90,14 @@
}
#define ARRAY_NEW(name, size) \
zend_array* (name) = (zend_array*)emalloc(sizeof(zend_array)); \
zend_array* (name) = emalloc(sizeof(zend_array)); \
zend_hash_init(name, size, NULL, ZVAL_PTR_DTOR, 0)
#define ARRAY_NEW_EX(name, other) \
ARRAY_NEW(name, zend_hash_num_elements(other))
#define ARRAY_CLONE(dest, src) \
zend_array* (dest) = zend_array_dup(src);
// Creates a new collection which wraps the given zend_array.
#define RETVAL_NEW_COLLECTION(ht) \
{ \
zend_object* _obj = create_collection_obj(); \
@ -168,8 +173,7 @@ static zend_always_inline void array_update_bucket(zend_array* ht, Bucket* bucke
static zend_always_inline zend_object* create_object(zend_class_entry* ce,
zend_object_handlers* handlers)
{
zend_object* obj = (zend_object*)ecalloc(1, sizeof(zend_object) +
zend_object_properties_size(ce));
zend_object* obj = emalloc(sizeof(zend_object) + zend_object_properties_size(ce));
zend_object_std_init(obj, ce);
object_properties_init(obj, ce);
obj->handlers = handlers;
@ -202,7 +206,7 @@ static zend_always_inline zend_array* array_group_fetch(zend_array* ht, zval* ke
zval* group_val = zend_hash_index_find(ht, Z_LVAL_P(key));
if (UNEXPECTED(group_val == NULL)) {
zval tmp_val;
group = (zend_array*)emalloc(sizeof(zend_array));
group = emalloc(sizeof(zend_array));
zend_hash_init(group, 8, NULL, ZVAL_PTR_DTOR, 0);
ZVAL_ARR(&tmp_val, group);
zend_hash_index_add(ht, Z_LVAL_P(key), &tmp_val);
@ -216,7 +220,7 @@ static zend_always_inline zend_array* array_group_fetch(zend_array* ht, zval* ke
zval* group_val = zend_hash_find(ht, Z_STR_P(key));
if (UNEXPECTED(group_val == NULL)) {
zval tmp_val;
group = (zend_array*)emalloc(sizeof(zend_array));
group = emalloc(sizeof(zend_array));
zend_hash_init(group, 8, NULL, ZVAL_PTR_DTOR, 0);
ZVAL_ARR(&tmp_val, group);
zend_hash_add(ht, Z_STR_P(key), &tmp_val);
@ -553,7 +557,7 @@ static zend_always_inline void array_slice_by(zend_array* ht, zend_array* other,
}
return;
}
Bucket* ref_this = (Bucket*)malloc(num_this * sizeof(Bucket));
Bucket* ref_this = malloc(num_this * sizeof(Bucket));
compare_func_t cmp = NULL;
equal_check_func_t eql;
uint32_t idx = 0;
@ -570,7 +574,7 @@ static zend_always_inline void array_slice_by(zend_array* ht, zend_array* other,
CMP_G = cmp;
zend_sort(ref_this, num_this, sizeof(Bucket), packed ? bucket_compare_with_idx : cmp,
(swap_func_t)zend_hash_bucket_packed_swap);
Bucket* ref_other = (Bucket*)malloc(num_other * sizeof(Bucket));
Bucket* ref_other = malloc(num_other * sizeof(Bucket));
uint32_t idx_other = 0;
ZEND_HASH_FOREACH_BUCKET(other, Bucket* bucket)
Bucket* dest = &ref_other[idx_other++];
@ -686,15 +690,16 @@ void collection_write_dimension(zval* object, zval* offset, zval* value)
Z_TRY_ADDREF_P(value);
}
void collection_write_property(zval* object, zval* member, zval* value, void** unused)
zobj_write_prop_ret_t collection_write_property(zval* object, zval* member, zval* value, void** unused)
{
collection_write_dimension(object, member, value);
#if PHP_VERSION_ID >= 70400
return value;
#endif
}
zval* collection_read_dimension(zval* object, zval* offset, int type, zval* rv)
{
// Note that we don't handle type. So don't do any fancy things with Collection
// such as fetching a reference of a value, etc.
zend_array* current = Z_COLLECTION_P(object);
zval* found = NULL;
if (Z_TYPE_P(offset) == IS_LONG) {
@ -999,7 +1004,7 @@ PHP_METHOD(Collection, binarySearchBy)
}
INIT_FCI(&fci, 2);
uint32_t range = to_idx - from_idx;
Bucket* ref = (Bucket*)malloc(range * sizeof(Bucket));
Bucket* ref = malloc(range * sizeof(Bucket));
compare_func_t cmp = NULL;
uint32_t idx = 0;
Bucket* bucket = current->arData + from_idx;
@ -1043,7 +1048,7 @@ PHP_METHOD(Collection, chunked)
zend_array* chunk = NULL;
ZEND_HASH_FOREACH_BUCKET(current, Bucket* bucket)
if (num_remaining == 0) {
chunk = (zend_array*)emalloc(sizeof(zend_array));
chunk = emalloc(sizeof(zend_array));
zend_hash_init(chunk, 8, NULL, ZVAL_PTR_DTOR, 0);
num_remaining = size;
}
@ -1270,7 +1275,7 @@ PHP_METHOD(Collection, copyOfRange)
PHP_METHOD(Collection, count)
{
zend_long count;
collection_count_elements(getThis(), &count);
collection_count_elements(&EX(This), &count);
RETVAL_LONG(count);
}
@ -1280,7 +1285,7 @@ PHP_METHOD(Collection, distinct)
compare_func_t cmp = NULL;
equal_check_func_t eql = NULL;
uint32_t num_elements = zend_hash_num_elements(current);
Bucket* ref = (Bucket*)malloc(num_elements * sizeof(Bucket));
Bucket* ref = malloc(num_elements * sizeof(Bucket));
ARRAY_CLONE(distinct, current);
uint32_t idx = 0;
ZEND_HASH_FOREACH_BUCKET(distinct, Bucket* bucket)
@ -1310,7 +1315,7 @@ PHP_METHOD(Collection, distinctBy)
compare_func_t cmp = NULL;
equal_check_func_t eql = NULL;
uint32_t num_elements = zend_hash_num_elements(current);
Bucket* ref = (Bucket*)malloc(num_elements * sizeof(Bucket));
Bucket* ref = malloc(num_elements * sizeof(Bucket));
ARRAY_CLONE(distinct, current);
uint32_t idx = 0;
INIT_FCI(&fci, 2);
@ -2324,7 +2329,7 @@ PHP_METHOD(Collection, onEach)
CALLBACK_KEYVAL_INVOKE(params, bucket);
zval_ptr_dtor(&retval);
ZEND_HASH_FOREACH_END();
RETVAL_ZVAL(getThis(), 1, 0);
RETVAL_ZVAL(&EX(This), 1, 0);
}
PHP_METHOD(Collection, partition)
@ -2731,7 +2736,7 @@ PHP_METHOD(Collection, sortBy)
zend_array* current = THIS_COLLECTION;
SEPARATE_CURRENT_COLLECTION(current);
uint32_t num_elements = zend_hash_num_elements(current);
zval* sort_by = (zval*)malloc(num_elements * sizeof(zval));
zval* sort_by = malloc(num_elements * sizeof(zval));
INIT_FCI(&fci, 2);
compare_func_t cmp = NULL;
uint32_t idx = 0;
@ -2769,7 +2774,7 @@ PHP_METHOD(Collection, sortByDescending)
zend_array* current = THIS_COLLECTION;
SEPARATE_CURRENT_COLLECTION(current);
uint32_t num_elements = zend_hash_num_elements(current);
zval* sort_by = (zval*)malloc(num_elements * sizeof(zval));
zval* sort_by = malloc(num_elements * sizeof(zval));
INIT_FCI(&fci, 2);
compare_func_t cmp = NULL;
uint32_t idx = 0;
@ -2868,7 +2873,7 @@ PHP_METHOD(Collection, sortedBy)
zend_array* current = THIS_COLLECTION;
ARRAY_CLONE(sorted, current);
uint32_t num_elements = zend_hash_num_elements(current);
zval* sort_by = (zval*)malloc(num_elements * sizeof(zval));
zval* sort_by = malloc(num_elements * sizeof(zval));
INIT_FCI(&fci, 2);
compare_func_t cmp = NULL;
uint32_t idx = 0;
@ -2907,7 +2912,7 @@ PHP_METHOD(Collection, sortedByDescending)
zend_array* current = THIS_COLLECTION;
ARRAY_CLONE(sorted, current);
uint32_t num_elements = zend_hash_num_elements(current);
zval* sort_by = (zval*)malloc(num_elements * sizeof(zval));
zval* sort_by = malloc(num_elements * sizeof(zval));
INIT_FCI(&fci, 2);
compare_func_t cmp = NULL;
uint32_t idx = 0;
@ -3098,7 +3103,7 @@ PHP_METHOD(Collection, takeLast)
zend_bool packed = HT_IS_PACKED(current);
uint32_t idx = current->nNumUsed;
zend_long num_taken = n;
Bucket** taken = (Bucket**)malloc(num_taken * sizeof(Bucket*));
Bucket** taken = malloc(num_taken * sizeof(Bucket*));
// Note that the original element orders should be preserved as in kotlin.
for (; num_taken > 0 && idx > 0; --idx) {
Bucket* bucket = current->arData + idx - 1;
@ -3132,7 +3137,7 @@ PHP_METHOD(Collection, takeLastWhile)
ARRAY_NEW(new_collection, 8);
zend_bool packed = HT_IS_PACKED(current);
uint32_t num_elements = zend_hash_num_elements(current);
Bucket** taken = (Bucket**)malloc(num_elements * sizeof(Bucket*));
Bucket** taken = malloc(num_elements * sizeof(Bucket*));
ZEND_HASH_REVERSE_FOREACH_BUCKET(current, Bucket* bucket)
CALLBACK_KEYVAL_INVOKE(params, bucket);
if (zend_is_true(&retval)) {
@ -3234,7 +3239,7 @@ PHP_METHOD(Collection, union)
uint32_t num_elements = zend_hash_num_elements(new_collection);
compare_func_t cmp = NULL;
equal_check_func_t eql = NULL;
Bucket* ref = (Bucket*)malloc(num_elements * sizeof(Bucket));
Bucket* ref = malloc(num_elements * sizeof(Bucket));
uint32_t idx = 0;
ZEND_HASH_FOREACH_BUCKET(new_collection, Bucket* bucket)
if (UNEXPECTED(cmp == NULL)) {
@ -3422,7 +3427,7 @@ PHP_METHOD(Pair, __construct)
ZEND_PARSE_PARAMETERS_END();
Z_TRY_ADDREF_P(first);
Z_TRY_ADDREF_P(second);
zend_object* current = Z_OBJ_P(getThis());
zend_object* current = Z_OBJ_P(&EX(This));
pair_update_first(current, first);
pair_update_second(current, second);
}

View File

@ -16,7 +16,7 @@
extern zend_module_entry collections_module_entry;
#define phpext_collections_ptr &collections_module_entry
#define PHP_COLLECTIONS_VERSION "0.1.0"
#define PHP_COLLECTIONS_VERSION "0.1.2"
#ifdef PHP_WIN32
#define PHP_COLLECTIONS_API __declspec(dllexport)
@ -28,6 +28,14 @@ extern zend_module_entry collections_module_entry;
#if PHP_VERSION_ID < 70100
#error "This extension requires PHP 7.1 and above."
#elif PHP_VERSION_ID >= 80000
#error "This extension does not yet support PHP 8.0 (which is under development)."
#endif
#if PHP_VERSION_ID >= 70400
typedef zval* zobj_write_prop_ret_t;
#else
typedef void zobj_write_prop_ret_t;
#endif
#define PHP_COLLECTIONS_COMPARE_NATURAL (1 << 0)
@ -62,7 +70,7 @@ void collection_write_dimension(zval* object, zval* offset, zval* value);
zval* collection_read_dimension(zval* object, zval* offset, int type, zval* rv);
void collection_unset_dimension(zval* object, zval* offset);
int collection_has_property(zval* object, zval* member, int has_set_exists, void**);
void collection_write_property(zval* object, zval* member, zval* value, void**);
zobj_write_prop_ret_t collection_write_property(zval* object, zval* member, zval* value, void**);
zval* collection_read_property(zval* object, zval* member, int type, void**, zval* rv);
void collection_unset_property(zval* object, zval* member, void**);