Skip to content

Commit

Permalink
More modernisation and refinement of the API
Browse files Browse the repository at this point in the history
Require item class name to be provided via abstract method
`getItemClassName()` instead of via the collection constructor.

Removed support for `ArrayAccess`, and replicate functionality via more
meaningful methods such as `Collection::has()` and `Collection::get()`.

Added the factory methods `Collection::from()` and
`Collection::empty()`.

Added helper methods `Collection::isEmpty()` and
`Collection::isNotEmpty()`.

Added `Collection::reverse()` helper to reverse the order of the
collection.
  • Loading branch information
benr77 committed Jan 7, 2024
1 parent 719f6f3 commit 0f54742
Show file tree
Hide file tree
Showing 15 changed files with 446 additions and 133 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Change Log
=====

# 0.2.1
- Require item class name to be provided via abstract method `getItemClassName()` instead of via the collection constructor.
- Removed support for `ArrayAccess`, and replicate functionality via more meaningful methods such as `Collection::has()` and `Collection::get()`.
- Added the factory methods `Collection::from()` and `Collection::empty()`.
- Added helper methods `Collection::isEmpty()` and `Collection::isNotEmpty()`.
- Added `Collection::reverse()` helper to reverse the order of the collection.

# 0.2.0
- Minimum PHP version bumped to 8.1.
- `ImmutableCollection::first()` now returns `null` instead of `false` when no item is found.
Expand Down
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ Then create a custom named collection to hold the `Foo` instances:
*/
final class FooCollection extends AbstractImmutableCollection
{
/**
* @param array<Foo> $items
*/
public function __construct(array $items)
public function getItemClassName(): string
{
parent::__construct(Foo::class, $items);
return Foo::class;
}
}
```
Expand Down
2 changes: 2 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
declare(strict_types=1);

use PhpCsFixer\Fixer\Import\NoUnusedImportsFixer;
use PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer;
use PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;
Expand All @@ -14,6 +15,7 @@

$ecsConfig->skip([
BlankLineAfterOpeningTagFixer::class,
NotOperatorWithSuccessorSpaceFixer::class,
]);

$ecsConfig->rules([
Expand Down
112 changes: 51 additions & 61 deletions src/AbstractImmutableCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
namespace Headsnet\Collections;

use ArrayIterator;
use Headsnet\Collections\Exception\ImmutabilityException;
use Headsnet\Collections\Exception\InvalidTypeException;
use Headsnet\Collections\Exception\ItemNotFoundException;
use Headsnet\Collections\Exception\OutOfRangeException;

/**
* @template TValue
* @implements ImmutableCollection<int, TValue>
* @implements ImmutableCollection<TValue>
*/
abstract class AbstractImmutableCollection implements ImmutableCollection
{
Expand All @@ -23,37 +22,50 @@ abstract class AbstractImmutableCollection implements ImmutableCollection
protected array $items = [];

/**
* Creates a new typed collection.
*
* @param string $itemClassName String representing the class name of the valid type for the items
* @param array<TValue> $items Array with all the objects to be added. They must be of the class $itemClassName.
* @param array<TValue> $items
*/
public function __construct(string $itemClassName, array $items = [])
public function __construct(array $items)
{
$itemClassName = $this->getItemClassName();
$this->itemClassName = $itemClassName;

foreach ($items as $item) {
if (! ($item instanceof $itemClassName)) {
if (!$item instanceof $itemClassName) {
throw new InvalidTypeException();
}

if (! in_array($item, $this->items, true)) {
if (!in_array($item, $this->items, true)) {
$this->items[] = $item;
}
}
}

public function getItemClassName(): string
/**
* @return class-string
*/
abstract public function getItemClassName(): string;

/**
* @param array<TValue> $items
*/
public static function from(array $items): static
{
return $this->itemClassName;
$class = static::class;

return new $class($items);
}

public static function empty(): static
{
$class = static::class;

return new $class([]);
}

/**
* @param int $index
*
* @return TValue
*/
public function getItem($index)
public function get(int $index)
{
if ($index >= $this->count()) {
throw new OutOfRangeException('Index: ' . $index);
Expand All @@ -62,14 +74,21 @@ public function getItem($index)
return $this->items[$index];
}

/**
* @param int $index
*/
public function indexExists($index): bool
public function has(int $index): bool
{
return $index < $this->count();
}

public function isEmpty(): bool
{
return $this->count() === 0;
}

public function isNotEmpty(): bool
{
return $this->count() > 0;
}

/**
* @return TValue|null
*/
Expand Down Expand Up @@ -103,13 +122,13 @@ public function lastOrFail()
}

/**
* @param AbstractImmutableCollection<TValue> $compareWith
* @param Collection<TValue> $compareWith
*/
public function equals(self $compareWith): bool
public function equals(Collection $compareWith): bool
{
foreach ($this->items as $index => $item) {
try {
if ($item !== $compareWith->getItem($index)) {
if ($item !== $compareWith->get($index)) {
return false;
}
} catch (OutOfRangeException) {
Expand Down Expand Up @@ -139,13 +158,23 @@ public function map(callable $func): array
/**
* @return static<TValue>
*/
public function filter(callable $func): AbstractImmutableCollection|static
public function filter(callable $func): Collection|static
{
$class = static::class;

return new $class(array_filter($this->items, $func));
}

/**
* @return static<TValue>
*/
public function reverse(): Collection|static
{
$class = static::class;

return new $class(array_reverse($this->items));
}

public function walk(callable $func): bool
{
return array_walk($this->items, $func);
Expand All @@ -159,10 +188,6 @@ public function toArray(): array
return $this->items;
}

//---------------------------------------------------------------------//
// Implementations //
//---------------------------------------------------------------------//

public function count(): int
{
return count($this->items);
Expand All @@ -175,39 +200,4 @@ public function getIterator(): ArrayIterator
{
return new ArrayIterator($this->items);
}

/**
* @param int $offset
* @param TValue $value
*/
public function offsetSet($offset, $value): void
{
throw new ImmutabilityException();
}

/**
* @param int $offset
*/
public function offsetUnset($offset): void
{
throw new ImmutabilityException();
}

/**
* @param int $offset
*
* @return TValue
*/
public function offsetGet($offset): mixed
{
return $this->getItem($offset);
}

/**
* @param int $offset
*/
public function offsetExists($offset): bool
{
return $this->indexExists($offset);
}
}
43 changes: 43 additions & 0 deletions src/AbstractMutableCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);

namespace Headsnet\Collections;

use Headsnet\Collections\Exception\ItemNotFoundException;

/**
* @template TValue
* @extends AbstractImmutableCollection<TValue>
* @implements MutableCollection<TValue>
*/
abstract class AbstractMutableCollection extends AbstractImmutableCollection implements MutableCollection
{
/**
* @param TValue $item
*/
public function add($item): void
{
$this->items[] = $item;
}

/**
* @param TValue $item
*/
public function remove($item): void
{
$this->items = array_filter(
$this->items,
fn ($storedItem): bool => $item !== $storedItem
);
}

public function removeAtPosition(int $index): void
{
if (!array_key_exists($index, $this->items)) {
throw new ItemNotFoundException();
}

unset($this->items[$index]);
$this->items = array_values($this->items);
}
}
89 changes: 89 additions & 0 deletions src/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

declare(strict_types=1);

namespace Headsnet\Collections;

use Headsnet\Collections\Exception\ItemNotFoundException;

/**
* @template TValue
*/
interface Collection
{
/**
* @param array<TValue> $items
*/
public static function from(array $items): static;

public function getItemClassName(): string;

/**
* @return TValue
*/
public function get(int $index);

public function has(int $index): bool;

public function isEmpty(): bool;

public function isNotEmpty(): bool;

/**
* @return TValue|null
*/
public function first();

/**
* @throws ItemNotFoundException
*
* @return TValue
*/
public function firstOrFail();

/**
* @return TValue|null
*/
public function last();

/**
* @throws ItemNotFoundException
*
* @return TValue
*/
public function lastOrFail();

/**
* @param Collection<TValue> $compareWith
*/
public function equals(Collection $compareWith): bool;

/**
* @param TValue $element
*/
public function contains($element): bool;

/**
* @return array<mixed>
*/
public function map(callable $func): array;

/**
* @return static<TValue>
*/
public function filter(callable $func): Collection|static;

/**
* @return static<TValue>
*/
public function reverse(): Collection|static;

public function walk(callable $func): bool;

/**
* @return array<int, TValue>
*/
public function toArray(): array;

public function count(): int;
}
Loading

0 comments on commit 0f54742

Please sign in to comment.