From 656dfc44b4b15cea6f0995a1806910264ba54449 Mon Sep 17 00:00:00 2001 From: Saade Date: Wed, 20 Mar 2024 14:02:16 -0300 Subject: [PATCH 1/3] fix: relationships-always-being-setup --- src/Forms/Components/AdjacencyList.php | 241 ------------------ .../Components/Concerns/HasRelationship.php | 234 ++++++++++++++++- 2 files changed, 232 insertions(+), 243 deletions(-) diff --git a/src/Forms/Components/AdjacencyList.php b/src/Forms/Components/AdjacencyList.php index 3e84bc9..6b07d43 100644 --- a/src/Forms/Components/AdjacencyList.php +++ b/src/Forms/Components/AdjacencyList.php @@ -2,248 +2,7 @@ namespace Saade\FilamentAdjacencyList\Forms\Components; -use Closure; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsToMany; -use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Arr; -use Saade\FilamentAdjacencyList\Forms\Components\Actions\Action; -use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships; - class AdjacencyList extends Component { use Concerns\HasRelationship; - - protected array | Closure | null $pivotAttributes = null; - - protected function setUp(): void - { - parent::setUp(); - - $this->afterStateHydrated(null); - - $this->loadStateFromRelationshipsUsing(static function (AdjacencyList $component) { - $component->clearCachedExistingRecords(); - - $component->fillFromRelationship(); - }); - - $this->addAction(function (Action $action): void { - $action->using(function (Component $component, array $data): void { - $relationship = $component->getRelationship(); - $model = $component->getRelatedModel(); - $pivotData = $component->getPivotAttributes() ?? []; - - if ($relationship instanceof BelongsToMany) { - $pivotColumns = $relationship->getPivotColumns(); - - $pivotData = Arr::only($data, $pivotColumns); - $data = Arr::except($data, $pivotColumns); - } - - $data = $component->mutateRelationshipDataBeforeCreate($data); - - if ($translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver()) { - $record = $translatableContentDriver->makeRecord($model, $data); - } else { - $record = new $model(); - $record->fill($data); - } - - if ($orderColumn = $component->getOrderColumn()) { - $record->{$orderColumn} = $pivotData[$orderColumn] = count($component->getState()); - } - - if ($relationship instanceof BelongsToMany) { - $record->save(); - - $relationship->attach($record, $pivotData); - - $component->cacheRecord($record); - - return; - } - - $relationship->save($record); - - $component->cacheRecord($record); - }); - }); - - $this->addChildAction(function (Action $action): void { - $action->using(function (Component $component, Model $parentRecord, array $data, array $arguments): void { - $relationship = $component->getRelationship(); - $model = $component->getRelatedModel(); - - $pivotData = $component->getPivotAttributes() ?? []; - - if ($relationship instanceof BelongsToMany) { - $pivotColumns = $relationship->getPivotColumns(); - - $pivotData = Arr::only($data, $pivotColumns); - $data = Arr::except($data, $pivotColumns); - } - - $data = $component->mutateRelationshipDataBeforeCreate($data); - - if ($translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver()) { - $record = $translatableContentDriver->makeRecord($model, $data); - } else { - $record = new $model(); - $record->fill($data); - } - - if ($orderColumn = $component->getOrderColumn()) { - $record->{$orderColumn} = $pivotData[$orderColumn] = count( - data_get( - $component->getState(), - $component->getRelativeStatePath($arguments['statePath']) . '.' . $component->getChildrenKey() - ) - ); - } - - if ($relationship instanceof BelongsToMany) { - $record->save(); - - $parentRecord->{$component->getChildrenKey()}()->syncWithPivotValues( - [$record->getKey()], - $pivotData - ); - - $component->cacheRecord($record); - - return; - } - - $parentRecord->{$component->getChildrenKey()}()->save($record); - - $component->cacheRecord($record); - }); - }); - - $this->editAction(function (Action $action): void { - $action->using(function (Component $component, Model $record, array $data): void { - $relationship = $component->getRelationship(); - - $translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver(); - - if ($relationship instanceof BelongsToMany) { - $pivot = $record->{$relationship->getPivotAccessor()}; - - $pivotColumns = $relationship->getPivotColumns(); - $pivotData = Arr::only($data, $pivotColumns); - - if (count($pivotColumns)) { - if ($translatableContentDriver) { - $translatableContentDriver->updateRecord($pivot, $pivotData); - } else { - $pivot->update($pivotData); - } - } - - $data = Arr::except($data, $pivotColumns); - } - - $data = $component->mutateRelationshipDataBeforeSave($data, $record); - - if ($translatableContentDriver) { - $translatableContentDriver->updateRecord($record, $data); - } else { - $record->update($data); - } - }); - }); - - $this->deleteAction(function (Action $action): void { - $action->using(function (Component $component, Model $record): void { - $relationship = $component->getRelationship(); - - if ($relationship instanceof BelongsToMany) { - $pivot = $record->{$relationship->getPivotAccessor()}; - - $pivot->delete(); - } - - $record->delete(); - - $component->deleteCachedRecord($record); - }); - }); - - $this->saveRelationshipsUsing(static function (AdjacencyList $component, ?array $state) { - if (! is_array($state)) { - $state = []; - } - - $cachedExistingRecords = $component->getCachedExistingRecords(); - $relationship = $component->getRelationship(); - $childrenKey = $component->getChildrenKey(); - $recordKeyName = $relationship->getRelated()->getKeyName(); - $orderColumn = $component->getOrderColumn(); - $pivotAttributes = $component->getPivotAttributes(); - - Arr::map( - $state, - $traverse = function (array $item, string $itemKey, array $siblings = []) use (&$traverse, &$cachedExistingRecords, $state, $relationship, $childrenKey, $recordKeyName, $orderColumn, $pivotAttributes): Model { - $record = $cachedExistingRecords->get($itemKey); - - /* Update item order */ - if ($orderColumn) { - $record->{$orderColumn} = $pivotAttributes[$orderColumn] = array_search($itemKey, array_keys($siblings ?: $state)); - } - - if ($relationship instanceof BelongsToMany) { - $record->save(); - } else { - $relationship->save($record); - } - - if ($children = data_get($item, $childrenKey)) { - $childrenRecords = collect($children)->map(fn (array $child, string $childKey) => $traverse($child, $childKey, $children)); - - if ($relationship instanceof BelongsToMany) { - $record->{$childrenKey}()->syncWithPivotValues( - $childrenRecords->pluck($recordKeyName), - $pivotAttributes() - ); - - return $record; - } - - $record->{$childrenKey}()->saveMany($childrenRecords); - } - - return $record; - } - ); - - // Clear cache - $component->fillFromRelationship(); - }); - - $this->dehydrated(false); - } - - public function getRelationship(): HasMany | BelongsToMany - { - if ($model = $this->getModelInstance()) { - if (! in_array(HasRecursiveRelationships::class, class_uses($model))) { - throw new \Exception('The model ' . $model::class . ' must use the ' . HasRecursiveRelationships::class . ' trait.'); - } - } - - return $model->{$this->getRelationshipName()}(); - } - - public function pivotAttributes(array | Closure | null $pivotAttributes): static - { - $this->pivotAttributes = $pivotAttributes; - - return $this; - } - - public function getPivotAttributes(): array - { - return $this->evaluate($this->pivotAttributes) ?? []; - } } diff --git a/src/Forms/Components/Concerns/HasRelationship.php b/src/Forms/Components/Concerns/HasRelationship.php index 3c43c2d..b23b711 100644 --- a/src/Forms/Components/Concerns/HasRelationship.php +++ b/src/Forms/Components/Concerns/HasRelationship.php @@ -5,7 +5,13 @@ use Closure; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\Relation; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Support\Arr; +use Saade\FilamentAdjacencyList\Forms\Components\Actions\Action; +use Saade\FilamentAdjacencyList\Forms\Components\AdjacencyList; +use Saade\FilamentAdjacencyList\Forms\Components\Component; +use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships; trait HasRelationship { @@ -23,11 +29,214 @@ trait HasRelationship protected ?Closure $mutateRelationshipDataBeforeSaveUsing = null; + protected array | Closure | null $pivotAttributes = null; + public function relationship(string | Closure | null $name = null, ?Closure $modifyQueryUsing = null): static { $this->relationship = $name ?? $this->getName(); $this->modifyRelationshipQueryUsing = $modifyQueryUsing; + $this->loadStateFromRelationshipsUsing(static function (AdjacencyList $component) { + $component->clearCachedExistingRecords(); + + $component->fillFromRelationship(); + }); + + $this->saveRelationshipsUsing(static function (AdjacencyList $component, ?array $state) { + if (! is_array($state)) { + $state = []; + } + + $cachedExistingRecords = $component->getCachedExistingRecords(); + $relationship = $component->getRelationship(); + $childrenKey = $component->getChildrenKey(); + $recordKeyName = $relationship->getRelated()->getKeyName(); + $orderColumn = $component->getOrderColumn(); + $pivotAttributes = $component->getPivotAttributes(); + + Arr::map( + $state, + $traverse = function (array $item, string $itemKey, array $siblings = []) use (&$traverse, &$cachedExistingRecords, $state, $relationship, $childrenKey, $recordKeyName, $orderColumn, $pivotAttributes): Model { + $record = $cachedExistingRecords->get($itemKey); + + /* Update item order */ + if ($orderColumn) { + $record->{$orderColumn} = $pivotAttributes[$orderColumn] = array_search($itemKey, array_keys($siblings ?: $state)); + } + + if ($relationship instanceof BelongsToMany) { + $record->save(); + } else { + $relationship->save($record); + } + + if ($children = data_get($item, $childrenKey)) { + $childrenRecords = collect($children)->map(fn (array $child, string $childKey) => $traverse($child, $childKey, $children)); + + if ($relationship instanceof BelongsToMany) { + $record->{$childrenKey}()->syncWithPivotValues( + $childrenRecords->pluck($recordKeyName), + $pivotAttributes() + ); + + return $record; + } + + $record->{$childrenKey}()->saveMany($childrenRecords); + } + + return $record; + } + ); + + // Clear cache + $component->fillFromRelationship(); + }); + + $this->addAction(function (Action $action): void { + $action->using(function (Component $component, array $data): void { + $relationship = $component->getRelationship(); + $model = $component->getRelatedModel(); + $pivotData = $component->getPivotAttributes() ?? []; + + if ($relationship instanceof BelongsToMany) { + $pivotColumns = $relationship->getPivotColumns(); + + $pivotData = Arr::only($data, $pivotColumns); + $data = Arr::except($data, $pivotColumns); + } + + $data = $component->mutateRelationshipDataBeforeCreate($data); + + if ($translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver()) { + $record = $translatableContentDriver->makeRecord($model, $data); + } else { + $record = new $model(); + $record->fill($data); + } + + if ($orderColumn = $component->getOrderColumn()) { + $record->{$orderColumn} = $pivotData[$orderColumn] = count($component->getState()); + } + + if ($relationship instanceof BelongsToMany) { + $record->save(); + + $relationship->attach($record, $pivotData); + + $component->cacheRecord($record); + + return; + } + + $relationship->save($record); + + $component->cacheRecord($record); + }); + }); + + $this->addChildAction(function (Action $action): void { + $action->using(function (Component $component, Model $parentRecord, array $data, array $arguments): void { + $relationship = $component->getRelationship(); + $model = $component->getRelatedModel(); + + $pivotData = $component->getPivotAttributes() ?? []; + + if ($relationship instanceof BelongsToMany) { + $pivotColumns = $relationship->getPivotColumns(); + + $pivotData = Arr::only($data, $pivotColumns); + $data = Arr::except($data, $pivotColumns); + } + + $data = $component->mutateRelationshipDataBeforeCreate($data); + + if ($translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver()) { + $record = $translatableContentDriver->makeRecord($model, $data); + } else { + $record = new $model(); + $record->fill($data); + } + + if ($orderColumn = $component->getOrderColumn()) { + $record->{$orderColumn} = $pivotData[$orderColumn] = count( + data_get( + $component->getState(), + $component->getRelativeStatePath($arguments['statePath']) . '.' . $component->getChildrenKey() + ) + ); + } + + if ($relationship instanceof BelongsToMany) { + $record->save(); + + $parentRecord->{$component->getChildrenKey()}()->syncWithPivotValues( + [$record->getKey()], + $pivotData + ); + + $component->cacheRecord($record); + + return; + } + + $parentRecord->{$component->getChildrenKey()}()->save($record); + + $component->cacheRecord($record); + }); + }); + + $this->editAction(function (Action $action): void { + $action->using(function (Component $component, Model $record, array $data): void { + $relationship = $component->getRelationship(); + + $translatableContentDriver = $component->getLivewire()->makeFilamentTranslatableContentDriver(); + + if ($relationship instanceof BelongsToMany) { + $pivot = $record->{$relationship->getPivotAccessor()}; + + $pivotColumns = $relationship->getPivotColumns(); + $pivotData = Arr::only($data, $pivotColumns); + + if (count($pivotColumns)) { + if ($translatableContentDriver) { + $translatableContentDriver->updateRecord($pivot, $pivotData); + } else { + $pivot->update($pivotData); + } + } + + $data = Arr::except($data, $pivotColumns); + } + + $data = $component->mutateRelationshipDataBeforeSave($data, $record); + + if ($translatableContentDriver) { + $translatableContentDriver->updateRecord($record, $data); + } else { + $record->update($data); + } + }); + }); + + $this->deleteAction(function (Action $action): void { + $action->using(function (Component $component, Model $record): void { + $relationship = $component->getRelationship(); + + if ($relationship instanceof BelongsToMany) { + $pivot = $record->{$relationship->getPivotAccessor()}; + + $pivot->delete(); + } + + $record->delete(); + + $component->deleteCachedRecord($record); + }); + }); + + $this->dehydrated(false); + return $this; } @@ -80,7 +289,16 @@ public function getOrderColumn(): ?string return $this->evaluate($this->orderColumn); } - abstract public function getRelationship(): ?Relation; + public function getRelationship(): HasMany | BelongsToMany + { + if ($model = $this->getModelInstance()) { + if (! in_array(HasRecursiveRelationships::class, class_uses($model))) { + throw new \Exception('The model ' . $model::class . ' must use the ' . HasRecursiveRelationships::class . ' trait.'); + } + } + + return $model->{$this->getRelationshipName()}(); + } public function getRelationshipName(): ?string { @@ -207,4 +425,16 @@ public function mutateRelationshipDataBeforeSave(array $data, Model $record): ar return $data; } + + public function pivotAttributes(array | Closure | null $pivotAttributes): static + { + $this->pivotAttributes = $pivotAttributes; + + return $this; + } + + public function getPivotAttributes(): array + { + return $this->evaluate($this->pivotAttributes) ?? []; + } } From e928ee4601861b924051d1ee389f89eb5b3fd08f Mon Sep 17 00:00:00 2001 From: Saade Date: Wed, 20 Mar 2024 14:19:45 -0300 Subject: [PATCH 2/3] fix: relationships always being requested by various methods --- src/Forms/Components/Actions/AddAction.php | 16 ++++++++++++---- .../Components/Actions/AddChildAction.php | 18 +++++++++++++----- src/Forms/Components/Actions/DeleteAction.php | 2 +- src/Forms/Components/Actions/EditAction.php | 11 ++++++++--- .../Components/Concerns/HasRelationship.php | 14 ++++++++++---- 5 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/Forms/Components/Actions/AddAction.php b/src/Forms/Components/Actions/AddAction.php index 3c6b022..1fe61e3 100644 --- a/src/Forms/Components/Actions/AddAction.php +++ b/src/Forms/Components/Actions/AddAction.php @@ -39,10 +39,18 @@ protected function setUp(): void ); $this->form( - fn (Component $component, Form $form): ?Form => match ($component->hasModal()) { - true => $component->getForm($form) - ->model($component->getRelatedModel()), - default => null, + function (Component $component, Form $form): ?Form { + if(! $component->hasModal()) { + return null; + } + + $form = $component->getForm($form); + + if( $model = $component->getRelatedModel()) { + $form->model($model); + } + + return $form; } ); diff --git a/src/Forms/Components/Actions/AddChildAction.php b/src/Forms/Components/Actions/AddChildAction.php index b8d3375..4d1d8fd 100644 --- a/src/Forms/Components/Actions/AddChildAction.php +++ b/src/Forms/Components/Actions/AddChildAction.php @@ -39,15 +39,23 @@ protected function setUp(): void ); $this->form( - fn (Component $component, Form $form): ?Form => match ($component->hasModal()) { - true => $component->getForm($form) - ->model($component->getRelatedModel()), - default => null, + function (Component $component, Form $form): ?Form { + if(! $component->hasModal()) { + return null; + } + + $form = $component->getForm($form); + + if( $model = $component->getRelatedModel()) { + $form->model($model); + } + + return $form; } ); $this->action(function (Component $component, array $arguments): void { - $parentRecord = $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']); + $parentRecord = $component->getRelatedModel() ? $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']) : null; $this->process(function (Component $component, array $arguments, array $data): void { $statePath = $component->getRelativeStatePath($arguments['statePath']); diff --git a/src/Forms/Components/Actions/DeleteAction.php b/src/Forms/Components/Actions/DeleteAction.php index 69ae02b..135a54e 100644 --- a/src/Forms/Components/Actions/DeleteAction.php +++ b/src/Forms/Components/Actions/DeleteAction.php @@ -29,7 +29,7 @@ protected function setUp(): void $this->modalSubmitActionLabel(fn (): string => __('filament-adjacency-list::adjacency-list.actions.delete.modal.actions.confirm')); $this->action(function (Component $component, array $arguments): void { - $record = $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']); + $record = $component->getRelatedModel() ? $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']) : null; $this->process(function (Component $component, array $arguments): void { $statePath = $component->getRelativeStatePath($arguments['statePath']); diff --git a/src/Forms/Components/Actions/EditAction.php b/src/Forms/Components/Actions/EditAction.php index 4c6c57b..25a7472 100644 --- a/src/Forms/Components/Actions/EditAction.php +++ b/src/Forms/Components/Actions/EditAction.php @@ -29,10 +29,15 @@ protected function setUp(): void $this->form( function (Component $component, Form $form, array $arguments): Form { - return $component + $form = $component ->getForm($form) - ->model($component->getCachedExistingRecords()->get($arguments['cachedRecordKey'])) ->statePath($arguments['statePath']); + + if( $component->getRelatedModel()) { + $form->model($component->getCachedExistingRecords()->get($arguments['cachedRecordKey'])); + } + + return $form; } ); @@ -43,7 +48,7 @@ function (Component $component, array $arguments): array { ); $this->action(function (Component $component, array $arguments): void { - $record = $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']); + $record = $component->getRelatedModel() ? $component->getCachedExistingRecords()->get($arguments['cachedRecordKey']) : null; $this->process(function (Component $component, array $arguments, array $data): void { $statePath = $component->getRelativeStatePath($arguments['statePath']); diff --git a/src/Forms/Components/Concerns/HasRelationship.php b/src/Forms/Components/Concerns/HasRelationship.php index b23b711..c44e3d6 100644 --- a/src/Forms/Components/Concerns/HasRelationship.php +++ b/src/Forms/Components/Concerns/HasRelationship.php @@ -289,15 +289,21 @@ public function getOrderColumn(): ?string return $this->evaluate($this->orderColumn); } - public function getRelationship(): HasMany | BelongsToMany + public function getRelationship(): HasMany | BelongsToMany | null { + $name = $this->getRelationshipName(); + + if (blank($name)) { + return null; + } + if ($model = $this->getModelInstance()) { if (! in_array(HasRecursiveRelationships::class, class_uses($model))) { throw new \Exception('The model ' . $model::class . ' must use the ' . HasRecursiveRelationships::class . ' trait.'); } } - return $model->{$this->getRelationshipName()}(); + return $model->{$name}(); } public function getRelationshipName(): ?string @@ -347,9 +353,9 @@ public function clearCachedExistingRecords(): void $this->cachedExistingRecords = null; } - public function getRelatedModel(): string + public function getRelatedModel(): ?string { - return $this->getRelationship()->getModel()::class; + return ($model = $this->getRelationship()?->getModel()) ? $model::class : null; } public function mutateRelationshipDataBeforeCreateUsing(?Closure $callback): static From 40fc1a1bbec699e0cbb1c4680adfdea0b4c7989e Mon Sep 17 00:00:00 2001 From: saade Date: Wed, 20 Mar 2024 17:20:06 +0000 Subject: [PATCH 3/3] Fix styling --- src/Forms/Components/Actions/AddAction.php | 4 ++-- src/Forms/Components/Actions/AddChildAction.php | 4 ++-- src/Forms/Components/Actions/EditAction.php | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Forms/Components/Actions/AddAction.php b/src/Forms/Components/Actions/AddAction.php index 1fe61e3..b23a8b7 100644 --- a/src/Forms/Components/Actions/AddAction.php +++ b/src/Forms/Components/Actions/AddAction.php @@ -40,13 +40,13 @@ protected function setUp(): void $this->form( function (Component $component, Form $form): ?Form { - if(! $component->hasModal()) { + if (! $component->hasModal()) { return null; } $form = $component->getForm($form); - if( $model = $component->getRelatedModel()) { + if ($model = $component->getRelatedModel()) { $form->model($model); } diff --git a/src/Forms/Components/Actions/AddChildAction.php b/src/Forms/Components/Actions/AddChildAction.php index 4d1d8fd..feaf61f 100644 --- a/src/Forms/Components/Actions/AddChildAction.php +++ b/src/Forms/Components/Actions/AddChildAction.php @@ -40,13 +40,13 @@ protected function setUp(): void $this->form( function (Component $component, Form $form): ?Form { - if(! $component->hasModal()) { + if (! $component->hasModal()) { return null; } $form = $component->getForm($form); - if( $model = $component->getRelatedModel()) { + if ($model = $component->getRelatedModel()) { $form->model($model); } diff --git a/src/Forms/Components/Actions/EditAction.php b/src/Forms/Components/Actions/EditAction.php index 25a7472..a0b0675 100644 --- a/src/Forms/Components/Actions/EditAction.php +++ b/src/Forms/Components/Actions/EditAction.php @@ -33,7 +33,7 @@ function (Component $component, Form $form, array $arguments): Form { ->getForm($form) ->statePath($arguments['statePath']); - if( $component->getRelatedModel()) { + if ($component->getRelatedModel()) { $form->model($component->getCachedExistingRecords()->get($arguments['cachedRecordKey'])); }