@@ -42,6 +42,16 @@ class ObjectMerge
42
42
const ARRAY_T = 'array ' ;
43
43
const OBJECT_T = 'object ' ;
44
44
45
+ /** @var bool */
46
+ private static $ _recurse ;
47
+ /** @var int */
48
+ private static $ _opts ;
49
+
50
+ /** @var int */
51
+ private static $ _depth = -1 ;
52
+ /** @var array */
53
+ private static $ _context = [];
54
+
45
55
// list of types considered "simple"
46
56
private static $ _SIMPLE_TYPES = array (
47
57
self ::NULL_T ,
@@ -58,7 +68,7 @@ class ObjectMerge
58
68
*/
59
69
public function __invoke (stdClass ...$ objects )
60
70
{
61
- return self ::doMerge (false , self ::DEFAULT_OPTS , $ objects );
71
+ return self ::_doMerge (false , self ::DEFAULT_OPTS , $ objects );
62
72
}
63
73
64
74
/**
@@ -67,7 +77,7 @@ public function __invoke(stdClass ...$objects)
67
77
*/
68
78
public static function merge (stdClass ...$ objects )
69
79
{
70
- return self ::doMerge (false , self ::DEFAULT_OPTS , $ objects );
80
+ return self ::_doMerge (false , self ::DEFAULT_OPTS , $ objects );
71
81
}
72
82
73
83
/**
@@ -76,7 +86,7 @@ public static function merge(stdClass ...$objects)
76
86
*/
77
87
public static function mergeRecursive (stdClass ...$ objects )
78
88
{
79
- return self ::doMerge (true , self ::DEFAULT_OPTS , $ objects );
89
+ return self ::_doMerge (true , self ::DEFAULT_OPTS , $ objects );
80
90
}
81
91
82
92
/**
@@ -86,7 +96,7 @@ public static function mergeRecursive(stdClass ...$objects)
86
96
*/
87
97
public static function mergeOpts ($ opts , stdClass ...$ objects )
88
98
{
89
- return self ::doMerge (false , $ opts , $ objects );
99
+ return self ::_doMerge (false , $ opts , $ objects );
90
100
}
91
101
92
102
/**
@@ -96,24 +106,50 @@ public static function mergeOpts($opts, stdClass ...$objects)
96
106
*/
97
107
public static function mergeRecursiveOpts ($ opts , stdClass ...$ objects )
98
108
{
99
- return self ::doMerge (true , $ opts , $ objects );
109
+ return self ::_doMerge (true , $ opts , $ objects );
110
+ }
111
+
112
+ private static function _down ()
113
+ {
114
+ self ::$ _depth ++;
115
+ }
116
+
117
+ private static function _up ()
118
+ {
119
+ self ::$ _depth --;
120
+ self ::$ _context = array_slice (self ::$ _context , 0 , self ::$ _depth , true );
121
+ }
122
+
123
+ /**
124
+ * @param string $prefix
125
+ * @return string
126
+ */
127
+ private static function _exceptionMessage ($ prefix )
128
+ {
129
+ return sprintf (
130
+ '%s - $recurse=%s; $opts=%d; $depth=%d; $context=%s ' ,
131
+ $ prefix ,
132
+ self ::$ _recurse ,
133
+ self ::$ _opts ,
134
+ self ::$ _depth ,
135
+ implode ('-> ' , self ::$ _context )
136
+ );
100
137
}
101
138
102
139
/**
103
- * @param int $opts
104
140
* @param int $opt
105
141
* @return bool
106
142
*/
107
- private static function optSet ( $ opts , $ opt )
143
+ private static function _optSet ( $ opt )
108
144
{
109
- return 0 !== ($ opts & $ opt );
145
+ return 0 !== (self :: $ _opts & $ opt );
110
146
}
111
147
112
148
/**
113
149
* @param mixed $in
114
150
* @return array|bool|float|int|stdClass|string|null
115
151
*/
116
- private static function newEmptyValue ($ in )
152
+ private static function _newEmptyValue ($ in )
117
153
{
118
154
$ inT = gettype ($ in );
119
155
if (self ::STRING_T === $ inT ) {
@@ -131,7 +167,7 @@ private static function newEmptyValue($in)
131
167
} elseif (self ::RESOURCE_T === $ inT || self ::NULL_T === $ inT ) {
132
168
return null ;
133
169
} else {
134
- throw new UnexpectedValueException (sprintf ( ' Unknown value type provided: %s ' , $ inT ));
170
+ throw new UnexpectedValueException (self :: _exceptionMessage ( " Unknown value type provided: { $ inT}" ));
135
171
}
136
172
}
137
173
@@ -140,7 +176,7 @@ private static function newEmptyValue($in)
140
176
* @param mixed $right
141
177
* @return array
142
178
*/
143
- private static function compareTypes ($ left , $ right )
179
+ private static function _compareTypes ($ left , $ right )
144
180
{
145
181
$ leftType = gettype ($ left );
146
182
$ rightType = gettype ($ right );
@@ -152,15 +188,15 @@ private static function compareTypes($left, $right)
152
188
}
153
189
154
190
/**
155
- * @param bool $recurse
156
- * @param int $opts
157
191
* @param array $leftValue
158
192
* @param array $rightValue
159
193
* @return array
160
194
*/
161
- private static function mergeArrayValues ( $ recurse , $ opts , array $ leftValue , array $ rightValue )
195
+ private static function _mergeArrayValues ( array $ leftValue , array $ rightValue )
162
196
{
163
- if (self ::optSet ($ opts , OBJECT_MERGE_OPT_MERGE_ARRAY_VALUES )) {
197
+ self ::_down ();
198
+
199
+ if (self ::_optSet (OBJECT_MERGE_OPT_MERGE_ARRAY_VALUES )) {
164
200
$ out = [];
165
201
166
202
$ lc = count ($ leftValue );
@@ -170,9 +206,7 @@ private static function mergeArrayValues($recurse, $opts, array $leftValue, arra
170
206
for ($ i = 0 ; $ i < $ limit ; $ i ++) {
171
207
$ leftDefined = array_key_exists ($ i , $ leftValue );
172
208
$ rightDefined = array_key_exists ($ i , $ rightValue );
173
- $ out [$ i ] = self ::mergeValues (
174
- $ recurse ,
175
- $ opts ,
209
+ $ out [$ i ] = self ::_mergeValues (
176
210
$ i ,
177
211
$ leftDefined ? $ leftValue [$ i ] : OBJECT_MERGE_UNDEFINED ,
178
212
$ rightDefined ? $ rightValue [$ i ] : OBJECT_MERGE_UNDEFINED
@@ -184,67 +218,61 @@ private static function mergeArrayValues($recurse, $opts, array $leftValue, arra
184
218
foreach ($ out as $ i => &$ v ) {
185
219
$ vt = gettype ($ v );
186
220
if (self ::OBJECT_T === $ vt ) {
187
- $ v = self ::mergeObjectValues ( $ recurse , $ opts , new stdClass (), $ v );
221
+ $ v = self ::_mergeObjectValues ( new stdClass (), $ v );
188
222
} elseif (self ::ARRAY_T === $ vt ) {
189
- $ v = self ::mergeArrayValues ( $ recurse , $ opts , [], $ v );
223
+ $ v = self ::_mergeArrayValues ( [], $ v );
190
224
}
191
225
}
192
226
}
193
227
194
- if (self ::optSet ( $ opts , OBJECT_MERGE_OPT_UNIQUE_ARRAYS )) {
195
- return array_values (array_unique ($ out , SORT_REGULAR ));
228
+ if (self ::_optSet ( OBJECT_MERGE_OPT_UNIQUE_ARRAYS )) {
229
+ $ out = array_values (array_unique ($ out , SORT_REGULAR ));
196
230
}
197
231
232
+ self ::_up ();
233
+
198
234
return $ out ;
199
235
}
200
236
201
237
/**
202
- * @param bool $recurse
203
- * @param int $opts
204
238
* @param stdClass $leftValue
205
239
* @param stdClass $rightValue
206
240
* @return stdClass
207
241
*/
208
- private static function mergeObjectValues ( $ recurse , $ opts , stdClass $ leftValue , stdClass $ rightValue )
242
+ private static function _mergeObjectValues ( stdClass $ leftValue , stdClass $ rightValue )
209
243
{
244
+ self ::_down ();
245
+
210
246
$ out = new stdClass ();
211
247
212
- foreach (array_merge (get_object_vars ($ leftValue ), get_object_vars ($ rightValue )) as $ k => $ v ) {
213
- $ leftDefined = property_exists ($ leftValue , $ k );
214
- $ rightDefined = property_exists ($ rightValue , $ k );
215
- $ out ->{$ k } = self ::mergeValues (
216
- $ recurse ,
217
- $ opts ,
218
- $ k ,
219
- $ leftDefined ? $ leftValue ->{$ k } : OBJECT_MERGE_UNDEFINED ,
220
- $ rightDefined ? $ rightValue ->{$ k } : OBJECT_MERGE_UNDEFINED
248
+ foreach (array_keys (get_object_vars ($ leftValue ) + get_object_vars ($ rightValue )) as $ key ) {
249
+ $ out ->{$ key } = self ::_mergeValues (
250
+ $ key ,
251
+ property_exists ($ leftValue , $ key ) ? $ leftValue ->{$ key } : OBJECT_MERGE_UNDEFINED ,
252
+ property_exists ($ rightValue , $ key ) ? $ rightValue ->{$ key } : OBJECT_MERGE_UNDEFINED
221
253
);
222
254
}
255
+
256
+ self ::_up ();
257
+
223
258
return $ out ;
224
259
}
225
260
226
261
/**
227
- * @param bool $recurse
228
- * @param int $opts
229
262
* @param string|int $key
230
263
* @param mixed $leftValue
231
264
* @param mixed $rightValue
232
265
* @return array|stdClass
233
266
*/
234
- private static function mergeValues ( $ recurse , $ opts , $ key , $ leftValue , $ rightValue )
267
+ private static function _mergeValues ( $ key , $ leftValue , $ rightValue )
235
268
{
269
+ self ::$ _context [self ::$ _depth ] = $ key ;
270
+
236
271
$ leftUndefined = OBJECT_MERGE_UNDEFINED === $ leftValue ;
237
272
$ rightUndefined = OBJECT_MERGE_UNDEFINED === $ rightValue ;
238
273
239
274
if ($ leftUndefined && $ rightUndefined ) {
240
- throw new LogicException (
241
- sprintf (
242
- 'Both left and right values are "undefined": $recurse=%s; $opts=%d; $key=%s ' ,
243
- $ recurse ? 'true ' : 'false ' ,
244
- $ opts ,
245
- $ key
246
- )
247
- );
275
+ throw new LogicException (self ::_exceptionMessage ('Both left and right values are "undefined" ' ));
248
276
}
249
277
250
278
// if the right value was undefined, return left value and move on.
@@ -255,35 +283,37 @@ private static function mergeValues($recurse, $opts, $key, $leftValue, $rightVal
255
283
// if left side undefined, create new empty representation of the right type to allow processing to continue
256
284
// todo: revisit this, bit wasteful.
257
285
if ($ leftUndefined ) {
258
- $ leftValue = self ::newEmptyValue ($ rightValue );
286
+ $ leftValue = self ::_newEmptyValue ($ rightValue );
259
287
}
260
288
261
- list ($ leftType , $ rightType , $ equalTypes ) = self ::compareTypes ($ leftValue , $ rightValue );
289
+ list ($ leftType , $ rightType , $ equalTypes ) = self ::_compareTypes ($ leftValue , $ rightValue );
262
290
263
291
if (!$ equalTypes ) {
264
- if (self ::optSet ( $ opts , OBJECT_MERGE_OPT_CONFLICT_EXCEPTION )) {
292
+ if (self ::_optSet ( OBJECT_MERGE_OPT_CONFLICT_EXCEPTION )) {
265
293
throw new UnexpectedValueException (
266
- sprintf (
267
- 'Field "%s" has type "%s" on incoming object, but has type "%s" on the root object ' ,
268
- $ key ,
269
- $ rightType ,
270
- $ leftType
294
+ self ::_exceptionMessage (
295
+ sprintf (
296
+ 'Field "%s" has type "%s" on incoming object, but has type "%s" on the root object ' ,
297
+ $ key ,
298
+ $ rightType ,
299
+ $ leftType
300
+ )
271
301
)
272
302
);
273
303
}
274
304
// todo: revisit this, inefficient.
275
- return self ::mergeValues ( $ recurse , $ opts , $ key , self ::newEmptyValue ($ rightValue ), $ rightValue );
305
+ return self ::_mergeValues ( $ key , self ::_newEmptyValue ($ rightValue ), $ rightValue );
276
306
}
277
307
278
- if (!$ recurse || in_array ($ leftType , self ::$ _SIMPLE_TYPES , true )) {
308
+ if (!self :: $ _recurse || in_array ($ leftType , self ::$ _SIMPLE_TYPES , true )) {
279
309
return $ rightValue ;
280
310
}
281
311
282
312
if (self ::ARRAY_T === $ leftType ) {
283
- return self ::mergeArrayValues ( $ recurse , $ opts , $ leftValue , $ rightValue );
313
+ return self ::_mergeArrayValues ( $ leftValue , $ rightValue );
284
314
}
285
315
286
- return self ::mergeObjectValues ( $ recurse , $ opts , $ leftValue , $ rightValue );
316
+ return self ::_mergeObjectValues ( $ leftValue , $ rightValue );
287
317
}
288
318
289
319
/**
@@ -292,14 +322,18 @@ private static function mergeValues($recurse, $opts, $key, $leftValue, $rightVal
292
322
* @param array $objects
293
323
* @return mixed|null
294
324
*/
295
- private static function doMerge ($ recurse , $ opts , array $ objects )
325
+ private static function _doMerge ($ recurse , $ opts , array $ objects )
296
326
{
297
327
if ([] === $ objects ) {
298
328
return null ;
299
329
}
300
330
301
331
$ root = null ;
302
332
333
+ // set state
334
+ self ::$ _recurse = $ recurse ;
335
+ self ::$ _opts = $ opts ;
336
+
303
337
foreach ($ objects as $ object ) {
304
338
if (null === $ object ) {
305
339
continue ;
@@ -310,9 +344,15 @@ private static function doMerge($recurse, $opts, array $objects)
310
344
continue ;
311
345
}
312
346
313
- $ root = self ::mergeObjectValues ( $ recurse , $ opts , $ root , !$ recurse ? clone $ object : $ object );
347
+ $ root = self ::_mergeObjectValues ( $ root , !$ recurse ? clone $ object : $ object );
314
348
}
315
349
350
+ // reset state
351
+ self ::$ _depth = -1 ;
352
+ self ::$ _context = [];
353
+ self ::$ _recurse = false ;
354
+ self ::$ _opts = 0 ;
355
+
316
356
return $ root ;
317
357
}
318
358
}
0 commit comments