-
-
Notifications
You must be signed in to change notification settings - Fork 144
Labels
Milestone
Description
There are five viable options:
Keep our current approach
final class Book implements DatabaseModel
{
use IsDatabaseModel;
public string $title;
public ?DateTimeImmutable $publishedAt = null;
/** @var Collection<Chapter> */
public Collection $chapters;
public Author $author;
}
// Migrations stay the same
$books = Book::query()
->where('COUNT(chapters) > 10')
->where('published_at IS NOT NULL')
->orderByDesc('published_at')
->with('author')
->limit(10)
->get();Pros:
- We already have it
- Inspired by Eloquent, so it feels very natural to a large group of PHP developers
Cons:
- I really don't like the tight coupling
- Query/object mapping is really complex and a mess
Doctrine
Pros:
- Existing, battle-tested ORM
- Also well known with the PHP community
Cons:
- Doctrine is pretty verbose, you need a lot of attributes and handholding to make it work
- It's a very classic approach to ORMs, not a super big con, but kind of in contrast with Tempest's philosophy
Mongo
Pros:
- A totally different way of thinking about ORMs, could be a "breath of fresh air"
- Andreas, who works at Mongo, is interested in collaborating if we go this route, might be an asset
Cons:
- Document databases are very different from relational ones
- Mongo isn't available by default on many servers and local installs, which I feel is still pretty important for many PHP developers.
- Higher entry barrier because of the two points above
LINQ-inspired
final class Book
{
public Id $id;
public string $title;
public ?DateTimeImmutable $publishedAt = null;
/** @var Collection<Chapter> */
public Collection $chapters;
public Author $author;
}
// Migrations would be exactly the same as now
$books = ModelQuery::for(Book::class)
->where(fn (Book $book) => $book->chapters->count() > 10)
->where(fn (Book $book) => $book->publishedAt !== null)
->orderByDesc(fn (Book $book) => $book->publishedAt)
->load(fn (Book $book) => $book->author)
->groupBy(fn (Book $book) => $book->author)
->limit(10)
->get();Pros:
- No more trait or interface on the model
- About ModelQuery: there could easily also be a variant like "EntityManager" that's injected for people who don't like using service location (internally, ModelQuery would have to reach within the container to resolve the database dependency)
- The coolest thing is the query builder, where we move away from strings, and instead use the object itself. You'd get autocompletion anywhere. We would have to jump through a LOT OF HOOPS to get this to work though 😅 Because none of the above closures are actually run! They are used as a template to build the query, which is executed when calling get (inspired by LINQ)
Cons:
- We'd have to parse the query statements manually. We're essentially building another language within PHP
Manual mapping
$query = new Query(<<<SQL
SELECT *
FROM books
INNER JOIN chapters ON chapters.book_id = books.id
INNER JOIN authors ON books.author_id = authors.id
WHERE book.published_at IS NOT NULL
HAVING COUNT(chapters) > 10
SQL);
$book = map($query)->collection()->to(Book::class);Pros:
- It's easy
- We don't have to reinvent a language between SQL and PHP, we just use SQL
- Users could easily add a layer on top of the mapper without any framework-defined abstractions (eg. repository classes)
Cons:
- Mapping between relational tables and objects isn't trivial, forcing people to do it themselves can become quite cumbersome, trying to do it for them (as with the above example) is currently not possible, and very complex
- People have gotten so used to ORMs that they don't know SQL anymore…
trungdev05