diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9c10d38 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/vendor +composer.phar +composer.lock +.DS_Store +.idea diff --git a/README.md b/README.md new file mode 100644 index 0000000..c5dac66 --- /dev/null +++ b/README.md @@ -0,0 +1,101 @@ +# Laravel HasMany Sync + +Allow sync method for Laravel Has Many Relationship. + +# Installation + +You can install the package via composer: + +``` +composer require alfa/laravel-has-many-sync +``` + +Register the ServiceProvider in `config/app.php` + +```php +'providers' => [ + // ... + Alfa\EloquentHasManySync\ServiceProvider::class, +], +``` + +# Usage + + +## Setup HasMany Relation + +```php +class Customer extends Model +{ + /** + * @return \Illuminate\Database\Eloquent\Relations\HasMany + */ + public function contacts() + { + return $this->hasMany(CustomerContact::class); + } +} +``` + +You can access the sync method like this: + +```php +$customer->contacts()->sync([ + [ + 'id' => 1, + 'name' => 'Alfa', + 'phone_number' => '123', + ], + [ + 'id' => null, + 'name' => 'Adhitya', + 'phone_number' => '234, + ] +]); +``` + +The sync method accepts an array of data to place on the intermediate table. Any data that are not in the given array will be removed from the intermediate table. So, after this operation is complete, only the data in the given array will exist in the intermediate table: + +### Syncing without deleting + +If you do not want to delete existing data, you may pass false value to the second parameter in the sync method. + +```php +$customer->contacts()->sync([ + [ + 'id' => 1, + 'name' => 'Alfa', + 'phone_number' => '123', + ], + [ + 'id' => null, + 'name' => 'Adhitya', + 'phone_number' => '234, + ] +], false); +``` + + +### Example usage in the controller. + +```php +class CustomersController extends Controller +{ + /** + * Update the specified resource in storage. + * + * @param CustomerRequest $request + * @param Customer $customer + * @return \Illuminate\Http\Response + */ + public function update(CustomerRequest $request, Customer $customer) + { + DB::transaction(function () use ($customer, $request) { + $customer->update($request->all()); + $customer->contacts()->sync($request->get('contacts', [])); + }); + + return redirect()->route('customers.index'); + } +} +``` \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..f88a1e3 --- /dev/null +++ b/composer.json @@ -0,0 +1,29 @@ +{ + "name": "alfa/laravel-has-many-sync", + "description": "Laravel has many sync", + "type": "library", + "require": { + "illuminate/support": "~5.4" + }, + "license": "MIT", + "authors": [ + { + "name": "Alfa Adhitya", + "email": "alfa2159@gmail.com" + } + ], + "autoload": { + "psr-4": { + "Alfa\\EloquentHasManySync\\": "src" + } + }, + "extra": { + "laravel": { + "providers": [ + "Alfa\\EloquentHasManySync\\ServiceProvider" + ] + } + }, + "minimum-stability": "dev", + "prefer-stable": true +} diff --git a/src/ServiceProvider.php b/src/ServiceProvider.php new file mode 100644 index 0000000..85ba4fe --- /dev/null +++ b/src/ServiceProvider.php @@ -0,0 +1,84 @@ + [], 'deleted' => [], 'updated' => [], + ]; + + /** @var HasMany $this */ + + // Get the primary key. + $relatedKeyName = $this->getRelated()->getKeyName(); + + // Get the current key values. + $current = $this->newQuery()->pluck($relatedKeyName)->all(); + + // Cast the given key to an integer if it is numeric. + $castKey = function ($value) { + if (is_null($value)) { + return $value; + } + + return is_numeric($value) ? (int) $value : (string) $value; + }; + + // Cast the given keys to integers if they are numeric and string otherwise. + $castKeys = function ($keys) use ($castKey) { + return (array) array_map(function ($key) use ($castKey) { + return $castKey($key); + }, $keys); + }; + + // Get any non-matching rows. + $deletedKeys = array_diff($current, $castKeys( + array_pluck($data, $relatedKeyName)) + ); + + if ($deleting && count($deletedKeys) > 0) { + $this->getRelated()->destroy($deletedKeys); + $changes['deleted'] = $deletedKeys; + } + + // Separate the submitted data into "update" and "new" + // We determine "newRows" as those whose $relatedKeyName (usually 'id') is null. + $newRows = array_where($data, function ($row) use ($relatedKeyName) { + return array_get($row, $relatedKeyName) === null; + }); + + // We determine "updateRows" as those whose $relatedKeyName (usually 'id') is set, not null. + $updatedRows = array_where($data, function ($row) use ($relatedKeyName) { + return array_get($row, $relatedKeyName) !== null; + }); + + if (count($newRows) > 0) { + $newRecords = $this->createMany($newRows); + $changes['created'] = $castKeys( + $newRecords->pluck($relatedKeyName)->toArray() + ); + } + + foreach ($updatedRows as $row) { + $this->getRelated()->where($relatedKeyName, $castKey(array_get($row, $relatedKeyName))) + ->update($row); + } + + $changes['updated'] = $castKeys(array_pluck($updatedRows, $relatedKeyName)); + + return $changes; + }); + } +}