Skip to content

Commit

Permalink
Added unidirectional links support
Browse files Browse the repository at this point in the history
  • Loading branch information
fulopattila122 committed May 31, 2024
1 parent 8da2aa7 commit 5c7e7e9
Show file tree
Hide file tree
Showing 9 changed files with 139 additions and 21 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

- Added the `priceGreaterThan`, `priceLessThan` and `priceBetween` methods to the ProductSearch class by [Matima](https://github.com/mahdirezaei-dev) in [#176](https://github.com/vanilophp/framework/pull/176)
- Added the `Macroable` trait to the `ProductSearch` class
- Added the unidirectional links feature
- Added the possibility to retrieve the link items directly using `linkItems()` method as `Get::the($type)->linkItems()->of($model)`
- Added the `link_items` helper (shortcut to Get::the()->linkItems()
- Changed the offline payment gateway's icon from a circle to a plug+x
Expand Down
1 change: 1 addition & 0 deletions src/Links/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
## Unreleased
##### 2024-XX-YY

- Added the unidirectional links feature
- Added the possibility to retrieve the link items directly using `linkItems()` method as `Get::the($type)->linkItems()->of($model)`
- Added the `link_items` helper (shortcut to Get::the()->linkItems()

Expand Down
7 changes: 7 additions & 0 deletions src/Links/Models/LinkGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@
* @property int $id
* @property int $link_type_id
* @property int|null $property_id
* @property int|null $root_item_id
* @property Carbon $created_at
* @property Carbon|null $updated_at
*
* @property-read LinkType $type
* @property-read Collection $items
* @property-read LinkGroupItem $rootItem
*
* @method static LinkGroup create(array $attributes)
*/
Expand All @@ -51,4 +53,9 @@ public function items(): HasMany
{
return $this->hasMany(LinkGroupItemProxy::modelClass(), 'link_group_id', 'id');
}

public function rootItem(): BelongsTo
{
return $this->belongsTo(LinkGroupItemProxy::modelClass(), 'root_item_id', 'id');
}
}
15 changes: 14 additions & 1 deletion src/Links/Query/Establish.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ final class Establish
use HasBaseModel;
use CachesMorphTypes;

private bool $unidirectional = false;

private string $wants = 'link';

public static function a(LinkType|string $type): self
Expand All @@ -39,6 +41,13 @@ public static function an(LinkType|string $type): self
return self::a($type);
}

public function unidirectional(): self
{
$this->unidirectional = true;

return $this;
}

public function link(): self
{
$this->wants = 'link';
Expand All @@ -59,11 +68,15 @@ public function and(Model ...$models): void
$destinationGroup = $groups->first();
if (null === $destinationGroup) {
$destinationGroup = $this->createNewLinkGroup();
LinkGroupItemProxy::create([
$rootItem = LinkGroupItemProxy::create([
'link_group_id' => $destinationGroup->id,
'linkable_id' => $this->baseModel->id,
'linkable_type' => $this->morphTypeOf($this->baseModel::class),
]);
if ($this->unidirectional) {
$destinationGroup->root_item_id = $rootItem->id;
$destinationGroup->save();
}
}

foreach ($models as $model) {
Expand Down
28 changes: 16 additions & 12 deletions src/Links/Query/Get.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,24 +45,28 @@ public function of(Model $model): Collection
$result = collect();
if ('linkItems' === $this->wants) {
$groups->each(function ($group) use ($result, $model) {
$result->push(
...$group
->items
->reject(fn ($item) => $item->linkable_id === $model->id)
);
if (is_null($group->root_item_id) || $group->rootItem->linkable_id === $model->id) {
$result->push(
...$group
->items
->reject(fn($item) => $item->linkable_id === $model->id)
);
}
});

return $result;
}

$groups->each(function ($group) use ($result, $model) {
$result->push(
...$group
->items
->map
->linkable
->reject(fn ($item) => $item->id === $model->id)
);
if (is_null($group->root_item_id) || $group->rootItem->linkable_id === $model->id) {
$result->push(
...$group
->items
->map
->linkable
->reject(fn($item) => $item->id === $model->id)
);
}
});

return $result;
Expand Down
34 changes: 34 additions & 0 deletions src/Links/Tests/Feature/QueryEstablishTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,40 @@ public function morphed_models_can_be_linked_together()
$this->assertCount(1, Get::the('variant')->links()->of($prod2));
}

/** @test */
public function unidirectional_links_can_be_established()
{
$phone = TestLinkableProduct::create(['name' => 'iPhone 15'])->fresh();
$case1 = TestLinkableProduct::create(['name' => 'iPhone 15 Plastic Case 1'])->fresh();
LinkType::create(['name' => 'Accessory']);

Establish::a('accessory')->unidirectional()->link()->between($phone)->and($case1);

$this->assertCount(1, $phone->links('accessory'));
$this->assertEquals($case1->id, $phone->links('accessory')->first()->id);

$this->assertCount(0, $case1->links('accessory'));
}

/** @test */
public function unidirectional_links_can_be_established_between_multiple_entries()
{
$phone = TestLinkableProduct::create(['name' => 'iPhone 16'])->fresh();
$case1 = TestLinkableProduct::create(['name' => 'iPhone 16 Plastic Case 1'])->fresh();
$case2 = TestLinkableProduct::create(['name' => 'iPhone 16 Plastic Case 2'])->fresh();
LinkType::create(['name' => 'Protection']);

Establish::a('protection')->unidirectional()->link()->between($phone)->and($case1);
Establish::a('protection')->link()->between($phone)->and($case2);

$this->assertCount(2, $phone->links('protection'));
$this->assertEquals($case1->id, $phone->links('protection')->first()->id);
$this->assertEquals($case2->id, $phone->links('protection')->last()->id);

$this->assertCount(0, $case1->links('protection'));
$this->assertCount(0, $case2->links('protection'));
}

protected function setUpDatabase($app)
{
$this->loadMigrationsFrom(dirname(__DIR__) . '/migrations_of_property_module');
Expand Down
29 changes: 29 additions & 0 deletions src/Links/Tests/Feature/QueryGetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Vanilo\Links\Models\LinkGroup;
use Vanilo\Links\Models\LinkGroupItem;
use Vanilo\Links\Models\LinkType;
use Vanilo\Links\Query\Establish;
use Vanilo\Links\Query\Get;
use Vanilo\Links\Tests\Dummies\Property;
use Vanilo\Links\Tests\Dummies\TestLinkableProduct;
Expand Down Expand Up @@ -279,6 +280,34 @@ public function the_link_groups_helper_returns_link_groups_in_which_the_model_is
$this->assertInstanceOf(LinkGroup::class, $variantGroups->first());
}

/** @test */
public function unidirectional_links_can_be_properly_queried()
{
$phone = TestLinkableProduct::create(['name' => 'iPhone 17'])->fresh();
$caseX = TestLinkableProduct::create(['name' => 'iPhone 17 Plastic Case X'])->fresh();
$caseY = TestLinkableProduct::create(['name' => 'iPhone 17 Plastic Case Y'])->fresh();
LinkType::create(['name' => 'Sleeves']);

Establish::a('sleeves')->unidirectional()->link()->between($phone)->and($caseX);
Establish::a('sleeves')->unidirectional()->link()->between($phone)->and($caseY);

$sleeves = Get::the('sleeves')->links()->of($phone);
$this->assertCount(2, $sleeves);
$this->assertEquals($caseX->id, $sleeves->first()->id);
$this->assertEquals($caseY->id, $sleeves->last()->id);

$this->assertCount(0, Get::the('sleeves')->links()->of($caseX));
$this->assertCount(0, Get::the('sleeves')->links()->of($caseY));

$sleeveItems = Get::the('sleeves')->linkItems()->of($phone);
$this->assertCount(2, $sleeveItems);
$this->assertEquals($caseX->id, $sleeveItems->first()->linkable_id);
$this->assertEquals($caseY->id, $sleeveItems->last()->linkable_id);

$this->assertCount(0, Get::the('sleeves')->linkItems()->of($caseX));
$this->assertCount(0, Get::the('sleeves')->linkItems()->of($caseY));
}

protected function setUpDatabase($app)
{
$this->loadMigrationsFrom(dirname(__DIR__) . '/migrations_of_property_module');
Expand Down
20 changes: 12 additions & 8 deletions src/Links/Traits/Linkable.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@ public function links(LinkType|string $type, $propertyId = null): Collection

// @todo Optimize this to a single query
$result = Collection::make();
foreach ($this->linkGroups()->filter(fn ($group) => $group->type->id === $type->id) as $group) {
$result->push(
...$group
->items
->map
->linkable
->reject(fn ($item) => $item->id === $this->id)
);

$groups = $this->linkGroups()->filter(fn ($group) => $group->type->id === $type->id);
foreach ($groups as $group) {
if (is_null($group->root_item_id) || $group->rootItem->linkable_id === $this->id) {
$result->push(
...$group
->items
->map
->linkable
->reject(fn ($item) => $item->id === $this->id)
);
}
}

return $result;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration {
public function up(): void
{
Schema::table('link_groups', function (Blueprint $table) {
$table->unsignedBigInteger('root_item_id')->nullable();

$table->foreign('root_item_id')->references('id')->on('link_group_items');
});
}

public function down(): void
{
Schema::table('link_groups', function (Blueprint $table) {
$table->dropColumn('root_item_id');
});
}
};

0 comments on commit 5c7e7e9

Please sign in to comment.