diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf96c9..2608d6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * refactor: rename the term 'large' to 'basic' * cleanup: Basic classes do not require soft and hard limits * cleanup: orderBy should be non-empty-array +* feat: initial version of basic repository ## 0.3.0 diff --git a/packages/collections-contracts/src/BasicRepository.php b/packages/collections-contracts/src/BasicRepository.php index c89888f..15a8408 100644 --- a/packages/collections-contracts/src/BasicRepository.php +++ b/packages/collections-contracts/src/BasicRepository.php @@ -21,4 +21,8 @@ */ interface BasicRepository extends BasicReadableRepository, BasicRecollection { + /** + * @param T $element + */ + public function remove(mixed $element): void; } diff --git a/packages/collections-contracts/src/Exception/InvalidArgumentException.php b/packages/collections-contracts/src/Exception/InvalidArgumentException.php new file mode 100644 index 0000000..0891667 --- /dev/null +++ b/packages/collections-contracts/src/Exception/InvalidArgumentException.php @@ -0,0 +1,18 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Contracts\Collections\Exception; + +class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface +{ +} diff --git a/packages/collections-orm/src/AbstractBasicRepository.php b/packages/collections-orm/src/AbstractBasicRepository.php new file mode 100644 index 0000000..52fefa4 --- /dev/null +++ b/packages/collections-orm/src/AbstractBasicRepository.php @@ -0,0 +1,178 @@ + + * + * For the full copyright and license information, please view the LICENSE file + * that was distributed with this source code. + */ + +namespace Rekalogika\Collections\ORM; + +use Doctrine\Common\Collections\Criteria; +use Doctrine\Common\Collections\Order; +use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\QueryBuilder; +use Rekalogika\Collections\ORM\Trait\QueryBuilderTrait; +use Rekalogika\Contracts\Collections\BasicRepository; +use Rekalogika\Contracts\Collections\Exception\NotFoundException; +use Rekalogika\Contracts\Collections\Exception\UnexpectedValueException; +use Rekalogika\Domain\Collections\Common\Configuration; +use Rekalogika\Domain\Collections\Common\CountStrategy; +use Rekalogika\Domain\Collections\Common\Trait\PageableTrait; + +/** + * @template TKey of array-key + * @template T of object + * @implements BasicRepository + */ +abstract class AbstractBasicRepository implements BasicRepository +{ + /** + * @use QueryBuilderTrait + */ + use QueryBuilderTrait; + + /** + * @use PageableTrait + */ + use PageableTrait; + + private QueryBuilder $queryBuilder; + + private ?int $count = 0; + + /** + * @var non-empty-array + */ + private readonly array $orderBy; + + /** + * @param null|array|string $orderBy + * @param int<1,max> $itemsPerPage + */ + public function __construct( + private EntityManagerInterface $entityManager, + array|string|null $orderBy = null, + private readonly int $itemsPerPage = 50, + private readonly CountStrategy $countStrategy = CountStrategy::Restrict, + ) { + // handle orderBy + + if ($orderBy === null) { + $orderBy = Configuration::$defaultOrderBy; + } + + if (\is_string($orderBy)) { + $orderBy = [$orderBy => Order::Ascending]; + } + + if (empty($orderBy)) { + throw new UnexpectedValueException('The order by clause cannot be empty.'); + } + + $this->orderBy = $orderBy; + + $criteria = Criteria::create()->orderBy($orderBy); + $this->queryBuilder = $this->createQueryBuilder('e')->addCriteria($criteria); + } + + /** + * @param null|array|string $orderBy + * @param int<1,max> $itemsPerPage + */ + protected function with( + null|EntityManagerInterface $entityManager = null, + array|string|null $orderBy = null, + ?int $itemsPerPage = null, + ?CountStrategy $countStrategy = null, + ): static { + // @phpstan-ignore-next-line + return new static( + entityManager: $entityManager ?? $this->entityManager, + orderBy: $orderBy ?? $this->orderBy, + itemsPerPage: $itemsPerPage ?? $this->itemsPerPage, + countStrategy: $countStrategy ?? $this->countStrategy, + ); + } + + // + // mandatory + // + + /** + * @return class-string + */ + abstract protected function getClass(): string; + + // + // misc + // + + // + // accessors for subclasses + // + + final protected function getEntityManager(): EntityManagerInterface + { + return $this->entityManager; + } + + final protected function createQueryBuilder( + string $alias, + ?string $indexBy = null + ): QueryBuilder { + return $this->getEntityManager()->createQueryBuilder() + ->select($alias) + ->from($this->getClass(), $alias, $indexBy); + } + + // + // interface methods + // + + public function getReference(int|string $key): object + { + return $this->getEntityManager() + ->getReference($this->getClass(), $key) + ?? throw new NotFoundException('Entity not found'); + } + + public function contains(mixed $element): bool + { + if (!\is_object($element)) { + return false; + } + + return $this->getEntityManager()->contains($element); + } + + public function containsKey(string|int $key): bool + { + return $this->get($key) !== null; + } + + public function get(string|int $key): mixed + { + return $this->getEntityManager()->find($this->getClass(), $key); + } + + public function getOrFail(string|int $key): mixed + { + return $this->get($key) ?? throw new NotFoundException('Entity not found'); + } + + public function add(mixed $element): void + { + $this->getEntityManager()->persist($element); + } + + public function remove(mixed $element): void + { + $this->getEntityManager()->remove($element); + } +} diff --git a/psalm.xml b/psalm.xml index 5f89a63..02f6572 100644 --- a/psalm.xml +++ b/psalm.xml @@ -42,6 +42,7 @@ +