@@ -106,31 +106,82 @@ public static function applyDecode(
106
106
public static function applyByReference (array |\stdClass &$ document , array $ patch ): void
107
107
{
108
108
self ::validateDecodedPatch ($ patch );
109
+ $ revert = [];
109
110
110
- foreach ($ patch as $ p ) {
111
- $ p = (array ) $ p ;
112
- $ path = self ::pathSplitter ($ p ['path ' ]);
113
-
114
- switch ($ p ['op ' ]) {
115
- case self ::OP_ADD :
116
- self ::opAdd ($ document , $ path , $ p ['value ' ]);
117
- break ;
118
- case self ::OP_REPLACE :
119
- self ::opReplace ($ document , $ path , $ p ['value ' ]);
120
- break ;
121
- case self ::OP_TEST :
122
- self ::opTest ($ document , $ path , $ p ['value ' ]);
123
- break ;
124
- case self ::OP_COPY :
125
- self ::opCopy ($ document , self ::pathSplitter ($ p ['from ' ]), $ path );
126
- break ;
127
- case self ::OP_MOVE :
128
- self ::opMove ($ document , self ::pathSplitter ($ p ['from ' ]), $ path );
129
- break ;
130
- case self ::OP_REMOVE :
131
- self ::opRemove ($ document , $ path );
132
- break ;
111
+ try {
112
+ foreach ($ patch as $ p ) {
113
+ $ p = (array ) $ p ;
114
+ $ path = self ::pathSplitter ($ p ['path ' ]);
115
+
116
+ switch ($ p ['op ' ]) {
117
+ case self ::OP_ADD :
118
+ $ previous = self ::opAdd ($ document , $ path , $ p ['value ' ]);
119
+
120
+ // there was nothing before
121
+ if (is_null ($ previous )) {
122
+ $ revert [] = ['op ' => 'remove ' , 'path ' => $ path ];
123
+ break ;
124
+ }
125
+
126
+ if (is_array ($ previous )) {
127
+ if (end ($ path ) === '- ' ) {
128
+ array_pop ($ path );
129
+ $ path [] = (string ) count ($ previous );
130
+ }
131
+ $ revert [] = ['op ' => 'remove ' , 'path ' => $ path ];
132
+ break ;
133
+ }
134
+
135
+ $ revert [] = ['op ' => 'replace ' , 'path ' => $ path , 'value ' => $ previous ];
136
+ break ;
137
+ case self ::OP_REPLACE :
138
+ $ previous = self ::opReplace ($ document , $ path , $ p ['value ' ]);
139
+ $ revert [] = ['op ' => 'replace ' , 'path ' => $ path , 'value ' => $ previous ];
140
+ break ;
141
+ case self ::OP_TEST :
142
+ self ::opTest ($ document , $ path , $ p ['value ' ]);
143
+ break ;
144
+ case self ::OP_COPY :
145
+ $ previous = self ::opCopy ($ document , self ::pathSplitter ($ p ['from ' ]), $ path );
146
+
147
+ if (is_array ($ previous ) && end ($ path ) === '- ' ) {
148
+ array_pop ($ path );
149
+ $ path [] = (string ) count ($ previous );
150
+ }
151
+
152
+ $ revert [] = ['op ' => 'remove ' , 'path ' => $ path ];
153
+ break ;
154
+ case self ::OP_MOVE :
155
+ $ from = self ::pathSplitter ($ p ['from ' ]);
156
+ self ::opMove ($ document , $ from , $ path );
157
+ $ revert [] = ['op ' => 'move ' , 'from ' => $ path , 'path ' => $ from ];
158
+ break ;
159
+ case self ::OP_REMOVE :
160
+ $ previous = self ::opRemove ($ document , $ path );
161
+ $ revert [] = ['op ' => 'add ' , 'path ' => $ path , 'value ' => $ previous ];
162
+ break ;
163
+ }
133
164
}
165
+ } catch (FastJsonPatchException $ e ) {
166
+ // Revert patch
167
+ foreach (array_reverse ($ revert ) as $ p ) {
168
+ switch ($ p ['op ' ]) {
169
+ case self ::OP_ADD :
170
+ self ::opAdd ($ document , $ p ['path ' ], $ p ['value ' ]);
171
+ break ;
172
+ case self ::OP_REPLACE :
173
+ self ::opReplace ($ document , $ p ['path ' ], $ p ['value ' ]);
174
+ break ;
175
+ case self ::OP_MOVE :
176
+ self ::opMove ($ document , $ p ['from ' ], $ p ['path ' ]);
177
+ break ;
178
+ case self ::OP_REMOVE :
179
+ self ::opRemove ($ document , $ p ['path ' ]);
180
+ break ;
181
+ }
182
+ }
183
+
184
+ throw $ e ;
134
185
}
135
186
}
136
187
@@ -194,11 +245,11 @@ public static function validatePatch(string $patch): void
194
245
* @param array<int|string, mixed>|\stdClass $document
195
246
* @param string[] $path
196
247
* @param mixed $value
197
- * @return void
248
+ * @return mixed the previous value at $path or null if there was no value before
198
249
*/
199
- private static function opAdd (array |\stdClass &$ document , array $ path , mixed $ value ): void
250
+ private static function opAdd (array |\stdClass &$ document , array $ path , mixed $ value ): mixed
200
251
{
201
- self ::documentWriter ($ document , $ path , $ value );
252
+ return self ::documentWriter ($ document , $ path , $ value );
202
253
}
203
254
204
255
/**
@@ -208,11 +259,11 @@ private static function opAdd(array|\stdClass &$document, array $path, mixed $va
208
259
* @link https://datatracker.ietf.org/doc/html/rfc6902/#section-4.2
209
260
* @param array<int|string, mixed>|\stdClass $document
210
261
* @param string[] $path
211
- * @return void
262
+ * @return mixed
212
263
*/
213
- private static function opRemove (array |\stdClass &$ document , array $ path ): void
264
+ private static function opRemove (array |\stdClass &$ document , array $ path ): mixed
214
265
{
215
- self ::documentRemover ($ document , $ path );
266
+ return self ::documentRemover ($ document , $ path );
216
267
}
217
268
218
269
/**
@@ -224,12 +275,13 @@ private static function opRemove(array|\stdClass &$document, array $path): void
224
275
* @param array<int|string, mixed>|\stdClass $document
225
276
* @param string[] $path
226
277
* @param mixed $value
227
- * @return void
278
+ * @return mixed
228
279
*/
229
- private static function opReplace (array |\stdClass &$ document , array $ path , mixed $ value ): void
280
+ private static function opReplace (array |\stdClass &$ document , array $ path , mixed $ value ): mixed
230
281
{
231
- self ::documentRemover ($ document , $ path );
282
+ $ previous = self ::documentRemover ($ document , $ path );
232
283
self ::documentWriter ($ document , $ path , $ value );
284
+ return $ previous ;
233
285
}
234
286
235
287
/**
@@ -240,12 +292,12 @@ private static function opReplace(array|\stdClass &$document, array $path, mixed
240
292
* @param array<int|string, mixed>|\stdClass $document
241
293
* @param string[] $from
242
294
* @param string[] $path
243
- * @return void
295
+ * @return mixed
244
296
*/
245
- private static function opMove (array |\stdClass &$ document , array $ from , array $ path ): void
297
+ private static function opMove (array |\stdClass &$ document , array $ from , array $ path ): mixed
246
298
{
247
299
$ value = self ::documentRemover ($ document , $ from );
248
- self ::documentWriter ($ document , $ path , $ value );
300
+ return self ::documentWriter ($ document , $ path , $ value );
249
301
}
250
302
251
303
/**
@@ -256,12 +308,12 @@ private static function opMove(array|\stdClass &$document, array $from, array $p
256
308
* @param array<int|string, mixed>|\stdClass $document
257
309
* @param string[] $from
258
310
* @param string[] $path
259
- * @return void
311
+ * @return mixed
260
312
*/
261
- private static function opCopy (array |\stdClass &$ document , array $ from , array $ path ): void
313
+ private static function opCopy (array |\stdClass &$ document , array $ from , array $ path ): mixed
262
314
{
263
315
$ value = self ::documentReader ($ document , $ from );
264
- self ::documentWriter ($ document , $ path , $ value );
316
+ return self ::documentWriter ($ document , $ path , $ value );
265
317
}
266
318
267
319
/**
@@ -295,17 +347,18 @@ private static function opTest(array|\stdClass &$document, array $path, mixed $v
295
347
* @param string[] $path
296
348
* @param mixed $value
297
349
* @param string[]|null $originalpath
298
- * @return void
350
+ * @return mixed the previous value at $path location
299
351
*/
300
352
private static function documentWriter (
301
353
array |\stdClass &$ document ,
302
354
array $ path ,
303
355
mixed $ value ,
304
356
?array $ originalpath = null
305
- ): void {
357
+ ): mixed {
306
358
if (count ($ path ) === 0 ) {
359
+ $ previous = $ document ;
307
360
$ document = $ value ;
308
- return ;
361
+ return $ previous ;
309
362
}
310
363
311
364
$ originalpath ??= $ path ;
@@ -330,17 +383,19 @@ private static function documentWriter(
330
383
}
331
384
332
385
if ($ isObject ) {
386
+ $ previous = $ document ->{$ node } ?? null ;
333
387
$ document ->{$ node } = $ value ;
334
- return ;
388
+ return $ previous ;
335
389
}
336
390
337
391
/** @phpstan-ignore-next-line */
338
392
$ documentLength = count ($ document );
339
393
$ node = $ appendToArray ? (string ) $ documentLength : $ node ;
340
394
341
395
if ((!empty ($ document ) && $ isAssociative ) || empty ($ document )) {
396
+ $ previous = $ document [$ node ] ?? [];
342
397
$ document [$ node ] = $ value ;
343
- return ;
398
+ return $ previous ;
344
399
}
345
400
346
401
if (!is_numeric ($ node )) {
@@ -361,16 +416,16 @@ private static function documentWriter(
361
416
);
362
417
}
363
418
419
+ $ previous = $ document ;
364
420
array_splice ($ document , $ nodeInt , 0 , is_array ($ value ) || is_object ($ value ) ? [$ value ] : $ value );
365
- return ;
421
+ return $ previous ;
366
422
}
367
423
368
424
if ($ isObject ) {
369
- self ::documentWriter ($ document ->{$ node }, $ path , $ value , $ originalpath );
370
- return ;
425
+ return self ::documentWriter ($ document ->{$ node }, $ path , $ value , $ originalpath );
371
426
}
372
427
373
- self ::documentWriter ($ document [$ node ], $ path , $ value , $ originalpath );
428
+ return self ::documentWriter ($ document [$ node ], $ path , $ value , $ originalpath );
374
429
}
375
430
376
431
/**
0 commit comments