Skip to content
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

PHPORM-238 Add support for withCount and other aggregations #3182

Open
wants to merge 11 commits into
base: 5.x
Choose a base branch
from

Conversation

GromNaN
Copy link
Member

@GromNaN GromNaN commented Oct 17, 2024

Fix PHPORM-238

Implement this feature from Laravel: https://laravel.com/docs/11.x/eloquent-relationships#counting-related-models
Counting relations with SQL is done using a subquery. For MongoDB, there is 2 ways:

  1. Making a query during eager loading, that count the objects for each result ID. This is an aggregation with a $group by foreign key.
  2. Using $lookup to load references and count them. The process would be similar for embedded relations (without the $lookup). But this doesn't work with hybrid relations.

Checklist

src/Query/Builder.php Outdated Show resolved Hide resolved
src/Query/Builder.php Outdated Show resolved Hide resolved
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
@GromNaN GromNaN marked this pull request as ready for review January 22, 2025 17:28
@GromNaN GromNaN requested a review from a team as a code owner January 22, 2025 17:28
@GromNaN GromNaN requested a review from jmikola January 22, 2025 17:28
tests/HybridRelationsTest.php Outdated Show resolved Hide resolved
$this->assertFalse(DB::table('items')->where('name', 'ladle')->exists());
$this->assertFalse(DB::table('items')->doesntExist());
$this->assertFalse(DB::table('items')->where('name', 'knife')->doesntExist());
$this->assertTrue(DB::table('items')->where('name', 'ladle')->doesntExist());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like basic tests for exists() and doesntExist() methods. How is this related to the PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that was not tested when working on the aggregate method. That could be part of PHPORM-285 that I had to postpone.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd agree that this is better off in a separate PR/commit. Or include it here but reference PHPORM-285 in the commit message (which can still be squashed at merge time, but at least we have a note pointing to the relevant issue).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed this tests and created #3257
I realized, only doesntExist was not tested.

tests/HybridRelationsTest.php Outdated Show resolved Hide resolved
['id' => 2, 'twos_min' => 4],
], $results->get());

$results = EloquentWithAggregateModel1::withAvg(['twos' => $filter], 'value')->where('id', 2);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In QueryBuilderTest, I noted there were two names for the same method: avg() and average(). Is there also a withAverage() method that should be tested?

tests/Eloquent/EloquentWithAggregateTest.php Show resolved Hide resolved
src/Eloquent/Builder.php Show resolved Hide resolved
src/Eloquent/Builder.php Show resolved Hide resolved
$modelIds = collect($models)->pluck($this->model->getKeyName())->all();

foreach ($this->withAggregate as $withAggregate) {
if ($withAggregate['relation'] instanceof HasMany) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this only applies some processing for HasMany relations, under what circumstances would different relations get appended to $this->withAggregate? And are they OK being left as-is?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding an exception in the "else" case. I thing the only other case if "HasOne", but I need to check "BelongsToMany".

src/Eloquent/Builder.php Show resolved Hide resolved
src/Eloquent/Builder.php Show resolved Hide resolved
Copy link
Member Author

@GromNaN GromNaN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I addressed most comments but need to add tests on the new error cases.

@@ -650,6 +650,14 @@ public function aggregateByGroup(string $function, array $columns = ['*'])
return $this->aggregate($function, $columns);
}

public function count($columns = '*')
{
// Can be removed when available in Laravel: https://github.com/laravel/framework/pull/53209
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then reverted again: laravel/framework#54196


public function testWithAggregate()
{
EloquentWithAggregateModel1::create(['id' => 1]);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ensures that the initial query filter on id works (perhaps I'm being too creative about what might fail).

tests/HybridRelationsTest.php Outdated Show resolved Hide resolved
tests/HybridRelationsTest.php Outdated Show resolved Hide resolved
$this->assertFalse(DB::table('items')->where('name', 'ladle')->exists());
$this->assertFalse(DB::table('items')->doesntExist());
$this->assertFalse(DB::table('items')->where('name', 'knife')->doesntExist());
$this->assertTrue(DB::table('items')->where('name', 'ladle')->doesntExist());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized that was not tested when working on the aggregate method. That could be part of PHPORM-285 that I had to postpone.


$relations = is_array($relations) ? $relations : [$relations];

foreach ($this->parseWithRelations($relations) as $name => $constraints) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's adding the constraint closure from the scope and the relationships.

src/Eloquent/Builder.php Outdated Show resolved Hide resolved
src/Eloquent/Builder.php Show resolved Hide resolved
src/Eloquent/Builder.php Show resolved Hide resolved
tests/Eloquent/EloquentWithAggregateTest.php Show resolved Hide resolved
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
src/Eloquent/Builder.php Fixed Show fixed Hide fixed
src/Eloquent/Builder.php Outdated Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants