Skip to content

Commit

Permalink
Add ::pluck() method/operation.
Browse files Browse the repository at this point in the history
  • Loading branch information
drupol committed Aug 16, 2019
1 parent d50b257 commit 8b9c53b
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ the methods always return the same values for the same inputs.
| `nth` | new Collection object | [Nth.php](./src/Operation/Append.php)
| `only` | new Collection object | [Only.php](./src/Operation/Append.php)
| `pad` | new Collection object | [Pad.php](./src/Operation/Append.php)
| `pluck` | new Collection object | [Pluck.php](./src/Operation/Pluck.php)
| `prepend` | new Collection object | [Prepend.php](./src/Operation/Append.php)
| `reduce` | mixed | [Collection.php](./src/Collection.php)
| `run` | new Collection object | [Collection.php](./src/Collection.php)
Expand Down
69 changes: 69 additions & 0 deletions spec/drupol/collection/CollectionSpec.php
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,75 @@ public function it_can_pad(): void
->shouldReturn(['A' => 'A', 'B' => 'B', 'C' => 'C', 'D' => 'D', 'E' => 'E', 0 => 'foo', 1 => 'foo', 2 => 'foo', 3 => 'foo', 4 => 'foo']);
}

public function it_can_pluck(): void
{
$six = new class() {
public $foo = [
'bar' => 5,
];
};

$input = [
[
'foo' => [
'bar' => 0,
],
],
[
'foo' => [
'bar' => 1,
],
],
[
'foo' => [
'bar' => 2,
],
],
Collection::withArray(
[
'foo' => [
'bar' => 3,
],
]
),
new \ArrayObject([
'foo' => [
'bar' => 4,
],
]),
new class() {
public $foo = [
'bar' => 5,
];
},
[
'foo' => [
'bar' => $six,
],
],
];

$this::with($input)
->pluck('foo')
->shouldIterateAs([0 => ['bar' => 0], 1 => ['bar' => 1], 2 => ['bar' => 2], 3 => ['bar' => 3], 4 => ['bar' => 4], 5 => ['bar' => 5], 6 => ['bar' => $six]]);

$this::with($input)
->pluck('foo.*')
->shouldIterateAs([0 => [0 => 0], 1 => [0 => 1], 2 => [0 => 2], 3 => [0 => 3], 4 => [0 => 4], 5 => [0 => 5], 6 => [0 => $six]]);

$this::with($input)
->pluck('foo.bar')
->shouldIterateAs([0 => 0, 1 => 1, 2 => 2, 3 => 3, 4 => 4, 5 => 5, 6 => $six]);

$this::with($input)
->pluck('foo.bar.*', 'taz')
->shouldIterateAs([0 => 'taz', 1 => 'taz', 2 => 'taz', 3 => 'taz', 4 => 'taz', 5 => 'taz', 6 => 'taz']);

$this::with($input)
->pluck('azerty', 'taz')
->shouldIterateAs([0 => 'taz', 1 => 'taz', 2 => 'taz', 3 => 'taz', 4 => 'taz', 5 => 'taz', 6 => 'taz']);
}

public function it_can_prepend(): void
{
$this
Expand Down
9 changes: 9 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use drupol\collection\Operation\Nth;
use drupol\collection\Operation\Only;
use drupol\collection\Operation\Pad;
use drupol\collection\Operation\Pluck;
use drupol\collection\Operation\Prepend;
use drupol\collection\Operation\Range;
use drupol\collection\Operation\Skip;
Expand Down Expand Up @@ -256,6 +257,14 @@ public function pad(int $size, $value): CollectionInterface
return $this->run(Pad::with($size, $value));
}

/**
* {@inheritdoc}
*/
public function pluck($pluck, $default = null): CollectionInterface
{
return $this->run(Pluck::with($pluck, $default));
}

/**
* {@inheritdoc}
*/
Expand Down
8 changes: 8 additions & 0 deletions src/Contract/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,14 @@ public function only(...$keys): self;
*/
public function pad(int $size, $value): self;

/**
* @param array|string $pluck
* @param null|mixed $default
*
* @return \drupol\collection\Contract\Collection
*/
public function pluck($pluck, $default = null): self;

/**
* Push an item onto the beginning of the collection.
*
Expand Down
78 changes: 78 additions & 0 deletions src/Operation/Pluck.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

declare(strict_types=1);

namespace drupol\collection\Operation;

use ArrayAccess;
use drupol\collection\Collection;
use drupol\collection\Contract\Collection as CollectionInterface;

/**
* Class Pluck.
*/
final class Pluck extends Operation
{
/**
* {@inheritdoc}
*/
public function run(CollectionInterface $collection): CollectionInterface
{
[$key, $default] = $this->parameters;
$operation = $this;

return Collection::withClosure(
static function () use ($key, $default, $collection, $operation) {
$key = \is_string($key) ? \explode('.', \trim($key, '.')) : $key;

foreach ($collection as $item) {
yield $operation->pick($item, $key, $default);
}
}
);
}

/**
* Get an item from an array or object using "dot" notation.
*
* @param mixed $target
* @param array $key
* @param mixed $default
*
* @throws \ReflectionException
*
* @return mixed
*/
private function pick($target, array $key, $default = null)
{
while (null !== $segment = \array_shift($key)) {
if ('*' === $segment) {
if (!\is_array($target)) {
return $default;
}

$result = [];

foreach ($target as $item) {
$result[] = $this->pick($item, $key);
}

return \in_array('*', $key, true) ? Collection::withArray($result)->collapse() : $result;
}

if ((true === \is_array($target)) && (true === \array_key_exists($segment, $target))) {
$target = $target[$segment];
} elseif (($target instanceof ArrayAccess) && (true === $target->offsetExists($segment))) {
$target = $target[$segment];
} elseif ($target instanceof CollectionInterface) {
$target = $target->get($segment, $default);
} elseif ((true === \is_object($target)) && (true === \property_exists($target, $segment))) {
$target = (new \ReflectionClass($target))->getProperty($segment)->getValue($target);
} else {
$target = $default;
}
}

return $target;
}
}

0 comments on commit 8b9c53b

Please sign in to comment.