Skip to content

Commit 04bb125

Browse files
committed
adding context and depth tracking for exception messages, and cleaning up internal api
1 parent 4e0f0b4 commit 04bb125

File tree

1 file changed

+99
-59
lines changed

1 file changed

+99
-59
lines changed

src/ObjectMerge.php

Lines changed: 99 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ class ObjectMerge
4242
const ARRAY_T = 'array';
4343
const OBJECT_T = 'object';
4444

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+
4555
// list of types considered "simple"
4656
private static $_SIMPLE_TYPES = array(
4757
self::NULL_T,
@@ -58,7 +68,7 @@ class ObjectMerge
5868
*/
5969
public function __invoke(stdClass ...$objects)
6070
{
61-
return self::doMerge(false, self::DEFAULT_OPTS, $objects);
71+
return self::_doMerge(false, self::DEFAULT_OPTS, $objects);
6272
}
6373

6474
/**
@@ -67,7 +77,7 @@ public function __invoke(stdClass ...$objects)
6777
*/
6878
public static function merge(stdClass ...$objects)
6979
{
70-
return self::doMerge(false, self::DEFAULT_OPTS, $objects);
80+
return self::_doMerge(false, self::DEFAULT_OPTS, $objects);
7181
}
7282

7383
/**
@@ -76,7 +86,7 @@ public static function merge(stdClass ...$objects)
7686
*/
7787
public static function mergeRecursive(stdClass ...$objects)
7888
{
79-
return self::doMerge(true, self::DEFAULT_OPTS, $objects);
89+
return self::_doMerge(true, self::DEFAULT_OPTS, $objects);
8090
}
8191

8292
/**
@@ -86,7 +96,7 @@ public static function mergeRecursive(stdClass ...$objects)
8696
*/
8797
public static function mergeOpts($opts, stdClass ...$objects)
8898
{
89-
return self::doMerge(false, $opts, $objects);
99+
return self::_doMerge(false, $opts, $objects);
90100
}
91101

92102
/**
@@ -96,24 +106,50 @@ public static function mergeOpts($opts, stdClass ...$objects)
96106
*/
97107
public static function mergeRecursiveOpts($opts, stdClass ...$objects)
98108
{
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+
);
100137
}
101138

102139
/**
103-
* @param int $opts
104140
* @param int $opt
105141
* @return bool
106142
*/
107-
private static function optSet($opts, $opt)
143+
private static function _optSet($opt)
108144
{
109-
return 0 !== ($opts & $opt);
145+
return 0 !== (self::$_opts & $opt);
110146
}
111147

112148
/**
113149
* @param mixed $in
114150
* @return array|bool|float|int|stdClass|string|null
115151
*/
116-
private static function newEmptyValue($in)
152+
private static function _newEmptyValue($in)
117153
{
118154
$inT = gettype($in);
119155
if (self::STRING_T === $inT) {
@@ -131,7 +167,7 @@ private static function newEmptyValue($in)
131167
} elseif (self::RESOURCE_T === $inT || self::NULL_T === $inT) {
132168
return null;
133169
} else {
134-
throw new UnexpectedValueException(sprintf('Unknown value type provided: %s', $inT));
170+
throw new UnexpectedValueException(self::_exceptionMessage("Unknown value type provided: {$inT}"));
135171
}
136172
}
137173

@@ -140,7 +176,7 @@ private static function newEmptyValue($in)
140176
* @param mixed $right
141177
* @return array
142178
*/
143-
private static function compareTypes($left, $right)
179+
private static function _compareTypes($left, $right)
144180
{
145181
$leftType = gettype($left);
146182
$rightType = gettype($right);
@@ -152,15 +188,15 @@ private static function compareTypes($left, $right)
152188
}
153189

154190
/**
155-
* @param bool $recurse
156-
* @param int $opts
157191
* @param array $leftValue
158192
* @param array $rightValue
159193
* @return array
160194
*/
161-
private static function mergeArrayValues($recurse, $opts, array $leftValue, array $rightValue)
195+
private static function _mergeArrayValues(array $leftValue, array $rightValue)
162196
{
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)) {
164200
$out = [];
165201

166202
$lc = count($leftValue);
@@ -170,9 +206,7 @@ private static function mergeArrayValues($recurse, $opts, array $leftValue, arra
170206
for ($i = 0; $i < $limit; $i++) {
171207
$leftDefined = array_key_exists($i, $leftValue);
172208
$rightDefined = array_key_exists($i, $rightValue);
173-
$out[$i] = self::mergeValues(
174-
$recurse,
175-
$opts,
209+
$out[$i] = self::_mergeValues(
176210
$i,
177211
$leftDefined ? $leftValue[$i] : OBJECT_MERGE_UNDEFINED,
178212
$rightDefined ? $rightValue[$i] : OBJECT_MERGE_UNDEFINED
@@ -184,67 +218,61 @@ private static function mergeArrayValues($recurse, $opts, array $leftValue, arra
184218
foreach ($out as $i => &$v) {
185219
$vt = gettype($v);
186220
if (self::OBJECT_T === $vt) {
187-
$v = self::mergeObjectValues($recurse, $opts, new stdClass(), $v);
221+
$v = self::_mergeObjectValues(new stdClass(), $v);
188222
} elseif (self::ARRAY_T === $vt) {
189-
$v = self::mergeArrayValues($recurse, $opts, [], $v);
223+
$v = self::_mergeArrayValues([], $v);
190224
}
191225
}
192226
}
193227

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));
196230
}
197231

232+
self::_up();
233+
198234
return $out;
199235
}
200236

201237
/**
202-
* @param bool $recurse
203-
* @param int $opts
204238
* @param stdClass $leftValue
205239
* @param stdClass $rightValue
206240
* @return stdClass
207241
*/
208-
private static function mergeObjectValues($recurse, $opts, stdClass $leftValue, stdClass $rightValue)
242+
private static function _mergeObjectValues(stdClass $leftValue, stdClass $rightValue)
209243
{
244+
self::_down();
245+
210246
$out = new stdClass();
211247

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
221253
);
222254
}
255+
256+
self::_up();
257+
223258
return $out;
224259
}
225260

226261
/**
227-
* @param bool $recurse
228-
* @param int $opts
229262
* @param string|int $key
230263
* @param mixed $leftValue
231264
* @param mixed $rightValue
232265
* @return array|stdClass
233266
*/
234-
private static function mergeValues($recurse, $opts, $key, $leftValue, $rightValue)
267+
private static function _mergeValues($key, $leftValue, $rightValue)
235268
{
269+
self::$_context[self::$_depth] = $key;
270+
236271
$leftUndefined = OBJECT_MERGE_UNDEFINED === $leftValue;
237272
$rightUndefined = OBJECT_MERGE_UNDEFINED === $rightValue;
238273

239274
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"'));
248276
}
249277

250278
// 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
255283
// if left side undefined, create new empty representation of the right type to allow processing to continue
256284
// todo: revisit this, bit wasteful.
257285
if ($leftUndefined) {
258-
$leftValue = self::newEmptyValue($rightValue);
286+
$leftValue = self::_newEmptyValue($rightValue);
259287
}
260288

261-
list($leftType, $rightType, $equalTypes) = self::compareTypes($leftValue, $rightValue);
289+
list($leftType, $rightType, $equalTypes) = self::_compareTypes($leftValue, $rightValue);
262290

263291
if (!$equalTypes) {
264-
if (self::optSet($opts, OBJECT_MERGE_OPT_CONFLICT_EXCEPTION)) {
292+
if (self::_optSet(OBJECT_MERGE_OPT_CONFLICT_EXCEPTION)) {
265293
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+
)
271301
)
272302
);
273303
}
274304
// todo: revisit this, inefficient.
275-
return self::mergeValues($recurse, $opts, $key, self::newEmptyValue($rightValue), $rightValue);
305+
return self::_mergeValues($key, self::_newEmptyValue($rightValue), $rightValue);
276306
}
277307

278-
if (!$recurse || in_array($leftType, self::$_SIMPLE_TYPES, true)) {
308+
if (!self::$_recurse || in_array($leftType, self::$_SIMPLE_TYPES, true)) {
279309
return $rightValue;
280310
}
281311

282312
if (self::ARRAY_T === $leftType) {
283-
return self::mergeArrayValues($recurse, $opts, $leftValue, $rightValue);
313+
return self::_mergeArrayValues($leftValue, $rightValue);
284314
}
285315

286-
return self::mergeObjectValues($recurse, $opts, $leftValue, $rightValue);
316+
return self::_mergeObjectValues($leftValue, $rightValue);
287317
}
288318

289319
/**
@@ -292,14 +322,18 @@ private static function mergeValues($recurse, $opts, $key, $leftValue, $rightVal
292322
* @param array $objects
293323
* @return mixed|null
294324
*/
295-
private static function doMerge($recurse, $opts, array $objects)
325+
private static function _doMerge($recurse, $opts, array $objects)
296326
{
297327
if ([] === $objects) {
298328
return null;
299329
}
300330

301331
$root = null;
302332

333+
// set state
334+
self::$_recurse = $recurse;
335+
self::$_opts = $opts;
336+
303337
foreach ($objects as $object) {
304338
if (null === $object) {
305339
continue;
@@ -310,9 +344,15 @@ private static function doMerge($recurse, $opts, array $objects)
310344
continue;
311345
}
312346

313-
$root = self::mergeObjectValues($recurse, $opts, $root, !$recurse ? clone $object : $object);
347+
$root = self::_mergeObjectValues($root, !$recurse ? clone $object : $object);
314348
}
315349

350+
// reset state
351+
self::$_depth = -1;
352+
self::$_context = [];
353+
self::$_recurse = false;
354+
self::$_opts = 0;
355+
316356
return $root;
317357
}
318358
}

0 commit comments

Comments
 (0)