@@ -106,31 +106,82 @@ public static function applyDecode(
106106 public static function applyByReference (array |\stdClass &$ document , array $ patch ): void
107107 {
108108 self ::validateDecodedPatch ($ patch );
109+ $ revert = [];
109110
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+ }
133164 }
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 ;
134185 }
135186 }
136187
@@ -194,11 +245,11 @@ public static function validatePatch(string $patch): void
194245 * @param array<int|string, mixed>|\stdClass $document
195246 * @param string[] $path
196247 * @param mixed $value
197- * @return void
248+ * @return mixed the previous value at $path or null if there was no value before
198249 */
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
200251 {
201- self ::documentWriter ($ document , $ path , $ value );
252+ return self ::documentWriter ($ document , $ path , $ value );
202253 }
203254
204255 /**
@@ -208,11 +259,11 @@ private static function opAdd(array|\stdClass &$document, array $path, mixed $va
208259 * @link https://datatracker.ietf.org/doc/html/rfc6902/#section-4.2
209260 * @param array<int|string, mixed>|\stdClass $document
210261 * @param string[] $path
211- * @return void
262+ * @return mixed
212263 */
213- private static function opRemove (array |\stdClass &$ document , array $ path ): void
264+ private static function opRemove (array |\stdClass &$ document , array $ path ): mixed
214265 {
215- self ::documentRemover ($ document , $ path );
266+ return self ::documentRemover ($ document , $ path );
216267 }
217268
218269 /**
@@ -224,12 +275,13 @@ private static function opRemove(array|\stdClass &$document, array $path): void
224275 * @param array<int|string, mixed>|\stdClass $document
225276 * @param string[] $path
226277 * @param mixed $value
227- * @return void
278+ * @return mixed
228279 */
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
230281 {
231- self ::documentRemover ($ document , $ path );
282+ $ previous = self ::documentRemover ($ document , $ path );
232283 self ::documentWriter ($ document , $ path , $ value );
284+ return $ previous ;
233285 }
234286
235287 /**
@@ -240,12 +292,12 @@ private static function opReplace(array|\stdClass &$document, array $path, mixed
240292 * @param array<int|string, mixed>|\stdClass $document
241293 * @param string[] $from
242294 * @param string[] $path
243- * @return void
295+ * @return mixed
244296 */
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
246298 {
247299 $ value = self ::documentRemover ($ document , $ from );
248- self ::documentWriter ($ document , $ path , $ value );
300+ return self ::documentWriter ($ document , $ path , $ value );
249301 }
250302
251303 /**
@@ -256,12 +308,12 @@ private static function opMove(array|\stdClass &$document, array $from, array $p
256308 * @param array<int|string, mixed>|\stdClass $document
257309 * @param string[] $from
258310 * @param string[] $path
259- * @return void
311+ * @return mixed
260312 */
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
262314 {
263315 $ value = self ::documentReader ($ document , $ from );
264- self ::documentWriter ($ document , $ path , $ value );
316+ return self ::documentWriter ($ document , $ path , $ value );
265317 }
266318
267319 /**
@@ -295,17 +347,18 @@ private static function opTest(array|\stdClass &$document, array $path, mixed $v
295347 * @param string[] $path
296348 * @param mixed $value
297349 * @param string[]|null $originalpath
298- * @return void
350+ * @return mixed the previous value at $path location
299351 */
300352 private static function documentWriter (
301353 array |\stdClass &$ document ,
302354 array $ path ,
303355 mixed $ value ,
304356 ?array $ originalpath = null
305- ): void {
357+ ): mixed {
306358 if (count ($ path ) === 0 ) {
359+ $ previous = $ document ;
307360 $ document = $ value ;
308- return ;
361+ return $ previous ;
309362 }
310363
311364 $ originalpath ??= $ path ;
@@ -330,17 +383,19 @@ private static function documentWriter(
330383 }
331384
332385 if ($ isObject ) {
386+ $ previous = $ document ->{$ node } ?? null ;
333387 $ document ->{$ node } = $ value ;
334- return ;
388+ return $ previous ;
335389 }
336390
337391 /** @phpstan-ignore-next-line */
338392 $ documentLength = count ($ document );
339393 $ node = $ appendToArray ? (string ) $ documentLength : $ node ;
340394
341395 if ((!empty ($ document ) && $ isAssociative ) || empty ($ document )) {
396+ $ previous = $ document [$ node ] ?? [];
342397 $ document [$ node ] = $ value ;
343- return ;
398+ return $ previous ;
344399 }
345400
346401 if (!is_numeric ($ node )) {
@@ -361,16 +416,16 @@ private static function documentWriter(
361416 );
362417 }
363418
419+ $ previous = $ document ;
364420 array_splice ($ document , $ nodeInt , 0 , is_array ($ value ) || is_object ($ value ) ? [$ value ] : $ value );
365- return ;
421+ return $ previous ;
366422 }
367423
368424 if ($ isObject ) {
369- self ::documentWriter ($ document ->{$ node }, $ path , $ value , $ originalpath );
370- return ;
425+ return self ::documentWriter ($ document ->{$ node }, $ path , $ value , $ originalpath );
371426 }
372427
373- self ::documentWriter ($ document [$ node ], $ path , $ value , $ originalpath );
428+ return self ::documentWriter ($ document [$ node ], $ path , $ value , $ originalpath );
374429 }
375430
376431 /**
0 commit comments