Skip to content

Commit

Permalink
You can now use junction attributes with ManyToMany::unlink() and Man…
Browse files Browse the repository at this point in the history
…yToMany:: synchronize()
  • Loading branch information
freost committed Apr 2, 2024
1 parent fae5153 commit ffa34d4
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The major version bump is due to dropped support for PHP `8.1` and a several bre
* The development error view now displays the Mako environment name.
* Added `Connection::blob()` method that allows you to easily fetch a blob column as stream.
* Added `Query::blob()` method that allows you to easily fetch a blob column as stream.
* The `ManyToMany::unlink()` and ManyToMany::synchronize()` methods now support junction attributes.

#### Improvements

Expand Down
74 changes: 62 additions & 12 deletions src/mako/database/midgard/relations/ManyToMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@
use mako\database\query\Query;
use mako\database\query\Raw;

use function array_combine;
use function array_diff;
use function array_fill;
use function array_is_list;
use function array_keys;
use function array_shift;
use function count;
use function end;
Expand Down Expand Up @@ -238,8 +242,12 @@ protected function getJunctionKeys(mixed $id): array
*/
protected function getJunctionAttributes(mixed $key, array $attributes): array
{
if (isset($attributes[$key])) {
return $attributes[$key];
if (array_is_list($attributes)) {
if (isset($attributes[$key])) {
return $attributes[$key];
}

return [];
}

return $attributes;
Expand All @@ -261,7 +269,8 @@ public function link(mixed $id, array $attributes = []): bool
foreach ($this->getJunctionKeys($id) as $key => $id) {
$columns = [$foreignKey => $foreignKeyValue, $junctionKey => $id];

$success = $success && $this->junction()->insert($columns + $this->getJunctionAttributes($key, $attributes));
$success = $success && $this->junction()
->insert($columns + $this->getJunctionAttributes($key, $attributes));
}

return $success;
Expand All @@ -281,7 +290,10 @@ public function updateLink(mixed $id, array $attributes): bool
$junctionKey = $this->getJunctionKey();

foreach ($this->getJunctionKeys($id) as $key => $id) {
$success = $success && (bool) $this->junction(true)->where($foreignKey, '=', $foreignKeyValue)->where($junctionKey, '=', $id)->update($this->getJunctionAttributes($key, $attributes));
$success = $success && (bool) $this->junction(true)
->where($foreignKey, '=', $foreignKeyValue)
->where($junctionKey, '=', $id)
->update($this->getJunctionAttributes($key, $attributes));
}

return $success;
Expand All @@ -290,40 +302,78 @@ public function updateLink(mixed $id, array $attributes): bool
/**
* Unlinks related records.
*/
public function unlink(mixed $id = null): bool
public function unlink(mixed $id = null, array $attributes = []): bool
{
$query = $this->junction(true)->where($this->getForeignKey(), '=', $this->origin->getPrimaryKeyValue());

if ($id !== null) {
$query->in($this->getJunctionKey(), $this->getJunctionKeys($id));
// If we have no attributes or the attributes are not a list
// then we can unlink all the related records in one go

if (empty($attributes) || !array_is_list($attributes)) {
if ($id !== null) {
$query->in($this->getJunctionKey(), $this->getJunctionKeys($id));
}

if (!empty($attributes)) {
foreach ($attributes as $column => $value) {
$query->where($column, '=', $value);
}
}

return (bool) $query->delete();
}

return (bool) $query->delete();
// We has a list of attributes so we need to unlink the records one by one

$success = true;

$iterable = $id === null ?
array_combine(array_keys($attributes), array_fill(0, count($attributes), null)) : $this->getJunctionKeys($id);

foreach ($iterable as $key => $id) {
$queryClone = clone $query;

if ($id !== null) {
$queryClone->where($this->getJunctionKey(), '=', $id);
}

foreach ($this->getJunctionAttributes($key, $attributes) as $column => $value) {
$queryClone->where($column, '=', $value);
}

$success = $success && (bool) $queryClone->delete();
}

return $success;
}

/**
* Synchronize related records.
*/
public function synchronize(array $ids): bool
public function synchronize(array $ids, array $attributes = []): bool
{
$success = true;

$keys = $this->getJunctionKeys($ids);

// Fetch existing links

$existing = $this->junction()->where($this->getForeignKey(), '=', $this->origin->getPrimaryKeyValue())->select([$this->getJunctionKey()])->all()->pluck($this->getJunctionKey());
$existing = $this->junction()
->where($this->getForeignKey(), '=', $this->origin->getPrimaryKeyValue())
->select([$this->getJunctionKey()])
->all()
->pluck($this->getJunctionKey());

// Link new relations

if (!empty($diff = array_diff($keys, $existing))) {
$success = $this->link($diff);
$success = $this->link($diff, $attributes);
}

// Unlink old relations

if (!empty($diff = array_diff($existing, $keys))) {
$success = $success && $this->unlink($diff);
$success = $success && $this->unlink($diff, $attributes);
}

// Return status
Expand Down
48 changes: 48 additions & 0 deletions tests/integration/database/midgard/relations/ManyToManyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ public function testLinkWithSameAttributes(): void
$group1 = ManyToManyGroup::get(1);
$group2 = ManyToManyGroup::get(4);

//

$this->assertEquals(1, count($user->groups()->all()));

$user->groups()->link([$group1->id, $group2->id], ['extra' => 'barfoo']);
Expand All @@ -456,6 +458,29 @@ public function testLinkWithSameAttributes(): void
$user->groups()->unlink([$group1->id, $group2->id]);

$this->assertEquals(1, count($user->groups()->all()));

//

$this->assertEquals(1, count($user->groups()->all()));

$user->groups()->link([$group1->id, $group2->id], ['extra' => 'barfoo']);

$groups = $user->groups()->alongWith(['extra'])->all();

$this->assertEquals(3, count($groups));

$this->assertSame('barfoo', $groups[1]->extra);
$this->assertSame('barfoo', $groups[2]->extra);

$user->groups()->unlink([$group1->id, $group2->id], ['extra' => 'foobar']);

$groups = $user->groups()->alongWith(['extra'])->all();

$this->assertEquals(3, count($groups));

$user->groups()->unlink([$group1->id, $group2->id], ['extra' => 'barfoo']);

$this->assertEquals(1, count($user->groups()->all()));
}

/**
Expand All @@ -468,6 +493,8 @@ public function testLinkWithDifferentAttributes(): void
$group1 = ManyToManyGroup::get(1);
$group2 = ManyToManyGroup::get(4);

//

$this->assertEquals(1, count($user->groups()->all()));

$user->groups()->link([$group1->id, $group2->id], [['extra' => 'barfoo'], ['extra' => 'bazbax']]);
Expand All @@ -482,6 +509,27 @@ public function testLinkWithDifferentAttributes(): void
$user->groups()->unlink([$group1->id, $group2->id]);

$this->assertEquals(1, count($user->groups()->all()));

//

$this->assertEquals(1, count($user->groups()->all()));

$user->groups()->link([$group1->id, $group2->id], [['extra' => 'barfoo'], ['extra' => 'bazbax']]);

$groups = $user->groups()->alongWith(['extra'])->all();

$this->assertEquals(3, count($groups));

$this->assertSame('barfoo', $groups[1]->extra);
$this->assertSame('bazbax', $groups[2]->extra);

$user->groups()->unlink([$group1->id, $group2->id], [['extra' => 'foobar'], ['extra' => 'baxbaz']]);

$this->assertEquals(3, count($user->groups()->all()));

$user->groups()->unlink([$group1->id, $group2->id], [['extra' => 'barfoo'], ['extra' => 'bazbax']]);

$this->assertEquals(1, count($user->groups()->all()));
}

/**
Expand Down

0 comments on commit ffa34d4

Please sign in to comment.