Merge / extend ObjectRepository with Collection interfaces (3.0) #5445

Open
opened 2026-01-22 15:07:57 +01:00 by admin · 1 comment
Owner

Originally created by @guiwoda on GitHub (Mar 6, 2017).

I'm opening this issue as a proposal and a place to debate this. I can (and want to, if you guys agree) implement this on ORM.NEXT, so it's not only a feature request! I just know beforehand that it'll be a lot of code changes, and don't want to send a PR without chatting a bit first.

BTW, I know of doctrine-dev IRC, but I don't usually get much feedback there, so don't be mad at me for trying something different this time 😅

Repository pattern

From PoEAA: (The repository) Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

Proposal

Make repositories expose a collection-like interface. Then, make both repositories and collections share implementations.

  • iterable repositories
  • implement all Collection methods
  • "query" methods could return a new instance of the Repository / Collection instead of fetching right away or falling back to QueryBuilder. The new instance would accumulate conditions and execute the query when iterated or otherwise initialized.
  • Merging both worlds would reduce userland complexity when working with associations or repositories: Associations are pre-filtered repositories.

Examples

Iterable repositories

$books = $entityManager->getRepository(Book::class);

foreach ($books as $book) { /* SELECT * FROM books; */ }

Associations as repositories

$author = $entityManager->find(Author::class, 1);
foreach ($author->getBooks() as $book) { /* SELECT * FROM books WHERE author_id = 1 */ }

// Nested calls could be lazily implemented, returning a new instance of the repo with the accumulated query
$author->getBooks()->findBy(['format' => $format])->first(); // SELECT ... WHERE format_id = :id LIMIT 1;

// This could also be improved somehow, Criteria is very verbose...
$filtered = $authors->getBooks()->matching(Criteria::create()->where(
    Criteria::expr()->eq('foo', 'bar')
));

// SELECT * FROM books WHERE author_id = 1 AND foo = 'bar' LIMIT 1;
$filtered->first();

Aggregate queries could be also lazy loaded and handled with a single API:

$books = $entityManager->getRepository(Book::class);

// No query yet, $featured instanceof Repository
$featured = $books->findBy(['featured' => true]);

// Could be answered with single query instead of loading entities to memory
$featured->average('price');
$featured->count();
$featured->sum('price');

$featured->map(function (Book $book) {
    // Loaded entities on memory
});

$featured->reduce(function ($carry, Book $book) {
    // Loaded entities on memory
}, 0);

Modeling system facades:

class Library
{
    private $books;
    private $authors;

    public function __construct(Repository $books, Repository $authors){ /* ... */ }

    public function featuredBooks()
    {
        // Could also be a custom method in repository, just illustrating here.
        return $this->books->findBy(['featured' => true]);
    }
}

class HomeController
{
    public function home(Library $library)
    {
         return view('home', ['books' => $library->featuredBooks()]);
    }
}
Originally created by @guiwoda on GitHub (Mar 6, 2017). I'm opening this issue as a proposal and a place to debate this. I can (and want to, if you guys agree) implement this on ORM.NEXT, so it's not only a feature request! I just know beforehand that it'll be a lot of code changes, and don't want to send a PR without chatting a bit first. BTW, I know of doctrine-dev IRC, but I don't usually get much feedback there, so don't be mad at me for trying something different this time :sweat_smile: # Repository pattern From PoEAA: _(The repository)_ Mediates between the domain and data mapping layers using a **collection-like interface** for accessing domain objects. ## Proposal Make repositories expose a collection-like interface. Then, make both repositories and collections share implementations. * iterable repositories * implement all Collection methods * "query" methods could return a new instance of the Repository / Collection instead of fetching right away or falling back to QueryBuilder. The new instance would accumulate conditions and execute the query when iterated or otherwise initialized. * Merging both worlds would reduce userland complexity when working with associations or repositories: Associations are pre-filtered repositories. ## Examples Iterable repositories ```php $books = $entityManager->getRepository(Book::class); foreach ($books as $book) { /* SELECT * FROM books; */ } ``` Associations as repositories ```php $author = $entityManager->find(Author::class, 1); foreach ($author->getBooks() as $book) { /* SELECT * FROM books WHERE author_id = 1 */ } // Nested calls could be lazily implemented, returning a new instance of the repo with the accumulated query $author->getBooks()->findBy(['format' => $format])->first(); // SELECT ... WHERE format_id = :id LIMIT 1; // This could also be improved somehow, Criteria is very verbose... $filtered = $authors->getBooks()->matching(Criteria::create()->where( Criteria::expr()->eq('foo', 'bar') )); // SELECT * FROM books WHERE author_id = 1 AND foo = 'bar' LIMIT 1; $filtered->first(); ``` Aggregate queries could be also lazy loaded and handled with a single API: ```php $books = $entityManager->getRepository(Book::class); // No query yet, $featured instanceof Repository $featured = $books->findBy(['featured' => true]); // Could be answered with single query instead of loading entities to memory $featured->average('price'); $featured->count(); $featured->sum('price'); $featured->map(function (Book $book) { // Loaded entities on memory }); $featured->reduce(function ($carry, Book $book) { // Loaded entities on memory }, 0); ``` Modeling system facades: ```php class Library { private $books; private $authors; public function __construct(Repository $books, Repository $authors){ /* ... */ } public function featuredBooks() { // Could also be a custom method in repository, just illustrating here. return $this->books->findBy(['featured' => true]); } } class HomeController { public function home(Library $library) { return view('home', ['books' => $library->featuredBooks()]); } } ```
Author
Owner

@guiwoda commented on GitHub (Mar 6, 2017):

May be related to https://github.com/doctrine/doctrine2/issues/6311

@guiwoda commented on GitHub (Mar 6, 2017): May be related to https://github.com/doctrine/doctrine2/issues/6311
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5445