├── .gitignore
├── src
└── WS
│ └── Utils
│ └── Collections
│ ├── HashCodeAware.php
│ ├── Set.php
│ ├── CollectionConstructorTrait.php
│ ├── IndexIterable.php
│ ├── Functions
│ ├── Group
│ │ ├── Aggregator
│ │ │ ├── Aggregator.php
│ │ │ ├── Count.php
│ │ │ ├── Last.php
│ │ │ ├── First.php
│ │ │ ├── Sum.php
│ │ │ ├── AddToSet.php
│ │ │ ├── AbstractFieldAggregator.php
│ │ │ ├── Max.php
│ │ │ ├── Min.php
│ │ │ └── Avg.php
│ │ └── Group.php
│ ├── Consumers.php
│ ├── Expression
│ │ ├── Operator
│ │ │ ├── OrOperator.php
│ │ │ ├── AndOperator.php
│ │ │ └── AbstractOperator.php
│ │ └── BoolExpression.php
│ ├── Converters.php
│ ├── ObjectFunctions.php
│ ├── Comparators.php
│ ├── Collectors.php
│ ├── Reorganizers.php
│ └── Predicates.php
│ ├── Iterator
│ ├── Iterator.php
│ ├── IterateResult.php
│ ├── IteratorFactory.php
│ └── CallbackIterator.php
│ ├── Stack.php
│ ├── MapEntry.php
│ ├── Queue.php
│ ├── MapFactory.php
│ ├── ArrayStrictList.php
│ ├── ListSequence.php
│ ├── Map.php
│ ├── ArrayQueue.php
│ ├── RemoveTraverseTrait.php
│ ├── ArrayStack.php
│ ├── Collection.php
│ ├── CollectionFactory.php
│ ├── HashSet.php
│ ├── ArrayList.php
│ ├── AbstractCollection.php
│ ├── ImmutableList.php
│ ├── DummyStreamDecorator.php
│ ├── HashMap.php
│ ├── Stream.php
│ └── SerialStream.php
├── tests
└── WS
│ └── Utils
│ └── Collections
│ ├── UnitConstraints
│ ├── StaticCompareCreation.php
│ ├── CollectionIsEqual.php
│ ├── CollectionIsNotEqual.php
│ ├── CollectionContainsSameElements.php
│ └── CollectionComparingConstraint.php
│ ├── HashMapTest.php
│ ├── Utils
│ ├── CollectionAwareTrait.php
│ ├── ExampleObject.php
│ ├── InvokeCounter.php
│ └── TestInteger.php
│ ├── HashSetTest.php
│ ├── ConsumersFunctionsTest.php
│ ├── ListInterfaceTestTrait.php
│ ├── Iterator
│ ├── IntGeneratorCallback.php
│ └── CallbackIteratorTest.php
│ ├── PredicateFunctionTest.php
│ ├── ObjectsFunctionsTest.php
│ ├── Functions
│ ├── StrImplodeAggregatorTest.php
│ ├── Expression
│ │ └── BoolExpressionTest.php
│ ├── ShuffleReorganizerTest.php
│ ├── RandomReorganizerTest.php
│ └── Group
│ │ └── Aggregator
│ │ ├── CountTest.php
│ │ ├── FirstTest.php
│ │ ├── AddToSetTest.php
│ │ ├── LastTest.php
│ │ ├── MaxTest.php
│ │ ├── MinTest.php
│ │ ├── SumTest.php
│ │ └── AvgTest.php
│ ├── MapFactoryTest.php
│ ├── AggregatorsFunctionsTest.php
│ ├── ImmutableStreamTest.php
│ ├── ConvertersFunctionsTest.php
│ ├── CollectionFactoryTest.php
│ ├── ImmutableListTest.php
│ ├── ArrayStrictListTest.php
│ ├── ArrayQueueTest.php
│ ├── AggregateGroupingTest.php
│ ├── ReorganizersFunctionsTest.php
│ ├── SetInterfaceTestTrait.php
│ ├── ArrayListTest.php
│ ├── ArrayStackTest.php
│ ├── ComparatorsTest.php
│ ├── ConditionsStreamTest.php
│ ├── MapInterfaceTestTrait.php
│ ├── CollectionInterfaceTestTrait.php
│ └── PredicatesFunctionsTest.php
├── phpunit.xml
├── LICENSE
├── composer.json
└── .github
└── workflows
└── php.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | vendor
3 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/HashCodeAware.php:
--------------------------------------------------------------------------------
1 |
4 | * @license MIT
5 | */
6 |
7 | namespace WS\Utils\Collections;
8 |
9 | interface Set extends Collection
10 | {
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/UnitConstraints/StaticCompareCreation.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | interface Aggregator
11 | {
12 | public function __invoke(Collection $collection);
13 | }
14 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/HashMapTest.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Count implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | return $collection->size();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/HashSetTest.php:
--------------------------------------------------------------------------------
1 | that dumps element
12 | * @return callable
13 | */
14 | public static function dump(): callable
15 | {
16 | return static function ($el) {
17 | var_dump($el);
18 | };
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/UnitConstraints/CollectionIsEqual.php:
--------------------------------------------------------------------------------
1 | equals($other);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/UnitConstraints/CollectionIsNotEqual.php:
--------------------------------------------------------------------------------
1 | equals($other);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Iterator/Iterator.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Expression\Operator;
7 |
8 | class OrOperator extends AbstractOperator
9 | {
10 |
11 | public function __invoke(bool $operand, $item): bool
12 | {
13 | if ($operand) {
14 | return true;
15 | }
16 | $checker = $this->checker;
17 | return $checker($item);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Expression/Operator/AndOperator.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Expression\Operator;
7 |
8 | class AndOperator extends AbstractOperator
9 | {
10 |
11 | public function __invoke(bool $operand, $item): bool
12 | {
13 | if (!$operand) {
14 | return false;
15 | }
16 | $checker = $this->checker;
17 | return $checker($item);
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/Last.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Last extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | if (!$last = $collection->stream()->findLast()) {
16 | return null;
17 | }
18 | return $this->getValue($last);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/First.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class First extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | if (!$first = $collection->stream()->findFirst()) {
16 | return null;
17 | }
18 |
19 | return $this->getValue($first);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/Sum.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Sum extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | $sum = 0;
16 | foreach ($collection as $element) {
17 | $sum += $this->getValue($element);
18 | }
19 | return $sum;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Expression/Operator/AbstractOperator.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Expression\Operator;
7 |
8 | abstract class AbstractOperator
9 | {
10 | protected $checker;
11 |
12 | public function __construct(callable $checker)
13 | {
14 | $this->checker = $checker;
15 | }
16 |
17 | public function getChecker(): callable
18 | {
19 | return $this->checker;
20 | }
21 |
22 | abstract public function __invoke(bool $operand, $item): bool;
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Stack.php:
--------------------------------------------------------------------------------
1 | key = $key;
16 | $this->value = $value;
17 | }
18 |
19 | /**
20 | * @return mixed
21 | */
22 | public function getKey()
23 | {
24 | return $this->key;
25 | }
26 |
27 | /**
28 | * @return mixed
29 | */
30 | public function getValue()
31 | {
32 | return $this->value;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/AddToSet.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 | use WS\Utils\Collections\HashSet;
10 |
11 | class AddToSet extends AbstractFieldAggregator implements Aggregator
12 | {
13 |
14 | public function __invoke(Collection $collection)
15 | {
16 | $set = new HashSet();
17 | foreach ($collection as $element) {
18 | $set->add($this->getValue($element));
19 | }
20 | return $set->toArray();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/AbstractFieldAggregator.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Functions\ObjectFunctions;
9 |
10 | abstract class AbstractFieldAggregator
11 | {
12 |
13 | private $fieldName;
14 |
15 | public function __construct(string $fieldName)
16 | {
17 | $this->fieldName = $fieldName;
18 | }
19 |
20 | protected function getValue($element)
21 | {
22 | return ObjectFunctions::getPropertyValue($element, $this->fieldName);
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ConsumersFunctionsTest.php:
--------------------------------------------------------------------------------
1 | stream()
23 | ->each(Consumers::dump())
24 | ;
25 | $str = ob_get_clean();
26 | $this->assertNotEmpty($str);
27 | }
28 | }
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/Max.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Max extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | $max = null;
16 | foreach ($collection as $element) {
17 | $value = $this->getValue($element);
18 | if ($max === null || $max < $value) {
19 | $max = $value;
20 | }
21 | }
22 | return $max;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Aggregator/Min.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Min extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | $min = null;
16 | foreach ($collection as $element) {
17 | $value = $this->getValue($element);
18 | if ($min === null || $min > $value) {
19 | $min = $value;
20 | }
21 | }
22 | return $min;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Queue.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group\Aggregator;
7 |
8 | use WS\Utils\Collections\Collection;
9 |
10 | class Avg extends AbstractFieldAggregator implements Aggregator
11 | {
12 |
13 | public function __invoke(Collection $collection)
14 | {
15 | $acc = null;
16 | $cnt = 0;
17 | foreach ($collection as $element) {
18 | $acc += $this->getValue($element);
19 | $cnt++;
20 | }
21 | if (!$cnt) {
22 | return null;
23 | }
24 | return $acc / $cnt;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Iterator/IterateResult.php:
--------------------------------------------------------------------------------
1 | value = $value;
16 |
17 | return $this;
18 | }
19 |
20 | public function setAsRunOut(): self
21 | {
22 | $this->isRunOut = true;
23 |
24 | return $this;
25 | }
26 |
27 | public function isRunOut(): bool
28 | {
29 | return $this->isRunOut;
30 | }
31 |
32 | public function getValue()
33 | {
34 | return $this->value;
35 | }
36 | }
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 | ./tests
13 |
14 |
15 |
16 |
17 | ./src
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ListInterfaceTestTrait.php:
--------------------------------------------------------------------------------
1 | createInstance(27, 'string', -11, 50);
18 | $iterator = $collection->getIterator();
19 |
20 | $this->assertInstanceOf(Traversable::class, $iterator);
21 | $this->assertEquals(27, $iterator->current());
22 | $iterator->next();
23 | $this->assertEquals('string', $iterator->current());
24 | $this->assertEquals(1, $iterator->key());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Utils/ExampleObject.php:
--------------------------------------------------------------------------------
1 | name = $name;
17 |
18 | return $this;
19 | }
20 |
21 | public function getName(): string
22 | {
23 | return $this->name;
24 | }
25 |
26 | public function field(): string
27 | {
28 | return $this->field;
29 | }
30 |
31 | public function setField(string $field): self
32 | {
33 | $this->field = $field;
34 |
35 | return $this;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Iterator/IntGeneratorCallback.php:
--------------------------------------------------------------------------------
1 | rest = $count;
23 | }
24 |
25 | public function __invoke()
26 | {
27 | $iterateResult = new IterateResult();
28 | if ($this->rest-- === 0) {
29 | $iterateResult->setAsRunOut();
30 |
31 | return $iterateResult;
32 | }
33 |
34 | return $iterateResult->setValue($this->counter++);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Utils/InvokeCounter.php:
--------------------------------------------------------------------------------
1 | f = $f;
19 | }
20 |
21 | public function __invoke()
22 | {
23 | $this->calls[] = func_get_args();
24 | if ($this->f !== null) {
25 | return call_user_func_array($this->f, func_get_args());
26 | }
27 | return null;
28 | }
29 |
30 | public function countOfInvokes(): int
31 | {
32 | return count($this->calls);
33 | }
34 |
35 | public function calls(): array
36 | {
37 | return $this->calls;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Iterator/IteratorFactory.php:
--------------------------------------------------------------------------------
1 | setAsRunOut();
16 | }
17 |
18 | return (new IterateResult())->setValue($current++);
19 | });
20 | }
21 |
22 | public static function reverseSequence($length): Iterator
23 | {
24 | $current = $length - 1;
25 | return new CallbackIterator(static function () use (& $current) {
26 | if ($current === -1) {
27 | return (new IterateResult())->setAsRunOut();
28 | }
29 |
30 | return (new IterateResult())->setValue($current--);
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Utils/TestInteger.php:
--------------------------------------------------------------------------------
1 | value = $value;
29 | }
30 |
31 | /**
32 | * @return string
33 | */
34 | public function getHashCode(): string
35 | {
36 | $hash = 7;
37 | $hash = 31 * $hash + $this->value;
38 | return $hash.'';
39 | }
40 |
41 | /**
42 | * @return int
43 | */
44 | public function getValue(): int
45 | {
46 | return $this->value;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/PredicateFunctionTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($f($i));
26 | }
27 | }
28 |
29 | /**
30 | * @test
31 | */
32 | public function notResistanceIntegratedChecking(): void
33 | {
34 | $actualSize = CollectionFactory::generate(10)
35 | ->stream()
36 | ->filter(Predicates::notResistance())
37 | ->getCollection()
38 | ->size();
39 |
40 | $this->assertEquals(10, $actualSize);
41 | }
42 | }
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ObjectsFunctionsTest.php:
--------------------------------------------------------------------------------
1 | property = 'value';
22 | $o->setName('nameValue');
23 | $o->setField('fieldValue');
24 |
25 | $this->assertEquals('value', ObjectFunctions::getPropertyValue($o, 'property'));
26 | $this->assertEquals('nameValue', ObjectFunctions::getPropertyValue($o, 'name'));
27 | $this->assertEquals('fieldValue', ObjectFunctions::getPropertyValue($o, 'field'));
28 |
29 | $this->expectException(RuntimeException::class);
30 | ObjectFunctions::getPropertyValue($o, 'undefinedProperty');
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 WorkSolutions
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Converters.php:
--------------------------------------------------------------------------------
1 |
15 | * @param string $name
16 | * @return Closure
17 | */
18 | public static function toPropertyValue(string $name): Closure
19 | {
20 | return static function ($obj) use ($name) {
21 | return ObjectFunctions::getPropertyValue($obj, $name);
22 | };
23 | }
24 |
25 | /**
26 | * Returns function that returns assoc array ['fieldName1' => 'value', 'fieldName2' => 'value2']
27 | * @param array $names
28 | * @return Closure
29 | */
30 | public static function toProperties(array $names): Closure
31 | {
32 | return static function ($obj) use ($names) {
33 | $res = [];
34 | foreach ($names as $name) {
35 | $res[$name] = ObjectFunctions::getPropertyValue($obj, $name);
36 | }
37 |
38 | return $res;
39 | };
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/MapFactory.php:
--------------------------------------------------------------------------------
1 | $value) {
28 | $map->put($key, $value);
29 | }
30 |
31 | return $map;
32 | }
33 |
34 | /**
35 | * Creates empty map object
36 | * @return Map
37 | */
38 | public static function emptyObject(): Map
39 | {
40 | return self::newObject();
41 | }
42 |
43 | /**
44 | * Creates empty Map object
45 | * @return Map
46 | */
47 | private static function newObject(): Map
48 | {
49 | return new HashMap();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/StrImplodeAggregatorTest.php:
--------------------------------------------------------------------------------
1 | assertIsCallable($f);
23 | }
24 |
25 | public function implodeCases(): array
26 | {
27 | return [
28 | [[1, 2, 3], ', ', '1, 2, 3'],
29 | [[], '1111', ''],
30 | [[1], '|', '1'],
31 | [null, '', '']
32 | ];
33 | }
34 |
35 | /**
36 | * @dataProvider implodeCases
37 | * @test
38 | * @param $data
39 | * @param $glue
40 | * @param $result
41 | */
42 | public function imploding($data, $glue, $result): void
43 | {
44 | $f = Collectors::concat($glue);
45 | $this->assertSame($f($this->toCollection($data)), $result);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Iterator/CallbackIteratorTest.php:
--------------------------------------------------------------------------------
1 | next();
20 | $one = $i->next();
21 | $this->assertEquals(1, $one);
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function hasNextChecking(): void
28 | {
29 | $i = new CallbackIterator(new IntGeneratorCallback(2));
30 | $i->next();
31 | $i->next();
32 |
33 | $this->assertFalse($i->hasNext());
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function getOverflowException(): void
40 | {
41 | $i = new CallbackIterator(new IntGeneratorCallback(2));
42 | $i->next();
43 | $i->next();
44 |
45 | $this->expectException(RuntimeException::class);
46 |
47 | $i->next();
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "worksolutions/php-collections",
3 | "description": "Collections library for php language",
4 | "minimum-stability": "dev",
5 | "license": "MIT",
6 | "version": "1.0.10",
7 | "authors": [
8 | {
9 | "name": "Maxim Sokolovsky",
10 | "email": "sokolovsky@worksolutions.ru"
11 | },
12 | {
13 | "name": "Anton Lytkin",
14 | "email": "a.lytkin@worksolutions.ru"
15 | },
16 | {
17 | "name": "Igor Pomiluyko",
18 | "email": "pomiluyko@worksolutions.ru"
19 | }
20 | ],
21 | "require": {
22 | "php": ">=7.1",
23 | "ext-json": "*"
24 | },
25 | "require-dev": {
26 | "phpunit/phpunit": "^7.5"
27 | },
28 | "suggest": {
29 | "phpbench/phpbench": "Uses only for benchmark purposes"
30 | },
31 | "autoload": {
32 | "psr-4": {"WS\\Utils\\Collections\\": "src/WS/Utils/Collections"}
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "WS\\Utils\\Collections\\": "tests/WS/Utils/Collections",
37 | "Benchmarks\\": "benchmarks"
38 | }
39 | },
40 | "scripts": {
41 | "test": [
42 | "phpunit"
43 | ],
44 | "test-with-coverage": [
45 | "phpunit --coverage-clover coverage.xml"
46 | ]
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Iterator/CallbackIterator.php:
--------------------------------------------------------------------------------
1 | generator = $f;
25 | }
26 |
27 | public function next()
28 | {
29 | if (!$this->hasNext()) {
30 | throw new RuntimeException('Iterated element is absent');
31 | }
32 | $value = $this->currentIterateResult->getValue();
33 | $this->currentIterateResult = null;
34 | return $value;
35 | }
36 |
37 | public function hasNext(): bool
38 | {
39 | if ($this->currentIterateResult === null) {
40 | $f = $this->generator;
41 | $this->currentIterateResult = $f();
42 | }
43 | return $this->currentIterateResult->isRunOut() === false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ArrayStrictList.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections;
7 |
8 | use InvalidArgumentException;
9 |
10 | class ArrayStrictList extends ArrayList
11 | {
12 | protected function afterElementsSet(): void
13 | {
14 | foreach ($this->elements as $element) {
15 | $this->afterElementAdd($element);
16 | }
17 | parent::afterElementsSet();
18 | }
19 |
20 | protected function afterElementAdd($element): void
21 | {
22 | $firstElement = $this->elements[0];
23 | if (null === $firstElement && !count($this->elements)) {
24 | return;
25 | }
26 | if (is_object($firstElement)) {
27 | if (!is_object($element) || (get_class($firstElement) !== get_class($element))) {
28 | throw new InvalidArgumentException('Collection must contain elements with identical types');
29 | }
30 | return;
31 | }
32 | if (is_object($element) || (gettype($firstElement) !== gettype($element))) {
33 | throw new InvalidArgumentException('Collection must contain elements with identical types');
34 | }
35 | parent::afterElementAdd($element);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/ObjectFunctions.php:
--------------------------------------------------------------------------------
1 | isPublic()) {
27 | return $object->{$fieldName};
28 | }
29 |
30 | if (isset($object->{$fieldName})) {
31 | return $object->{$fieldName};
32 | }
33 |
34 | if (method_exists($object, $fieldName)) {
35 | return $object->{$fieldName}();
36 | }
37 |
38 | if (method_exists($object, 'get'.$fieldName)) {
39 | return $object->{'get'.$fieldName}();
40 | }
41 | throw new RuntimeException("Field $fieldName is not exist for object ".var_export($object, true));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/MapFactoryTest.php:
--------------------------------------------------------------------------------
1 | size());
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function mapFromIterableIsCreated(): void
28 | {
29 | $arrayIterator = new ArrayIterator(['a' => 'A', 'b' => 'B']);
30 | $map = MapFactory::fromIterable($arrayIterator);
31 | self::assertEquals(2, $map->size());
32 | self::assertEquals(['a', 'b'], $map->keys()->toArray());
33 | self::assertEquals(['A', 'B'], $map->values()->toArray());
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function mapFromAssocArrayCreated(): void
40 | {
41 | $assoc = ['a' => 'A', 'b' => 'B'];
42 | $map = MapFactory::assoc($assoc);
43 | self::assertEquals(2, $map->size());
44 | self::assertEquals(['a', 'b'], $map->keys()->toArray());
45 | self::assertEquals(['A', 'B'], $map->values()->toArray());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ListSequence.php:
--------------------------------------------------------------------------------
1 |
54 | */
55 | public function stream(): Stream;
56 | }
57 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/AggregatorsFunctionsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals('1, 2, 3', $f(self::toCollection(1, 2, 3)));
22 | }
23 |
24 | /**
25 | * @test
26 | */
27 | public function stringImplodingIntegration(): void
28 | {
29 | $res = self::toCollection(1, 2, 3)
30 | ->stream()
31 | ->collect(Collectors::concat(', '));
32 |
33 | $this->assertEquals('1, 2, 3', $res);
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function averageCalculating(): void
40 | {
41 | $f = Collectors::average();
42 | $this->assertEquals(2, $f(self::toCollection(1, 2, 3)));
43 | }
44 |
45 | /**
46 | * @test
47 | */
48 | public function averageCalculatingIntegration(): void
49 | {
50 | $res = self::toCollection(1, 2, 3)
51 | ->stream()
52 | ->collect(Collectors::average());
53 |
54 | $this->assertEquals(2, $res);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ImmutableStreamTest.php:
--------------------------------------------------------------------------------
1 | stream();
23 |
24 | $collection = $stream->getCollection();
25 |
26 | $stream
27 | ->reorganize(function (Collection $c) {
28 | $c->clear();
29 | return $this->toCollection();
30 | });
31 |
32 | self::assertThat($collection, CollectionIsEqual::to([1, 2, 3]));
33 | }
34 |
35 | /**
36 | * @test
37 | */
38 | public function shouldBeImmutableInCollectWithCollection(): void
39 | {
40 | $stream = self::toCollection(1, 2, 3)
41 | ->stream();
42 |
43 | $collection = $stream->getCollection();
44 | $stream
45 | ->collect(static function (Collection $c) {
46 | $c->clear();
47 | return 1;
48 | });
49 |
50 | self::assertThat($collection, CollectionIsEqual::to([1, 2, 3]));
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ArrayQueue.php:
--------------------------------------------------------------------------------
1 | add($element);
20 | }
21 |
22 | public function poll()
23 | {
24 | if ($this->isEmpty()) {
25 | throw new RuntimeException('Queue is empty');
26 | }
27 |
28 | $this->pointer--;
29 | return array_shift($this->elements);
30 | }
31 |
32 | public function peek()
33 | {
34 | if ($this->isEmpty()) {
35 | throw new RuntimeException('Queue is empty');
36 | }
37 |
38 | return $this->elements[0];
39 | }
40 |
41 | public function stream(): Stream
42 | {
43 | return new SerialStream($this);
44 | }
45 |
46 | public function getIndexIterator(): Iterator
47 | {
48 | return IteratorFactory::directSequence($this->size());
49 | }
50 |
51 | protected function afterElementAdd($element): void
52 | {
53 | }
54 |
55 | protected function afterElementsSet(): void
56 | {
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Comparators.php:
--------------------------------------------------------------------------------
1 | for scalar value compares such as int, bool, float, string
14 | * @return callable
15 | */
16 | public static function scalarComparator(): Closure
17 | {
18 | return static function ($a, $b) {
19 | return $a <=> $b;
20 | };
21 | }
22 |
23 | /**
24 | * Returns for scalar object property value compares such as int, bool, float, string
25 | * @param string $property
26 | * @return callable
27 | */
28 | public static function objectPropertyComparator(string $property): Closure
29 | {
30 | return static function ($a, $b) use ($property) {
31 | return ObjectFunctions::getPropertyValue($a, $property) <=> ObjectFunctions::getPropertyValue($b, $property);
32 | };
33 | }
34 |
35 | /**
36 | * Returns for scalar value compares such as int, bool, float, string
37 | * @param callable $f
38 | * @return Closure
39 | */
40 | public static function callbackComparator(callable $f): Closure
41 | {
42 | return static function ($a, $b) use ($f) {
43 | return $f($a) <=> $f($b);
44 | };
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Expression/BoolExpressionTest.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Expression;
7 |
8 | use PHPUnit\Framework\TestCase;
9 | use WS\Utils\Collections\CollectionFactory;
10 |
11 | class BoolExpressionTest extends TestCase
12 | {
13 |
14 | public function cases()
15 | {
16 | return [
17 | [
18 | [1, 2, 3],
19 | BoolExpression::with(function ($element) {
20 | return is_int($element);
21 | })
22 | ->or(function ($element) {
23 | return $element === 77;
24 | })
25 | ->and(function ($element) {
26 | return in_array($element, [1, 3]);
27 | }),
28 | [1, 3],
29 | ],
30 | ];
31 | }
32 |
33 | /**
34 | * @test
35 | * @dataProvider cases
36 | * @param array $sequence
37 | * @param BoolExpression $expression
38 | * @param array $expected
39 | */
40 | public function filterByExpression(array $sequence, BoolExpression $expression, array $expected)
41 | {
42 |
43 | $result = CollectionFactory::from($sequence)
44 | ->stream()
45 | ->filter($expression)
46 | ->getCollection()
47 | ->toArray();
48 |
49 | self::assertEquals($expected, $result);
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/.github/workflows/php.yml:
--------------------------------------------------------------------------------
1 | name: Unit tests
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | - dev
8 | paths:
9 | - '!*.md'
10 | pull_request:
11 | branches: [ master ]
12 |
13 | jobs:
14 | build:
15 | runs-on: ubuntu-latest
16 | strategy:
17 | matrix:
18 | php-versions: ['7.1', '7.2', '7.3', '7.4']
19 | name: Testing with PHP ${{ matrix.php-versions }}
20 |
21 | steps:
22 | - uses: actions/checkout@v2
23 |
24 | - name: Setup PHP
25 | uses: shivammathur/setup-php@v2
26 | with:
27 | php-version: ${{ matrix.php-versions }}
28 |
29 | - name: Get Composer Cache Directory
30 | id: composer-cache
31 | run: |
32 | echo "::set-output name=dir::$(composer config cache-files-dir)"
33 |
34 | - name: Cache Composer Downloads
35 | uses: actions/cache@v1.2.0
36 | with:
37 | path: ${{ steps.composer-cache.outputs.dir }}
38 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
39 | restore-keys: |
40 | ${{ runner.os }}-composer-
41 |
42 | - name: Cache PHP dependencies
43 | uses: actions/cache@v1.2.0
44 | with:
45 | path: vendor
46 | key: ${{ runner.OS }}-build-${{ hashFiles('**/composer.lock') }}
47 |
48 | - name: Validate composer.json and composer.lock
49 | run: composer validate
50 |
51 | - name: Install dependencies
52 | run: composer install --dev --no-progress
53 |
54 | - name: Run test suite
55 | run: composer run-script test
56 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/UnitConstraints/CollectionContainsSameElements.php:
--------------------------------------------------------------------------------
1 | expectedCollection = $this->normalize($expectedCollection);
20 | }
21 |
22 | private function normalize($collection)
23 | {
24 | if (is_array($collection)) {
25 | return CollectionFactory::from($collection);
26 | }
27 |
28 | return $collection;
29 | }
30 |
31 | protected function matches($other): bool
32 | {
33 | $this->normalize($other);
34 | if (!$other instanceof Collection) {
35 | throw new RuntimeException('Value of comparision need to be a Collection');
36 | }
37 |
38 | return array_diff($other->toArray(), $this->expectedCollection->toArray()) === [];
39 | }
40 |
41 | public function toString(): string
42 | {
43 | return sprintf(
44 | 'is accepted by %s',
45 | $this->exporter()->export($this->expectedCollection)
46 | );
47 | }
48 |
49 | public static function with($expectedValue): CollectionContainsSameElements
50 | {
51 | return new self($expectedValue);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/RemoveTraverseTrait.php:
--------------------------------------------------------------------------------
1 | isEmpty()) {
15 | return false;
16 | }
17 | $fMatch = static function ($tested) use ($element): bool {
18 | return $tested === $element;
19 | };
20 | if ($element instanceof HashCodeAware) {
21 | $fMatch = static function ($tested) use ($element): bool {
22 | if ($tested instanceof HashCodeAware) {
23 | return $tested->getHashCode() === $element->getHashCode();
24 | }
25 | return $tested === $element;
26 | };
27 | }
28 | $indexIterator = $this->getIndexIterator();
29 | $elements = $this->getElements();
30 | while ($indexIterator->hasNext()) {
31 | $index = $indexIterator->next();
32 | if ($fMatch($elements[$index])) {
33 | unset($elements[$index]);
34 | $this->setElements(array_values($elements));
35 | return true;
36 | }
37 | }
38 | return false;
39 | }
40 |
41 | abstract public function getIndexIterator(): Iterator;
42 |
43 | abstract public function isEmpty(): bool;
44 |
45 | abstract protected function setElements(array $elements): void;
46 |
47 | abstract protected function getElements(): array;
48 | }
49 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Expression/BoolExpression.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Expression;
7 |
8 | use WS\Utils\Collections\Functions\Expression\Operator\AbstractOperator;
9 | use WS\Utils\Collections\Functions\Expression\Operator\AndOperator;
10 | use WS\Utils\Collections\Functions\Expression\Operator\OrOperator;
11 |
12 | class BoolExpression
13 | {
14 |
15 | /** @var AbstractOperator[] */
16 | private $operators = [];
17 |
18 | public function __construct(callable $checker)
19 | {
20 | $this->operators[] = new AndOperator($checker);
21 | }
22 |
23 | public static function with(callable $checker): self
24 | {
25 | return new self($checker);
26 | }
27 |
28 | public function __invoke($item): bool
29 | {
30 | $operand = null;
31 | foreach ($this->operators as $operator) {
32 | if ($operand === null) {
33 | if (!$operand = $operator->getChecker()($item)) {
34 | return false;
35 | }
36 | continue;
37 | }
38 | if (!$operand = $operator($operand, $item)) {
39 | return false;
40 | }
41 | }
42 | return true;
43 | }
44 |
45 | public function and(callable $checker): self
46 | {
47 | $this->operators[] = new AndOperator($checker);
48 | return $this;
49 | }
50 |
51 | public function or(callable $checker): self
52 | {
53 | $this->operators[] = new OrOperator($checker);
54 | return $this;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/UnitConstraints/CollectionComparingConstraint.php:
--------------------------------------------------------------------------------
1 | expectedCollection = $this->normalize($expectedCollection);
21 | }
22 |
23 | private function normalize($collection)
24 | {
25 | if (is_array($collection)) {
26 | return CollectionFactory::from($collection);
27 | }
28 |
29 | return $collection;
30 | }
31 |
32 | protected function matches($other): bool
33 | {
34 | $this->normalize($other);
35 | if (!$other instanceof Collection) {
36 | throw new RuntimeException('Value of comparision need to be a Collection');
37 | }
38 |
39 | return $this->comparingResult($this->expectedCollection, $other);
40 | }
41 |
42 | public function toString(): string
43 | {
44 | return sprintf(
45 | 'is accepted by %s',
46 | $this->exporter->export($this->expectedCollection)
47 | );
48 | }
49 |
50 | public static function to($expectedValue): CollectionComparingConstraint
51 | {
52 | return new static($expectedValue);
53 | }
54 |
55 | abstract public function comparingResult(Collection $expectedCollection, Collection $other): bool;
56 | }
57 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ConvertersFunctionsTest.php:
--------------------------------------------------------------------------------
1 | setName('first'),
25 | (new ExampleObject())->setName('second'),
26 | (new ExampleObject())->setName('third')
27 | )
28 | ->stream()
29 | ->map(Converters::toPropertyValue('name'))
30 | ->getCollection()
31 | ;
32 |
33 | $this->assertThat($collection, CollectionIsEqual::to(['first', 'second', 'third']));
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function propertyValueAssocConverting(): void
40 | {
41 | $array = self::toCollection(
42 | (new ExampleObject())->setName('first')->setField('f1'),
43 | (new ExampleObject())->setName('second')->setField('f2'),
44 | (new ExampleObject())->setName('third')->setField('f3')
45 | )
46 | ->stream()
47 | ->map(Converters::toProperties(['name', 'field']))
48 | ->getCollection()
49 | ->toArray();
50 |
51 | $this->assertSame($array, [
52 | ['name' => 'first', 'field' => 'f1'],
53 | ['name' => 'second', 'field' => 'f2'],
54 | ['name' => 'third', 'field' => 'f3']
55 | ]);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ArrayStack.php:
--------------------------------------------------------------------------------
1 | add($element);
28 | }
29 |
30 | /**
31 | * Gets element from the top of stack
32 | *
33 | * @return mixed
34 | */
35 | public function pop()
36 | {
37 | if ($this->isEmpty()) {
38 | throw new RuntimeException('Stack is empty');
39 | }
40 |
41 | return array_pop($this->elements);
42 | }
43 |
44 | /**
45 | * Retrieves, but does not remove
46 | *
47 | * @return mixed
48 | */
49 | public function peek()
50 | {
51 | if ($this->isEmpty()) {
52 | throw new RuntimeException('Stack is empty');
53 | }
54 |
55 | return $this->elements[count($this->elements) - 1];
56 | }
57 |
58 | public function stream(): Stream
59 | {
60 | return new SerialStream($this);
61 | }
62 |
63 | public function toArray(): array
64 | {
65 | return array_reverse($this->elements);
66 | }
67 |
68 | public function getIndexIterator(): Iterator
69 | {
70 | return IteratorFactory::reverseSequence($this->size());
71 | }
72 |
73 | protected function afterElementAdd($element): void
74 | {
75 | }
76 |
77 | protected function afterElementsSet(): void
78 | {
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Collection.php:
--------------------------------------------------------------------------------
1 | compare($list, function ($list) {
32 | /** @var Collection $shuffledCollection */
33 | $shuffledCollection = Reorganizers::shuffle()($this->toCollection($list));
34 |
35 | return $shuffledCollection->toArray();
36 | });
37 | }
38 |
39 | /**
40 | * @dataProvider cases
41 | * @test
42 | */
43 | public function integration($list)
44 | {
45 |
46 | $this->compare($list, function ($list) {
47 | return $this->toCollection($list)
48 | ->stream()
49 | ->reorganize(Reorganizers::shuffle())
50 | ->getCollection()
51 | ->toArray();
52 | });
53 | }
54 |
55 | private function compare($list, callable $shuffle): void
56 | {
57 | $shuffled = [];
58 | for ($i = 0; $i < 10; $i++) {
59 | $shuffled = $shuffle($list);
60 | if ($shuffled !== $list) {
61 | break;
62 | }
63 | if (count($list) === 1 && count($shuffled) === 1) {
64 | $this->assertTrue(true);
65 | return;
66 | }
67 | }
68 |
69 | $this->assertNotEquals($list, $shuffled);
70 | $this->assertCount(count($list), $shuffled);
71 |
72 | foreach ($list as $item) {
73 | $found = array_search($item, $shuffled, true);
74 | if ($found === false) {
75 | $this->fail("Item {$item} is not found in shuffled array");
76 | }
77 |
78 | $shuffled = array_diff($shuffled, [$item]);
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/RandomReorganizerTest.php:
--------------------------------------------------------------------------------
1 | toCollection($elements))->toArray();
38 |
39 | $this->analyze($elements, $randomized, $count);
40 | }
41 |
42 | /**
43 | * @test
44 | */
45 | public function failedReorganize(): void
46 | {
47 | $this->expectException(RuntimeException::class);
48 | $this->toCollection([1, 2, 3])
49 | ->stream()
50 | ->reorganize(static function () {
51 | return [1, 2, 3];
52 | })
53 | ->getCollection()
54 | ->toArray()
55 | ;
56 | }
57 |
58 | /**
59 | * @dataProvider cases
60 | * @test
61 | * @param array $elements
62 | * @param int $count
63 | */
64 | public function integrate(array $elements, int $count): void
65 | {
66 | $randomized = $this->toCollection($elements)
67 | ->stream()
68 | ->reorganize(Reorganizers::random($count))
69 | ->getCollection()
70 | ->toArray()
71 | ;
72 |
73 | $this->analyze($elements, $randomized, $count);
74 | }
75 |
76 | private function analyze(array $input, array $randomized, int $count): void
77 | {
78 | $this->assertTrue(count($input) >= count($randomized));
79 | $this->assertTrue(count($randomized) <= $count);
80 | if (count($input) >= $count) {
81 | $this->assertCount($count, $randomized);
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/CollectionFactory.php:
--------------------------------------------------------------------------------
1 | for $times
16 | * @param int $times
17 | * @param callable|null $generator
18 | * @return Collection
19 | */
20 | public static function generate(int $times, ?callable $generator = null): Collection
21 | {
22 | if ($times < 0) {
23 | throw new RuntimeException('The count of values ($times) must be a positive value');
24 | }
25 | $generator = $generator ?? static function (int $index) {
26 | return $index;
27 | };
28 |
29 | $collection = self::toCollection();
30 | for ($i = 0; $i < $times; $i++) {
31 | $collection->add($generator($i));
32 | }
33 |
34 | return $collection;
35 | }
36 |
37 | /**
38 | * Generate collection of int numbers between $from and $to. If $to arg is absent $from - is count of numbers
39 | * @param int $from
40 | * @param int $to
41 | * @return Collection
42 | */
43 | public static function numbers(int $from, ?int $to = null): Collection
44 | {
45 | if ($to === null) {
46 | $to = $from - 1;
47 | $from = 0;
48 | }
49 |
50 | if ($from > $to) {
51 | throw new RuntimeException('FROM need to be less than TO');
52 | }
53 | $list = new ArrayList();
54 | for ($i = $from; $i <= $to; $i++) {
55 | $list->add($i);
56 | }
57 |
58 | return $list;
59 | }
60 |
61 | public static function from(array $values): Collection
62 | {
63 | return new ArrayList($values);
64 | }
65 |
66 | public static function fromStrict(array $values): Collection
67 | {
68 | return new ArrayStrictList($values);
69 | }
70 |
71 | public static function fromIterable(iterable $iterable): Collection
72 | {
73 | $list = ArrayList::of();
74 | foreach ($iterable as $item) {
75 | $list->add($item);
76 | }
77 |
78 | return $list;
79 | }
80 |
81 | public static function empty(): Collection
82 | {
83 | return ArrayList::of();
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/CollectionFactoryTest.php:
--------------------------------------------------------------------------------
1 | size();
23 | $this->assertEquals(10, $sizeOfTen);
24 | }
25 |
26 | /**
27 | * @test
28 | */
29 | public function failSequenceGenerating(): void
30 | {
31 | $this->expectException(RuntimeException::class);
32 | CollectionFactory::generate(-10);
33 | }
34 |
35 | /**
36 | * @test
37 | */
38 | public function sequenceWithGeneratorGenerating(): void
39 | {
40 | $i = 0;
41 | $sizeOfTen = CollectionFactory::generate(10, static function () use (& $i) {
42 | return $i++;
43 | })
44 | ->size();
45 | $this->assertEquals(10, $sizeOfTen);
46 | }
47 |
48 | /**
49 | * @test
50 | */
51 | public function numbersFactoryTest(): void
52 | {
53 | $sizeOfTwenty = CollectionFactory::numbers(-10, 10)->size();
54 | $this->assertEquals(21, $sizeOfTwenty);
55 |
56 | $sizeOfTen = CollectionFactory::numbers(10)->size();
57 | $this->assertEquals(10, $sizeOfTen);
58 |
59 | $this->expectException(RuntimeException::class);
60 | CollectionFactory::numbers(-10);
61 | }
62 |
63 | /**
64 | * @test
65 | */
66 | public function creatingFromArray(): void
67 | {
68 | $collection = CollectionFactory::from([1, 2, 3]);
69 |
70 | $this->assertEquals(3, $collection->size());
71 | $this->assertThat($collection, CollectionIsEqual::to([1, 2, 3]));
72 | }
73 |
74 | /**
75 | * @test
76 | */
77 | public function creatingFromStrictArray(): void
78 | {
79 | $collection = CollectionFactory::fromStrict([4, 5, 6]);
80 |
81 | $this->assertEquals(3, $collection->size());
82 | $this->assertThat($collection, CollectionIsEqual::to([4, 5, 6]));
83 | }
84 |
85 | /**
86 | * @test
87 | */
88 | public function fromIteratorCollectionGenerating(): void
89 | {
90 | $collection = CollectionFactory::fromIterable(new ArrayIterator([1, 2, 3]));
91 | $this->assertThat($collection, CollectionIsEqual::to([1, 2, 3]));
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/CountTest.php:
--------------------------------------------------------------------------------
1 | 'asdf'],
19 | [],
20 | [],
21 | ['one' => 999],
22 | ['one' => false],
23 | ['one' => true],
24 | ],
25 | 6
26 | ],
27 | [
28 | [
29 | ['count' => 1],
30 | ['count' => 2],
31 | ['count' => 3],
32 | ],
33 | 3
34 | ],
35 | [
36 | [
37 | new class () {
38 | public $test = 1;
39 | },
40 | new class () {
41 | public $test = 1;
42 | },
43 | new class () {
44 | public $test = 1;
45 | },
46 | new class () {
47 | public $test = 15;
48 | },
49 | ],
50 | 4
51 | ],
52 | [
53 | [
54 | new class () {
55 | public function getSum()
56 | {
57 | return 10;
58 | }
59 | },
60 | new class () {
61 | public function getSum()
62 | {
63 | return 10;
64 | }
65 | },
66 | new class () {
67 | public function getSum()
68 | {
69 | return 20;
70 | }
71 | },
72 | ],
73 | 3
74 | ],
75 | ];
76 | }
77 |
78 | /**
79 | * @dataProvider cases
80 | * @test
81 | * @param $collection
82 | * @param $expected
83 | */
84 | public function callSuccess($collection, $expected)
85 | {
86 | $aggregator = new Count();
87 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ImmutableListTest.php:
--------------------------------------------------------------------------------
1 | createInstance();
47 |
48 | $this->expectException(RuntimeException::class);
49 | call_user_func_array([$list, $method], $args);
50 | }
51 |
52 | /**
53 | * @test
54 | */
55 | public function gettingListInfo(): void
56 | {
57 | $list = $this->createInstance(1, 2, 3);
58 |
59 | $this->assertEquals(2, $list->get(1));
60 | $this->assertTrue($list->contains(3));
61 | $this->assertEquals(3, $list->size());
62 | $this->assertEquals(2, $list->indexOf(3));
63 | $this->assertEquals(0, $list->lastIndexOf(1));
64 | $this->assertFalse($list->isEmpty());
65 | $this->assertTrue($list->equals(ArrayList::of(1, 2, 3)));
66 | $this->assertEquals([1, 2, 3], $list->toArray());
67 | $this->assertThat($list->copy(), CollectionIsEqual::to($list));
68 | /** @noinspection UnnecessaryAssertionInspection */
69 | $this->assertInstanceOf(Stream::class, $list->stream());
70 | /** @noinspection PhpUnhandledExceptionInspection */
71 | $this->assertInstanceOf(Traversable::class, $list->getIterator());
72 | }
73 |
74 | /**
75 | * @test
76 | */
77 | public function creating(): void
78 | {
79 | $instance = ImmutableList::fromCollection($this->createInstance(1, 2));
80 | $this->assertEquals(2, $instance->size());
81 |
82 | $instance = ImmutableList::of(1, 2);
83 | $this->assertEquals(2, $instance->size());
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/HashSet.php:
--------------------------------------------------------------------------------
1 | internalMap = new HashMap();
21 | if ($elements !== null) {
22 | foreach ($elements as $element) {
23 | $this->add($element);
24 | }
25 | }
26 | }
27 |
28 | public function add($element): bool
29 | {
30 | return $this->internalMap->put($element, null);
31 | }
32 |
33 | public function stream(): Stream
34 | {
35 | return new SerialStream($this);
36 | }
37 |
38 | public function merge(Collection $collection): bool
39 | {
40 | foreach ($collection as $item) {
41 | $this->add($item);
42 | }
43 | return true;
44 | }
45 |
46 | public function clear(): void
47 | {
48 | $this->internalMap = new HashMap();
49 | }
50 |
51 | public function remove($element): bool
52 | {
53 | return $this->internalMap->remove($element);
54 | }
55 |
56 | public function contains($element): bool
57 | {
58 | return $this->internalMap->containsKey($element);
59 | }
60 |
61 | public function equals(Collection $collection): bool
62 | {
63 | if ($this->size() !== $collection->size()) {
64 | return false;
65 | }
66 | foreach ($collection as $item) {
67 | if (!$this->contains($item)) {
68 | return false;
69 | }
70 | }
71 | return true;
72 | }
73 |
74 | public function size(): int
75 | {
76 | return $this->internalMap->size();
77 | }
78 |
79 | public function isEmpty(): bool
80 | {
81 | return $this->size() === 0;
82 | }
83 |
84 | public function toArray(): array
85 | {
86 | return $this->internalMap->keys()->toArray();
87 | }
88 |
89 | public function copy(): Collection
90 | {
91 | return new static($this->toArray());
92 | }
93 |
94 | public function getIterator()
95 | {
96 | return new ArrayIterator($this->toArray());
97 | }
98 |
99 | public function addAll(iterable $elements): bool
100 | {
101 | $res = true;
102 | foreach ($elements as $element) {
103 | !$this->add($element) && $res = false;
104 | }
105 | return $res;
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/FirstTest.php:
--------------------------------------------------------------------------------
1 | 'asdf'],
19 | [],
20 | [],
21 | ['one' => 999],
22 | ['one' => false],
23 | ['one' => true],
24 | ],
25 | 'asdf',
26 | ],
27 | [
28 | 'count',
29 | [
30 | [],
31 | ['count' => 2],
32 | ['count' => 3],
33 | ],
34 | null
35 | ],
36 | [
37 | 'test',
38 | [
39 | new class () {
40 | public $test = 1;
41 | },
42 | new class () {
43 | public $test = 1;
44 | },
45 | new class () {
46 | public $test = 1;
47 | },
48 | new class () {
49 | public $test = 15;
50 | },
51 | ],
52 | 1
53 | ],
54 | [
55 | 'sum',
56 | [
57 | new class () {
58 | public function getSum()
59 | {
60 | return 10;
61 | }
62 | },
63 | new class () {
64 | public function getSum()
65 | {
66 | return 10;
67 | }
68 | },
69 | new class () {
70 | public function getSum()
71 | {
72 | return 20;
73 | }
74 | },
75 | ],
76 | 10
77 | ],
78 | ];
79 | }
80 |
81 | /**
82 | * @dataProvider cases
83 | * @test
84 | * @param $column
85 | * @param $collection
86 | * @param $expected
87 | */
88 | public function callSuccess($column, $collection, $expected)
89 | {
90 | $aggregator = new First($column);
91 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/AddToSetTest.php:
--------------------------------------------------------------------------------
1 | 'asdf'],
20 | [],
21 | [],
22 | ['one' => 999],
23 | ['one' => false],
24 | ['one' => true],
25 | ],
26 | ['asdf', null, 999, false, true]
27 | ],
28 | [
29 | 'count',
30 | [
31 | ['count' => 1],
32 | ['count' => 2],
33 | ['count' => 3],
34 | ],
35 | [1, 2, 3]
36 | ],
37 | [
38 | 'test',
39 | [
40 | new class () {
41 | public $test = 1;
42 | },
43 | new class () {
44 | public $test = 1;
45 | },
46 | new class () {
47 | public $test = 1;
48 | },
49 | new class () {
50 | public $test = 15;
51 | },
52 | ],
53 | [1, 15]
54 | ],
55 | [
56 | 'sum',
57 | [
58 | new class () {
59 | public function getSum()
60 | {
61 | return 10;
62 | }
63 | },
64 | new class () {
65 | public function getSum()
66 | {
67 | return 10;
68 | }
69 | },
70 | new class () {
71 | public function getSum()
72 | {
73 | return 20;
74 | }
75 | },
76 | ],
77 | [10, 20]
78 | ],
79 | ];
80 | }
81 |
82 | /**
83 | * @dataProvider cases
84 | * @test
85 | * @param $column
86 | * @param $collection
87 | * @param $expected
88 | */
89 | public function callSuccess($column, $collection, $expected)
90 | {
91 | $aggregator = new AddToSet($column);
92 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/LastTest.php:
--------------------------------------------------------------------------------
1 | 'asdf'],
25 | [],
26 | [],
27 | ['one' => 999],
28 | ['one' => false],
29 | ['one' => true],
30 | ],
31 | true,
32 | ],
33 | [
34 | 'count',
35 | [
36 | [],
37 | ['count' => 2],
38 | ['count' => 3],
39 | ],
40 | 3
41 | ],
42 | [
43 | 'test',
44 | [
45 | new class () {
46 | public $test = 1;
47 | },
48 | new class () {
49 | public $test = 1;
50 | },
51 | new class () {
52 | public $test = 1;
53 | },
54 | new class () {
55 | public $test = 15;
56 | },
57 | ],
58 | 15
59 | ],
60 | [
61 | 'sum',
62 | [
63 | new class () {
64 | public function getSum()
65 | {
66 | return 10;
67 | }
68 | },
69 | new class () {
70 | public function getSum()
71 | {
72 | return 10;
73 | }
74 | },
75 | new class () {
76 | public function getSum()
77 | {
78 | return 20;
79 | }
80 | },
81 | ],
82 | 20
83 | ],
84 | ];
85 | }
86 |
87 | /**
88 | * @dataProvider cases
89 | * @test
90 | * @param $column
91 | * @param $collection
92 | * @param $expected
93 | */
94 | public function callSuccess($column, $collection, $expected)
95 | {
96 | $aggregator = new Last($column);
97 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ArrayList.php:
--------------------------------------------------------------------------------
1 | elements[$index];
21 | }
22 |
23 | public function set($element, int $index)
24 | {
25 | if (!isset($this->elements[$index])) {
26 | throw new OutOfRangeException("Index $index is out of list range with size: {$this->size()} ");
27 | }
28 | $res = $this->elements[$index];
29 | $this->elements[$index] = $element;
30 | $this->afterElementAdd($element);
31 |
32 | return $res;
33 | }
34 |
35 | public function indexOf($element): ?int
36 | {
37 | return array_search($element, $this->elements, true) ?: null;
38 | }
39 |
40 | public function remove($element): bool
41 | {
42 | if (is_object($element) && $element instanceof HashCodeAware) {
43 | return $this->removeThroughHashCode($element);
44 | }
45 | $key = array_search($element, $this->elements, true);
46 | if (false === $key) {
47 | return false;
48 | }
49 | $this->removeAt($key);
50 | return true;
51 | }
52 |
53 | public function lastIndexOf($element): ?int
54 | {
55 | $reverseIndex = array_search($element, array_reverse($this->elements), true);
56 | if ($reverseIndex === false) {
57 | return null;
58 | }
59 |
60 | return count($this->elements) - $reverseIndex - 1;
61 | }
62 |
63 | public function removeAt(int $index)
64 | {
65 | $size = $this->size();
66 | if ($index >= $size) {
67 | return null;
68 | }
69 |
70 | $el = $this->elements[$index];
71 | unset($this->elements[$index]);
72 | $this->pointer--;
73 | if ($this->pointer === -1) {
74 | return $el;
75 | }
76 | $this->elements = array_merge(
77 | array_slice($this->elements, 0, $index),
78 | array_slice($this->elements, $index)
79 | );
80 | return $el;
81 | }
82 |
83 | private function removeThroughHashCode(HashCodeAware $element): bool
84 | {
85 | foreach ($this->elements as $i => $iElement) {
86 | if ($iElement instanceof HashCodeAware && $iElement->getHashCode() === $element->getHashCode()) {
87 | $this->removeAt($i);
88 | return true;
89 | }
90 | }
91 | return false;
92 | }
93 |
94 | protected function afterElementAdd($element): void
95 | {
96 | }
97 |
98 | protected function afterElementsSet(): void
99 | {
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/MaxTest.php:
--------------------------------------------------------------------------------
1 | 0],
29 | [],
30 | ['price' => 23],
31 | [],
32 | ['price' => 10],
33 | ],
34 | 23
35 | ],
36 | [
37 | 'count',
38 | [
39 | ['count' => 1],
40 | ['count' => 2],
41 | ['count' => 3],
42 | ['count' => 4],
43 | ['count' => 5],
44 | ],
45 | 5
46 | ],
47 | [
48 | 'test',
49 | [
50 | new class () {
51 | public $test = 1;
52 | },
53 | new class () {
54 | public $test = 2;
55 | },
56 | new class () {
57 | public $test = 3;
58 | },
59 | new class () {
60 | public $test = 4;
61 | },
62 | new class () {
63 | public $test = 15;
64 | },
65 | ],
66 | 15
67 | ],
68 | [
69 | 'sum',
70 | [
71 | new class () {
72 | public function getSum()
73 | {
74 | return 10;
75 | }
76 | },
77 | new class () {
78 | public function getSum()
79 | {
80 | return 20;
81 | }
82 | },
83 | ],
84 | 20
85 | ],
86 | ];
87 | }
88 |
89 | /**
90 | * @dataProvider cases
91 | * @test
92 | * @param $column
93 | * @param $collection
94 | * @param $expected
95 | */
96 | public function callSuccess($column, $collection, $expected)
97 | {
98 | $aggregator = new Max($column);
99 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/MinTest.php:
--------------------------------------------------------------------------------
1 | 0],
29 | [],
30 | ['price' => 23],
31 | [],
32 | ['price' => 10],
33 | ],
34 | 0
35 | ],
36 | [
37 | 'count',
38 | [
39 | ['count' => 1],
40 | ['count' => 2],
41 | ['count' => 3],
42 | ['count' => 4],
43 | ['count' => 5],
44 | ],
45 | 1
46 | ],
47 | [
48 | 'test',
49 | [
50 | new class () {
51 | public $test = 1;
52 | },
53 | new class () {
54 | public $test = 2;
55 | },
56 | new class () {
57 | public $test = -3;
58 | },
59 | new class () {
60 | public $test = 4;
61 | },
62 | new class () {
63 | public $test = 15;
64 | },
65 | ],
66 | -3
67 | ],
68 | [
69 | 'sum',
70 | [
71 | new class () {
72 | public function getSum()
73 | {
74 | return 10;
75 | }
76 | },
77 | new class () {
78 | public function getSum()
79 | {
80 | return 20;
81 | }
82 | },
83 | ],
84 | 10
85 | ],
86 | ];
87 | }
88 |
89 | /**
90 | * @dataProvider cases
91 | * @test
92 | * @param $column
93 | * @param $collection
94 | * @param $expected
95 | */
96 | public function callSuccess($column, $collection, $expected)
97 | {
98 | $aggregator = new Min($column);
99 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/SumTest.php:
--------------------------------------------------------------------------------
1 | 0],
29 | [],
30 | ['price' => 23],
31 | [],
32 | ['price' => 10],
33 | ],
34 | 33
35 | ],
36 | [
37 | 'count',
38 | [
39 | ['count' => 1],
40 | ['count' => 2],
41 | ['count' => 3],
42 | ['count' => 4],
43 | ['count' => 5],
44 | ],
45 | 15
46 | ],
47 | [
48 | 'test',
49 | [
50 | new class () {
51 | public $test = 1;
52 | },
53 | new class () {
54 | public $test = 2;
55 | },
56 | new class () {
57 | public $test = -3;
58 | },
59 | new class () {
60 | public $test = 4;
61 | },
62 | new class () {
63 | public $test = 15;
64 | },
65 | ],
66 | 19
67 | ],
68 | [
69 | 'sum',
70 | [
71 | new class () {
72 | public function getSum()
73 | {
74 | return 10;
75 | }
76 | },
77 | new class () {
78 | public function getSum()
79 | {
80 | return 20;
81 | }
82 | },
83 | ],
84 | 30
85 | ],
86 | ];
87 | }
88 |
89 | /**
90 | * @dataProvider cases
91 | * @test
92 | * @param $column
93 | * @param $collection
94 | * @param $expected
95 | */
96 | public function callSuccess($column, $collection, $expected)
97 | {
98 | $aggregator = new Sum($column);
99 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/AbstractCollection.php:
--------------------------------------------------------------------------------
1 | setElements($elements);
19 | }
20 |
21 | public static function of(...$elements): self
22 | {
23 | return new static($elements ?: null);
24 | }
25 |
26 | public function add($element): bool
27 | {
28 | if ($this->pointer === PHP_INT_MAX) {
29 | return false;
30 | }
31 | $this->pointer++;
32 | $this->elements[] = $element;
33 | $this->afterElementAdd($element);
34 | return true;
35 | }
36 |
37 | public function addAll(iterable $elements): bool
38 | {
39 | foreach ($elements as $element) {
40 | $this->elements[] = $element;
41 | $this->afterElementAdd($element);
42 | }
43 | $newPointer = count($this->elements) - 1;
44 | if ($newPointer > PHP_INT_MAX) {
45 | $this->elements = array_slice($this->elements, 0, $this->pointer);
46 | } else {
47 | $this->pointer = $newPointer;
48 | }
49 | return true;
50 | }
51 |
52 | public function merge(Collection $collection): bool
53 | {
54 | return $this->addAll($collection->toArray());
55 | }
56 |
57 | public function clear(): void
58 | {
59 | $this->setElements([]);
60 | }
61 |
62 | public function contains($element): bool
63 | {
64 | return in_array($element, $this->elements, true);
65 | }
66 |
67 | public function equals(Collection $collection): bool
68 | {
69 | return $this->toArray() === $collection->toArray();
70 | }
71 |
72 | public function size(): int
73 | {
74 | return $this->pointer + 1;
75 | }
76 |
77 | public function isEmpty(): bool
78 | {
79 | return $this->pointer === -1;
80 | }
81 |
82 | public function toArray(): array
83 | {
84 | return $this->elements;
85 | }
86 |
87 | public function getIterator()
88 | {
89 | yield from $this->toArray();
90 | }
91 |
92 | public function copy(): Collection
93 | {
94 | return clone $this;
95 | }
96 |
97 | protected function setElements(array $elements): void
98 | {
99 | $this->elements = array_values($elements);
100 | $this->pointer = count($elements) - 1;
101 | $this->afterElementsSet();
102 | }
103 |
104 | protected function getElements(): array
105 | {
106 | return $this->elements;
107 | }
108 |
109 | abstract protected function afterElementAdd($element): void;
110 |
111 | abstract protected function afterElementsSet(): void;
112 |
113 | abstract public function stream(): Stream;
114 | }
115 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ArrayStrictListTest.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections;
7 |
8 | use InvalidArgumentException;
9 | use PHPUnit\Framework\TestCase;
10 |
11 | class ArrayStrictListTest extends TestCase
12 | {
13 | public function createInstance(...$args): ListSequence
14 | {
15 | return ArrayStrictList::of(...$args);
16 | }
17 |
18 | public function strictCases(): array
19 | {
20 | return [
21 | [1, 2, 3],
22 | [1.1, 2.2, 3.3],
23 | ['one', 'two', 'three'],
24 | [true, false, true],
25 | [[1], ['2'], [3.3]],
26 | [null, null, null],
27 | [
28 | function () {
29 | return '1';
30 | },
31 | function () {
32 | return 'two';
33 | },
34 | function () {
35 | return 3;
36 | },
37 | ],
38 | [
39 | $object = new class () {},
40 | clone $object,
41 | clone $object,
42 | ],
43 | ];
44 | }
45 |
46 | public function notStrictCases(): array
47 | {
48 | return [
49 | [1, '2', 3],
50 | [1.1, 2.2, 3],
51 | ['one', 'two', 3.3],
52 | ['true', false, true],
53 | [[1], null, [3.3]],
54 | [null, null, []],
55 | [
56 | function () {
57 | return '1';
58 | },
59 | new class () {},
60 | function () {
61 | return 3;
62 | },
63 | ],
64 | [
65 | $object = new class () {},
66 | clone $object,
67 | new class () {},
68 | ],
69 | ];
70 | }
71 |
72 | /**
73 | * @test
74 | * @dataProvider strictCases
75 | * @doesNotPerformAssertions
76 | * @param $sequence
77 | */
78 | public function creatingFromStrict(...$sequence): void
79 | {
80 | $this->createInstance(...$sequence);
81 | }
82 |
83 | /**
84 | * @test
85 | * @dataProvider notStrictCases
86 | * @param $sequence
87 | */
88 | public function creatingFromNotStrict(...$sequence): void
89 | {
90 | self::expectException(InvalidArgumentException::class);
91 | $this->createInstance(...$sequence);
92 | }
93 |
94 | /**
95 | * @test
96 | */
97 | public function addingElementsToStrictArray()
98 | {
99 | self::expectException(InvalidArgumentException::class);
100 |
101 | $this
102 | ->createInstance(1, 2, 3)
103 | ->add('4');
104 |
105 | $this
106 | ->createInstance('one', 'two', 'three')
107 | ->addAll(['4', 5, '6']);
108 |
109 | $object = new class () {};
110 | $this
111 | ->createInstance($object, clone $object, clone $object)
112 | ->set(new class () {}, 2);
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/Functions/Group/Aggregator/AvgTest.php:
--------------------------------------------------------------------------------
1 | 25],
27 | [],
28 | [],
29 | [],
30 | ['count' => 25],
31 | ],
32 | 10
33 | ],
34 | [
35 | 'count',
36 | [
37 | ['count' => 1],
38 | ['count' => 2],
39 | ['count' => 3],
40 | ['count' => 4],
41 | ['count' => 5],
42 | ],
43 | 3
44 | ],
45 | [
46 | 'test',
47 | [
48 | new class () {
49 | public $test = 1;
50 | },
51 | new class () {
52 | public $test = 2;
53 | },
54 | new class () {
55 | public $test = 3;
56 | },
57 | new class () {
58 | public $test = 4;
59 | },
60 | new class () {
61 | public $test = 15;
62 | },
63 | ],
64 | 5
65 | ],
66 | [
67 | 'sum',
68 | [
69 | new class () {
70 | public function getSum()
71 | {
72 | return 10;
73 | }
74 | },
75 | new class () {
76 | public function getSum()
77 | {
78 | return 20;
79 | }
80 | },
81 | ],
82 | 15
83 | ],
84 | ];
85 | }
86 |
87 | /**
88 | * @dataProvider cases
89 | * @test
90 | * @param $column
91 | * @param $collection
92 | * @param $expected
93 | */
94 | public function callSuccess($column, $collection, $expected)
95 | {
96 | $aggregator = new Avg($column);
97 | self::assertEquals($expected, $aggregator($this->toCollection($collection)));
98 | }
99 |
100 | /**
101 | * @test
102 | */
103 | public function raisedException()
104 | {
105 | $this->expectException(RuntimeException::class);
106 | $aggregator = new Avg('test');
107 | $aggregator($this->toCollection([new stdClass(), new stdClass(), new stdClass()]));
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/ImmutableList.php:
--------------------------------------------------------------------------------
1 | decoratedList = new ArrayList($elements);
18 | }
19 |
20 | public static function fromCollection(Collection $collection): self
21 | {
22 | return new static($collection->toArray());
23 | }
24 |
25 | public static function of(...$elements): self
26 | {
27 | return new static($elements ?: null);
28 | }
29 |
30 | public function stream(): Stream
31 | {
32 | return $this->decoratedList->stream();
33 | }
34 |
35 | public function remove($element): bool
36 | {
37 | throw $this->createBlockingException();
38 | }
39 |
40 | public function get(int $index)
41 | {
42 | return $this->decoratedList->get($index);
43 | }
44 |
45 | public function set($element, int $index)
46 | {
47 | throw $this->createBlockingException();
48 | }
49 |
50 | public function indexOf($element): ?int
51 | {
52 | return $this->decoratedList->indexOf($element);
53 | }
54 |
55 | public function lastIndexOf($element): ?int
56 | {
57 | return $this->decoratedList->lastIndexOf($element);
58 | }
59 |
60 | public function removeAt(int $index)
61 | {
62 | throw $this->createBlockingException();
63 | }
64 |
65 | private function createBlockingException(): RuntimeException
66 | {
67 | return new RuntimeException('Is immutable list. Everything modifier call is prohibited');
68 | }
69 |
70 | public function add($element): bool
71 | {
72 | throw $this->createBlockingException();
73 | }
74 |
75 | public function addAll(iterable $elements): bool
76 | {
77 | throw $this->createBlockingException();
78 | }
79 |
80 | public function merge(Collection $collection): bool
81 | {
82 | throw $this->createBlockingException();
83 | }
84 |
85 | public function clear(): void
86 | {
87 | throw $this->createBlockingException();
88 | }
89 |
90 | public function contains($element): bool
91 | {
92 | return $this->decoratedList->contains($element);
93 | }
94 |
95 | public function equals(Collection $collection): bool
96 | {
97 | return $this->decoratedList->equals($collection);
98 | }
99 |
100 | public function size(): int
101 | {
102 | return $this->decoratedList->size();
103 | }
104 |
105 | public function isEmpty(): bool
106 | {
107 | return $this->decoratedList->isEmpty();
108 | }
109 |
110 | public function toArray(): array
111 | {
112 | return $this->decoratedList->toArray();
113 | }
114 |
115 | public function copy(): Collection
116 | {
117 | return $this->decoratedList->copy();
118 | }
119 |
120 | public function getIterator()
121 | {
122 | return $this->decoratedList->getIterator();
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ArrayQueueTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($queueProvider, $queue->toArray());
27 | }
28 |
29 | public function testOffer()
30 | {
31 | $queueProvider = [100, 0, 20, 10, 9];
32 | $queue = ArrayQueue::of(...$queueProvider);
33 | $this->assertTrue($queue->offer(100));
34 | $this->assertTrue($queue->offer(100));
35 | $this->assertTrue($queue->offer(101));
36 | $this->assertEquals(count($queueProvider) + 3, $queue->size());
37 | $this->assertEquals(array_merge($queueProvider, [100, 100, 101]), $queue->toArray());
38 | }
39 |
40 | public function testPoll()
41 | {
42 | $queueProvider = [100, 0, 20, 10, 9];
43 | $queue = ArrayQueue::of(...$queueProvider);
44 |
45 | $this->assertEquals(100, $queue->poll());
46 | $this->assertEquals(count($queueProvider) - 1, $queue->size());
47 | $this->assertEquals(0, $queue->poll());
48 | $this->assertEquals(20, $queue->poll());
49 | $this->assertEquals(count($queueProvider) - 3, $queue->size());
50 | }
51 |
52 | public function testPollEmpty()
53 | {
54 | $this->expectException(RuntimeException::class);
55 | $queueProvider = [];
56 | $queue = ArrayQueue::of(...$queueProvider);
57 | $queue->poll();
58 | }
59 |
60 | public function testPeek()
61 | {
62 | $queueProvider = [100, 0, 20, 10, 9];
63 | $queue = ArrayQueue::of(...$queueProvider);
64 |
65 | $this->assertEquals(100, $queue->peek());
66 | $this->assertEquals(count($queueProvider), $queue->size());
67 | $this->assertEquals(100, $queue->peek());
68 | $this->assertEquals(100, $queue->peek());
69 | $this->assertEquals(count($queueProvider), $queue->size());
70 | $queue->poll();
71 | $queue->poll();
72 | $this->assertEquals(20, $queue->peek());
73 | }
74 |
75 | public function testPeekEmpty()
76 | {
77 | $this->expectException(RuntimeException::class);
78 | $queueProvider = [];
79 | $queue = ArrayQueue::of(...$queueProvider);
80 | $queue->peek();
81 | }
82 |
83 | public function testAll() {
84 | $queue = ArrayQueue::of();
85 | $queue->add(2);
86 | $queue->add(3);
87 | $queue->add(1);
88 |
89 | $this->assertEquals(3, $queue->size());
90 |
91 | $this->assertEquals(2, $queue->peek());
92 | $this->assertEquals(2, $queue->poll());
93 | $queue->add(10);
94 | $this->assertFalse($queue->isEmpty());
95 | $this->assertEquals(3, $queue->peek());
96 | $this->assertEquals(3, $queue->poll());
97 | $this->assertEquals(1, $queue->poll());
98 | $this->assertEquals(10, $queue->poll());
99 | $this->assertEquals(0, $queue->size());
100 | $this->assertTrue($queue->isEmpty());
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/AggregateGroupingTest.php:
--------------------------------------------------------------------------------
1 | stream()
26 | ->collect(Collectors::group())
27 | ;
28 |
29 | $this->assertEquals(1, $map->get(1));
30 | $this->assertEquals(3, $map->get(2));
31 | $this->assertEquals(2, $map->get(3));
32 | }
33 |
34 | /**
35 | * @test
36 | */
37 | public function groupingByObjects(): void
38 | {
39 | $o1 = new SplObjectStorage();
40 | $o2 = new SplObjectStorage();
41 | $o3 = new SplObjectStorage();
42 |
43 | $map = self::toCollection($o1, $o2, $o3, $o3, $o2, $o2)
44 | ->stream()
45 | ->collect(Collectors::group())
46 | ;
47 |
48 | $this->assertEquals(1, $map->get($o1));
49 | $this->assertEquals(3, $map->get($o2));
50 | $this->assertEquals(2, $map->get($o3));
51 | }
52 |
53 | /**
54 | * @test
55 | */
56 | public function groupingByObjectsWithHashCode(): void
57 | {
58 | $map = self::toCollection(
59 | new TestInteger(1),
60 | new TestInteger(2),
61 | new TestInteger(3),
62 | new TestInteger(3),
63 | new TestInteger(2),
64 | new TestInteger(2)
65 | )
66 | ->stream()
67 | ->collect(Collectors::group())
68 | ;
69 |
70 | $this->assertEquals(1, $map->get(new TestInteger(1)));
71 | $this->assertEquals(3, $map->get(new TestInteger(2)));
72 | $this->assertEquals(2, $map->get(new TestInteger(3)));
73 | }
74 |
75 | /**
76 | * @test
77 | */
78 | public function groupingByFunction(): void
79 | {
80 | $map = self::toCollection(
81 | 1, 2, 3, 3, 2, 2
82 | )
83 | ->stream()
84 | ->collect(Collectors::groupBy(static function ($v) {
85 | return $v * 10;
86 | }))
87 | ;
88 |
89 | $this->assertEquals(1, $map->get(10));
90 | $this->assertEquals(3, $map->get(20));
91 | $this->assertEquals(2, $map->get(30));
92 | }
93 |
94 | /**
95 | * @test
96 | */
97 | public function groupingByObjectProperty(): void
98 | {
99 | $o1 = (new ExampleObject())->setName('first');
100 | $o2 = (new ExampleObject())->setName('second');
101 | $o3 = (new ExampleObject())->setName('third');
102 | $o31 = (new ExampleObject())->setName('third');
103 | $o21 = (new ExampleObject())->setName('second');
104 | $o22 = (new ExampleObject())->setName('second');
105 |
106 | $map = self::toCollection($o1, $o2, $o3, $o31, $o21, $o22)
107 | ->stream()
108 | ->collect(Collectors::groupByProperty('name'))
109 | ;
110 |
111 | $this->assertEquals(1, $map->get('first'));
112 | $this->assertEquals(3, $map->get('second'));
113 | $this->assertEquals(2, $map->get('third'));
114 | }
115 | }
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Collectors.php:
--------------------------------------------------------------------------------
1 | >
16 | * @package WS\Utils\Collections\Functions
17 | */
18 | class Collectors
19 | {
20 | /**
21 | * Returns function with interface for concatenating collection strings
22 | * @param string $delimiter
23 | * @return Closure
24 | */
25 | public static function concat(string $delimiter = ''): Closure
26 | {
27 | return static function (Collection $collection) use ($delimiter) {
28 | return implode($delimiter, $collection->toArray());
29 | };
30 | }
31 |
32 | /**
33 | * Returns closure for getting average collection value
34 | * @return Closure
35 | */
36 | public static function average(): Closure
37 | {
38 | /**
39 | * @param Collection $collection
40 | * @return float|int
41 | */
42 | return static function (Collection $collection) {
43 | $array = $collection->toArray();
44 |
45 | return array_sum($array) / count($array);
46 | };
47 | }
48 |
49 | /**
50 | * Returns closure for getting >. keys - collection uniq values, values - count of repeats
51 | * @return Closure
52 | */
53 | public static function group(): Closure
54 | {
55 | return static function (Collection $collection): Map {
56 | $groupBy = self::groupBy(static function ($el) {
57 | return $el;
58 | });
59 | return $groupBy($collection);
60 | };
61 | }
62 |
63 | /**
64 | * Returns closure for getting map >. keys - value uniq fieldName values, values - count of repeats
65 | * @param string $property
66 | * @return Closure
67 | */
68 | public static function groupByProperty(string $property): Closure
69 | {
70 | return static function (Collection $collection) use ($property): Map {
71 | $fGetValue = static function ($obj) use ($property) {
72 | return ObjectFunctions::getPropertyValue($obj, $property);
73 | };
74 |
75 | $groupBy = self::groupBy($fGetValue);
76 | return $groupBy($collection);
77 | };
78 | }
79 |
80 | /**
81 | * Returns closure for getting map >. keys - value uniq fieldName values, values - count of repeats
82 | * @param callable $f
83 | * @return Closure
84 | */
85 | public static function groupBy(callable $f): Closure
86 | {
87 | return static function (Collection $collection) use ($f): Map {
88 | $group = new HashMap();
89 | $collection
90 | ->stream()
91 | ->each(static function ($el) use ($group, $f) {
92 | $value = $f($el);
93 | $count = 0;
94 | if (($gCount = $group->get($value)) !== null) {
95 | $count = $gCount;
96 | }
97 | $group->put($value, $count + 1);
98 | });
99 | return $group;
100 | };
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ReorganizersFunctionsTest.php:
--------------------------------------------------------------------------------
1 | stream()
27 | ->reorganize(Reorganizers::chunk(2))
28 | ->getCollection()
29 | ;
30 |
31 | $this->assertEquals(3, $chunkedCollection->size());
32 | $this->assertThat($chunkedCollection->stream()->findFirst(), CollectionIsEqual::to([1, 2]));
33 | $this->assertThat($chunkedCollection->stream()->findLast(), CollectionIsEqual::to([5, 6]));
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function collapsing(): void
40 | {
41 | $collection = self::toCollection([1, 2], [3, 4], [5, 6]);
42 |
43 | $collapsedCollection = $collection
44 | ->stream()
45 | ->reorganize(Reorganizers::collapse())
46 | ->getCollection()
47 | ;
48 | $this->assertThat($collapsedCollection, CollectionIsEqual::to([1, 2, 3, 4, 5, 6]));
49 | }
50 |
51 | /**
52 | * @test
53 | */
54 | public function singleDepthCollapsing(): void
55 | {
56 | $collection = self::toCollection([1, 2], [3, 4], [5, 6, [7, 8]]);
57 |
58 | $collapsedCollection = $collection
59 | ->stream()
60 | ->reorganize(Reorganizers::collapse(1))
61 | ->getCollection()
62 | ;
63 | $this->assertThat($collapsedCollection, CollectionIsEqual::to([1, 2, 3, 4, 5, 6, [7, 8]]));
64 | }
65 |
66 | /**
67 | * @test
68 | */
69 | public function numericDepthCollapsing(): void
70 | {
71 | $collection = self::toCollection([1, 2], [3, 4], [5, 6, [7, 8, [9, 10]]]);
72 |
73 | $collapsedCollection = $collection
74 | ->stream()
75 | ->reorganize(Reorganizers::collapse(3))
76 | ->getCollection()
77 | ;
78 | $this->assertThat($collapsedCollection, CollectionIsEqual::to([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]));
79 | }
80 |
81 | /**
82 | * @test
83 | */
84 | public function filterDistinctElements(): void
85 | {
86 | $o1 = new TestInteger(1);
87 | $o2 = new TestInteger(2);
88 | $o3 = new TestInteger(3);
89 | $o4 = new TestInteger(1);
90 | $o5 = new TestInteger(2);
91 |
92 | $uniqCollection = CollectionFactory::from([$o1, $o2, $o3, $o4, $o5])
93 | ->stream()
94 | ->filter(Predicates::lockDuplicated())
95 | ->getCollection()
96 | ;
97 |
98 | self::assertThat($uniqCollection, CollectionIsEqual::to([$o1, $o2, $o3]));
99 | }
100 |
101 | /**
102 | * @test
103 | */
104 | public function filterDistinctCastedValues(): void
105 | {
106 | $caster = static function (int $number) {
107 | return $number % 2;
108 | };
109 |
110 | $result = CollectionFactory::numbers(0, 10)
111 | ->stream()
112 | ->filter(Predicates::lockDuplicated($caster))
113 | ->toArray()
114 | ;
115 |
116 | $this->assertCount(2, $result);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/SetInterfaceTestTrait.php:
--------------------------------------------------------------------------------
1 | createInstance();
23 |
24 | $instance->add(1);
25 | $instance->add(1);
26 | $instance->add(1);
27 |
28 | self::assertEquals(1, $instance->size());
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function uniquenessObjectElements(): void
35 | {
36 | $instance = $this->createInstance();
37 |
38 | $ob = new SplObjectStorage();
39 | $instance->add($ob);
40 | $instance->add($ob);
41 | $instance->add($ob);
42 |
43 | self::assertEquals(1, $instance->size());
44 | }
45 |
46 | /**
47 | * @test
48 | */
49 | public function uniquenessHashCodeAwareObjects(): void
50 | {
51 | $instance = $this->createInstance();
52 |
53 | $instance->add(new TestInteger(1));
54 | $instance->add(new TestInteger(1));
55 | $instance->add(new TestInteger(1));
56 |
57 | self::assertEquals(1, $instance->size());
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function equalsSetChecking(): void
64 | {
65 | $instance = $this->createInstance();
66 | $instance->add(1);
67 | $instance->add(2);
68 | $instance->add(3);
69 |
70 | $anotherInstance = $this->createInstance();
71 | $anotherInstance->add(3);
72 | $anotherInstance->add(2);
73 | $anotherInstance->add(1);
74 |
75 | self::assertThat($anotherInstance, CollectionIsEqual::to($instance));
76 | }
77 |
78 | /**
79 | * @test
80 | */
81 | public function notEqualsSetCheckingRight(): void
82 | {
83 | $instance = $this->createInstance();
84 | $instance->add(1);
85 | $instance->add(2);
86 | $instance->add(3);
87 | $instance->add(4);
88 |
89 | $anotherInstance = $this->createInstance();
90 | $anotherInstance->add(3);
91 | $anotherInstance->add(2);
92 | $anotherInstance->add(1);
93 |
94 | self::assertThat($anotherInstance, CollectionIsNotEqual::to($instance));
95 | }
96 |
97 | /**
98 | * @test
99 | */
100 | public function notEqualsSetCheckingBack(): void
101 | {
102 | $instance = $this->createInstance();
103 | $instance->add(1);
104 | $instance->add(2);
105 | $instance->add(3);
106 |
107 | $anotherInstance = $this->createInstance();
108 | $anotherInstance->add(4);
109 | $anotherInstance->add(3);
110 | $anotherInstance->add(2);
111 | $anotherInstance->add(1);
112 |
113 | self::assertThat($anotherInstance, CollectionIsNotEqual::to($instance));
114 | }
115 |
116 | /**
117 | * @test
118 | */
119 | public function notEqualsSetCheckingDiffElements(): void
120 | {
121 | $instance = $this->createInstance();
122 | $instance->add(1);
123 | $instance->add(2);
124 | $instance->add(3);
125 |
126 | $anotherInstance = $this->createInstance();
127 | $anotherInstance->add(4);
128 | $anotherInstance->add(2);
129 | $anotherInstance->add(1);
130 |
131 | self::assertThat($anotherInstance, CollectionIsNotEqual::to($instance));
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/DummyStreamDecorator.php:
--------------------------------------------------------------------------------
1 | decoratedStream = $originalStream;
19 | }
20 |
21 | public function each(callable $consumer): Stream
22 | {
23 | return $this;
24 | }
25 |
26 | public function walk(callable $consumer, ?int $limit = null): Stream
27 | {
28 | return $this;
29 | }
30 |
31 | public function filter(callable $predicate): Stream
32 | {
33 | return $this;
34 | }
35 |
36 | public function reorganize(callable $reorganizer): Stream
37 | {
38 | return $this;
39 | }
40 |
41 | public function allMatch(callable $predicate): bool
42 | {
43 | return $this->decoratedStream->allMatch($predicate);
44 | }
45 |
46 | public function anyMatch(callable $predicate): bool
47 | {
48 | return $this->decoratedStream->anyMatch($predicate);
49 | }
50 |
51 | public function map(callable $converter): Stream
52 | {
53 | return $this;
54 | }
55 |
56 | public function collect(callable $collector)
57 | {
58 | return $this->decoratedStream->collect($collector);
59 | }
60 |
61 | public function findAny()
62 | {
63 | return $this->decoratedStream->findAny();
64 | }
65 |
66 | public function findFirst(callable $filter = null)
67 | {
68 | return $this->decoratedStream->findFirst();
69 | }
70 |
71 | public function findLast()
72 | {
73 | return $this->decoratedStream->findLast();
74 | }
75 |
76 | public function min(callable $comparator)
77 | {
78 | return $this->decoratedStream->min($comparator);
79 | }
80 |
81 | public function max(callable $comparator)
82 | {
83 | return $this->decoratedStream->max($comparator);
84 | }
85 |
86 | public function sort(callable $comparator): Stream
87 | {
88 | return $this;
89 | }
90 |
91 | public function sortBy(callable $extractor): Stream
92 | {
93 | return $this;
94 | }
95 |
96 | public function sortDesc(callable $comparator): Stream
97 | {
98 | return $this;
99 | }
100 |
101 | public function sortByDesc(callable $extractor): Stream
102 | {
103 | return $this;
104 | }
105 |
106 | public function reverse(): Stream
107 | {
108 | return $this;
109 | }
110 |
111 | public function reduce(callable $accumulator, $initialValue = null)
112 | {
113 | return $this->decoratedStream->reduce($accumulator, $initialValue);
114 | }
115 |
116 | public function limit(int $size): Stream
117 | {
118 | return $this;
119 | }
120 |
121 | public function getCollection(): Collection
122 | {
123 | return $this->decoratedStream->getCollection();
124 | }
125 |
126 | public function when(bool $condition): Stream
127 | {
128 | if ($condition) {
129 | return $this->decoratedStream;
130 | }
131 |
132 | return $this;
133 | }
134 |
135 | public function always(): Stream
136 | {
137 | return $this->decoratedStream;
138 | }
139 |
140 | /**
141 | * @inheritDoc
142 | */
143 | public function toArray(): array
144 | {
145 | return $this
146 | ->getCollection()
147 | ->toArray()
148 | ;
149 | }
150 |
151 | /**
152 | * @inheritDoc
153 | */
154 | public function getSet(): Set
155 | {
156 | return new HashSet($this->toArray());
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/HashMap.php:
--------------------------------------------------------------------------------
1 |
4 | * @license MIT
5 | */
6 |
7 | namespace WS\Utils\Collections;
8 |
9 | use ArrayIterator;
10 | use RuntimeException;
11 |
12 | class HashMap implements Map
13 | {
14 | private $entries = [];
15 |
16 | public function put($key, $value): bool
17 | {
18 | $this->entries[$this->getKeyHash($key)] = new MapEntry($key, $value);
19 |
20 | return true;
21 | }
22 |
23 | public function getIterator()
24 | {
25 | return new class($this->entries) extends ArrayIterator {
26 | private $entries;
27 | public function __construct(array $entries)
28 | {
29 | $hashToValueArray = array_map(static function (MapEntry $entry) {
30 | return $entry->getValue();
31 | }, $entries);
32 | parent::__construct($hashToValueArray);
33 | $this->entries = $entries;
34 | }
35 |
36 | public function key()
37 | {
38 | $arrayKey = parent::key();
39 | /** @var MapEntry $entry */
40 | $entry = $this->entries[$arrayKey];
41 | return $entry->getKey();
42 | }
43 | };
44 | }
45 |
46 | public function values(): Collection
47 | {
48 | $values = [];
49 | /** @var MapEntry $entry */
50 | foreach ($this->entries as $entry) {
51 | $values[] = $entry->getValue();
52 | }
53 | return new ArrayList($values);
54 | }
55 |
56 | public function keys(): Collection
57 | {
58 | $keys = [];
59 | /** @var MapEntry $entry */
60 | foreach ($this->entries as $entry) {
61 | $keys[] = $entry->getKey();
62 | }
63 | return new ArrayList($keys);
64 | }
65 |
66 | /**
67 | * @param $key
68 | * @return string
69 | */
70 | private function getKeyHash($key): string
71 | {
72 | if (is_scalar($key)) {
73 | return $key.'';
74 | }
75 | if ($key instanceof HashCodeAware) {
76 | return $key->getHashCode();
77 | }
78 | if (is_object($key)) {
79 | return spl_object_hash($key);
80 | }
81 | if ($key === null) {
82 | return '__NULL__';
83 | }
84 | if (is_array($key)) {
85 | return md5(json_encode($key));
86 | }
87 | throw new RuntimeException("The type of $key is not supported");
88 | }
89 |
90 | public function remove($key): bool
91 | {
92 | $res = $this->containsKey($key);
93 |
94 | if (!$res) {
95 | return false;
96 | }
97 | unset($this->entries[$this->getKeyHash($key)]);
98 | return true;
99 | }
100 |
101 | /**
102 | * @inheritDoc
103 | */
104 | public function containsKey($key): bool
105 | {
106 | return isset($this->entries[$this->getKeyHash($key)]);
107 | }
108 |
109 | public function size(): int
110 | {
111 | return count($this->entries);
112 | }
113 |
114 | /**
115 | * @inheritDoc
116 | */
117 | public function get($key)
118 | {
119 | if (!$this->containsKey($key)) {
120 | return null;
121 | }
122 |
123 | $entry = $this->entries[$this->getKeyHash($key)];
124 | return $entry->getValue();
125 | }
126 |
127 | /**
128 | * @inheritDoc
129 | */
130 | public function containsValue($tested): bool
131 | {
132 | foreach ($this->entries as $entry) {
133 | if ($entry->getValue() === $tested) {
134 | return true;
135 | }
136 | }
137 | return false;
138 | }
139 |
140 | public function stream(): Stream
141 | {
142 | return new SerialStream(CollectionFactory::from(array_values($this->entries)));
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ArrayListTest.php:
--------------------------------------------------------------------------------
1 | createInstance(1, 2, 3);
28 |
29 | self::assertEquals(1, $list->get(0));
30 | self::assertEquals(2, $list->get(1));
31 | self::assertEquals(3, $list->get(2));
32 | }
33 |
34 | /**
35 | * @test
36 | */
37 | public function settingAtIndex(): void
38 | {
39 | $list = $this->createInstance(1, 2, 3);
40 |
41 | $list->set(4, 1);
42 |
43 | self::assertEquals(1, $list->get(0));
44 | self::assertEquals(4, $list->get(1));
45 | self::assertEquals(3, $list->get(2));
46 |
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function settingIndexIntoOutOfRange(): void
53 | {
54 | $list = $this->createInstance(1, 2, 3);
55 |
56 | $this->expectException(OutOfRangeException::class);
57 | $list->set(4, 3);
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function gettingIndexOf(): void
64 | {
65 | $list = $this->createInstance(1, 2, 3);
66 |
67 | self::assertEquals(0, $list->indexOf(1));
68 | self::assertEquals(1, $list->indexOf(2));
69 | self::assertEquals(2, $list->indexOf(3));
70 | }
71 |
72 | /**
73 | * @test
74 | */
75 | public function indexOfObjectGetting(): void
76 | {
77 | $i1 = new TestInteger(1);
78 | $i2 = new TestInteger(2);
79 | $i3 = new TestInteger(3);
80 |
81 | $list = $this->createInstance($i1, $i2, $i3);
82 |
83 | self::assertEquals(0, $list->indexOf($i1));
84 | self::assertEquals(1, $list->indexOf($i2));
85 | self::assertEquals(2, $list->indexOf($i3));
86 | }
87 |
88 | /**
89 | * @test
90 | */
91 | public function lastIndexOfElementGetting(): void
92 | {
93 | $i0 = new TestInteger(0);
94 | $i1 = new TestInteger(1);
95 | $i2 = new TestInteger(2);
96 | $i3 = new TestInteger(3);
97 |
98 | $list = $this->createInstance($i0, $i1, $i2, $i3, $i2, $i1);
99 |
100 | self::assertEquals(4, $list->lastIndexOf($i2));
101 | self::assertEquals(5, $list->lastIndexOf($i1));
102 | self::assertEquals(3, $list->lastIndexOf($i3));
103 | self::assertEquals(0, $list->lastIndexOf($i0));
104 | }
105 |
106 | /**
107 | * @test
108 | */
109 | public function lastIndexGettingOfEmptyCollection(): void
110 | {
111 | $list = $this->createInstance();
112 |
113 | $res = $list->lastIndexOf(10);
114 |
115 | self::assertNull($res);
116 | }
117 |
118 | /**
119 | * @test
120 | */
121 | public function removingAtPositionElement(): void
122 | {
123 | $list = $this->createInstance(1, 2, 3);
124 |
125 | $el = $list->removeAt(0);
126 |
127 | self::assertEquals(1, $el);
128 | self::assertEquals(2, $list->get(0));
129 | self::assertEquals(2, $list->size());
130 | }
131 |
132 | /**
133 | * @test
134 | */
135 | public function removingAtEmptyCollection(): void
136 | {
137 | $list = $this->createInstance();
138 |
139 | $res = $list->removeAt(10);
140 |
141 | self::assertNull($res);
142 | }
143 |
144 | /**
145 | * @test
146 | */
147 | public function removingWithoutGaps(): void
148 | {
149 | $list = $this->createInstance(1, 2, 3, 4);
150 |
151 | $list->remove(2);
152 |
153 | self::assertEquals(3, $list->size());
154 | self::assertEquals(3, $list->get(1));
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ArrayStackTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(3, $stack->peek());
28 | $this->assertEquals(3, $stack->size());
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function peekingEmpty(): void
35 | {
36 | $this->expectException(RuntimeException::class);
37 |
38 | $stack = new ArrayStack();
39 | $stack->peek();
40 | }
41 |
42 | /**
43 | * @test
44 | */
45 | public function pushing(): void
46 | {
47 | $stack = ArrayStack::of(100, 0, 50);
48 |
49 | $this->assertTrue($stack->push(10));
50 | $this->assertTrue($stack->push(10));
51 | $this->assertEquals(5, $stack->size());
52 | $this->assertTrue($stack->push(15));
53 | $this->assertEquals(15, $stack->peek());
54 | $this->assertEquals(6, $stack->size());
55 | }
56 |
57 | /**
58 | * @test
59 | */
60 | public function popping(): void
61 | {
62 | $stack = ArrayStack::of(100, 0, '50', 'asd', 20);
63 |
64 | $this->assertEquals(20, $stack->pop());
65 | $this->assertEquals('asd', $stack->pop());
66 | $this->assertEquals('50', $stack->pop());
67 | $this->assertEquals(0, $stack->pop());
68 | $this->assertEquals(100, $stack->pop());
69 | }
70 |
71 | /**
72 | * @test
73 | */
74 | public function emptyPopping(): void
75 | {
76 | $this->expectException(RuntimeException::class);
77 |
78 | $stack = new ArrayStack();
79 | $stack->pop();
80 | }
81 |
82 | /**
83 | * @test
84 | */
85 | public function iterating(): void
86 | {
87 | $stack = ArrayStack::of(9, 8, 'a', 'n');
88 |
89 | $elements = [];
90 | /** @noinspection PhpUnhandledExceptionInspection */
91 | foreach ($stack->getIterator() as $element) {
92 | $elements[] = $element;
93 | }
94 | $this->assertEquals(['n', 'a', 8, 9], $elements);
95 |
96 | $elements = [];
97 | /** @noinspection PhpUnhandledExceptionInspection */
98 | foreach ($stack->getIterator() as $element) {
99 | $elements[] = $element;
100 | }
101 | $this->assertEquals(['n', 'a', 8, 9], $elements);
102 | }
103 |
104 | /**
105 | * @test
106 | */
107 | public function merging(): void
108 | {
109 | $stack1 = ArrayStack::of(1, 7);
110 | $stack2 = ArrayStack::of('c', 'a');
111 |
112 | $stack1->merge($stack2);
113 | $this->assertEquals(['c', 'a', 7, 1], $stack1->toArray());
114 |
115 | $stack2->merge($stack2);
116 | $this->assertEquals(['c', 'a', 'a', 'c'], $stack2->toArray());
117 | }
118 |
119 | /**
120 | * @test
121 | */
122 | public function equivalency(): void
123 | {
124 | $stack1 = ArrayStack::of(1, 7);
125 | $stack2 = ArrayStack::of(1, 7);
126 |
127 | $this->assertTrue($stack1->equals($stack2));
128 | $this->assertTrue($stack2->equals($stack1));
129 | }
130 |
131 | /**
132 | * @test
133 | */
134 | public function absentEquivalency(): void
135 | {
136 | $stack1 = ArrayStack::of(1, 7);
137 | $stack2 = ArrayStack::of(7, 1);
138 | $stack3 = ArrayStack::of(1, 7, 7);
139 | $stack4 = ArrayStack::of('1', '7');
140 |
141 | $this->assertFalse($stack1->equals($stack2));
142 | $this->assertFalse($stack1->equals($stack3));
143 | $this->assertFalse($stack1->equals($stack4));
144 | $this->assertFalse($stack2->equals($stack3));
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ComparatorsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals($comparator($a, $b), $expected);
42 | }
43 |
44 | public function objectComparatorTestCases(): array
45 | {
46 | $sequence = [];
47 |
48 | for ($i = 0; $i < 10; $i++) {
49 | $obj = new ExampleObject();
50 | $sequence[] = $obj;
51 | $obj->property = $i;
52 | $obj->setField($i);
53 | $obj->setName($i);
54 | }
55 |
56 | $cases = [];
57 | for ($i = 0; $i < 2; $i++) {
58 | $shuffledSequence = (new ArrayList($sequence))
59 | ->stream()
60 | ->collect(Reorganizers::shuffle())
61 | ->toArray()
62 | ;
63 | $cases[] = [$shuffledSequence, 'property', $sequence];
64 | }
65 | for ($i = 0; $i < 2; $i++) {
66 | $shuffledSequence = (new ArrayList($sequence))
67 | ->stream()
68 | ->collect(Reorganizers::shuffle())
69 | ->toArray()
70 | ;
71 | $cases[] = [$shuffledSequence, 'name', $sequence];
72 | }
73 | for ($i = 0; $i < 2; $i++) {
74 | $shuffledSequence = (new ArrayList($sequence))
75 | ->stream()
76 | ->collect(Reorganizers::shuffle())
77 | ->toArray()
78 | ;
79 | $cases[] = [$shuffledSequence, 'field', $sequence];
80 | }
81 |
82 | return $cases;
83 | }
84 |
85 | /**
86 | * @dataProvider objectComparatorTestCases
87 | * @test
88 | * @param $sequence
89 | * @param $field
90 | * @param $expected
91 | */
92 | public function objectComparatorChecking($sequence, $field, $expected): void
93 | {
94 | $actual = (new ArrayList($sequence))
95 | ->stream()
96 | ->sort(Comparators::objectPropertyComparator($field))
97 | ->getCollection()
98 | ->toArray();
99 |
100 | $this->assertEquals($expected, $actual);
101 | }
102 |
103 | /**
104 | * @dataProvider scalarComparatorCases
105 | * @test
106 | * @param $a
107 | * @param $b
108 | * @param $expected
109 | */
110 | public function callbackComparatorChecking($a, $b, $expected): void
111 | {
112 | $f = new InvokeCounter(static function ($value) {
113 | return $value;
114 | });
115 | $comparator = Comparators::callbackComparator($f);
116 | $this->assertEquals($comparator($a, $b), $expected);
117 | $this->assertTrue($f->countOfInvokes() > 0);
118 | }
119 |
120 | /**
121 | * @test
122 | */
123 | public function integrateCallbackComparatorChecking(): void
124 | {
125 | $f = new InvokeCounter(static function ($value) {
126 | return $value;
127 | });
128 |
129 | $sourceCollection = CollectionFactory::numbers(10);
130 | $shuffledCollection = $sourceCollection
131 | ->stream()
132 | ->reorganize(Reorganizers::shuffle())
133 | ->getCollection();
134 |
135 | $this->assertThat($sourceCollection, CollectionIsNotEqual::to($shuffledCollection));
136 |
137 | $sortedCollection = $shuffledCollection->stream()
138 | ->sort(Comparators::callbackComparator($f))
139 | ->getCollection()
140 | ;
141 | $this->assertThat($sourceCollection, CollectionIsEqual::to($sortedCollection));
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Reorganizers.php:
--------------------------------------------------------------------------------
1 | that shuffles elements
20 | * @return Closure
21 | */
22 | public static function shuffle(): Closure
23 | {
24 | return static function (Collection $collection): Collection {
25 | $array = $collection->toArray();
26 | shuffle($array);
27 |
28 | return self::collectionConstructor($array);
29 | };
30 | }
31 |
32 | /**
33 | * Returns Closure that gets $count random elements from collection
34 | * @param int $count
35 | * @return Closure
36 | */
37 | public static function random(int $count = 1): Closure
38 | {
39 | return static function (Collection $collection) use ($count): Collection {
40 | /**
41 | * Collection
42 | */
43 | $resultCollection = self::collectionConstructor();
44 | if ($count === 0) {
45 | return $resultCollection;
46 | }
47 |
48 | $collectionSize = $collection->size();
49 | $expectedCount = $count;
50 |
51 | if ($collectionSize < $expectedCount) {
52 | return $resultCollection;
53 | }
54 |
55 | $rest = $collectionSize;
56 | $generated = 0;
57 | $multiplicity = (int)round($collectionSize / $expectedCount);
58 |
59 | $rangeRandomizer = static function () use ($multiplicity): int {
60 | return random_int(0, $multiplicity - 1);
61 | };
62 |
63 | $trier = static function () use (& $generated, & $rest, $expectedCount, $rangeRandomizer): bool {
64 | $rest--;
65 | if ($generated === $expectedCount) {
66 | return false;
67 | }
68 | if ($generated + $rest + 1 <= $expectedCount) {
69 | return true;
70 | }
71 | if ($rangeRandomizer() !== 0) {
72 | return false;
73 | }
74 | $generated++;
75 | return true;
76 | };
77 |
78 | return $collection
79 | ->stream()
80 | ->filter($trier)
81 | ->getCollection();
82 | };
83 | }
84 |
85 | /**
86 | * Returns Closure that split collection into sub collections with $size
87 | * @param int $size
88 | * @return Closure
89 | */
90 | public static function chunk(int $size): Closure
91 | {
92 | return static function (Collection $collection) use ($size): Collection {
93 | $chunkCollection = self::collectionConstructor();
94 | $currentChunk = self::collectionConstructor();
95 | $pointer = $size;
96 | $collection
97 | ->stream()
98 | ->each(static function ($el) use ($size, $chunkCollection, & $currentChunk, & $pointer) {
99 | $pointer--;
100 | $currentChunk->add($el);
101 |
102 | if ($pointer === 0) {
103 | $chunkCollection->add($currentChunk);
104 | $currentChunk = self::collectionConstructor();
105 | $pointer = $size;
106 | }
107 | })
108 | ;
109 | return $chunkCollection;
110 | };
111 | }
112 |
113 | /**
114 | * Returns Closure that collapses a collection of arrays into a single, flat collection
115 | * @param int $depth Depth of collapses. The 0 value is without
116 | * @return Closure
117 | */
118 | public static function collapse(int $depth = 0): Closure
119 | {
120 | if ($depth === 0) {
121 | $depth = null;
122 | }
123 | return static function (Collection $collection) use ($depth): Collection {
124 | $flatIterable = static function (iterable $collection, $depth) use (& $flatIterable): array {
125 | $goToDepth = $depth > 0 || $depth === null;
126 | if ($depth !== null) {
127 | $depth--;
128 | }
129 |
130 | $res = [];
131 | foreach ($collection as $item) {
132 | if (is_iterable($item) && $goToDepth) {
133 | $toPush = $flatIterable($item, $depth);
134 | array_unshift($toPush, $res);
135 | array_push(...$toPush);
136 | $res = array_shift($toPush);
137 | } else {
138 | $res[] = $item;
139 | }
140 | }
141 | return $res;
142 | };
143 |
144 | return self::collectionConstructor($flatIterable($collection, $depth));
145 | };
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Stream.php:
--------------------------------------------------------------------------------
1 |
108 | * @return Stream
109 | */
110 | public function sortBy(callable $extractor): Stream;
111 |
112 | /**
113 | * @param callable $comparator
114 | * @return Stream
115 | */
116 | public function sortDesc(callable $comparator): Stream;
117 |
118 | /**
119 | * Sorts desc elements with value extractor via common scalar sort method
120 | * @param callable $extractor function for getting value
121 | * @return Stream
122 | */
123 | public function sortByDesc(callable $extractor): Stream;
124 |
125 | /**
126 | * Placed elements in reverse order
127 | * @return Stream
128 | */
129 | public function reverse(): Stream;
130 |
131 | /**
132 | * Reduce collection to single value with accumulator
133 | * @param callable $accumulator
134 | * @param mixed|null $initialValue
135 | * @return mixed
136 | */
137 | public function reduce(callable $accumulator, $initialValue = null);
138 |
139 | /**
140 | * Limits amount of stream collection elements
141 | * @param int $size
142 | * @return Stream
143 | */
144 | public function limit(int $size): Stream;
145 |
146 | /**
147 | * If condition is false stream became inert with stream functions and if next call will be true stream became operated again
148 | * @param bool $condition
149 | * @return Stream
150 | */
151 | public function when(bool $condition): Stream;
152 |
153 | /**
154 | * Returns collection
155 | * @return Collection
156 | */
157 | public function getCollection(): Collection;
158 |
159 | /**
160 | * Clear current condition. The same as when(true)
161 | * @return Stream
162 | */
163 | public function always(): Stream;
164 |
165 | /**
166 | * Returns array as collection represent
167 | * @return array
168 | */
169 | public function toArray(): array;
170 |
171 | /**
172 | * Creates a set structure
173 | * @return Set
174 | */
175 | public function getSet(): Set;
176 | }
177 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Group/Group.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | namespace WS\Utils\Collections\Functions\Group;
7 |
8 | use WS\Utils\Collections\Collection;
9 | use WS\Utils\Collections\CollectionFactory;
10 | use WS\Utils\Collections\Functions\ObjectFunctions;
11 |
12 | class Group
13 | {
14 |
15 | private $key;
16 | private $aggregators;
17 |
18 | public function __construct(string $key)
19 | {
20 | $this->key = $key;
21 | }
22 |
23 | public function __invoke(Collection $collection): array
24 | {
25 | $groupedResult = $this->group($collection);
26 | if (!$this->aggregators) {
27 | return $groupedResult;
28 | }
29 | return $this->applyAggregators($groupedResult);
30 | }
31 |
32 | private function group(Collection $collection): array
33 | {
34 | $result = [];
35 | foreach ($collection as $element) {
36 | if (!$groupKey = ObjectFunctions::getPropertyValue($element, $this->key)) {
37 | continue;
38 | }
39 | if (!isset($result[$groupKey])) {
40 | $result[$groupKey] = CollectionFactory::empty();
41 | }
42 | $result[$groupKey]->add($element);
43 | }
44 | return $result;
45 | }
46 |
47 | private function applyAggregators(array $groupedResult): array
48 | {
49 | $result = [];
50 | foreach ($groupedResult as $groupKey => $items) {
51 | foreach ($this->aggregators as $item) {
52 | [$destKey, $aggregator] = $item;
53 | $result[$groupKey][$destKey] = $aggregator($items);
54 | }
55 | }
56 | return $result;
57 | }
58 |
59 | /**
60 | * Create new instance of Group and use $key as group key
61 | * @param string $key
62 | * @return static
63 | */
64 | public static function by(string $key): self
65 | {
66 | return new self($key);
67 | }
68 |
69 | /**
70 | * Will calculate sum of items based on $sourceKey and put it to $destKey
71 | * @param string $sourceKey
72 | * @param string|null $destKey
73 | * @return $this
74 | */
75 | public function sum(string $sourceKey, string $destKey = null): self
76 | {
77 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\Sum($sourceKey));
78 | }
79 |
80 | /**
81 | * Will calculate minimum value of items based on $sourceKey and put it to $destKey
82 | * @param string $sourceKey
83 | * @param string|null $destKey
84 | * @return $this
85 | */
86 | public function min(string $sourceKey, string $destKey = null): self
87 | {
88 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\Min($sourceKey));
89 | }
90 |
91 | /**
92 | * Will calculate maximum value of items based on $sourceKey and put it to $destKey
93 | * @param string $sourceKey
94 | * @param string|null $destKey
95 | * @return $this
96 | */
97 | public function max(string $sourceKey, string $destKey = null): self
98 | {
99 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\Max($sourceKey));
100 | }
101 |
102 | /**
103 | * Will calculate average value of items based on $sourceKey and put it to $destKey
104 | * @param string $sourceKey
105 | * @param string|null $destKey
106 | * @return $this
107 | */
108 | public function avg(string $sourceKey, string $destKey = null): self
109 | {
110 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\Avg($sourceKey));
111 | }
112 |
113 | /**
114 | * Will find unique values of items based on $sourceKey and put it to $destKey
115 | * @param string $sourceKey
116 | * @param string|null $destKey
117 | * @return $this
118 | */
119 | public function addToSet(string $sourceKey, string $destKey = null): self
120 | {
121 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\AddToSet($sourceKey));
122 | }
123 |
124 | /**
125 | * Will return first value of items based on $sourceKey and put it to $destKey
126 | * @param string $sourceKey
127 | * @param string|null $destKey
128 | * @return $this
129 | */
130 | public function first(string $sourceKey, string $destKey = null): self
131 | {
132 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\First($sourceKey));
133 | }
134 |
135 | /**
136 | * Will return last value of items based on $sourceKey and put it to $destKey
137 | * @param string $sourceKey
138 | * @param string|null $destKey
139 | * @return $this
140 | */
141 | public function last(string $sourceKey, string $destKey = null): self
142 | {
143 | return $this->addAggregator($destKey ?? $sourceKey, new Aggregator\Last($sourceKey));
144 | }
145 |
146 | /**
147 | * Will calculate count of items in group and put it to $destKey
148 | * @param string $destKey
149 | * @return $this
150 | */
151 | public function count(string $destKey): self
152 | {
153 | return $this->addAggregator($destKey, new Aggregator\Count());
154 | }
155 |
156 | /**
157 | * Add custom $aggregator with interface Aggregator\Aggregator|
158 | * @param string $destKey
159 | * @param callable $aggregator
160 | * @return $this
161 | */
162 | public function addAggregator(string $destKey, callable $aggregator): self
163 | {
164 | $this->aggregators[] = [$destKey, $aggregator];
165 | return $this;
166 | }
167 |
168 | }
169 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/ConditionsStreamTest.php:
--------------------------------------------------------------------------------
1 | stream()
25 | ->when(false)
26 | ->filter(Predicates::lessThan(4))
27 | ->getCollection()
28 | ;
29 | self::assertEquals(10, $collection->size());
30 | }
31 |
32 | /**
33 | * @test
34 | */
35 | public function keepingOriginalStream(): void
36 | {
37 | $collection = CollectionFactory::numbers(10)
38 | ->stream()
39 | ->always()
40 | ->filter(Predicates::eachEven())
41 | ->getCollection()
42 | ;
43 |
44 | self::assertEquals(5, $collection->size());
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function obtainNormalStreamFromDummy(): void
51 | {
52 | $collection = CollectionFactory::numbers(10)
53 | ->stream()
54 | ->when(false)
55 | ->filter(Predicates::lessOrEqual(5))
56 | ->when(true)
57 | ->filter(Predicates::greaterOrEqual(5))
58 | ->getCollection()
59 | ;
60 |
61 | self::assertThat($collection, CollectionIsEqual::to([5, 6, 7, 8, 9]));
62 | }
63 |
64 | /**
65 | * @test
66 | */
67 | public function obtainNormalStreamWithAlwaysCondition(): void
68 | {
69 | $collection = CollectionFactory::numbers(10)
70 | ->stream()
71 | ->when(false)
72 | ->filter(Predicates::lessOrEqual(5))
73 | ->always()
74 | ->filter(Predicates::greaterOrEqual(5))
75 | ->getCollection()
76 | ;
77 |
78 | self::assertThat($collection, CollectionIsEqual::to([5, 6, 7, 8, 9]));
79 | }
80 |
81 | /**
82 | * @test
83 | */
84 | public function dontObtainNormalStreamFromDummy(): void
85 | {
86 | $sourceCollection = CollectionFactory::numbers(10);
87 | $resultCollection = $sourceCollection
88 | ->stream()
89 | ->when(false)
90 | ->filter(Predicates::lessOrEqual(5))
91 | ->when(false)
92 | ->filter(Predicates::greaterOrEqual(5))
93 | ->getCollection()
94 | ;
95 |
96 | self::assertThat($resultCollection, CollectionIsEqual::to($sourceCollection));
97 | }
98 |
99 | /**
100 | * @test
101 | */
102 | public function usingWithoutDummyDecorator(): void
103 | {
104 | $collection = CollectionFactory::numbers(10)
105 | ->stream()
106 | ->when(true)
107 | ->filter(Predicates::lessOrEqual(6))
108 | ->when(true)
109 | ->filter(Predicates::greaterOrEqual(4))
110 | ->getCollection()
111 | ;
112 |
113 | self::assertThat($collection, CollectionIsEqual::to([4, 5, 6]));
114 | }
115 |
116 | public function streamModifiers(): array
117 | {
118 | return [
119 | ['each', new InvokeCounter()],
120 | ['walk', new InvokeCounter()],
121 | ['filter', new InvokeCounter()],
122 | ['reorganize', new InvokeCounter()],
123 | ['map', new InvokeCounter()],
124 | ['sort', new InvokeCounter()],
125 | ['sortBy', new InvokeCounter()],
126 | ['sortDesc', new InvokeCounter()],
127 | ['sortByDesc', new InvokeCounter()],
128 | ['reverse'],
129 | ['limit', 2]
130 | ];
131 | }
132 |
133 | /**
134 | * @dataProvider streamModifiers
135 | * @test
136 | * @param $method
137 | * @param mixed ...$args
138 | */
139 | public function shouldNotModifyStream($method, ...$args): void
140 | {
141 | $stream = CollectionFactory::numbers(10)
142 | ->stream()
143 | ->when(false)
144 | ;
145 | $sourceCollection = $stream
146 | ->getCollection();
147 |
148 | call_user_func_array([$stream, $method], $args);
149 |
150 | foreach ($args as $arg) {
151 | if ($arg instanceof InvokeCounter && $arg->countOfInvokes() > 0) {
152 | self::fail("Modifier callback shouldn't be called");
153 | }
154 | }
155 |
156 | self::assertThat($sourceCollection, CollectionIsEqual::to($stream->getCollection()));
157 | }
158 |
159 | public function dummyStreamWrapperMethods(): array
160 | {
161 | $f = static function() {};
162 | return [
163 | ['allMatch', $f],
164 | ['anyMatch', $f],
165 | ['collect', $f],
166 | ['findAny', $f],
167 | ['findFirst', $f],
168 | ['findLast', $f],
169 | ['min', $f],
170 | ['max', $f],
171 | ['reduce', $f],
172 | ['getCollection']
173 | ];
174 | }
175 |
176 | /**
177 | * @dataProvider dummyStreamWrapperMethods
178 | * @test
179 | * @param $method
180 | * @param array $args
181 | */
182 | public function dummyStreamAsWrapper($method, ...$args): void
183 | {
184 | /** @var Stream|MockObject $mockStream */
185 | $mockStream = $this->getMockBuilder(Stream::class)
186 | ->getMock()
187 | ;
188 |
189 | $mockStream
190 | ->expects(new InvokedCount(1))
191 | ->method($method)
192 | ;
193 |
194 | $dummyStream = new DummyStreamDecorator($mockStream);
195 | call_user_func_array([$dummyStream, $method], $args);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/MapInterfaceTestTrait.php:
--------------------------------------------------------------------------------
1 | createInstance();
27 | $instance->put($obj1, null);
28 | self::assertTrue($instance->containsKey($obj2));
29 |
30 | self::assertNull($instance->get($obj2));
31 | }
32 |
33 | /**
34 | * @test
35 | */
36 | public function checkCount(): void
37 | {
38 | $instance = $this->createInstance();
39 | $instance->put(1,1);
40 | $instance->put(2,1);
41 | $instance->put(2,1);
42 | $instance->put(3,1);
43 | $instance->put(4,1);
44 | $instance->put(5,1);
45 |
46 | $instance->remove(4);
47 |
48 | self::assertEquals(4, $instance->size());
49 | }
50 |
51 | /**
52 | * @test
53 | */
54 | public function iterate(): void
55 | {
56 | $instance = $this->createInstance();
57 | for ($i = 10; $i < 20; $i++) {
58 | $instance->put($i, $i+1);
59 | }
60 |
61 | $i = 10;
62 | foreach ($instance as $k => $v) {
63 | self::assertSame($k, $v - 1);
64 | self::assertSame($i, $k);
65 | $i++;
66 | }
67 | }
68 |
69 | /**
70 | * @test
71 | */
72 | public function keySetGetting(): void
73 | {
74 | $instance = $this->createInstance();
75 | $instance->put(1,1);
76 | $instance->put(2,1);
77 | $instance->put(2,1);
78 | $instance->put(3,1);
79 | $instance->put(4,1);
80 | $instance->put(null,1);
81 |
82 | $instance->remove(4);
83 |
84 | $set = $instance->keys();
85 |
86 | self::assertEquals(4, $set->size());
87 | }
88 |
89 | /**
90 | * @test
91 | */
92 | public function valuesGetting(): void
93 | {
94 | $instance = $this->createInstance();
95 | $instance->put(1,1);
96 | $instance->put(2,1);
97 | $instance->put(2,1);
98 | $instance->put(3,1);
99 | $instance->put(4,1);
100 | $instance->put(5,1);
101 |
102 | $instance->remove(4);
103 |
104 | $values = $instance->values();
105 |
106 | self::assertEquals(4, $values->size());
107 | }
108 |
109 | /**
110 | * @test
111 | */
112 | public function hashCodeAwareChecking(): void
113 | {
114 | $o1 = new TestInteger(1);
115 | $o2 = new TestInteger(2);
116 | $o3 = new TestInteger(3);
117 |
118 | $instance = $this->createInstance();
119 | $instance->put($o1, null);
120 | $instance->put($o2, null);
121 | $instance->put($o3, null);
122 |
123 | self::assertTrue($instance->containsKey(new TestInteger(1)));
124 | self::assertTrue($instance->containsKey(new TestInteger(2)));
125 | self::assertTrue($instance->containsKey(new TestInteger(3)));
126 | }
127 |
128 | /**
129 | * @test
130 | */
131 | public function containsValueChecking(): void
132 | {
133 | $instance = $this->createInstance();
134 | $instance->put(1, 1);
135 | $instance->put(2, 2);
136 | $instance->put(3, 3);
137 |
138 | self::assertTrue($instance->containsValue(1));
139 | self::assertFalse($instance->containsValue(4));
140 | }
141 |
142 | /**
143 | * @test
144 | */
145 | public function getting(): void
146 | {
147 | $instance = $this->createInstance();
148 | $instance->put(1, 1);
149 | $instance->put(2, 2);
150 | $instance->put(3, 3);
151 |
152 | self::assertNotNull($instance->get(1));
153 | self::assertNull($instance->get(4));
154 | }
155 |
156 | /**
157 | * @test
158 | */
159 | public function usingSimpleArrayAsKey(): void
160 | {
161 | $instance = $this->createInstance();
162 | $instance->put([1, 2, 3], 1);
163 | $instance->put([1, 2, 3], 2);
164 | $instance->put([1, 3, 3], 3);
165 |
166 | self::assertEquals(2, $instance->size());
167 | self::assertTrue($instance->containsKey([1, 2, 3]));
168 | self::assertTrue($instance->containsKey([1, 3, 3]));
169 | }
170 |
171 | /**
172 | * @test
173 | */
174 | public function removingOfAbsent(): void
175 | {
176 | $instance = $this->createInstance();
177 | $instance->put(1, 1);
178 | $instance->put(2, 2);
179 | $instance->put(3, 3);
180 |
181 | self::assertFalse($instance->remove(4));
182 | }
183 |
184 | /**
185 | * @test
186 | */
187 | public function usingFunctionAsKey(): void
188 | {
189 | $instance = $this->createInstance();
190 | $instance->put(static function () {}, null);
191 | self::assertFalse($instance->containsKey(static function () {}));
192 | }
193 |
194 | /**
195 | * @test
196 | */
197 | public function unsupportedKeyType(): void
198 | {
199 | $this->expectException(Exception::class);
200 | $map = $this->createInstance();
201 | $f = null;
202 | try {
203 | $f = fopen(__FILE__, 'rb');
204 | $map->put($f, null);
205 | } catch (Exception $exception) {
206 | fclose($f);
207 | /** @noinspection PhpUnhandledExceptionInspection */
208 | throw $exception;
209 | }
210 | }
211 |
212 | /**
213 | * @test
214 | */
215 | public function foreachObjectKeyValueChecking(): void
216 | {
217 | $map = $this->createInstance();
218 |
219 | $map->put(new SplObjectStorage(), 1);
220 | $map->put(new SplObjectStorage(), 2);
221 | $map->put(new SplObjectStorage(), 3);
222 |
223 | foreach ($map as $splObjectStorage => $intValue) {
224 | self::assertThat($splObjectStorage, self::isInstanceOf(SplObjectStorage::class));
225 | }
226 | }
227 |
228 | /**
229 | * @test
230 | */
231 | public function foreachAnyKeyValueChecking(): void
232 | {
233 | $map = $this->createInstance();
234 |
235 | $map->put(null, 1);
236 | $map->put(false, 2);
237 | $map->put(true, 3);
238 |
239 | foreach ($map as $k => $int) {
240 | switch ($int) {
241 | case 1:
242 | self::assertThat($k, self::isNull());
243 | break;
244 | case 2:
245 | self::assertThat($k, self::isFalse());
246 | break;
247 | case 3:
248 | self::assertThat($k, self::isTrue());
249 | break;
250 | }
251 | }
252 | }
253 |
254 | /**
255 | * @test
256 | */
257 | public function streamGetting(): void
258 | {
259 | $map = $this->createInstance();
260 |
261 | $map->put('1', 1);
262 | $map->put('2', 2);
263 | $map->put('3', 3);
264 |
265 | $stream = $map->stream();
266 | self::assertThat($stream, self::isInstanceOf(Stream::class));
267 |
268 | self::assertGreaterThan(0, $stream->getCollection()->size());
269 |
270 | $stream
271 | ->each(static function (MapEntry $mapEntry) {
272 | self::assertThat($mapEntry->getKey() === ''.$mapEntry->getValue(), self::isTrue());
273 | });
274 | }
275 | }
276 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/CollectionInterfaceTestTrait.php:
--------------------------------------------------------------------------------
1 | createInstance(1, 2);
20 | $this->assertEquals(2, $instance->size());
21 |
22 | $this->assertTrue($instance->add(-76));
23 | $this->assertEquals(3, $instance->size());
24 |
25 | $anotherInstance = $this->createInstance();
26 | $this->assertTrue($anotherInstance->add('string'));
27 | $anotherInstance->merge($instance);
28 | $this->assertEquals(4, $anotherInstance->size());
29 | }
30 |
31 | /**
32 | * @test
33 | */
34 | public function merging(): void
35 | {
36 | /** @var Collection $collection */
37 | $collection = $this->createInstance(1, 2);
38 |
39 | /** @var Collection $anotherCollection */
40 | $anotherCollection = $this->createInstance(3, 4, 5);
41 |
42 | $clonedCollection = clone $collection;
43 | $collection->merge($anotherCollection);
44 | $this->assertThat($collection, CollectionContainsSameElements::with([1, 2, 3, 4, 5]));
45 | $anotherCollection->merge($clonedCollection);
46 | $this->assertThat($anotherCollection, CollectionContainsSameElements::with([3, 4, 5, 1, 2]));
47 | }
48 |
49 | /**
50 | * @test
51 | */
52 | public function clearing(): void
53 | {
54 | /** @var Collection $collection */
55 | $collection = $this->createInstance(27, 'string');
56 | $collection->clear();
57 | $this->assertEquals(0, $collection->size());
58 | }
59 |
60 | /**
61 | * @test
62 | */
63 | public function removing(): void
64 | {
65 | /** @var Collection $collection */
66 | $collection = $this->createInstance(27, 'string', -11, 50);
67 |
68 | $this->assertTrue($collection->remove(-11));
69 | $this->assertEquals(3, $collection->size());
70 | $this->assertThat($collection, CollectionContainsSameElements::with([27, 'string', 50]));
71 |
72 | $this->assertTrue($collection->remove('string'));
73 | $this->assertEquals(2, $collection->size());
74 | $this->assertThat($collection, CollectionContainsSameElements::with([27, 50]));
75 |
76 | $this->assertFalse($collection->remove(89));
77 | $this->assertEquals(2, $collection->size());
78 | $this->assertThat($collection, CollectionContainsSameElements::with([27, 50]));
79 | }
80 |
81 | /**
82 | * @test
83 | */
84 | public function removingAbsent(): void
85 | {
86 | /** @var Collection $collection */
87 | $collection = $this->createInstance(1, 2, 3);
88 | $removingRes = $collection->remove(4);
89 |
90 | $this->assertFalse($removingRes);
91 | $this->assertEquals(3, $collection->size());
92 | }
93 |
94 | /**
95 | * @test
96 | */
97 | public function removingFromEmptyCollection(): void
98 | {
99 | $collection = $this->createInstance();
100 |
101 | $removingRes = $collection->remove(4);
102 |
103 | $this->assertFalse($removingRes);
104 | $this->assertEquals(0, $collection->size());
105 | }
106 |
107 | /**
108 | * @test
109 | */
110 | public function containingCheck(): void
111 | {
112 | $collction = $this->createInstance(27, 'string', -11, 50);
113 | $this->assertTrue($collction->contains('string'));
114 | $this->assertTrue($collction->contains(-11));
115 | $this->assertFalse($collction->contains(11));
116 | }
117 |
118 | /**
119 | * @test
120 | */
121 | public function equivalence(): void
122 | {
123 | $collection = $this->createInstance(189, 11, 789);
124 | $anotherCollection = $this->createInstance(189, 11, 789);
125 | $this->assertTrue($collection->equals($anotherCollection));
126 | $this->assertTrue($anotherCollection->equals($collection));
127 | }
128 |
129 | /**
130 | * @test
131 | */
132 | public function sizeDetection(): void
133 | {
134 | $collection = $this->createInstance(27, 'string', -11, 50);
135 | $this->assertEquals(4, $collection->size());
136 | $collection->remove(-11);
137 | $this->assertEquals(3, $collection->size());
138 | $collection->add('anotherString');
139 | $this->assertEquals(4, $collection->size());
140 | }
141 |
142 | /**
143 | * @test
144 | */
145 | public function emptiness(): void
146 | {
147 | $collection = $this->createInstance(27, 'string', -11, 50);
148 | $this->assertFalse($collection->isEmpty());
149 | $collection->clear();
150 | $this->assertTrue($collection->isEmpty());
151 | }
152 |
153 | /**
154 | * @test
155 | */
156 | public function arrayGenerating(): void
157 | {
158 | $collection = $this->createInstance(27, 'string', -11, 50);
159 | $this->assertThat($collection, CollectionContainsSameElements::with([27, 'string', -11, 50]));
160 | }
161 |
162 | /**
163 | * @test
164 | */
165 | public function coping(): void
166 | {
167 | /** @var Collection $i1 */
168 | $i1 = $this->createInstance(3, 2, 1);
169 | $i2 = $i1->copy();
170 |
171 | $this->assertEquals($i1->toArray(), $i2->toArray());
172 | $this->assertNotSame($i1, $i2);
173 | }
174 |
175 | /**
176 | * @test
177 | */
178 | public function streaming(): void
179 | {
180 | $i = $this->createInstance();
181 |
182 | $this->assertInstanceOf(Stream::class, $i->stream());
183 | }
184 |
185 | /**
186 | * @test
187 | */
188 | public function addingGroupOffElements(): void
189 | {
190 | /** @var Collection $i */
191 | $i = $this->createInstance(1, 2, 3);
192 |
193 | $i->addAll([4, 5, 6]);
194 |
195 | $this->assertEquals(6, $i->size());
196 | $this->assertThat($i, CollectionContainsSameElements::with([1, 2, 3, 4, 5, 6]));
197 | }
198 |
199 | /**
200 | * @test
201 | */
202 | public function removingWithCollectionAwareInterface(): void
203 | {
204 | $i1 = new TestInteger(1);
205 | $i2 = new TestInteger(2);
206 | $i3 = new TestInteger(3);
207 |
208 | /** @var Collection $collection */
209 | $collection = $this->createInstance($i1, $i2, $i3);
210 | $res = $collection->remove(new TestInteger(2));
211 |
212 | $this->assertTrue($res);
213 | $this->assertEquals(2, $collection->size());
214 | $this->assertFalse($collection->contains($i2));
215 | }
216 |
217 | /**
218 | * @test
219 | */
220 | public function removingByAwareCollectionInterfaceWithMixedCollection(): void
221 | {
222 | $i1 = new TestInteger(1);
223 | $i2 = new TestInteger(2);
224 | $i3 = new TestInteger(3);
225 | $i4 = 4;
226 |
227 | /** @var Collection $collection */
228 | $collection = $this->createInstance($i1, $i2, $i3,$i4);
229 |
230 | $res = $collection->remove(new TestInteger(2));
231 |
232 | $this->assertTrue($res);
233 | $this->assertEquals(3, $collection->size());
234 | $this->assertFalse($collection->contains($i2));
235 |
236 | $this->assertTrue($collection->contains(4));
237 |
238 | $res = $collection->remove(4);
239 |
240 | $this->assertTrue($res);
241 | $this->assertEquals(2, $collection->size());
242 | $this->assertFalse($collection->contains(4));
243 |
244 | $res = $collection->remove(new TestInteger(2));
245 |
246 | $this->assertFalse($res);
247 | $this->assertEquals(2, $collection->size());
248 | $this->assertFalse($collection->contains($i2));
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/tests/WS/Utils/Collections/PredicatesFunctionsTest.php:
--------------------------------------------------------------------------------
1 | assertEquals(5, $collection->size());
26 |
27 | $notNullSize = $collection
28 | ->stream()
29 | ->filter(Predicates::notNull())
30 | ->getCollection()
31 | ->size()
32 | ;
33 | $this->assertEquals(3, $notNullSize);
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function notResistanceFilter(): void
40 | {
41 | $collection = self::toCollection(null, 1, 2, 3, null);
42 |
43 | $after = $collection
44 | ->stream()
45 | ->filter(Predicates::notResistance())
46 | ->getCollection()
47 | ;
48 |
49 | $this->assertThat($after, CollectionIsEqual::to($collection));
50 | }
51 |
52 | /**
53 | * @test
54 | */
55 | public function lockFilter(): void
56 | {
57 | $collection = self::toCollection(null, 1, 2, 3, null)
58 | ->stream()
59 | ->filter(Predicates::lock())
60 | ->getCollection()
61 | ;
62 |
63 | $this->assertTrue($collection->isEmpty());
64 | }
65 |
66 | /**
67 | * @test
68 | */
69 | public function eachEvenFunctionChecking():void
70 | {
71 | $collection = self::toCollection(1, 2, 3, 4, 5)
72 | ->stream()
73 | ->filter(Predicates::eachEven())
74 | ->getCollection()
75 | ;
76 | $this->assertThat($collection, CollectionIsEqual::to([2, 4]));
77 | }
78 |
79 | /**
80 | * @test
81 | */
82 | public function eachNthChecking(): void
83 | {
84 | $collection = self::toCollection(1, 2, 3, 4, 5, 6, 7)
85 | ->stream()
86 | ->filter(Predicates::nth(3))
87 | ->getCollection()
88 | ;
89 | $this->assertThat($collection, CollectionIsEqual::to([3, 6]));
90 | }
91 |
92 | /**
93 | * @test
94 | */
95 | public function matchingPropertyFiltering(): void
96 | {
97 | $o1 = (new ExampleObject())->setName('first');
98 | $o2 = (new ExampleObject())->setName('first');
99 | $o3 = (new ExampleObject())->setName('second');
100 | $o4 = (new ExampleObject())->setName('third');
101 |
102 | $collection = self::toCollection($o1, $o2, $o3, $o4);
103 |
104 | $filtered = $collection
105 | ->stream()
106 | ->filter(Predicates::where('name', 'first'))
107 | ->getCollection()
108 | ;
109 | $this->assertThat($filtered, CollectionIsEqual::to([$o1, $o2]));
110 |
111 | $filtered = $collection
112 | ->stream()
113 | ->filter(Predicates::whereNot('name', 'third'))
114 | ->getCollection()
115 | ;
116 | $this->assertThat($filtered, CollectionIsEqual::to([$o1, $o2, $o3]));
117 |
118 | $filtered = $collection
119 | ->stream()
120 | ->filter(Predicates::whereIn('name', ['second', 'third']))
121 | ->getCollection()
122 | ;
123 | $this->assertThat($filtered, CollectionIsEqual::to([$o3, $o4]));
124 |
125 | $filtered = $collection
126 | ->stream()
127 | ->filter(Predicates::whereNotIn('name', ['second', 'third']))
128 | ->getCollection()
129 | ;
130 | $this->assertThat($filtered, CollectionIsEqual::to([$o1, $o2]));
131 | }
132 |
133 | /**
134 | * @test
135 | */
136 | public function comparingPropertiesFiltering(): void
137 | {
138 | $o1 = (new ExampleObject())->setField(1);
139 | $o2 = (new ExampleObject())->setField(2);
140 | $o3 = (new ExampleObject())->setField(3);
141 | $o4 = (new ExampleObject())->setField(4);
142 |
143 | $collection = self::toCollection($o1, $o2, $o3, $o4);
144 |
145 | $filtered = $collection
146 | ->stream()
147 | ->filter(Predicates::whereGreaterThan('field', 2))
148 | ->getCollection()
149 | ;
150 | $this->assertThat($filtered, CollectionIsEqual::to([$o3, $o4]));
151 |
152 | $filtered = $collection
153 | ->stream()
154 | ->filter(Predicates::whereLessThan('field', 2))
155 | ->getCollection()
156 | ;
157 | $this->assertThat($filtered, CollectionIsEqual::to([$o1]));
158 |
159 | $filtered = $collection
160 | ->stream()
161 | ->filter(Predicates::whereGreaterOrEqual('field', 2))
162 | ->getCollection()
163 | ;
164 | $this->assertThat($filtered, CollectionIsEqual::to([$o2, $o3, $o4]));
165 |
166 | $filtered = $collection
167 | ->stream()
168 | ->filter(Predicates::whereLessOrEqual('field', 2))
169 | ->getCollection()
170 | ;
171 | $this->assertThat($filtered, CollectionIsEqual::to([$o1, $o2]));
172 | }
173 |
174 | /**
175 | * @test
176 | */
177 | public function matchingValuesFiltering(): void
178 | {
179 | $collection = self::toCollection(1, 2, 3, 4, 5);
180 |
181 | $filtered = $collection
182 | ->stream()
183 | ->filter(Predicates::equal(2))
184 | ->getCollection()
185 | ;
186 | $this->assertThat($filtered, CollectionIsEqual::to([2]));
187 |
188 | $filtered = $collection
189 | ->stream()
190 | ->filter(Predicates::not(2))
191 | ->getCollection()
192 | ;
193 | $this->assertThat($filtered, CollectionIsEqual::to([1, 3, 4, 5]));
194 |
195 | $filtered = $collection
196 | ->stream()
197 | ->filter(Predicates::greaterThan(3))
198 | ->getCollection()
199 | ;
200 | $this->assertThat($filtered, CollectionIsEqual::to([4, 5]));
201 |
202 | $filtered = $collection
203 | ->stream()
204 | ->filter(Predicates::greaterOrEqual(3))
205 | ->getCollection()
206 | ;
207 | $this->assertThat($filtered, CollectionIsEqual::to([3, 4, 5]));
208 |
209 | $filtered = $collection
210 | ->stream()
211 | ->filter(Predicates::lessThan(3))
212 | ->getCollection()
213 | ;
214 | $this->assertThat($filtered, CollectionIsEqual::to([1, 2]));
215 |
216 | $filtered = $collection
217 | ->stream()
218 | ->filter(Predicates::lessOrEqual(3))
219 | ->getCollection()
220 | ;
221 | $this->assertThat($filtered, CollectionIsEqual::to([1, 2, 3]));
222 |
223 | $filtered = $collection
224 | ->stream()
225 | ->filter(Predicates::in([3, 4, 5, 6]))
226 | ->getCollection()
227 | ;
228 | $this->assertThat($filtered, CollectionIsEqual::to([3, 4, 5]));
229 |
230 | $filtered = $collection
231 | ->stream()
232 | ->filter(Predicates::notIn([3, 4, 5, 6]))
233 | ->getCollection()
234 | ;
235 | $this->assertThat($filtered, CollectionIsEqual::to([1, 2]));
236 | }
237 |
238 | /**
239 | * @test
240 | */
241 | public function firstValueStreaming(): void
242 | {
243 | // Arrange
244 | $FIRST_VALUE = 5;
245 | $stream = self::toCollection($FIRST_VALUE, 6, 7)->stream();
246 |
247 | $runningCount = 0;
248 | // Act
249 | $stream
250 | ->filter(Predicates::first())
251 | ->each(static function ($value) use ($FIRST_VALUE, & $runningCount) {
252 | $runningCount++;
253 | self::assertEquals($FIRST_VALUE, $value);
254 | });
255 |
256 | // Assert
257 | self::assertEquals(1, $runningCount);
258 | }
259 | }
260 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/SerialStream.php:
--------------------------------------------------------------------------------
1 | list = $collection->copy();
21 | } else {
22 | $this->list = $this->emptyList();
23 | $this->list->addAll($collection->toArray());
24 | }
25 | }
26 |
27 | /**
28 | * @inheritDoc
29 | */
30 | public function each(callable $consumer): Stream
31 | {
32 | $i = 0;
33 | foreach ($this->list as $item) {
34 | $consumer($item, $i++);
35 | }
36 | return $this;
37 | }
38 |
39 | /**
40 | * @inheritDoc
41 | */
42 | public function filter(callable $predicate): Stream
43 | {
44 | $collection = $this->list;
45 | $this->list = $this->emptyList();
46 |
47 | foreach ($collection as $item) {
48 | if ($predicate($item)) {
49 | $this->list->add($item);
50 | }
51 | }
52 |
53 | return $this;
54 | }
55 |
56 | public function reorganize(callable $reorganizer): Stream
57 | {
58 | $reorganizedCollection = $reorganizer($this->list->copy());
59 | if (! $reorganizedCollection instanceof Collection) {
60 | throw new RuntimeException('Result set of reorganizer call must be instance of Collection interface');
61 | }
62 | $this->list = $reorganizedCollection;
63 |
64 | return $this;
65 | }
66 |
67 | /**
68 | * @inheritDoc
69 | */
70 | public function allMatch(callable $predicate): bool
71 | {
72 | foreach ($this->list as $item) {
73 | if (!$predicate($item)) {
74 | return false;
75 | }
76 | }
77 | return true;
78 | }
79 |
80 | /**
81 | * @inheritDoc
82 | */
83 | public function anyMatch(callable $predicate): bool
84 | {
85 | foreach ($this->list as $item) {
86 | if ($predicate($item)) {
87 | return true;
88 | }
89 | }
90 |
91 | return false;
92 | }
93 |
94 | /**
95 | * @inheritDoc
96 | */
97 | public function map(callable $converter): Stream
98 | {
99 | $collection = $this->list;
100 | $this->list = $this->emptyList();
101 |
102 | foreach ($collection as $item) {
103 | $this->list->add($converter($item));
104 | }
105 | return $this;
106 | }
107 |
108 | /**
109 | * @inheritDoc
110 | */
111 | public function sort(callable $comparator): Stream
112 | {
113 | $collection = $this->getCollection();
114 | $this->list = $this->emptyList();
115 |
116 | $array = $collection->toArray();
117 | usort($array, $comparator);
118 | foreach ($array as $item) {
119 | $this->list->add($item);
120 | }
121 |
122 | return $this;
123 | }
124 |
125 | public function sortBy(callable $extractor): Stream
126 | {
127 | $values = [];
128 | $map = [];
129 | $this->each(static function ($el) use ($extractor, & $map, & $values) {
130 | $value = $extractor($el);
131 | if (!is_scalar($value)) {
132 | throw new RuntimeException('Only scalar value can be as result of sort extractor');
133 | }
134 | $values[] = $value;
135 | $map[$value.''][] = $el;
136 | });
137 | sort($values);
138 | $newList = $this->emptyList();
139 | foreach ($values as $value) {
140 | $key = $value.'';
141 | $els = $map[$key] ?? [];
142 | $newList->addAll($els);
143 | unset($map[$key]);
144 | }
145 | $this->list = $newList;
146 |
147 | return $this;
148 | }
149 |
150 | public function sortByDesc(callable $extractor): Stream
151 | {
152 | $this->sortBy($extractor)
153 | ->reverse();
154 |
155 | return $this;
156 | }
157 |
158 | /**
159 | * @inheritDoc
160 | */
161 | public function sortDesc(callable $comparator): Stream
162 | {
163 | $this->sort($comparator)
164 | ->reverse();
165 |
166 | return $this;
167 | }
168 |
169 | public function reverse(): Stream
170 | {
171 | $array = $this->list->toArray();
172 | $reversedArray = array_reverse($array);
173 | $this->list = $this->emptyList();
174 | $this->list->addAll($reversedArray);
175 | return $this;
176 | }
177 |
178 | /**
179 | * @inheritDoc
180 | */
181 | public function collect(callable $collector)
182 | {
183 | return $collector($this->getCollection()->copy());
184 | }
185 |
186 | /**
187 | * @inheritDoc
188 | */
189 | public function findAny()
190 | {
191 | $size = $this->list->size();
192 | if ($size === 0) {
193 | return null;
194 | }
195 | /** @noinspection PhpUnhandledExceptionInspection */
196 | $rIndex = random_int(0, $size - 1);
197 | $pointer = 0;
198 | $item = null;
199 | foreach ($this->list as $item) {
200 | if ($rIndex === $pointer++) {
201 | break;
202 | }
203 | }
204 | return $item;
205 | }
206 |
207 | /**
208 | * @inheritDoc
209 | */
210 | public function findFirst(callable $filter = null)
211 | {
212 | if (!$filter) {
213 | /** @noinspection LoopWhichDoesNotLoopInspection */
214 | foreach ($this->list as $item) {
215 | return $item;
216 | }
217 | return null;
218 | }
219 | /** @noinspection LoopWhichDoesNotLoopInspection */
220 | foreach ($this->list as $item) {
221 | if ($filter($item)) {
222 | return $item;
223 | }
224 | }
225 | return null;
226 | }
227 |
228 | /**
229 | * @inheritDoc
230 | */
231 | public function min(callable $comparator)
232 | {
233 | $collection = $this->getCollection();
234 | if ($collection->size() === 0) {
235 | return null;
236 | }
237 |
238 | $array = $collection->toArray();
239 |
240 | $el = array_shift($array);
241 |
242 | foreach ($array as $item) {
243 | if ($comparator($item, $el) < 0) {
244 | $el = $item;
245 | }
246 | }
247 |
248 | return $el;
249 | }
250 |
251 | /**
252 | * @inheritDoc
253 | */
254 | public function max(callable $comparator)
255 | {
256 | $collection = $this->getCollection();
257 | if ($collection->size() === 0) {
258 | return null;
259 | }
260 |
261 | $array = $collection->toArray();
262 | $el = null;
263 |
264 | foreach ($array as $item) {
265 | if ($comparator($item, $el) > 0) {
266 | $el = $item;
267 | }
268 | }
269 |
270 | return $el;
271 | }
272 |
273 | /**
274 | * @inheritDoc
275 | */
276 | public function reduce(callable $accumulator, $initialValue = null)
277 | {
278 | $accumulate = $initialValue;
279 | foreach ($this->list as $item) {
280 | $accumulate = $accumulator($item, $accumulate);
281 | }
282 | return $accumulate;
283 | }
284 |
285 | public function getCollection(): Collection
286 | {
287 | return $this->list->copy();
288 | }
289 |
290 | private function emptyList(): Collection
291 | {
292 | return ArrayList::of();
293 | }
294 |
295 | public function findLast()
296 | {
297 | $array = $this->list->toArray();
298 | return array_pop($array);
299 | }
300 |
301 | public function walk(callable $consumer, ?int $limit = null): Stream
302 | {
303 | $iterationsCount = $limit ?? $this->list->size();
304 | foreach ($this->list as $i => $item) {
305 | $consumerRes = $consumer($item, $i);
306 | if ($consumerRes === false) {
307 | break;
308 | }
309 | if ($i + 1 >= $iterationsCount) {
310 | break;
311 | }
312 | }
313 |
314 | return $this;
315 | }
316 |
317 | public function limit(int $size): Stream
318 | {
319 | $newCollection = $this->emptyList();
320 | $this->walk(static function ($el) use ($newCollection) {
321 | $newCollection->add($el);
322 | }, $size);
323 |
324 | $this->list = $newCollection;
325 | return $this;
326 | }
327 |
328 | public function when(bool $condition): Stream
329 | {
330 | if (!$condition) {
331 | return new DummyStreamDecorator($this);
332 | }
333 |
334 | return $this;
335 | }
336 |
337 | public function always(): Stream
338 | {
339 | return $this;
340 | }
341 |
342 | /**
343 | * @inheritDoc
344 | */
345 | public function toArray(): array
346 | {
347 | return $this
348 | ->getCollection()
349 | ->toArray()
350 | ;
351 | }
352 |
353 | /**
354 | * @inheritDoc
355 | */
356 | public function getSet(): Set
357 | {
358 | return new HashSet($this->toArray());
359 | }
360 | }
361 |
--------------------------------------------------------------------------------
/src/WS/Utils/Collections/Functions/Predicates.php:
--------------------------------------------------------------------------------
1 | blocked all tries
16 | * @return Closure
17 | */
18 | public static function lock(): Closure
19 | {
20 | return static function (): bool {
21 | return false;
22 | };
23 | }
24 |
25 | /**
26 | * Returns passed all not null elements
27 | * @return Closure
28 | */
29 | public static function notNull(): Closure
30 | {
31 | return static function ($el): bool {
32 | return $el !== null;
33 | };
34 | }
35 |
36 | /**
37 | * Returns passed all tries
38 | * @return Closure
39 | */
40 | public static function notResistance(): Closure
41 | {
42 | return static function (): bool {
43 | return true;
44 | };
45 | }
46 |
47 | /**
48 | * Returns passed each even element of call
49 | * @return Closure
50 | */
51 | public static function eachEven(): Closure
52 | {
53 | $isEven = false;
54 | return static function () use (& $isEven) {
55 | $res = $isEven;
56 | $isEven = !$isEven;
57 |
58 | return $res;
59 | };
60 | }
61 |
62 | /**
63 | * Returns passed each nth element of call
64 | * @param $number
65 | * @return Closure
66 | */
67 | public static function nth($number): Closure
68 | {
69 | $counter = 0;
70 | return static function () use ($number, & $counter) {
71 | $res = ++$counter % $number === 0;
72 | if ($res) {
73 | $counter = 0;
74 | }
75 | return $res;
76 | };
77 | }
78 |
79 | /**
80 | * Returns passed with equal value
81 | * @param $value
82 | * @return Closure
83 | */
84 | public static function equal($value): Closure
85 | {
86 | return static function ($el) use ($value): bool {
87 | return $el === $value;
88 | };
89 | }
90 |
91 | /**
92 | * Returns passed unique element at once
93 | */
94 | public static function lockDuplicated(?callable $caster = null): Closure
95 | {
96 | $set = new HashSet();
97 | return static function ($el) use ($set, $caster): bool {
98 | $el = isset($caster) ? $caster($el) : $el;
99 | $res = !$set->contains($el);
100 | $set->add($el);
101 | return $res;
102 | };
103 | }
104 |
105 | /**
106 | * Returns passed with less than value comparing
107 | * @param $value
108 | * @return Closure
109 | */
110 | public static function lessThan($value): Closure
111 | {
112 | return static function ($el) use ($value): bool {
113 | return $el < $value;
114 | };
115 | }
116 |
117 | /**
118 | * Returns passed with less or equal value comparing
119 | * @param $value
120 | * @return Closure
121 | */
122 | public static function lessOrEqual($value): Closure
123 | {
124 | return static function ($el) use ($value): bool {
125 | return $el <= $value;
126 | };
127 | }
128 |
129 | /**
130 | * Returns passed with more than value comparing
131 | * @param $value
132 | * @return Closure
133 | */
134 | public static function greaterThan($value): Closure
135 | {
136 | return static function ($el) use ($value): bool {
137 | return $el > $value;
138 | };
139 | }
140 |
141 | /**
142 | * Returns passed with more or equal value comparing
143 | * @param $value
144 | * @return Closure
145 | */
146 | public static function greaterOrEqual($value): Closure
147 | {
148 | return static function ($el) use ($value): bool {
149 | return $el >= $value;
150 | };
151 | }
152 |
153 | /**
154 | * Returns passed with not value comparing
155 | * @param $value
156 | * @return Closure
157 | */
158 | public static function not($value): Closure
159 | {
160 | return static function ($el) use ($value): bool {
161 | return $el !== $value;
162 | };
163 | }
164 |
165 | /**
166 | * Returns passed with include of set value comparing
167 | * @param array $values
168 | * @return Closure
169 | */
170 | public static function in(array $values): Closure
171 | {
172 | return static function ($el) use ($values): bool {
173 | return in_array($el, $values, true);
174 | };
175 | }
176 |
177 | /**
178 | * Returns passed with not include of set value comparing
179 | * @param array $values
180 | * @return Closure
181 | */
182 | public static function notIn(array $values): Closure
183 | {
184 | return static function ($el) use ($values): bool {
185 | return !in_array($el, $values, true);
186 | };
187 | }
188 |
189 | /**
190 | * Returns passed with where object property value comparing
191 | * @param string $property
192 | * @param $value
193 | * @return Closure
194 | */
195 | public static function where(string $property, $value): Closure
196 | {
197 | return static function ($ob) use ($property, $value) {
198 | return $value === ObjectFunctions::getPropertyValue($ob, $property);
199 | };
200 | }
201 |
202 | /**
203 | * Returns passed with where not object property value comparing
204 | * @param string $property
205 | * @param $value
206 | * @return Closure
207 | */
208 | public static function whereNot(string $property, $value): Closure
209 | {
210 | return static function ($ob) use ($property, $value) {
211 | return $value !== ObjectFunctions::getPropertyValue($ob, $property);
212 | };
213 | }
214 |
215 | /**
216 | * Returns passed with where present in a set of object property value comparing
217 | * @param string $property
218 | * @param array $values
219 | * @return Closure
220 | */
221 | public static function whereIn(string $property, array $values): Closure
222 | {
223 | return static function ($ob) use ($property, $values) {
224 | return in_array(ObjectFunctions::getPropertyValue($ob, $property), $values, true);
225 | };
226 | }
227 |
228 | /**
229 | * Returns passed with where not present in a set of object property value comparing
230 | * @param string $property
231 | * @param array $values
232 | * @return Closure
233 | */
234 | public static function whereNotIn(string $property, array $values): Closure
235 | {
236 | return static function ($ob) use ($property, $values) {
237 | return !in_array(ObjectFunctions::getPropertyValue($ob, $property), $values, true);
238 | };
239 | }
240 |
241 | /**
242 | * Returns passed with more than object property value comparing
243 | * @param string $property
244 | * @param $value
245 | * @return Closure
246 | */
247 | public static function whereGreaterThan(string $property, $value): Closure
248 | {
249 | return static function ($ob) use ($property, $value) {
250 | return ObjectFunctions::getPropertyValue($ob, $property) > $value;
251 | };
252 | }
253 |
254 | /**
255 | * Returns passed with less than object property value comparing
256 | * @param string $property
257 | * @param $value
258 | * @return Closure
259 | */
260 | public static function whereLessThan(string $property, $value): Closure
261 | {
262 | return static function ($ob) use ($property, $value) {
263 | return ObjectFunctions::getPropertyValue($ob, $property) < $value;
264 | };
265 | }
266 |
267 | /**
268 | * Returns passed with more or equal object property value comparing
269 | * @param string $property
270 | * @param $value
271 | * @return Closure
272 | */
273 | public static function whereGreaterOrEqual(string $property, $value): Closure
274 | {
275 | return static function ($ob) use ($property, $value) {
276 | return ObjectFunctions::getPropertyValue($ob, $property) >= $value;
277 | };
278 | }
279 |
280 | /**
281 | * Returns passed with less or equal object property value comparing
282 | * @param string $property
283 | * @param $value
284 | * @return Closure
285 | */
286 | public static function whereLessOrEqual(string $property, $value): Closure
287 | {
288 | return static function ($ob) use ($property, $value) {
289 | return ObjectFunctions::getPropertyValue($ob, $property) <= $value;
290 | };
291 | }
292 |
293 | /**
294 | * Returns passed only one time with first element
295 | * @return Closure
296 | */
297 | public static function first(): Closure
298 | {
299 | $runOut = false;
300 | return static function () use (& $runOut) {
301 | if ($runOut) {
302 | return false;
303 | }
304 | $runOut = true;
305 | return true;
306 | };
307 | }
308 | }
309 |
--------------------------------------------------------------------------------