44
55namespace Korridor \LaravelHasManySync ;
66
7+ use Illuminate \Database \Eloquent \Model ;
8+ use Illuminate \Database \Eloquent \ModelNotFoundException ;
79use Illuminate \Database \Eloquent \Relations \HasMany ;
810use Illuminate \Support \Arr ;
911use Illuminate \Support \ServiceProvider as BaseServiceProvider ;
@@ -17,37 +19,51 @@ class ServiceProvider extends BaseServiceProvider
1719 */
1820 public function boot (): void
1921 {
20- HasMany::macro ('sync ' , function (array $ data , bool $ deleting = true ): array {
22+
23+ HasMany::macro ('sync ' , function (array $ data , bool $ deleting = true , bool $ throwOnIdNotInScope = true ): array {
2124 $ changes = [
2225 'created ' => [], 'deleted ' => [], 'updated ' => [],
2326 ];
2427
25- /** @var HasMany $this */
28+ /** @var HasMany<Model> $this */
2629
2730 // Get the primary key.
2831 $ relatedKeyName = $ this ->getRelated ()->getKeyName ();
2932
3033 // Get the current key values.
31- $ current = $ this ->newQuery ()->pluck ($ relatedKeyName )->all ();
34+ $ currentIds = $ this ->newQuery ()->pluck ($ relatedKeyName )->all ();
3235
3336 // Cast the given key to an integer if it is numeric.
3437 $ castKey = function ($ value ) {
3538 if (is_null ($ value )) {
36- return $ value ;
39+ return null ;
3740 }
3841
3942 return is_numeric ($ value ) ? (int ) $ value : (string ) $ value ;
4043 };
4144
4245 // Cast the given keys to integers if they are numeric and string otherwise.
43- $ castKeys = function ($ keys ) use ($ castKey ) {
46+ $ castKeys = function ($ keys ) use ($ castKey ): array {
4447 return (array ) array_map (function ($ key ) use ($ castKey ) {
4548 return $ castKey ($ key );
4649 }, $ keys );
4750 };
4851
52+ // The new ids, without null values
53+ $ dataIds = Arr::where ($ castKeys (Arr::pluck ($ data , $ relatedKeyName )), function ($ value ) {
54+ return !is_null ($ value );
55+ });
56+
57+ $ problemKeys = array_diff ($ dataIds , $ currentIds );
58+ if ($ throwOnIdNotInScope && count ($ problemKeys ) > 0 ) {
59+ throw (new ModelNotFoundException ())->setModel (
60+ get_class ($ this ->getRelated ()),
61+ $ problemKeys
62+ );
63+ }
64+
4965 // Get any non-matching rows.
50- $ deletedKeys = array_diff ($ current , $ castKeys (Arr:: pluck ( $ data , $ relatedKeyName )) );
66+ $ deletedKeys = array_diff ($ currentIds , $ dataIds );
5167
5268 if ($ deleting && count ($ deletedKeys ) > 0 ) {
5369 $ this ->getRelated ()->destroy ($ deletedKeys );
@@ -56,13 +72,15 @@ public function boot(): void
5672
5773 // Separate the submitted data into "update" and "new"
5874 // We determine "newRows" as those whose $relatedKeyName (usually 'id') is null.
59- $ newRows = Arr::where ($ data , function ($ row ) use ($ relatedKeyName ) {
60- return null === Arr::get ($ row , $ relatedKeyName );
75+ $ newRows = Arr::where ($ data , function (array $ row ) use ($ relatedKeyName ) {
76+ $ id = Arr::get ($ row , $ relatedKeyName );
77+ return $ id === null ;
6178 });
6279
6380 // We determine "updateRows" as those whose $relatedKeyName (usually 'id') is set, not null.
64- $ updatedRows = Arr::where ($ data , function ($ row ) use ($ relatedKeyName ) {
65- return null !== Arr::get ($ row , $ relatedKeyName );
81+ $ updateRows = Arr::where ($ data , function (array $ row ) use ($ relatedKeyName , $ problemKeys ) {
82+ $ id = Arr::get ($ row , $ relatedKeyName );
83+ return $ id !== null && !in_array ($ id , $ problemKeys , true );
6684 });
6785
6886 if (count ($ newRows ) > 0 ) {
@@ -72,14 +90,16 @@ public function boot(): void
7290 );
7391 }
7492
75- foreach ($ updatedRows as $ row ) {
76- $ this ->getRelated ()
93+ foreach ($ updateRows as $ row ) {
94+ $ updateModel = $ this ->getRelated ()
95+ ->newQuery ()
7796 ->where ($ relatedKeyName , $ castKey (Arr::get ($ row , $ relatedKeyName )))
78- ->first ()
79- ->update ($ row );
97+ ->firstOrFail ();
98+
99+ $ updateModel ->update ($ row );
80100 }
81101
82- $ changes ['updated ' ] = $ castKeys (Arr::pluck ($ updatedRows , $ relatedKeyName ));
102+ $ changes ['updated ' ] = $ castKeys (Arr::pluck ($ updateRows , $ relatedKeyName ));
83103
84104 return $ changes ;
85105 });
0 commit comments