kaskadia / doctrine-repository-wrapper-uuid
Provides an abstraction layer above the Doctrine Repository in Laravel which enables better type safety when interacting with entities. This package uses UUID as the primary identifier.
Package info
gitlab.com/kaskadia/doctrine-repository-wrapper-uuid
pkg:composer/kaskadia/doctrine-repository-wrapper-uuid
Requires
- php: ^8.1||^8.2||^8.3||^8.4||^8.5
- ext-json: *
- laravel-doctrine/orm: 1.6.*||1.7.*||1.8.*||^2.0.0||^2.1.0||^3
- ramsey/uuid: ^3.9||^4.0
- ramsey/uuid-doctrine: ^1.6||^2.1.0
Requires (Dev)
- illuminate/pagination: ^8.0||^9.1||^10.0.0||^11.0.0||^12.0||^13.0
- illuminate/support: ^8.0||^9.1||^10.0.0||^11.0.0||^12.0||^13.0
- mockery/mockery: 1.6.x-dev
- phpunit/phpunit: ^10.0||^11.0||^12.0
README
Provides an abstraction layer above the Doctrine Repository in Laravel which enables better type safety when interacting with entities. This package uses UUID (v7) as the primary identifier.
Requirements
- PHP ^8.1
laravel-doctrine/orm1.6–1.8 or ^2.0/^2.1/^3ramsey/uuid^3.9 or ^4.0ramsey/uuid-doctrine^1.6 or ^2.1
Installation
composer require kaskadia/doctrine-repository-wrapper-uuid
The package's service provider (DoctrineRepositoryWrapperProvider) is auto-discovered by Laravel
and binds IDoctrineWrapperRepository to DoctrineWrapperRepository in the container.
Entities
Extend EntityBase to get a UUIDv7 primary key generated automatically on construction (and left
alone on hydration, since Doctrine sets $id directly when loading from the database):
use Doctrine\ORM\Mapping\{Column, Entity, Table};
use Kaskadia\Lib\DoctrineRepositoryWrapperUuid\Entities\EntityBase;
use Kaskadia\Lib\DoctrineRepositoryWrapperUuid\Entities\Interfaces\IEntity;
use Kaskadia\Lib\DoctrineRepositoryWrapperUuid\Traits\Entities\Types;
#[Entity]
#[Table(name: 'products')]
class Product extends EntityBase implements IEntity {
use Types; // optional: adds mapped `name` and `slug` columns + accessors
#[Column]
protected bool $active = true;
public function isActive(): bool {
return $this->active;
}
}
getId(): string returns the UUID. setId() only takes effect if the id isn't already set, so
entities are effectively immutable after creation/hydration.
Repositories
Build your own repository classes on top of IDoctrineWrapperRepository (which wraps a Doctrine
EntityRepository) and the BaseRepository trait, which adds delegation plus pagination helpers:
use Kaskadia\Lib\DoctrineRepositoryWrapperUuid\Repositories\Interfaces\IDoctrineWrapperRepository;
use Kaskadia\Lib\DoctrineRepositoryWrapperUuid\Traits\Repositories\BaseRepository;
class ProductRepository {
use BaseRepository;
protected IDoctrineWrapperRepository $repository;
public function __construct(IDoctrineWrapperRepository $repository) {
$repository->initializeRepository(Product::class);
$this->repository = $repository;
}
}
Resolve it through the container (or construct it directly) and call:
$repo = app(ProductRepository::class);
$repo->findAll(); // Collection<Product>
$repo->findBy(['active' => true]); // Collection<Product>
$product = new Product();
$repo->saveAndFlush($product); // persist + flush, returns $this
$repo->deleteAndFlush($product); // remove + flush, returns $this
Pagination
Three methods bridge Doctrine queries into Laravel's native LengthAwarePaginator:
// 1. Paginate a query/query builder you built yourself
$repo->paginate($queryBuilder, perPage: 15, page: $request->integer('page', 1));
// 2. Paginate by exact-match criteria, mirroring findBy()
$repo->paginateBy(
criteria: ['active' => true, 'status' => ['draft', 'published']],
orderBy: ['name' => 'ASC'],
perPage: 15,
page: $request->integer('page', 1),
);
// 3. Paginate with full control over the QueryBuilder (joins, LIKE, ranges, etc.)
$repo->paginateWith(function (QueryBuilder $qb) use ($search) {
$qb->andWhere('e.name LIKE :search')->setParameter('search', "%{$search}%");
}, perPage: 15, page: $request->integer('page', 1));
paginateBy() criteria rules: scalar values match with =, arrays match with IN (...), and
null matches with IS NULL. Dotted field names (association paths like category.slug) are
supported; the . is replaced with _ when naming the bound parameter.
Testing
composer install
vendor/bin/phpunit # full suite
vendor/bin/phpunit --filter testMethodName # single test
XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text # coverage (requires xdebug)
License
MIT