Skip to content

Commit 31072f8

Browse files
committed
adding callback support
1 parent f1f54fd commit 31072f8

File tree

8 files changed

+622
-26
lines changed

8 files changed

+622
-26
lines changed

README.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,129 @@ class stdClass#56 (1) {
5555
*/
5656
```
5757

58+
## Callback
59+
If you wish to sometimes manually handle the merging of two values, you may do so using the provided `_callback`
60+
functions.
61+
62+
### Callback providing
63+
64+
You may provide any php-callable notation you wish, including:
65+
66+
```php
67+
object_merge_callback(0, 'function_name', ...$objects);
68+
object_merge_callback(0, $closure, ...$objects);
69+
object_merge_callback(0, ['FQCN', 'function_name'], ...$objects);
70+
object_merge_callback(0, [$instance, 'function_name'], ...$objects);
71+
```
72+
73+
### Callback arguments
74+
75+
The callback function will be provided exactly one parameter, and it will always be an instance of
76+
[ObjectMergeState](./src/ObjectMergeState.php).
77+
78+
### Callback response
79+
80+
If the callback function returns _anything_ other than an instance of [ObjectMergeResult](./src/ObjectMergeResult.php),
81+
it is used outright as the value of the merge, without further processing or recursion.
82+
83+
See comments on [ObjectMergeResult](./src/ObjectMergeResult.php) for how each parameter is handled.
84+
85+
```php
86+
use DCarbone\ObjectMergeResult;
87+
use DCarbone\ObjectMergeState;
88+
89+
/**
90+
* @param ObjectMergeState $state
91+
* @return ObjectMergeResult|null
92+
*/
93+
function merge_int_to_null(ObjectMergeState $state)
94+
{
95+
if (is_int($state->leftValue)) {
96+
return null;
97+
}
98+
return new ObjectMergeResult(true);
99+
}
100+
101+
/**
102+
* @return ObjectMergeResult
103+
*/
104+
function merge_always_continue()
105+
{
106+
return new ObjectMergeResult(true);
107+
}
108+
109+
/**
110+
* @param ObjectMergeState $state
111+
* @return mixed
112+
*/
113+
function merge_use_left_side(ObjectMergeState $state)
114+
{
115+
return $state->leftValue;
116+
}
117+
118+
$o1 = json_decode('{"int1":1,"str1":"string","int2":2,"float":3.2,"arr":[]}');
119+
$o2 = json_decode('{"int1":-3432,"str1":"sandwiches","int2":' . PHP_INT_MAX . ',"float":2.3,"arr":["onevalue"]}');
120+
121+
$out1 = object_merge_callback(0, 'merge_int_to_null', $o1, $o2);
122+
$out2 = object_merge_callback(0, 'merge_always_continue', $o1, $o2);
123+
$out3 = object_merge_callback(0, 'merge_use_left_side', $o1, $o2);
124+
125+
var_dump($out1);
126+
/*
127+
class stdClass#87 (5) {
128+
public $int1 =>
129+
NULL
130+
public $str1 =>
131+
string(10) "sandwiches"
132+
public $int2 =>
133+
NULL
134+
public $float =>
135+
double(2.3)
136+
public $arr =>
137+
array(1) {
138+
[0] =>
139+
string(8) "onevalue"
140+
}
141+
}
142+
*/
143+
144+
var_dump($out2);
145+
/*
146+
class stdClass#160 (5) {
147+
public $int1 =>
148+
int(-3432)
149+
public $str1 =>
150+
string(10) "sandwiches"
151+
public $int2 =>
152+
int(9223372036854775807)
153+
public $float =>
154+
double(2.3)
155+
public $arr =>
156+
array(1) {
157+
[0] =>
158+
string(8) "onevalue"
159+
}
160+
}
161+
*/
162+
163+
var_dump($out3);
164+
/*
165+
class stdClass#123 (5) {
166+
public $int1 =>
167+
int(1)
168+
public $str1 =>
169+
string(6) "string"
170+
public $int2 =>
171+
int(2)
172+
public $float =>
173+
double(3.2)
174+
public $arr =>
175+
array(0) {
176+
}
177+
}
178+
*/
179+
```
180+
58181
## Merge Options
59182
The `object_merge` and `object_merge_recursive` functions have sister functions named `object_merge_opts` and
60183
`object_merge_recursive_opts` respectively. Each of these has a required `$opts` argument that must be a bitwise

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
"autoload-dev": {
3535
"files": [
3636
"files/constants.php",
37-
"files/functions.php"
37+
"files/functions.php",
38+
"tests/functions.php"
3839
],
3940
"psr-4": {
4041
"DCarbone\\": "src/",

files/functions.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,28 @@ function object_merge_recursive_opts($opts, stdClass ...$objects)
7474
{
7575
return ObjectMerge::mergeRecursiveOpts($opts, ...$objects);
7676
}
77+
}
78+
if (!function_exists('object_merge_callback')) {
79+
/**
80+
* @param int $opts
81+
* @param callable $cb
82+
* @param stdClass ...$objects
83+
* @return stdClass|null
84+
*/
85+
function object_merge_callback($opts, $cb, stdClass ...$objects)
86+
{
87+
return ObjectMerge::mergeCallback($opts, $cb, ...$objects);
88+
}
89+
}
90+
if (!function_exists('object_merge_recursive_callback')) {
91+
/**
92+
* @param int $opts
93+
* @param callable $cb
94+
* @param stdClass ...$objects
95+
* @return stdClass|null
96+
*/
97+
function object_merge_recursive_callback($opts, $cb, stdClass ...$objects)
98+
{
99+
return ObjectMerge::mergeRecursiveCallback($opts, $cb, ...$objects);
100+
}
77101
}

src/ObjectMerge.php

Lines changed: 100 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,32 +43,34 @@ class ObjectMerge
4343
const OBJECT_T = 'object';
4444

4545
/** @var bool */
46-
private static $_recurse;
46+
private static $_recursive;
4747
/** @var int */
4848
private static $_opts;
49+
/** @var callable */
50+
private static $_cb;
4951

5052
/** @var int */
5153
private static $_depth = -1;
5254
/** @var array */
5355
private static $_context = [];
5456

5557
// list of types considered "simple"
56-
private static $_SIMPLE_TYPES = array(
58+
private static $_SIMPLE_TYPES = [
5759
self::NULL_T,
5860
self::RESOURCE_T,
5961
self::STRING_T,
6062
self::BOOLEAN_T,
6163
self::INTEGER_T,
6264
self::DOUBLE_T
63-
);
65+
];
6466

6567
/**
6668
* @param stdClass ...$objects
6769
* @return stdClass|null
6870
*/
6971
public function __invoke(stdClass ...$objects)
7072
{
71-
return self::_doMerge(false, self::DEFAULT_OPTS, $objects);
73+
return self::_doMerge(false, self::DEFAULT_OPTS, null, $objects);
7274
}
7375

7476
/**
@@ -77,7 +79,7 @@ public function __invoke(stdClass ...$objects)
7779
*/
7880
public static function merge(stdClass ...$objects)
7981
{
80-
return self::_doMerge(false, self::DEFAULT_OPTS, $objects);
82+
return self::_doMerge(false, self::DEFAULT_OPTS, null, $objects);
8183
}
8284

8385
/**
@@ -86,7 +88,7 @@ public static function merge(stdClass ...$objects)
8688
*/
8789
public static function mergeRecursive(stdClass ...$objects)
8890
{
89-
return self::_doMerge(true, self::DEFAULT_OPTS, $objects);
91+
return self::_doMerge(true, self::DEFAULT_OPTS, null, $objects);
9092
}
9193

9294
/**
@@ -96,7 +98,7 @@ public static function mergeRecursive(stdClass ...$objects)
9698
*/
9799
public static function mergeOpts($opts, stdClass ...$objects)
98100
{
99-
return self::_doMerge(false, $opts, $objects);
101+
return self::_doMerge(false, $opts, null, $objects);
100102
}
101103

102104
/**
@@ -106,7 +108,69 @@ public static function mergeOpts($opts, stdClass ...$objects)
106108
*/
107109
public static function mergeRecursiveOpts($opts, stdClass ...$objects)
108110
{
109-
return self::_doMerge(true, $opts, $objects);
111+
return self::_doMerge(true, $opts, null, $objects);
112+
}
113+
114+
/**
115+
* @param int $opts
116+
* @param callable $cb
117+
* @param stdClass ...$objects
118+
* @return stdClass
119+
*/
120+
public static function mergeCallback($opts, $cb, stdClass ...$objects)
121+
{
122+
return self::_doMerge(false, $opts, $cb, $objects);
123+
}
124+
125+
/**
126+
* @param int $opts
127+
* @param callable $cb
128+
* @param stdClass ...$objects
129+
* @return stdClass
130+
*/
131+
public static function mergeRecursiveCallback($opts, $cb, stdClass ...$objects)
132+
{
133+
return self::_doMerge(true, $opts, $cb, $objects);
134+
}
135+
136+
/**
137+
* @return ObjectMergeState
138+
*/
139+
public static function partialState()
140+
{
141+
return self::_partialState();
142+
}
143+
144+
/**
145+
* @return ObjectMergeState
146+
*/
147+
private static function _partialState()
148+
{
149+
$state = new ObjectMergeState();
150+
151+
$state->recursive = self::$_recursive;
152+
$state->opts = self::$_opts;
153+
$state->depth = self::$_depth;
154+
$state->context = self::$_context;
155+
156+
return $state;
157+
}
158+
159+
/**
160+
* @param string|int $key
161+
* @param mixed $leftValue
162+
* @param mixed $rightValue
163+
* @return ObjectMergeState
164+
*/
165+
private static function _fullState($key, $leftValue, $rightValue)
166+
{
167+
$state = self::_partialState();
168+
169+
$state->key = $key;
170+
$state->leftValue = $leftValue;
171+
$state->rightValue = $rightValue;
172+
173+
return $state;
110174
}
111175

112176
private static function _down()
@@ -127,9 +191,9 @@ private static function _up()
127191
private static function _exceptionMessage($prefix)
128192
{
129193
return sprintf(
130-
'%s - $recurse=%s; $opts=%d; $depth=%d; $context=%s',
194+
'%s - $recursive=%s; $opts=%d; $depth=%d; $context=%s',
131195
$prefix,
132-
self::$_recurse,
196+
self::$_recursive,
133197
self::$_opts,
134198
self::$_depth,
135199
implode('->', self::$_context)
@@ -270,6 +334,23 @@ private static function _mergeValues($key, $leftValue, $rightValue)
270334
{
271335
self::$_context[self::$_depth] = $key;
272336

337+
if (isset(self::$_cb)) {
338+
$res = call_user_func(self::$_cb, self::_fullState($key, $leftValue, $rightValue));
339+
$resT = gettype($res);
340+
if (self::OBJECT_T === $resT) {
341+
if ($res instanceof ObjectMergeResult && !$res->shouldContinue()) {
342+
$finalValue = $res->getFinalValue();
343+
if (OBJECT_MERGE_UNDEFINED !== $finalValue) {
344+
return $finalValue;
345+
}
346+
$leftValue = $res->getLeftValue();
347+
$rightValue = $res->getRightValue();
348+
}
349+
} else {
350+
return $res;
351+
}
352+
}
353+
273354
$leftUndefined = object_merge_value_undefined($leftValue, self::$_opts);
274355
$rightUndefined = object_merge_value_undefined($rightValue, self::$_opts);
275356

@@ -310,7 +391,7 @@ private static function _mergeValues($key, $leftValue, $rightValue)
310391
return self::_mergeValues($key, self::_newEmptyValue($rightValue), $rightValue);
311392
}
312393

313-
if (!self::$_recurse || in_array($leftType, self::$_SIMPLE_TYPES, true)) {
394+
if (!self::$_recursive || in_array($leftType, self::$_SIMPLE_TYPES, true)) {
314395
return $rightValue;
315396
}
316397

@@ -322,12 +403,13 @@ private static function _mergeValues($key, $leftValue, $rightValue)
322403
}
323404

324405
/**
325-
* @param bool $recurse
406+
* @param bool $recursive
326407
* @param int $opts
408+
* @param callable|null $cb
327409
* @param array $objects
328410
* @return mixed|null
329411
*/
330-
private static function _doMerge($recurse, $opts, array $objects)
412+
private static function _doMerge($recursive, $opts, $cb, array $objects)
331413
{
332414
if ([] === $objects) {
333415
return null;
@@ -336,8 +418,9 @@ private static function _doMerge($recurse, $opts, array $objects)
336418
$root = null;
337419

338420
// set state
339-
self::$_recurse = $recurse;
421+
self::$_recursive = $recursive;
340422
self::$_opts = $opts;
423+
self::$_cb = $cb;
341424

342425
foreach ($objects as $object) {
343426
if (null === $object) {
@@ -349,14 +432,15 @@ private static function _doMerge($recurse, $opts, array $objects)
349432
continue;
350433
}
351434

352-
$root = self::_mergeObjectValues($root, !$recurse ? clone $object : $object);
435+
$root = self::_mergeObjectValues($root, !$recursive ? clone $object : $object);
353436
}
354437

355438
// reset state
356439
self::$_depth = -1;
357440
self::$_context = [];
358-
self::$_recurse = false;
441+
self::$_recursive = false;
359442
self::$_opts = 0;
443+
self::$_cb = null;
360444

361445
return $root;
362446
}

0 commit comments

Comments
 (0)