From 98f5dddada3560a7d8d5f94227d28811ff8eb1d2 Mon Sep 17 00:00:00 2001 From: Angger Pradana Date: Sun, 7 Nov 2021 20:54:03 +0700 Subject: [PATCH] add: class to menage collaction data from array --- CHANGELOG.md | 5 +- .../AbstractCollectionImmutable.php | 156 +++++++++ src/System/Collection/Collection.php | 161 +++++++++ src/System/Collection/CollectionImmutable.php | 9 + tests/Colllection/BasicCollectionTest.php | 318 ++++++++++++++++++ 5 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 src/System/Collection/AbstractCollectionImmutable.php create mode 100644 src/System/Collection/Collection.php create mode 100644 src/System/Collection/CollectionImmutable.php create mode 100644 tests/Colllection/BasicCollectionTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db81c09..759d8279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.1.3] - 2021-11-07 ### Added -- Add feture to ```generate::class``` class, property, const and method as string +- Add feature to ```generate::class``` class, property, const and method as string +- Add feature to collact array ```Collection::class``` and ```CollectionImmutable::class``` ### Removed - Remove ```Router::view``` diff --git a/src/System/Collection/AbstractCollectionImmutable.php b/src/System/Collection/AbstractCollectionImmutable.php new file mode 100644 index 00000000..478fc257 --- /dev/null +++ b/src/System/Collection/AbstractCollectionImmutable.php @@ -0,0 +1,156 @@ + $item) { + $this->set($key, $item); + } + } + + public function __get($name) + { + return $this->get($name); + } + + public function all(): array + { + return $this->collection; + } + + public function get(string $name, $default = null) + { + return $this->collection[$name] ?? $default; + } + + protected function set(string $name, $value) + { + $this->collection[$name] = $value; + return $this; + } + + public function has(string $key) + { + return array_key_exists($key, $this->collection); + } + + public function contain($item) + { + return in_array($item, $this->collection); + } + + public function keys(): array + { + return array_keys($this->collection); + } + + public function items(): array + { + return array_values($this->collection); + } + + public function count(): int + { + return count($this->collection); + } + + public function countBy(callable $condition): int + { + $count = 0; + foreach ($this->collection as $key => $item) { + $do_somethink = call_user_func($condition, $item, $key); + + $count += $do_somethink === true ? 1 : 0; + } + + return $count; + } + + public function each(callable $callable) + { + if (! is_callable($callable)) { + return $this; + } + + foreach ($this->collection as $key => $item) { + $do_somethink = call_user_func($callable, $item, $key); + + // stop looping if callable returning false + if ($do_somethink === false) { + break; + } + } + + return $this; + } + + public function dumb() + { + var_dump($this->collection); + + return $this; + } + + public function some(callable $condition): bool + { + if (! is_callable($condition)) { + return $this; + } + + foreach ($this->collection as $key => $item) { + $call = call_user_func($condition, $item, $key); + + if ($call === true) { + return true; + } + } + + return false; + } + + public function every(callable $condition): bool + { + if (! is_callable($condition)) { + return $this; + } + + foreach ($this->collection as $key => $item) { + $call = call_user_func($condition, $item, $key); + + if ($call === false) { + return false; + } + } + + return true; + } + + public function json(): string + { + return json_encode($this->collection); + } + + public function first($default = null) + { + $key = array_key_first($this->collection) ?? 0; + return $this->collection[$key] ?? $default; + } + + public function last($default = null) + { + $key = array_key_last($this->collection); + return $this->collection[$key] ?? $default; + } + + public function isEmpty(): bool + { + return empty($this->collection); + } + +} + diff --git a/src/System/Collection/Collection.php b/src/System/Collection/Collection.php new file mode 100644 index 00000000..ca7853ef --- /dev/null +++ b/src/System/Collection/Collection.php @@ -0,0 +1,161 @@ +set($name, $value); + } + + public function clear() + { + $this->collection = []; + return $this; + } + + public function add(array $params) + { + foreach ($params as $key => $item) { + $this->set($key, $item); + } + return $this; + } + + public function remove(string $name) + { + if ($this->has($name)) { + unset($this->collection[$name]); + } + + return $this; + } + + public function set(string $name, $value) + { + parent::set($name, $value); + + return $this; + } + + public function replace(array $new_collection) + { + $this->collection = []; + foreach ($new_collection as $key => $item) { + $this->set($key, $item); + } + + return $this; + } + + public function map(callable $callable) + { + if (! is_callable($callable)) { + return $this; + } + + $new_collection = []; + foreach ($this->collection as $key => $item) { + $new_collection[$key] = call_user_func($callable, $item, $key); + } + + $this->replace($new_collection); + + return $this; + } + + public function filter(callable $condition_true) + { + if (! is_callable($condition_true)) { + return $this; + } + + $new_collection = []; + foreach ($this->collection as $key => $item) { + $call = call_user_func($condition_true, $item, $key); + $condition = is_bool($call) ? $call : false; + + if ($condition === true) { + $new_collection[$key] = $item; + } + } + + $this->replace($new_collection); + + return $this; + } + + public function reject(callable $condition_true) + { + if (! is_callable($condition_true)) { + return $this; + } + + $new_collection = []; + foreach ($this->collection as $key => $item) { + $call = call_user_func($condition_true, $item, $key); + $condition = is_bool($call) ? $call : false; + + if ($condition === false) { + $new_collection[$key] = $item; + } + } + + $this->replace($new_collection); + + return $this; + } + + public function reverse() + { + return $this->replace(array_reverse($this->collection)); + } + + public function sort() + { + asort($this->collection); + + return $this; + } + + public function sortDesc() + { + arsort($this->collection); + + return $this; + } + + public function sortBy(callable $callable) + { + uasort($this->collection, $callable); + + return $this; + } + + public function sortByDecs(callable $callable) + { + return $this->sortBy($callable)->reverse(); + } + + public function sortKey() + { + ksort($this->collection); + + return $this; + } + + public function sortKeyDesc() + { + krsort($this->collection); + + return $this; + } + + public function clone() + { + return new Collection($this->collection); + } + +} diff --git a/src/System/Collection/CollectionImmutable.php b/src/System/Collection/CollectionImmutable.php new file mode 100644 index 00000000..ff80117c --- /dev/null +++ b/src/System/Collection/CollectionImmutable.php @@ -0,0 +1,9 @@ + 'mangga', + 'buah_2' => 'jeruk', + 'buah_3' => 'apel', + 'buah_4' => 'melon', + 'buah_5' => 'rambutan', + 'buah_6' => 'peer', + ]; + $test = new Collection($original); + + // getter + $this->assertEquals($test->buah_1, 'mangga', 'add new item colection using __set'); + $this->assertEquals($test->get('buah_1'), 'mangga', 'add new item collection using set()'); + + // add new item + $test->set('buah_7', 'kelengkeng'); + $test->buah_8 = 'cherry'; + $this->assertEquals($test->buah_8, 'cherry', 'get item colection using __get'); + $this->assertEquals($test->get('buah_7'), 'kelengkeng', 'get item colection using get()'); + + // raname item + $test->set('buah_7', 'durian'); + $test->buah_8 = 'nanas'; + $this->assertEquals($test->buah_8, 'nanas', 'replece exis item colection using __get'); + $this->assertEquals($test->get('buah_7'), 'durian', 'replece exis item colection using get()'); + + // cek array key + $this->assertTrue($test->has('buah_1'), 'collection have item with key'); + + // cek contain + $this->assertTrue($test->contain('mangga'), 'collection have item'); + + // remove item + $test->remove('buah_2'); + $this->assertFalse($test->has('buah_2'), 'remove some item using key'); + + // reset to origin + $test->replace($original); + + // count + $this->assertEquals($test->count(), 6, 'count item in collection'); + + // count by + $countBy = $test->countBy(function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $this->assertEquals(4, $countBy, 'count item in collection with some condition'); + + // first and last item cek + $this->assertEquals("mangga", $test->first('bukan buah'), 'get first item in collection'); + $this->assertEquals("peer", $test->last('bukan buah'), 'get last item in collection'); + + // test clear and empty cek + $this->assertFalse($test->isEmpty()); + $test->clear(); + $this->assertTrue($test->isEmpty(), 'cek collection empty'); + // same with origin + $test->replace($original); + $this->assertEquals($test->all(), $original, 'replace axis collection with new data'); + + // test array keys and vules + $keys = array_keys($original); + $items = array_values($original); + $this->assertEquals($keys, $test->keys(), 'get all key in collection'); + $this->assertEquals($items, $test->items(), 'get all item value in collection'); + + // each funtion + $test->each(function($item, $key) use ($original) { + $this->assertTrue(in_array($item, $original), 'test each with value'); + $this->assertTrue(array_key_exists($key, $original), 'test each with key'); + }); + + // map funtion + $test->map(fn($item) => ucfirst($item)); + $copy_origin = array_map(fn($item) => ucfirst($item), $original); + $this->assertEquals($test->all(), $copy_origin, 'replace some/all item using map'); + $test->replace($original); + + // filter funtion + $test->filter(function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $copy_origin = array_filter($original, function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $this->assertEquals($test->all(), $copy_origin, 'filter item in collection'); + $test->replace($original); + + // test the collection have item with e letter + $some = $test->some(function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $this->assertTrue($some, 'test the collection have item with "e" letter'); + + // test the collection every item dont have 'x' letter + $every = $test->every(function($item) { + // find letter contain 'x' letter + return strpos($item, "x") === false + ? true + : false; + }); + $this->assertTrue($every, 'collection every item dont have "x" letter'); + + // json output + $json = json_encode($original); + $this->assertJsonStringEqualsJsonString($test->json(), $json, "collection convert to json string"); + + // collection reverse + $copy_origin = $original; + $this->assertEquals( + $test->reverse()->all(), + array_reverse($copy_origin), + "test reverse collection" + ); + $test->replace($original); + + // sort collection + // sort asc + $this->assertEquals( + $test->sort()->first(), + "apel", + "testing sort asc collection" + ); + // sort desc + $this->assertEquals( + $test->sortDesc()->first(), + "rambutan", + "testing sort desc collection" + ); + // sort using collback + $test->sortBy(function($a, $b) { + if ($a == $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + }); + $this->assertEquals( + $test->first(), + "apel", + "sort using user define asceding" + ); + $test->sortByDecs(function($a, $b) { + if ($a == $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + }); + $this->assertEquals( + $test->first(), + "rambutan", + "sort using user define decsending" + ); + + // sort colection by key + $this->assertEquals( + $test->sortKey()->first(), + "mangga", + "sort collection asc with key" + ); + $this->assertEquals( + $test->sortKeyDesc()->first(), + "peer", + "sort collection desc with key" + ); + $test->replace($original); + + // clone collection + $this->assertEquals( + $test->clone()->reverse()->first(), + $test->last(), + "clone collection without interupt original" + ); + + // reject + $copy_origin = $original; + unset($copy_origin['buah_2']); + $this->assertEquals( + $test->reject(fn($item) => $item == "jeruk")->all(), + $copy_origin, + 'its like filter but the oposite' + ); + } + + /** @test */ + public function it_collection_imutable_funtional_worK_properly(): void + { + $original = [ + 'buah_1' => 'mangga', + 'buah_2' => 'jeruk', + 'buah_3' => 'apel', + 'buah_4' => 'melon', + 'buah_5' => 'rambutan', + 'buah_6' => 'peer', + ]; + $test = new CollectionImmutable($original); + + // getter + $this->assertEquals($test->buah_1, 'mangga', 'add new item colection using __set'); + $this->assertEquals($test->get('buah_1'), 'mangga', 'add new item collection using set()'); + + // cek array key + $this->assertTrue($test->has('buah_1'), 'collection have item with key'); + + // cek contain + $this->assertTrue($test->contain('mangga'), 'collection have item'); + + // count + $this->assertEquals($test->count(), 6, 'count item in collection'); + + // count by + $countBy = $test->countBy(function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $this->assertEquals(4, $countBy, 'count item in collection with some condition'); + + // first and last item cek + $this->assertEquals("mangga", $test->first('bukan buah'), 'get first item in collection'); + $this->assertEquals("peer", $test->last('bukan buah'), 'get last item in collection'); + + // test array keys and vules + $keys = array_keys($original); + $items = array_values($original); + $this->assertEquals($keys, $test->keys(), 'get all key in collection'); + $this->assertEquals($items, $test->items(), 'get all item value in collection'); + + // each funtion + $test->each(function($item, $key) use ($original) { + $this->assertTrue(in_array($item, $original), 'test each with value'); + $this->assertTrue(array_key_exists($key, $original), 'test each with key'); + }); + + // test the collection have item with e letter + $some = $test->some(function($item) { + // find letter contain 'e' letter + return strpos($item, "e") !== false + ? true + : false; + }); + $this->assertTrue($some, 'test the collection have item with "e" letter'); + + // test the collection every item dont have 'x' letter + $every = $test->every(function($item) { + // find letter contain 'x' letter + return strpos($item, "x") === false + ? true + : false; + }); + $this->assertTrue($every, 'collection every item dont have "x" letter'); + + // json output + $json = json_encode($original); + $this->assertJsonStringEqualsJsonString($test->json(), $json, "collection convert to json string"); + + } + + /** @test */ + public function it_collection_chain_work_great(): void + { + $origin = [0, 1, 2, 3, 4]; + $collection = new Collection($origin); + + $chain = $collection + ->add($origin) + ->remove(0) + ->set(0, 0) + ->clear() + ->replace($origin) + ->each(fn($el) => in_array($el, $origin)) + ->map(fn($el) => $el + 100 - (2 * 50)) // equal +0 + ->filter(fn($el) => $el > -1) + ->sort() + ->sortDesc() + ->sortKey() + ->sortKeyDesc() + ->sortBy(function($a, $b) { + if ($a == $b) { + return 0; + } + return ($a < $b) ? -1 : 1; + }) + ->sortByDecs(function($a, $b) { + if ($b == $a) { + return 0; + } + return ($b < $a) ? -1 : 1; + }) + ->all() + ; + $this->assertEquals($chain, $origin, "all collection with chain is wotk"); + } + +}