Skip to content

Additional functionality for supporting classes. #8

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions src/Domain/ChainRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<?php

declare(strict_types=1);

namespace GeekCell\Ddd\Domain;

use Assert;
use GeekCell\Ddd\Contracts\Domain\Paginator;
use GeekCell\Ddd\Contracts\Domain\Repository as RepositoryInterface;
use Traversable;

abstract class ChainRepository implements RepositoryInterface
{
/**
* @var array<class-string<RepositoryInterface>, RepositoryInterface>
*/
private array $repositories;

/**
* @var class-string<RepositoryInterface>
*/
private string $primaryRepositoryKey;

/**
* ChainRepository constructor.
*/
public function __construct(RepositoryInterface ...$repositories)
{
foreach ($repositories as $repository) {
$this->repositories[get_class($repository)] = $repository;
}

// Select the first repository as primary by default
$firstRepository = reset($repositories);
$this->selectPrimary($firstRepository);
}

/**
* Selects the primary repository, which will be used for collection,
* pagination and count.
*
* @param RepositoryInterface $repository
*
* @throws Assert\AssertionFailedException
*/
protected function selectPrimary(RepositoryInterface $repository): void
{
$className = get_class($repository);
Assert\Assertion::keyExists($this->repositories, $className);

$this->primaryRepositoryKey = $className;
}

/**
* Returns the currently selected primary repository.
*
* @return RepositoryInterface
*/
protected function getPrimary(): RepositoryInterface
{
return $this->repositories[$this->primaryRepositoryKey];
}

/**
* Delegate operation to the currently selected primary repository.
* For more advanced scenarios, this method can be overridden.
*
* @inheritDoc
*/
public function collect(): Collection
{
return $this->getPrimary()->collect();
}

/**
* Delegate operation to the currently selected primary repository.
* For more advanced scenarios, this method can be overridden.
*
* @inheritDoc
*/
public function paginate(int $itemsPerPage, int $currentPage = 1): Paginator
{
return $this->getPrimary()->paginate($itemsPerPage, $currentPage);
}

/**
* Delegate operation to the currently selected primary repository.
* For more advanced scenarios, this method can be overridden.
*
* @inheritDoc
*/
public function count(): int
{
return $this->getPrimary()->count();
}

/**
* Delegate operation to the currently selected primary repository.
* For more advanced scenarios, this method can be overridden.
*
* @inheritDoc
*/
public function getIterator(): Traversable
{
return $this->getPrimary()->getIterator();
}
}
134 changes: 130 additions & 4 deletions src/Domain/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,154 @@

namespace GeekCell\Ddd\Domain;

use ArrayAccess;
use ArrayIterator;
use Assert;
use Countable;
use IteratorAggregate;
use Traversable;

class Collection implements Countable, IteratorAggregate
class Collection implements ArrayAccess, Countable, IteratorAggregate
{
/**
* @template T of object
* @extends IteratorAggregate<T>
*
* @param T[] $items
* @param class-string<T> $itemType
*
* @throws Assert\AssertionFailedException
*/
final public function __construct(
private readonly array $items = [],
?string $itemType = null,
private ?string $itemType = null,
) {
if ($itemType !== null) {
Assert\Assertion::allIsInstanceOf($items, $itemType);
}
}

/**
* Add one or more items to the collection. It **does not** modify the
* current collection, but returns a new one.
*
* @param mixed $item One or more items to add to the collection.
* @return static
*
* @throws Assert\AssertionFailedException
*/
public function add(mixed $item): static
{
if (!is_array($item)) {
$item = [$item];
}

if ($this->itemType !== null) {
Assert\Assertion::allIsInstanceOf($item, $this->itemType);
}

return new static([...$this->items, ...$item], $this->itemType);
}

/**
* Filter the collection using the given callback. It **does not** modify
* the current collection, but returns a new one.
*
* @param callable $callback The callback to use for filtering.
* @return static
*/
public function filter(callable $callback): static
{
return new static(
array_filter($this->items, $callback),
$this->itemType,
);
}

/**
* Map the collection using the given callback. It **does not** modify
* the current collection, but returns a new one.
*
* @param callable $callback The callback to use for mapping.
* @param bool $inferTypes Whether to infer the type of the items in the
* collection based on the first item in the
* mapping result. Defaults to `true`.
*
* @return static
*/
public function map(callable $callback, bool $inferTypes = true): static
{
$mapResult = array_map($callback, $this->items);
$firstItem = reset($mapResult);

if ($firstItem === false || !is_object($firstItem)) {
return new static($mapResult);
}

if ($inferTypes && $this->itemType !== null) {
return new static($mapResult, get_class($firstItem));
}

return new static($mapResult);
}

/**
* Reduce the collection using the given callback.
*
* @param callable $callback The callback to use for reducing.
* @param mixed $initial The initial value to use for reducing.
*
* @return mixed
*/
public function reduce(callable $callback, mixed $initial = null): mixed
{
return array_reduce($this->items, $callback, $initial);
}

/**
* @inheritDoc
*/
public function getIterator(): Traversable
public function offsetExists(mixed $offset): bool
{
return new ArrayIterator($this->items);
if (!is_int($offset)) {
return false;
}

return isset($this->items[$offset]);
}

/**
* @inheritDoc
*/
public function offsetGet(mixed $offset): mixed
{
if (!$this->offsetExists($offset)) {
return null;
}

return $this->items[$offset];
}

/**
* This method is not supported since it would break the immutability of the
* collection.
*
* @inheritDoc
*/
public function offsetSet(mixed $offset, mixed $value): void
{
// Unsupported since it would break the immutability of the collection.
}

/**
* This method is not supported since it would break the immutability of the
* collection.
*
* @inheritDoc
*/
public function offsetUnset(mixed $offset): void
{
// Unsupported since it would break the immutability of the collection.
}

/**
Expand All @@ -43,4 +161,12 @@ public function count(): int
{
return count($this->items);
}

/**
* @inheritDoc
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->items);
}
}
2 changes: 1 addition & 1 deletion src/Infrastructure/InMemory/CommandBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use GeekCell\Ddd\Contracts\Application\Command;
use GeekCell\Ddd\Contracts\Application\CommandBus as CommandBusInterface;
use GeekCell\Ddd\Contracts\Application\CommandHandler;
use GeekCell\Ddd\Support\Attributes\For\Command as ForCommand;
use GeekCell\Ddd\Support\Attributes\ForType\Command as ForCommand;

class CommandBus extends AbstractBus implements CommandBusInterface
{
Expand Down
54 changes: 53 additions & 1 deletion src/Infrastructure/InMemory/Paginator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@

namespace GeekCell\Ddd\Infrastructure\InMemory;

use ArrayAccess;
use EmptyIterator;
use GeekCell\Ddd\Contracts\Domain\Paginator as PaginatorInterface;
use GeekCell\Ddd\Domain\Collection;
use LimitIterator;
use Traversable;

class Paginator implements PaginatorInterface
class Paginator implements PaginatorInterface, ArrayAccess
{
public function __construct(
private readonly Collection $collection,
Expand Down Expand Up @@ -52,6 +53,57 @@ public function getTotalItems(): int
return count($this->collection);
}

/**
* @inheritDoc
*/
public function offsetExists(mixed $offset): bool
{
if (!is_int($offset)) {
return false;
}

return $offset >= 0 && $offset < $this->count();
}

/**
* @inheritDoc
*/
public function offsetGet(mixed $offset): mixed
{
if (!$this->offsetExists($offset)) {
return null;
}

$realOffset = $offset + $this->getCurrentPage();
foreach ($this->getIterator() as $index => $item) {
if ($index === $realOffset) {
return $item;
}
}

return null;
}

/**
* This method is not supported since it is not appropriate for a paginator.
*
* @inheritDoc
*/
public function offsetSet(mixed $offset, mixed $value): void
{
// Unsupported since it is not appropriate for a paginator.
}

/**
* This method is not supported since it is not appropriate for a paginator.
*
* @inheritDoc
*/
public function offsetUnset(mixed $offset): void
{
// Unsupported since it is not appropriate for a paginator.
}

/**
* @inheritDoc
*/
Expand Down
2 changes: 1 addition & 1 deletion src/Infrastructure/InMemory/QueryBus.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use GeekCell\Ddd\Contracts\Application\Query;
use GeekCell\Ddd\Contracts\Application\QueryBus as QueryBusInterface;
use GeekCell\Ddd\Contracts\Application\QueryHandler;
use GeekCell\Ddd\Support\Attributes\For\Query as ForQuery;
use GeekCell\Ddd\Support\Attributes\ForType\Query as ForQuery;

final class QueryBus extends AbstractBus implements QueryBusInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace GeekCell\Ddd\Support\Attributes\For;
namespace GeekCell\Ddd\Support\Attributes\ForType;

use Attribute;
use GeekCell\Ddd\Contracts\Application\Command as CommandInterface;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

namespace GeekCell\Ddd\Support\Attributes\For;
namespace GeekCell\Ddd\Support\Attributes\ForType;

use Attribute;
use GeekCell\Ddd\Contracts\Application\Query as QueryInterface;
Expand Down
Loading