Skip to content

Commit

Permalink
Refactor populateRelation()
Browse files Browse the repository at this point in the history
  • Loading branch information
Tigrov committed Jun 5, 2024
1 parent 014af65 commit 5f01c9a
Showing 1 changed file with 79 additions and 133 deletions.
212 changes: 79 additions & 133 deletions src/ActiveRelationTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use function array_diff_key;
use function array_fill_keys;
use function array_filter;
use function array_flip;
use function array_intersect_key;
use function array_keys;
use function array_merge;
Expand Down Expand Up @@ -248,20 +249,18 @@ public function populateRelation(string $name, array &$primaryModels): array
}

if (!$this->multiple && count($primaryModels) === 1) {
$model = $this->onePopulate();
$models = [$this->onePopulate()];
$this->populateInverseRelation($models, $primaryModels);

$primaryModel = reset($primaryModels);

if ($primaryModel instanceof ActiveRecordInterface) {
$primaryModel->populateRelation($name, $model);
$primaryModel->populateRelation($name, $models[0]);
} else {
$primaryModels[key($primaryModels)][$name] = $model;
}

if ($this->inverseOf !== null) {
$this->populateInverseRelation($primaryModels, [$model], $name, $this->inverseOf);
$primaryModels[key($primaryModels)][$name] = $models[0];
}

return [$model];
return $models;
}

/**
Expand All @@ -273,6 +272,8 @@ public function populateRelation(string $name, array &$primaryModels): array
$this->indexBy(null);
$models = $this->all();

$this->populateInverseRelation($models, $primaryModels);

if (isset($viaModels, $viaQuery)) {
$buckets = $this->buildBuckets($models, $viaModels, $viaQuery);
} else {
Expand All @@ -297,51 +298,7 @@ public function populateRelation(string $name, array &$primaryModels): array
$link = $this->link;
}

foreach ($primaryModels as $i => $primaryModel) {
$keys = null;

if ($this->multiple && count($link) === 1) {
$primaryModelKey = reset($link);

if ($primaryModel instanceof ActiveRecordInterface) {
$keys = $primaryModel->getAttribute($primaryModelKey);
} else {
$keys = $primaryModel[$primaryModelKey] ?? null;
}
}

if (is_array($keys)) {
$value = [];

foreach ($keys as $key) {
$key = (string) $key;

if (isset($buckets[$key])) {
$value[] = $buckets[$key];
}
}

if ($indexBy !== null) {
/** if indexBy is set, array_merge will cause renumbering of numeric array */
$value = array_replace(...$value);
} else {
$value = array_merge(...$value);
}
} else {
$key = $this->getModelKey($primaryModel, $link);
$value = $buckets[$key] ?? ($this->multiple ? [] : null);
}

if ($primaryModel instanceof ActiveRecordInterface) {
$primaryModel->populateRelation($name, $value);
} else {
$primaryModels[$i][$name] = $value;
}
}

if ($this->inverseOf !== null) {
$this->populateInverseRelation($primaryModels, $models, $name, $this->inverseOf);
}
$this->populateRelationFromBuckets($primaryModels, $buckets, $name, $link);

return $models;
}
Expand All @@ -350,84 +307,57 @@ public function populateRelation(string $name, array &$primaryModels): array
* @throws \Yiisoft\Definitions\Exception\InvalidConfigException
*/
private function populateInverseRelation(
array &$primaryModels,
array $models,
string $primaryName,
string $name
array &$models,
array $primaryModels,
): void {
if (empty($models) || empty($primaryModels)) {
if ($this->inverseOf === null || empty($models) || empty($primaryModels)) {
return;
}

$name = $this->inverseOf;
$model = reset($models);

if ($model instanceof ActiveRecordInterface) {
$this->populateInverseRelationToModels($models, $primaryModels, $name);
return;
}
/** @var ActiveQuery $relation */
$relation = is_array($model)
? $this->getARInstance()->relationQuery($name)
: $model->relationQuery($name);

$primaryModel = reset($primaryModels);
$link = $relation->getLink();
$buckets = $relation->buildBuckets($primaryModels);

if ($primaryModel instanceof ActiveRecordInterface) {
if ($this->multiple) {
foreach ($primaryModels as $primaryModel) {
$models = $primaryModel->relation($primaryName);
if (!empty($models)) {
$this->populateInverseRelationToModels($models, $primaryModels, $name);
$primaryModel->populateRelation($primaryName, $models);
}
}
} else {
foreach ($primaryModels as $primaryModel) {
$model = $primaryModel->relation($primaryName);
if (!empty($model)) {
$models = [$model];
$this->populateInverseRelationToModels($models, $primaryModels, $name);
$primaryModel->populateRelation($primaryName, $models[0]);
}
}
}
} else {
if ($this->multiple) {
foreach ($primaryModels as &$primaryModel) {
if (!empty($primaryModel[$primaryName])) {
$this->populateInverseRelationToModels($primaryModel[$primaryName], $primaryModels, $name);
}
}
} else {
foreach ($primaryModels as &$primaryModel) {
if (!empty($primaryModel[$primaryName])) {
$models = [$primaryModel[$primaryName]];
$this->populateInverseRelationToModels($models, $primaryModels, $name);
$primaryModel[$primaryName] = $models[0];
}
}
}
if ($relation->getMultiple() && $relation->getIndexBy() !== null) {
$buckets = $this->indexBuckets($buckets, $relation->getIndexBy());

Check failure on line 329 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

PossiblyNullArgument

src/ActiveRelationTrait.php:329:54: PossiblyNullArgument: Argument 2 of Yiisoft\ActiveRecord\ActiveQuery::indexBuckets cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 329 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.2, psalm.xml) / PHP 8.2-ubuntu-latest

PossiblyNullArgument

src/ActiveRelationTrait.php:329:54: PossiblyNullArgument: Argument 2 of Yiisoft\ActiveRecord\ActiveQuery::indexBuckets cannot be null, possibly null value provided (see https://psalm.dev/078)

Check failure on line 329 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

PossiblyNullArgument

src/ActiveRelationTrait.php:329:54: PossiblyNullArgument: Argument 2 of Yiisoft\ActiveRecord\ActiveQuery::indexBuckets cannot be null, possibly null value provided (see https://psalm.dev/078)

Check warning on line 329 in src/ActiveRelationTrait.php

View check run for this annotation

Codecov / codecov/patch

src/ActiveRelationTrait.php#L329

Added line #L329 was not covered by tests
}

$relation->populateRelationFromBuckets($models, $buckets, $name, $link);
}

private function populateInverseRelationToModels(array &$models, array $primaryModels, string $name): void
{
$model = reset($models);
$isArray = is_array($model);
private function populateRelationFromBuckets(
array &$models,
array $buckets,
string $name,
array $link
): void {
$indexBy = $this->getIndexBy();
$default = $this->multiple ? [] : null;

/** @var ActiveQuery $relation */
$relation = $isArray ? $this->getARInstance()->relationQuery($name) : $model->relationQuery($name);
$buckets = $relation->buildBuckets($primaryModels);
$link = $relation->getLink();
$default = $relation->getMultiple() ? [] : null;
foreach ($models as &$model) {
$keys = $this->getModelKeys($model, $link);

if ($isArray) {
/** @var array $model */
foreach ($models as &$model) {
$key = $this->getModelKey($model, $link);
$model[$name] = $buckets[$key] ?? $default;
}
} else {
/** @var ActiveRecordInterface $model */
foreach ($models as $model) {
$key = $this->getModelKey($model, $link);
$model->populateRelation($name, $buckets[$key] ?? $default);
$value = match (count($keys)) {
0 => $default,
1 => $buckets[$keys[0]] ?? $default,
default => !$this->multiple
? $default
: ($indexBy !== null
? array_replace(...array_intersect_key($buckets, array_flip($keys)))
: array_merge(...array_intersect_key($buckets, array_flip($keys)))),

Check failure on line 354 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

NamedArgumentNotAllowed

src/ActiveRelationTrait.php:354:42: NamedArgumentNotAllowed: Method array_merge called with named unpacked array array<array-key, mixed> (array with string keys) (see https://psalm.dev/268)

Check failure on line 354 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.2, psalm.xml) / PHP 8.2-ubuntu-latest

NamedArgumentNotAllowed

src/ActiveRelationTrait.php:354:42: NamedArgumentNotAllowed: Method array_merge called with named unpacked array array<array-key, mixed> (array with string keys) (see https://psalm.dev/268)

Check failure on line 354 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

NamedArgumentNotAllowed

src/ActiveRelationTrait.php:354:42: NamedArgumentNotAllowed: Method array_merge called with named unpacked array array<array-key, mixed> (array with string keys) (see https://psalm.dev/268)

Check warning on line 354 in src/ActiveRelationTrait.php

View check run for this annotation

Codecov / codecov/patch

src/ActiveRelationTrait.php#L351-L354

Added lines #L351 - L354 were not covered by tests
};

if ($model instanceof ActiveRecordInterface) {
$model->populateRelation($name, $value);
} else {
$model[$name] = $value;
}
}
}
Expand All @@ -445,9 +375,17 @@ private function buildBuckets(
$viaVia = null;

foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, $linkValues);
$map[$key2][$key1] = true;
$key1 = $this->getModelKeys($viaModel, $viaLinkKeys);
$key2 = $this->getModelKeys($viaModel, $linkValues);
$flags = array_fill_keys($key1, true);

foreach ($key2 as $key) {
if (isset($map[$key])) {
$map[$key] += $flags;
} else {
$map[$key] = $flags;
}
}
}

if ($viaQuery !== null) {
Expand All @@ -473,18 +411,22 @@ private function buildBuckets(

if (isset($map)) {
foreach ($models as $model) {
$key = $this->getModelKey($model, $linkKeys);
if (isset($map[$key])) {
foreach (array_keys($map[$key]) as $key2) {
/** @psalm-suppress InvalidArrayOffset */
$keys = $this->getModelKeys($model, $linkKeys);
$filtered = array_intersect_key($map, array_flip($keys));

foreach ($filtered as $keys2) {
foreach (array_keys($keys2) as $key2) {
$buckets[$key2][] = $model;

Check failure on line 419 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

InvalidArrayOffset

src/ActiveRelationTrait.php:419:25: InvalidArrayOffset: Cannot access value on variable $buckets[$key2] using a key-of<array<array-key, mixed>> offset, expecting array-key (see https://psalm.dev/115)

Check failure on line 419 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.2, psalm.xml) / PHP 8.2-ubuntu-latest

InvalidArrayOffset

src/ActiveRelationTrait.php:419:25: InvalidArrayOffset: Cannot access value on variable $buckets[$key2] using a key-of<array<array-key, mixed>> offset, expecting array-key (see https://psalm.dev/115)

Check failure on line 419 in src/ActiveRelationTrait.php

View workflow job for this annotation

GitHub Actions / psalm (8.1, psalm.xml) / PHP 8.1-ubuntu-latest

InvalidArrayOffset

src/ActiveRelationTrait.php:419:25: InvalidArrayOffset: Cannot access value on variable $buckets[$key2] using a key-of<array<array-key, mixed>> offset, expecting array-key (see https://psalm.dev/115)
}
}
}
} else {
foreach ($models as $model) {
$key = $this->getModelKey($model, $linkKeys);
$buckets[$key][] = $model;
$keys = $this->getModelKeys($model, $linkKeys);

foreach ($keys as $key) {
$buckets[$key][] = $model;
}
}
}

Expand Down Expand Up @@ -636,30 +578,34 @@ protected function filterByModels(array $models): void
$this->andWhere(['in', $attributes, $values]);
}

private function getModelKey(ActiveRecordInterface|array $activeRecord, array $attributes): string
private function getModelKeys(ActiveRecordInterface|array $activeRecord, array $attributes): array
{
$key = [];

if (is_array($activeRecord)) {
foreach ($attributes as $attribute) {
if (isset($activeRecord[$attribute])) {
$key[] = (string) $activeRecord[$attribute];
$key[] = is_array($activeRecord[$attribute])
? $activeRecord[$attribute]

Check warning on line 589 in src/ActiveRelationTrait.php

View check run for this annotation

Codecov / codecov/patch

src/ActiveRelationTrait.php#L589

Added line #L589 was not covered by tests
: (string) $activeRecord[$attribute];
}
}
} else {
foreach ($attributes as $attribute) {
$value = $activeRecord->getAttribute($attribute);

if ($value !== null) {
$key[] = (string) $value;
$key[] = is_array($value)
? $value

Check warning on line 599 in src/ActiveRelationTrait.php

View check run for this annotation

Codecov / codecov/patch

src/ActiveRelationTrait.php#L599

Added line #L599 was not covered by tests
: (string) $value;
}
}
}

return match (count($key)) {
0 => '',
1 => $key[0],
default => serialize($key),
0 => [],
1 => is_array($key[0]) ? $key[0] : [$key[0]],
default => [serialize($key)],
};
}

Expand Down

0 comments on commit 5f01c9a

Please sign in to comment.