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

[9.x] Add ability to define "with" relations as a nested array #42690

Merged
merged 3 commits into from
Jun 9, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 90 additions & 14 deletions src/Illuminate/Database/Eloquent/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -1430,22 +1430,13 @@ public function newModelInstance($attributes = [])
*/
protected function parseWithRelations(array $relations)
{
$results = [];
if ($relations === []) {
return [];
}

foreach ($relations as $name => $constraints) {
// If the "name" value is a numeric key, we can assume that no constraints
// have been specified. We will just put an empty Closure there so that
// we can treat these all the same while we are looping through them.
if (is_numeric($name)) {
$name = $constraints;

[$name, $constraints] = str_contains($name, ':')
? $this->createSelectWithConstraint($name)
: [$name, static function () {
//
}];
}
$results = [];

foreach ($this->prepareNestedWithRelationships($relations) as $name => $constraints) {
// We need to separate out any nested includes, which allows the developers
// to load deep relationships using "dots" without stating each level of
// the relationship with its own key in the array of eager-load names.
Expand All @@ -1457,6 +1448,91 @@ protected function parseWithRelations(array $relations)
return $results;
}

/**
* Prepare nested with relationships.
*
* @param array $relations
* @param string $prefix
* @return array
*/
protected function prepareNestedWithRelationships($relations, $prefix = '')
{
$preparedRelationships = [];

if ($prefix !== '') {
$prefix .= '.';
}

// If any of the relationships are formatted with the [$attribute => array()]
// syntax, we shall loop over the nested relations and prepend each key of
// the array while flattening into the traditional dot notation format.
foreach ($relations as $key => $value) {
if (! is_string($key) || ! is_array($value)) {
continue;
}

[$attribute, $attributeSelectConstraint] = $this->parseNameAndAttributeSelectionConstraint($key);

$preparedRelationships = array_merge(
$preparedRelationships,
["{$prefix}{$attribute}" => $attributeSelectConstraint],
$this->prepareNestedWithRelationships($value, "{$prefix}{$attribute}"),
);

unset($relations[$key]);
}

// We now know that the remaining relationships are in a dot notation format.
// The values maybe a string or a Closure. We'll loop over them and ensure
// existing Closures are merged and strings are made into a constraint.
foreach ($relations as $key => $value) {
if (is_numeric($key) && is_string($value)) {
[$key, $value] = $this->parseNameAndAttributeSelectionConstraint($value);
}

$preparedRelationships[$prefix.$key] = $this->combineContraints([
$value,
$preparedRelationships[$prefix.$key] ?? static function () {
//
},
]);
}

return $preparedRelationships;
}

/**
* Combine an array of constraints into a single constraint.
*
* @param array $constriants
* @return \Closure
*/
protected function combineContraints(array $constraints)
timacdonald marked this conversation as resolved.
Show resolved Hide resolved
{
return function ($builder) use ($constraints) {
foreach ($constraints as $constraint) {
$builder = $constraint($builder) ?? $builder;
}

return $builder;
};
}

/**
* Parse the attribute select constraints from the name.
*
* @param string $name
* @return array
*/
protected function parseNameAndAttributeSelectionConstraint($name)
{
return str_contains($name, ':')
? $this->createSelectWithConstraint($name)
: [$name, static function () {
//
}];
}

/**
* Create a constraint to select the given columns for the relation.
*
Expand Down
6 changes: 3 additions & 3 deletions tests/Database/DatabaseEloquentBuilderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -842,7 +842,7 @@ public function testEagerLoadParsingSetsProperRelationships()
}]);
$eagers = $builder->getEagerLoads();

$this->assertSame('foo', $eagers['orders']());
$this->assertSame('foo', $eagers['orders']($this->getBuilder()));

$builder = $this->getBuilder();
$builder->with(['orders.lines' => function () {
Expand All @@ -852,7 +852,7 @@ public function testEagerLoadParsingSetsProperRelationships()

$this->assertInstanceOf(Closure::class, $eagers['orders']);
$this->assertNull($eagers['orders']());
$this->assertSame('foo', $eagers['orders.lines']());
$this->assertSame('foo', $eagers['orders.lines']($this->getBuilder()));

$builder = $this->getBuilder();
$builder->with('orders.lines', function () {
Expand All @@ -862,7 +862,7 @@ public function testEagerLoadParsingSetsProperRelationships()

$this->assertInstanceOf(Closure::class, $eagers['orders']);
$this->assertNull($eagers['orders']());
$this->assertSame('foo', $eagers['orders.lines']());
$this->assertSame('foo', $eagers['orders.lines']($this->getBuilder()));
}

public function testQueryPassThru()
Expand Down
Loading