Skip to content

Instantly share code, notes, and snippets.

@sebastiaanluca
Last active March 7, 2023 23:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save sebastiaanluca/79a59adf4eb08de7974a4874e365462c to your computer and use it in GitHub Desktop.
Save sebastiaanluca/79a59adf4eb08de7974a4874e365462c to your computer and use it in GitHub Desktop.
A HasMany relation sync method implementation using macros
<?php
$business->locations()->sync([1, 42, 16, 8]);
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Collection as BaseCollection;
use Illuminate\Support\ServiceProvider;
class EloquentServiceProvider extends ServiceProvider
{
public function boot() : void
{
/**
* Detach any models not in the given array and attach any new models.
*
* @param \Illuminate\Support\Collection|\Illuminate\Database\Eloquent\Model|array $ids
* @param bool $detaching
*
* @return array
*/
HasMany::macro('sync', function ($ids, bool $detaching = true) : array {
$changes = [
'attached' => [], 'detached' => [], 'updated' => [],
];
$current = $this->getResults()->modelKeys();
$records = $this->parseIds($ids);
$detach = array_diff($current, $records);
if ($detaching && count($detach) > 0) {
$this->detach($detach);
$changes['detached'] = $detach;
}
$changes = array_merge(
$changes,
$this->syncNew($records, $current)
);
return $changes;
});
/**0
* Get all of the IDs from the given mixed value.
*
* @param mixed $value
*
* @return array
*/
HasMany::macro('parseIds', function ($value) {
if ($value instanceof Model) {
return [$value->{$this->relatedKey}];
}
if ($value instanceof Collection) {
return $value->pluck($this->relatedKey)->all();
}
if ($value instanceof BaseCollection) {
return $value->toArray();
}
return (array) $value;
});
HasMany::macro('detach', function (array $detach) : void {
$related = $this->getRelated();
$models = $related::withoutGlobalScopes()->findMany($detach);
$models->each(function (Model $model) : void {
$model->{$this->getForeignKeyName()} = null;
$model->save();
});
});
/**
* Attach all of the records that aren't in the given current records.
*
* @param array $records
* @param array $current
* @param bool $touch
*
* @return array
*/
HasMany::macro('syncNew', function (array $records, array $current) : array {
$changes = ['attached' => [], 'updated' => []];
$related = $this->getRelated();
$models = $related::withoutGlobalScopes()->findMany($records);
$this->saveMany($models);
foreach ($models as $model) {
if (! in_array($model->getKey(), $current)) {
$changes['attached'][] = $model->getKey();
} else {
$changes['updated'][] = $model->getKey();
}
}
return $changes;
});
}
}
@pildit
Copy link

pildit commented Apr 5, 2022

I may be wrong but it seems that HasMany does not have a relatedKey property ?

@sebastiaanluca
Copy link
Author

Could have changed or could've been that way forever, no idea 😄 TBH this is old, hacky code from 2 years ago, so wouldn't use it anymore.

@pildit
Copy link

pildit commented Apr 5, 2022

But it looks really neat, i can change it in order to work. I think it's a good point to start

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment