From 1b6bc3c97aa1e7a5aeedd504bfe3ec1664dd9008 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Thu, 9 Dec 2021 17:46:00 +0100 Subject: [PATCH 01/11] Add new functions --- examples/example/ArrayValue-skip.php | 8 ++ examples/example/ArrayValue-take.php | 15 +++ examples/example/AssocValue-keys.php | 12 +++ examples/example/IterableValue-keys.php | 29 ++++++ examples/example/IterableValue-take.php | 18 ++++ spec/AssocArraySpec.php | 7 ++ spec/InfiniteIterableValueSpec.php | 130 +++++++++++++++++++++++- spec/PlainArraySpec.php | 14 ++- spec/PlainStringsArraySpec.php | 13 ++- src/ArrayValue.php | 10 ++ src/AssocValue.php | 2 +- src/InfiniteIterableValue.php | 96 ++++++++++++++--- src/IterableValue.php | 49 ++++++--- src/PlainArray.php | 16 +++ src/PlainStringsArray.php | 10 ++ src/StringsArray.php | 4 + 16 files changed, 401 insertions(+), 32 deletions(-) create mode 100644 examples/example/ArrayValue-skip.php create mode 100644 examples/example/ArrayValue-take.php create mode 100644 examples/example/AssocValue-keys.php create mode 100644 examples/example/IterableValue-keys.php create mode 100644 examples/example/IterableValue-take.php diff --git a/examples/example/ArrayValue-skip.php b/examples/example/ArrayValue-skip.php new file mode 100644 index 0000000..9d042c8 --- /dev/null +++ b/examples/example/ArrayValue-skip.php @@ -0,0 +1,8 @@ +skip(2)->toArray()); +echo PHP_EOL; diff --git a/examples/example/ArrayValue-take.php b/examples/example/ArrayValue-take.php new file mode 100644 index 0000000..7669824 --- /dev/null +++ b/examples/example/ArrayValue-take.php @@ -0,0 +1,15 @@ +skip(2)->take(4)->toArray()); +echo PHP_EOL; + +var_export($letters->take(3)->toArray()); +echo PHP_EOL; + +var_export($letters->take(100)->toArray()); +echo PHP_EOL; + diff --git a/examples/example/AssocValue-keys.php b/examples/example/AssocValue-keys.php new file mode 100644 index 0000000..65732e3 --- /dev/null +++ b/examples/example/AssocValue-keys.php @@ -0,0 +1,12 @@ + 'zero', '1' => 'one']); + +$keys = $assoc + ->map(fn(string $val, int $key): string => $val) + ->keys() + ->toArray(); + +var_dump($keys); diff --git a/examples/example/IterableValue-keys.php b/examples/example/IterableValue-keys.php new file mode 100644 index 0000000..26cc7a0 --- /dev/null +++ b/examples/example/IterableValue-keys.php @@ -0,0 +1,29 @@ + 'zero', '1' => 'one']); + +$keys = $assoc + ->map(fn(string $val, int $key): string => $val) + ->keys() + ->toArray(); + +var_dump($keys); + +$pairs = [['0', 'zero'], ['1', 'one'], ['1', 'one one']]; + +$iterator = function () use ($pairs) { + foreach ($pairs as [$key, $item]) { + yield $key => $item; + } +}; + +$assoc = Wrap::iterable($iterator()); + +$keys = $assoc + ->map(fn(string $val, string $key): string => $val) + ->keys() + ->toArray(); + +var_dump($keys); diff --git a/examples/example/IterableValue-take.php b/examples/example/IterableValue-take.php new file mode 100644 index 0000000..53a0406 --- /dev/null +++ b/examples/example/IterableValue-take.php @@ -0,0 +1,18 @@ +skip(2)->take(4)->toArray()); +echo PHP_EOL; + +var_export(Wrap::iterable($range(0, PHP_INT_MAX))->take(3)->toArray()); +echo PHP_EOL; + +var_export(Wrap::iterable($range(0, PHP_INT_MAX))->skip(5000)->take(2)->toArray()); +echo PHP_EOL; diff --git a/spec/AssocArraySpec.php b/spec/AssocArraySpec.php index 38e79f7..db351b0 100644 --- a/spec/AssocArraySpec.php +++ b/spec/AssocArraySpec.php @@ -422,4 +422,11 @@ function it_does_not_allow_mutations_trough_ArrayAccess() $this->shouldThrow(\BadMethodCallException::class)->during('offsetSet', ['a', 'mutated 1']); $this->shouldThrow(\BadMethodCallException::class)->during('offsetUnset', ['a']); } + + function it_handles_numeric_strings_key_as_int_from_array() + { + $this->beConstructedWith(['0' => 'zero', '1' => 'one']); + $this->map(fn(string $val, int $key): string => $val) + ->keys()->toArray()->shouldEqual([0, 1]); + } } diff --git a/spec/InfiniteIterableValueSpec.php b/spec/InfiniteIterableValueSpec.php index 4779a2a..e78fa02 100644 --- a/spec/InfiniteIterableValueSpec.php +++ b/spec/InfiniteIterableValueSpec.php @@ -9,6 +9,7 @@ use GW\Value\Wrap; use PhpSpec\Exception\Example\FailureException; use PhpSpec\ObjectBehavior; +use function range; final class InfiniteIterableValueSpec extends ObjectBehavior { @@ -19,6 +20,31 @@ function it_can_be_converted_to_array_value() $this->toArrayValue()->shouldBeLike(Wrap::array($items)); } + function it_can_be_converted_to_simple_assoc_array() + { + $items = ['item 1', 'item 2', 'item 3']; + $this->beConstructedWith($items); + $this->toAssocArray()->shouldBeLike([0 => 'item 1', 1 => 'item 2', 2 => 'item 3']); + } + + function it_can_be_converted_to_keyed_assoc_array() + { + $items = ['foo' => 'item 1', 'bar' => 'item 2']; + $this->beConstructedWith($items); + $this->toAssocArray()->shouldBeLike(['foo' => 'item 1', 'bar' => 'item 2']); + } + + function it_can_be_converted_to_keyed_assoc_array_with_map_filter() + { + $items = ['foo' => 'item 1', 'bar' => 'item 2']; + $this->beConstructedWith($items); + $this + ->map(fn(string $value, string $key): string => "{$value} mod") + ->filter(fn(string $value): bool => true) + ->toAssocArray() + ->shouldBeLike(['foo' => 'item 1 mod', 'bar' => 'item 2 mod']); + } + function it_returns_items() { $items = ['item 1', 'item 2', 'item 3']; @@ -26,6 +52,13 @@ function it_returns_items() $this->toArray()->shouldReturn($items); } + function it_returns_keys() + { + $items = ['item 1', 'item 2', 'item 3']; + $this->beConstructedWith($items); + $this->keys()->toArray()->shouldReturn([0, 1, 2]); + } + function it_maps_string_items_with_closure() { $this->beConstructedWith(['item 1', 'item 2', 'item 3']); @@ -41,8 +74,7 @@ function it_maps_string_items_with_closure() function it_maps_items_with_php_callable() { $this->beConstructedWith(['100', '50.12', '', true, false]); - - $mapped = $this->map('intval'); + $mapped = $this->map(fn(mixed $value): int => (int)$value); $mapped->shouldNotBe($this); $mapped->toArray()->shouldBeLike([100, 50, 0, 1, 0]); @@ -184,12 +216,22 @@ function it_slices_given_part() { $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); - $this->slice(0, 1)->shouldNotBe($this); + $this->slice(0, 1)->toArray()->shouldNotBe($this->toArray()); $this->slice(0, 1)->toArray()->shouldBeLike(['item 1']); $this->slice(1, 4)->toArray()->shouldBeLike(['item 2', 'item 3', 'item 4', 'item 5']); $this->slice(5, 1)->toArray()->shouldBeLike(['item 6']); } + function it_skips_and_takes_given_part() + { + $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); + + $this->take(1)->toArray()->shouldNotBe($this->toArray()); + $this->take(1)->toArray()->shouldBeLike(['item 1']); + $this->skip(1)->take(4)->toArray()->shouldBeLike(['item 2', 'item 3', 'item 4', 'item 5']); + $this->skip(5)->take(1)->toArray()->shouldBeLike(['item 6']); + } + function it_slice_and_do_not_take_elements_above_end_index() { $takenElements = 0; @@ -518,6 +560,88 @@ function it_finds_null_when_no_item_matches_condition() ->shouldReturn(null); } + function it_handles_repeated_keys_properly_with_toArray() + { + $iterator = function () { + foreach (range(0, 5) as $item) { + yield 0 => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->toArray()->shouldEqual([0, 1, 2, 3, 4, 5]); + } + + function it_handles_repeated_keys_properly_with_toAssocArray() + { + $iterator = function () { + foreach (range(0, 5) as $item) { + yield 0 => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->toAssocArray()->shouldEqual([0 => 5]); + } + + function it_handles_repeated_keys_properly_with_keys() + { + $iterator = function () { + foreach (range(0, 5) as $item) { + yield 0 => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->keys()->toArray()->shouldEqual([0, 0, 0, 0, 0, 0]); + } + + function it_handles_repeated_keys_properly_with_map() + { + $iterator = function () { + foreach (range(0, 5) as $item) { + yield 0 => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->map(fn(int $value, int $key): int => $key)->toArray()->shouldEqual([0, 0, 0, 0, 0, 0]); + } + + function it_handles_repeated_keys_properly_with_slice() + { + $iterator = function () { + foreach (range(0, 5) as $item) { + yield 0 => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->slice(1, 2)->toArray()->shouldEqual([1, 2]); + } + + function it_handles_numeric_strings_key_as_int_from_array() + { + $this->beConstructedWith(['0' => 'zero', '1' => 'one']); + $this->map(fn(string $val, int $key): string => $val) + ->keys()->toArray()->shouldEqual([0, 1]); + } + + function it_handles_numeric_strings_key_as_string_from_iterator() + { + $pairs = [['0', 'zero'], ['1', 'one'], ['1', 'one one']]; + + $iterator = function () use ($pairs) { + foreach ($pairs as [$key, $item]) { + yield $key => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->map(fn(string $val, string $key): string => $val) + ->keys()->toArray()->shouldEqual(['0', '1', '1']); + } + private function entityComparator(): \Closure { return function (DummyEntity $entityA, DummyEntity $entityB): int { diff --git a/spec/PlainArraySpec.php b/spec/PlainArraySpec.php index ac79fe6..9ba1127 100644 --- a/spec/PlainArraySpec.php +++ b/spec/PlainArraySpec.php @@ -55,7 +55,7 @@ function it_maps_items_with_php_callable() { $this->beConstructedWith(['100', '50.12', '', true, false]); - $mapped = $this->map('intval'); + $mapped = $this->map(fn(mixed $value): int => (int)$value); $mapped->shouldNotBe($this); $mapped->toArray()->shouldBeLike([100, 50, 0, 1, 0]); @@ -341,12 +341,22 @@ function it_slices_given_part() { $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); - $this->slice(0, 1)->shouldNotBe($this); + $this->slice(0, 1)->toArray()->shouldNotBe($this->toArray()); $this->slice(0, 1)->toArray()->shouldBeLike(['item 1']); $this->slice(1, 4)->toArray()->shouldBeLike(['item 2', 'item 3', 'item 4', 'item 5']); $this->slice(5, 1)->toArray()->shouldBeLike(['item 6']); } + function it_skips_and_takes_given_part() + { + $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); + + $this->take(1)->toArray()->shouldNotBe($this->toArray()); + $this->take(1)->toArray()->shouldBeLike(['item 1']); + $this->skip(1)->take(4)->toArray()->shouldBeLike(['item 2', 'item 3', 'item 4', 'item 5']); + $this->skip(5)->take(1)->toArray()->shouldBeLike(['item 6']); + } + function it_allows_to_remove_slice_from_array_with_splice() { $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); diff --git a/spec/PlainStringsArraySpec.php b/spec/PlainStringsArraySpec.php index 405ce4e..42ca689 100644 --- a/spec/PlainStringsArraySpec.php +++ b/spec/PlainStringsArraySpec.php @@ -51,7 +51,7 @@ function it_can_be_sliced() $strings = ['string 1', 'string 2', 'string 3', 'string 4']; $this->beConstructedWithStrings(...$strings); - $this->slice(0, 1)->shouldNotBe($this); + $this->slice(0, 1)->toNativeStrings()->shouldNotBe($this->toNativeStrings()); $this->slice(0, 1)->toNativeStrings()->shouldBeLike(['string 1']); $this->slice(3, 1)->toNativeStrings()->shouldBeLike(['string 4']); $this->slice(1, 2)->toNativeStrings()->shouldBeLike(['string 2', 'string 3']); @@ -59,6 +59,17 @@ function it_can_be_sliced() $this->slice(0, 500)->toNativeStrings()->shouldBeLike($strings); } + function it_skips_and_takes_given_part() + { + $strings = ['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']; + $this->beConstructedWithStrings(...$strings); + + $this->take(1)->toNativeStrings()->shouldNotBe($this->toNativeStrings()); + $this->take(1)->toNativeStrings()->shouldBeLike(['item 1']); + $this->skip(1)->take(4)->toNativeStrings()->shouldBeLike(['item 2', 'item 3', 'item 4', 'item 5']); + $this->skip(5)->take(1)->toNativeStrings()->shouldBeLike(['item 6']); + } + function it_can_be_spliced() { $this->beConstructedWithStrings('string 1', 'string 2', 'string 3', 'string 4'); diff --git a/src/ArrayValue.php b/src/ArrayValue.php index 54dfafe..3c7c0d9 100644 --- a/src/ArrayValue.php +++ b/src/ArrayValue.php @@ -151,6 +151,16 @@ public function join(ArrayValue $other): ArrayValue; */ public function slice(int $offset, int $length): ArrayValue; + /** + * @phpstan-return ArrayValue + */ + public function skip(int $length): ArrayValue; + + /** + * @phpstan-return ArrayValue + */ + public function take(int $length): ArrayValue; + /** * @phpstan-param ArrayValue $replacement * @phpstan-return ArrayValue diff --git a/src/AssocValue.php b/src/AssocValue.php index 4dc8d8e..4e1e2e1 100644 --- a/src/AssocValue.php +++ b/src/AssocValue.php @@ -41,7 +41,7 @@ public function filterEmpty(): AssocValue; /** * @template TNewValue - * @param callable(TValue $value, TKey $key=):TNewValue $transformer + * @param callable(TValue,TKey $key=):TNewValue $transformer * @phpstan-return AssocValue */ public function map(callable $transformer): AssocValue; diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index 738e948..ecf73b0 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -36,7 +36,7 @@ private static function fromStack(IterableValueStack $stack): self } /** - * @param callable(TValue $value):void $callback + * @param callable(TValue):void $callback * @phpstan-return InfiniteIterableValue */ public function each(callable $callback): InfiniteIterableValue @@ -49,7 +49,7 @@ public function each(callable $callback): InfiniteIterableValue } /** - * @param (callable(TValue $lueA,TValue):int) | null $comparator + * @param (callable(TValue,TValue):int) | null $comparator * @phpstan-return InfiniteIterableValue */ public function unique(?callable $comparator = null): InfiniteIterableValue @@ -90,7 +90,7 @@ public function toArray(): array } /** - * @phpstan-param callable(TValue $value):bool $filter + * @phpstan-param callable(TValue):bool $filter * @phpstan-return InfiniteIterableValue */ public function filter(callable $filter): InfiniteIterableValue @@ -101,9 +101,9 @@ public function filter(callable $filter): InfiniteIterableValue * @phpstan-return iterable */ static function (iterable $iterable) use ($filter): iterable { - foreach ($iterable as $value) { + foreach ($iterable as $key => $value) { if ($filter($value)) { - yield $value; + yield $key => $value; } } } @@ -120,7 +120,7 @@ public function filterEmpty(): InfiniteIterableValue /** * @template TNewValue - * @param callable(TValue $value): TNewValue $transformer + * @param callable(TValue,TKey $key=):TNewValue $transformer * @phpstan-return InfiniteIterableValue */ public function map(callable $transformer): InfiniteIterableValue @@ -131,8 +131,8 @@ public function map(callable $transformer): InfiniteIterableValue * @phpstan-return iterable */ static function (iterable $iterable) use ($transformer): iterable { - foreach ($iterable as $value) { - yield $transformer($value); + foreach ($iterable as $key => $value) { + yield $key => $transformer($value, $key); } } )); @@ -140,7 +140,7 @@ static function (iterable $iterable) use ($transformer): iterable { /** * @template TNewValue - * @param callable(TValue $value): iterable $transformer + * @param callable(TValue):iterable $transformer * @phpstan-return InfiniteIterableValue */ public function flatMap(callable $transformer): InfiniteIterableValue @@ -166,6 +166,28 @@ public function toArrayValue(): ArrayValue return Wrap::array($this->toArray()); } + /** + * @phpstan-return array + */ + public function toAssocArray(): array + { + $return = []; + + foreach ($this->stack->iterate() as $key => $value) { + $return[$key] = $value; + } + + return $return; + } + + /** + * @phpstan-return AssocValue + */ + public function toAssocValue(): AssocValue + { + return Wrap::assocArray($this->toAssocArray()); + } + /** * @phpstan-param TValue $value * @phpstan-return InfiniteIterableValue @@ -231,12 +253,12 @@ public function slice(int $offset, int $length): InfiniteIterableValue * @phpstan-return iterable */ static function (iterable $iterable) use ($offset, $length): iterable { - foreach ($iterable as $value) { + foreach ($iterable as $key => $value) { if ($offset-- > 0) { continue; } - yield $value; + yield $key => $value; if (--$length <= 0) { break; @@ -246,9 +268,39 @@ static function (iterable $iterable) use ($offset, $length): iterable { )); } + /** + * @phpstan-return InfiniteIterableValue + */ + public function skip(int $length): InfiniteIterableValue + { + return self::fromStack($this->stack->push( + /** + * @phpstan-param iterable $iterable + * @phpstan-return iterable + */ + static function (iterable $iterable) use ($length): iterable { + foreach ($iterable as $key => $value) { + if ($length-- > 0) { + continue; + } + + yield $key => $value; + } + } + )); + } + + /** + * @phpstan-return InfiniteIterableValue + */ + public function take(int $length): InfiniteIterableValue + { + return $this->slice(0, $length); + } + /** * @phpstan-param ArrayValue $other - * @param (callable(TValue,TValue):int) | null $comparator + * @phpstan-param (callable(TValue,TValue):int) | null $comparator * @phpstan-return InfiniteIterableValue */ public function diff(ArrayValue $other, ?callable $comparator = null): InfiniteIterableValue @@ -278,7 +330,7 @@ static function (iterable $iterable) use ($other, $comparator): iterable { /** * @phpstan-param ArrayValue $other - * @param callable(TValue $valueA, TValue $valueB):int | null $comparator + * @phpstan-param (callable(TValue,TValue):int) | null $comparator * @phpstan-return InfiniteIterableValue */ public function intersect(ArrayValue $other, ?callable $comparator = null): InfiniteIterableValue @@ -459,6 +511,24 @@ public function last() return $value; } + /** + * @phpstan-return InfiniteIterableValue + */ + public function keys(): InfiniteIterableValue + { + return self::fromStack($this->stack->push( + /** + * @phpstan-param iterable $iterable + * @phpstan-return iterable + */ + static function (iterable $iterable): iterable { + foreach ($iterable as $key => $value) { + yield $key; + } + } + )); + } + /** * @phpstan-return iterable */ diff --git a/src/IterableValue.php b/src/IterableValue.php index 0e63793..db441cc 100644 --- a/src/IterableValue.php +++ b/src/IterableValue.php @@ -14,13 +14,13 @@ interface IterableValue extends IteratorAggregate // IterableValue own /** - * @phpstan-param callable(TValue $value):void $callback + * @phpstan-param callable(TValue):void $callback * @phpstan-return IterableValue */ public function each(callable $callback): IterableValue; /** - * @phpstan-param callable(TValue $value):bool $filter + * @phpstan-param callable(TValue):bool $filter * @phpstan-return IterableValue */ public function filter(callable $filter): IterableValue; @@ -32,14 +32,14 @@ public function filterEmpty(): IterableValue; /** * @template TNewValue - * @phpstan-param callable(TValue $value): TNewValue $transformer + * @param callable(TValue,TKey $key=):TNewValue $transformer * @phpstan-return IterableValue */ public function map(callable $transformer): IterableValue; /** * @phpstan-template TNewValue - * @phpstan-param callable(TValue $value): iterable $transformer + * @phpstan-param callable(TValue):iterable $transformer * @phpstan-return IterableValue */ public function flatMap(callable $transformer): IterableValue; @@ -56,14 +56,24 @@ public function join(iterable $other): IterableValue; public function slice(int $offset, int $length): IterableValue; /** - * @phpstan-param callable(TValue $valueA, TValue $valueB):int | null $comparator + * @phpstan-return IterableValue + */ + public function skip(int $length): IterableValue; + + /** + * @phpstan-return IterableValue + */ + public function take(int $length): IterableValue; + + /** + * @phpstan-param (callable(TValue,TValue):int) | null $comparator * @phpstan-return IterableValue */ public function unique(?callable $comparator = null): IterableValue; /** * @template TNewValue - * @phpstan-param callable(TNewValue $reduced, TValue $value): TNewValue $transformer + * @phpstan-param callable(TNewValue,TValue):TNewValue $transformer * @phpstan-param TNewValue $start * @phpstan-return TNewValue */ @@ -88,14 +98,14 @@ public function push($value): IterableValue; /** * @phpstan-param ArrayValue $other - * @phpstan-param callable(TValue $valueA, TValue $valueB):int | null $comparator + * @phpstan-param (callable(TValue,TValue):int) | null $comparator * @phpstan-return IterableValue */ public function diff(ArrayValue $other, ?callable $comparator = null): IterableValue; /** * @phpstan-param ArrayValue $other - * @phpstan-param callable(TValue $valueA, TValue $valueB):int | null $comparator + * @phpstan-param (callable(TValue,TValue):int) | null $comparator * @phpstan-return IterableValue */ public function intersect(ArrayValue $other, ?callable $comparator = null): IterableValue; @@ -115,6 +125,16 @@ public function last(); */ public function toArrayValue(): ArrayValue; + /** + * @phpstan-return array + */ + public function toAssocArray(): array; + + /** + * @phpstan-return AssocValue + */ + public function toAssocValue(): AssocValue; + /** * @phpstan-return TValue[] */ @@ -131,24 +151,29 @@ public function chunk(int $size): IterableValue; public function flatten(): IterableValue; /** - * @param callable(TValue $value):bool $filter + * @param callable(TValue):bool $filter */ public function any(callable $filter): bool; /** - * @param callable(TValue $value):bool $filter + * @param callable(TValue):bool $filter */ public function every(callable $filter): bool; /** - * @param callable(TValue $value):bool $filter + * @param callable(TValue):bool $filter * @phpstan-return ?TValue */ public function find(callable $filter); /** - * @param callable(TValue $value):bool $filter + * @param callable(TValue):bool $filter * @phpstan-return ?TValue */ public function findLast(callable $filter); + + /** + * @phpstan-return IterableValue + */ + public function keys(): IterableValue; } diff --git a/src/PlainArray.php b/src/PlainArray.php index c41a087..af922d0 100644 --- a/src/PlainArray.php +++ b/src/PlainArray.php @@ -160,6 +160,22 @@ public function slice(int $offset, int $length): PlainArray return new self(new Slice($this->items, $offset, $length)); } + /** + * @phpstan-return PlainArray + */ + public function skip(int $length): PlainArray + { + return $this->slice($length, $this->count() - $length); + } + + /** + * @phpstan-return PlainArray + */ + public function take(int $length): PlainArray + { + return $this->slice(0, $length); + } + /** * @phpstan-param ArrayValue $replacement * @phpstan-return PlainArray diff --git a/src/PlainStringsArray.php b/src/PlainStringsArray.php index b74e437..3e3b5e2 100644 --- a/src/PlainStringsArray.php +++ b/src/PlainStringsArray.php @@ -37,6 +37,16 @@ public function slice(int $offset, int $length): PlainStringsArray return new self($this->strings->slice($offset, $length)); } + public function skip(int $length): PlainStringsArray + { + return $this->slice($length, $this->count() - $length); + } + + public function take(int $length): PlainStringsArray + { + return $this->slice(0, $length); + } + public function splice(int $offset, int $length, ?StringsArray $replacement = null): PlainStringsArray { return new self( diff --git a/src/StringsArray.php b/src/StringsArray.php index aebab13..33f526d 100644 --- a/src/StringsArray.php +++ b/src/StringsArray.php @@ -124,6 +124,10 @@ public function join(StringsArray $other): StringsArray; public function slice(int $offset, int $length): StringsArray; + public function skip(int $length): StringsArray; + + public function take(int $length): StringsArray; + public function splice(int $offset, int $length, ?StringsArray $replacement = null): StringsArray; /** From 6c048e29e737c5235976784ef702f1a49353c3c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Thu, 9 Dec 2021 17:48:54 +0100 Subject: [PATCH 02/11] fix --- spec/InfiniteIterableValueSpec.php | 2 +- spec/PlainArraySpec.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/InfiniteIterableValueSpec.php b/spec/InfiniteIterableValueSpec.php index e78fa02..720d392 100644 --- a/spec/InfiniteIterableValueSpec.php +++ b/spec/InfiniteIterableValueSpec.php @@ -74,7 +74,7 @@ function it_maps_string_items_with_closure() function it_maps_items_with_php_callable() { $this->beConstructedWith(['100', '50.12', '', true, false]); - $mapped = $this->map(fn(mixed $value): int => (int)$value); + $mapped = $this->map(fn($value): int => (int)$value); $mapped->shouldNotBe($this); $mapped->toArray()->shouldBeLike([100, 50, 0, 1, 0]); diff --git a/spec/PlainArraySpec.php b/spec/PlainArraySpec.php index 9ba1127..c0c554c 100644 --- a/spec/PlainArraySpec.php +++ b/spec/PlainArraySpec.php @@ -55,7 +55,7 @@ function it_maps_items_with_php_callable() { $this->beConstructedWith(['100', '50.12', '', true, false]); - $mapped = $this->map(fn(mixed $value): int => (int)$value); + $mapped = $this->map(fn($value): int => (int)$value); $mapped->shouldNotBe($this); $mapped->toArray()->shouldBeLike([100, 50, 0, 1, 0]); From 2d547f7ee85caedc04623fb490dba39a9cbee6af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Fri, 10 Dec 2021 11:13:17 +0100 Subject: [PATCH 03/11] remove codeclimate --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index bc5b296..6ac77f3 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ [![Build Status](https://github.com/gowork/values/workflows/tests/badge.svg)](https://packagist.org/packages/gowork/values) [![License](https://poser.pugx.org/gowork/values/license)](https://packagist.org/packages/gowork/values) [![Latest Stable Version](https://poser.pugx.org/gowork/values/v)](//packagist.org/packages/gowork/values) -[![Maintainability](https://api.codeclimate.com/v1/badges/b4fde36fad9d09cce9eb/maintainability)](https://codeclimate.com/github/gowork/values/maintainability) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/gowork/values/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/gowork/values/?branch=master) ## Installation From 0784a2ccccda9838eb2cb3a07315d4532ead783e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Fri, 10 Dec 2021 11:17:07 +0100 Subject: [PATCH 04/11] codeclimate --- .codeclimate.yml | 13 +++++++++++++ README.md | 1 + 2 files changed, 14 insertions(+) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..b157b77 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,13 @@ +engines: + phpcodesniffer: + enabled: true + checks: + Generic WhiteSpace ScopeIndent IncorrectExact: + enabled: false + phpmd: + enabled: true + checks: + UnusedLocalVariable: + enabled: false + CleanCode/StaticAccess: + enabled: false diff --git a/README.md b/README.md index 6ac77f3..bc5b296 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ [![Build Status](https://github.com/gowork/values/workflows/tests/badge.svg)](https://packagist.org/packages/gowork/values) [![License](https://poser.pugx.org/gowork/values/license)](https://packagist.org/packages/gowork/values) [![Latest Stable Version](https://poser.pugx.org/gowork/values/v)](//packagist.org/packages/gowork/values) +[![Maintainability](https://api.codeclimate.com/v1/badges/b4fde36fad9d09cce9eb/maintainability)](https://codeclimate.com/github/gowork/values/maintainability) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/gowork/values/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/gowork/values/?branch=master) ## Installation From 35bd45582cc373c33b9213be7b7e5d1843f7a39a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Fri, 10 Dec 2021 11:19:40 +0100 Subject: [PATCH 05/11] codeclimate --- .codeclimate.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.codeclimate.yml b/.codeclimate.yml index b157b77..4d8b100 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -11,3 +11,9 @@ engines: enabled: false CleanCode/StaticAccess: enabled: false + + sonar-php: + enabled: true + checks: + php:S1448: + enabled: false From f4f0107b75447c7e462add71a0f8236e6c0879c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:26:16 +0100 Subject: [PATCH 06/11] modifications --- spec/InfiniteIterableValueSpec.php | 19 ++++++++++++++++++ src/ArrayValue.php | 2 +- src/Arrayable/Slice.php | 4 ++-- src/InfiniteIterableValue.php | 31 ++++++++++++++---------------- src/IterableValue.php | 3 ++- src/PlainArray.php | 4 ++-- src/PlainStringsArray.php | 2 +- src/StringsArray.php | 6 ++---- 8 files changed, 43 insertions(+), 28 deletions(-) diff --git a/spec/InfiniteIterableValueSpec.php b/spec/InfiniteIterableValueSpec.php index 720d392..473c918 100644 --- a/spec/InfiniteIterableValueSpec.php +++ b/spec/InfiniteIterableValueSpec.php @@ -222,6 +222,25 @@ function it_slices_given_part() $this->slice(5, 1)->toArray()->shouldBeLike(['item 6']); } + function it_returns_empty_set_for_slice_with_zero_length() + { + $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); + + $this->slice(0, 0)->toArray()->shouldBeLike([]); + $this->slice(1, 0)->toArray()->shouldBeLike([]); + $this->slice(4, 0)->toArray()->shouldBeLike([]); + $this->slice(55, 0)->toArray()->shouldBeLike([]); + } + + function it_throws_when_used_on_negative_offset_or_length_in_slice() + { + $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); + + $this->shouldThrow()->during('slice', [-1, 10]); + $this->shouldThrow()->during('slice', [-1, -110]); + $this->shouldThrow()->during('slice', [1, -110]); + } + function it_skips_and_takes_given_part() { $this->beConstructedWith(['item 1', 'item 2', 'item 3', 'item 4', 'item 5', 'item 6']); diff --git a/src/ArrayValue.php b/src/ArrayValue.php index 3c7c0d9..6a441f0 100644 --- a/src/ArrayValue.php +++ b/src/ArrayValue.php @@ -149,7 +149,7 @@ public function join(ArrayValue $other): ArrayValue; /** * @phpstan-return ArrayValue */ - public function slice(int $offset, int $length): ArrayValue; + public function slice(int $offset, int $length = null): ArrayValue; /** * @phpstan-return ArrayValue diff --git a/src/Arrayable/Slice.php b/src/Arrayable/Slice.php index ee4a2ce..70e46d0 100644 --- a/src/Arrayable/Slice.php +++ b/src/Arrayable/Slice.php @@ -14,10 +14,10 @@ final class Slice implements Arrayable /** @var Arrayable */ private Arrayable $arrayable; private int $offset; - private int $length; + private ?int $length; /** @param Arrayable $arrayable */ - public function __construct(Arrayable $arrayable, int $offset, int $length) + public function __construct(Arrayable $arrayable, int $offset, int $length = null) { $this->arrayable = $arrayable; $this->offset = $offset; diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index ecf73b0..fef2ee2 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -3,6 +3,7 @@ namespace GW\Value; use function count; +use const PHP_INT_MAX; /** * @template TKey @@ -245,8 +246,18 @@ static function (iterable $iterable) use ($other): iterable { /** * @phpstan-return InfiniteIterableValue */ - public function slice(int $offset, int $length): InfiniteIterableValue + public function slice(int $offset, int $length = PHP_INT_MAX): InfiniteIterableValue { + if ($offset < 0) { + throw new \InvalidArgumentException('Start offset must be non-negative'); + } + if ($length < 0) { + throw new \InvalidArgumentException('Length must be non-negative'); + } + if ($length === 0) { + return new self([]); + } + return self::fromStack($this->stack->push( /** * @phpstan-param iterable $iterable @@ -273,21 +284,7 @@ static function (iterable $iterable) use ($offset, $length): iterable { */ public function skip(int $length): InfiniteIterableValue { - return self::fromStack($this->stack->push( - /** - * @phpstan-param iterable $iterable - * @phpstan-return iterable - */ - static function (iterable $iterable) use ($length): iterable { - foreach ($iterable as $key => $value) { - if ($length-- > 0) { - continue; - } - - yield $key => $value; - } - } - )); + return $this->slice($length); } /** @@ -519,7 +516,7 @@ public function keys(): InfiniteIterableValue return self::fromStack($this->stack->push( /** * @phpstan-param iterable $iterable - * @phpstan-return iterable + * @phpstan-return iterable */ static function (iterable $iterable): iterable { foreach ($iterable as $key => $value) { diff --git a/src/IterableValue.php b/src/IterableValue.php index db441cc..23c51b1 100644 --- a/src/IterableValue.php +++ b/src/IterableValue.php @@ -3,6 +3,7 @@ namespace GW\Value; use IteratorAggregate; +use const PHP_INT_MAX; /** * @template TKey @@ -53,7 +54,7 @@ public function join(iterable $other): IterableValue; /** * @phpstan-return IterableValue */ - public function slice(int $offset, int $length): IterableValue; + public function slice(int $offset, int $length = PHP_INT_MAX): IterableValue; /** * @phpstan-return IterableValue diff --git a/src/PlainArray.php b/src/PlainArray.php index af922d0..c20e286 100644 --- a/src/PlainArray.php +++ b/src/PlainArray.php @@ -155,7 +155,7 @@ public function join(ArrayValue $other): PlainArray /** * @phpstan-return PlainArray */ - public function slice(int $offset, int $length): PlainArray + public function slice(int $offset, int $length = null): PlainArray { return new self(new Slice($this->items, $offset, $length)); } @@ -165,7 +165,7 @@ public function slice(int $offset, int $length): PlainArray */ public function skip(int $length): PlainArray { - return $this->slice($length, $this->count() - $length); + return $this->slice($length); } /** diff --git a/src/PlainStringsArray.php b/src/PlainStringsArray.php index 3e3b5e2..a9ef1d7 100644 --- a/src/PlainStringsArray.php +++ b/src/PlainStringsArray.php @@ -32,7 +32,7 @@ public function join(StringsArray $other): PlainStringsArray return new self($this->strings->join($other->toArrayValue())); } - public function slice(int $offset, int $length): PlainStringsArray + public function slice(int $offset, int $length = null): PlainStringsArray { return new self($this->strings->slice($offset, $length)); } diff --git a/src/StringsArray.php b/src/StringsArray.php index 33f526d..2dd9314 100644 --- a/src/StringsArray.php +++ b/src/StringsArray.php @@ -108,21 +108,19 @@ public function offsetGet($offset): StringValue; /** * @param int $offset * @param StringValue|string $value - * @return void * @throws BadMethodCallException For immutable types. */ public function offsetSet($offset, $value): void; /** * @param int $offset - * @return void * @throws BadMethodCallException For immutable types. */ public function offsetUnset($offset): void; public function join(StringsArray $other): StringsArray; - public function slice(int $offset, int $length): StringsArray; + public function slice(int $offset, int $length = null): StringsArray; public function skip(int $length): StringsArray; @@ -166,7 +164,7 @@ public function first(): ?StringValue; public function last(): ?StringValue; /** - * @param callable(StringValue $value): bool $filter + * @param callable(StringValue):bool $filter */ public function find(callable $filter): ?StringValue; From 788ac4d76fbc746dad67837d288fc7d9d61526ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:28:34 +0100 Subject: [PATCH 07/11] fix --- src/InfiniteIterableValue.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index fef2ee2..bfdba3c 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -516,7 +516,7 @@ public function keys(): InfiniteIterableValue return self::fromStack($this->stack->push( /** * @phpstan-param iterable $iterable - * @phpstan-return iterable + * @phpstan-return iterable */ static function (iterable $iterable): iterable { foreach ($iterable as $key => $value) { From a1d125998d28c00a6221407bde7fd399d108d4ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:32:34 +0100 Subject: [PATCH 08/11] add test for object keys --- spec/InfiniteIterableValueSpec.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/InfiniteIterableValueSpec.php b/spec/InfiniteIterableValueSpec.php index 473c918..01958ea 100644 --- a/spec/InfiniteIterableValueSpec.php +++ b/spec/InfiniteIterableValueSpec.php @@ -661,6 +661,21 @@ function it_handles_numeric_strings_key_as_string_from_iterator() ->keys()->toArray()->shouldEqual(['0', '1', '1']); } + function it_handles_object_keys() + { + $pairs = [[(object)['foo' => 'bar'], 'zero'], [(object)['foo' => 'baz'], 'one one']]; + + $iterator = function () use ($pairs) { + foreach ($pairs as [$key, $item]) { + yield $key => $item; + } + }; + + $this->beConstructedWith($iterator()); + $this->map(fn(string $val, object $key): string => $val) + ->keys()->toArray()->shouldBeLike([(object)['foo' => 'bar'], (object)['foo' => 'baz']]); + } + private function entityComparator(): \Closure { return function (DummyEntity $entityA, DummyEntity $entityB): int { From 7dde5f7ca8cb2ee627338db789507c7f1cc4218a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:48:45 +0100 Subject: [PATCH 09/11] change length of slice to null --- src/InfiniteIterableValue.php | 6 +++--- src/IterableValue.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index bfdba3c..6286225 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -246,7 +246,7 @@ static function (iterable $iterable) use ($other): iterable { /** * @phpstan-return InfiniteIterableValue */ - public function slice(int $offset, int $length = PHP_INT_MAX): InfiniteIterableValue + public function slice(int $offset, int $length = null): InfiniteIterableValue { if ($offset < 0) { throw new \InvalidArgumentException('Start offset must be non-negative'); @@ -265,13 +265,13 @@ public function slice(int $offset, int $length = PHP_INT_MAX): InfiniteIterableV */ static function (iterable $iterable) use ($offset, $length): iterable { foreach ($iterable as $key => $value) { - if ($offset-- > 0) { + if ($offset !== 0 && $offset-- > 0) { continue; } yield $key => $value; - if (--$length <= 0) { + if ($length !== null && --$length <= 0) { break; } } diff --git a/src/IterableValue.php b/src/IterableValue.php index 23c51b1..0ca0b10 100644 --- a/src/IterableValue.php +++ b/src/IterableValue.php @@ -54,7 +54,7 @@ public function join(iterable $other): IterableValue; /** * @phpstan-return IterableValue */ - public function slice(int $offset, int $length = PHP_INT_MAX): IterableValue; + public function slice(int $offset, int $length = null): IterableValue; /** * @phpstan-return IterableValue From b5c52a1e9d765ee5058a756904a36c1f39f20554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:57:11 +0100 Subject: [PATCH 10/11] Add ? --- src/ArrayValue.php | 2 +- src/Arrayable/Slice.php | 2 +- src/InfiniteIterableValue.php | 2 +- src/IterableValue.php | 2 +- src/PlainArray.php | 2 +- src/PlainStringsArray.php | 2 +- src/StringsArray.php | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ArrayValue.php b/src/ArrayValue.php index 6a441f0..aebc1c1 100644 --- a/src/ArrayValue.php +++ b/src/ArrayValue.php @@ -149,7 +149,7 @@ public function join(ArrayValue $other): ArrayValue; /** * @phpstan-return ArrayValue */ - public function slice(int $offset, int $length = null): ArrayValue; + public function slice(int $offset, ?int $length = null): ArrayValue; /** * @phpstan-return ArrayValue diff --git a/src/Arrayable/Slice.php b/src/Arrayable/Slice.php index 70e46d0..ab1c4f9 100644 --- a/src/Arrayable/Slice.php +++ b/src/Arrayable/Slice.php @@ -17,7 +17,7 @@ final class Slice implements Arrayable private ?int $length; /** @param Arrayable $arrayable */ - public function __construct(Arrayable $arrayable, int $offset, int $length = null) + public function __construct(Arrayable $arrayable, int $offset, ?int $length = null) { $this->arrayable = $arrayable; $this->offset = $offset; diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index 6286225..451a258 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -246,7 +246,7 @@ static function (iterable $iterable) use ($other): iterable { /** * @phpstan-return InfiniteIterableValue */ - public function slice(int $offset, int $length = null): InfiniteIterableValue + public function slice(int $offset, ?int $length = null): InfiniteIterableValue { if ($offset < 0) { throw new \InvalidArgumentException('Start offset must be non-negative'); diff --git a/src/IterableValue.php b/src/IterableValue.php index 0ca0b10..a3f4704 100644 --- a/src/IterableValue.php +++ b/src/IterableValue.php @@ -54,7 +54,7 @@ public function join(iterable $other): IterableValue; /** * @phpstan-return IterableValue */ - public function slice(int $offset, int $length = null): IterableValue; + public function slice(int $offset, ?int $length = null): IterableValue; /** * @phpstan-return IterableValue diff --git a/src/PlainArray.php b/src/PlainArray.php index c20e286..5037c46 100644 --- a/src/PlainArray.php +++ b/src/PlainArray.php @@ -155,7 +155,7 @@ public function join(ArrayValue $other): PlainArray /** * @phpstan-return PlainArray */ - public function slice(int $offset, int $length = null): PlainArray + public function slice(int $offset, ?int $length = null): PlainArray { return new self(new Slice($this->items, $offset, $length)); } diff --git a/src/PlainStringsArray.php b/src/PlainStringsArray.php index a9ef1d7..b050955 100644 --- a/src/PlainStringsArray.php +++ b/src/PlainStringsArray.php @@ -32,7 +32,7 @@ public function join(StringsArray $other): PlainStringsArray return new self($this->strings->join($other->toArrayValue())); } - public function slice(int $offset, int $length = null): PlainStringsArray + public function slice(int $offset, ?int $length = null): PlainStringsArray { return new self($this->strings->slice($offset, $length)); } diff --git a/src/StringsArray.php b/src/StringsArray.php index 2dd9314..3357cb6 100644 --- a/src/StringsArray.php +++ b/src/StringsArray.php @@ -120,7 +120,7 @@ public function offsetUnset($offset): void; public function join(StringsArray $other): StringsArray; - public function slice(int $offset, int $length = null): StringsArray; + public function slice(int $offset, ?int $length = null): StringsArray; public function skip(int $length): StringsArray; From 30fa1d72a735c9045e03640035176f9264a79b78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bronis=C5=82aw=20Bia=C5=82ek?= Date: Sat, 11 Dec 2021 15:59:13 +0100 Subject: [PATCH 11/11] fix --- src/InfiniteIterableValue.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/InfiniteIterableValue.php b/src/InfiniteIterableValue.php index 451a258..612e20d 100644 --- a/src/InfiniteIterableValue.php +++ b/src/InfiniteIterableValue.php @@ -257,15 +257,16 @@ public function slice(int $offset, ?int $length = null): InfiniteIterableValue if ($length === 0) { return new self([]); } + $offsetSet = $offset > 0; return self::fromStack($this->stack->push( /** * @phpstan-param iterable $iterable * @phpstan-return iterable */ - static function (iterable $iterable) use ($offset, $length): iterable { + static function (iterable $iterable) use ($offsetSet, $offset, $length): iterable { foreach ($iterable as $key => $value) { - if ($offset !== 0 && $offset-- > 0) { + if ($offsetSet && $offset-- > 0) { continue; }