Compare commits

...

8 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
7 changed files with 105 additions and 81 deletions

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,8 +1,7 @@
dist: xenial
group: edge
language: php
dist: bionic
php:
- 7.1
- 7.2
@ -12,5 +11,9 @@ php:
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.

113
README.md
View File

@ -1,69 +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.3 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.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'];
@ -74,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

@ -90,7 +90,7 @@
}
#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))
@ -173,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*)emalloc(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;
@ -207,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);
@ -221,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);
@ -558,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;
@ -575,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++];
@ -694,7 +693,7 @@ void collection_write_dimension(zval* object, zval* offset, zval* value)
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
#if PHP_VERSION_ID >= 70400
return value;
#endif
}
@ -1005,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;
@ -1049,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;
}
@ -1286,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)
@ -1316,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);
@ -2737,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;
@ -2775,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;
@ -2874,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;
@ -2913,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;
@ -3104,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;
@ -3138,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)) {
@ -3240,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)) {

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.1"
#define PHP_COLLECTIONS_VERSION "0.1.2"
#ifdef PHP_WIN32
#define PHP_COLLECTIONS_API __declspec(dllexport)
@ -28,6 +28,8 @@ 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