diff --git a/src/functions/array.php b/src/functions/array.php index 49cbb5f..231bfdb 100644 --- a/src/functions/array.php +++ b/src/functions/array.php @@ -112,3 +112,26 @@ function array_path_unset(array &$array, array $parents, &$keyExisted = null) { $keyExisted = FALSE; } } + +function array_copy(array $array) +{ + // values array may contain sub array passed as a reference to a sub object. + // this code removes such refs from the array. + // Here's "foreach rec optimized" version which showed the best result + // performance results (1000 cycles): + // get_values - 0.001758 + // foreach rec optimized - 0.008587 + // foreach recursion - 0.015547 + // serialize\unserialze - 0.020816 + // json encode\decode - 0.078953 + $copiedArray = []; + foreach($array as $key => $value) { + if(is_array($value)) { + $value = array_copy($value); + } + + $copiedArray[$key] = $value; + } + + return $copiedArray; +} diff --git a/src/functions/objects.php b/src/functions/objects.php index 4ec1e4a..ddd588e 100644 --- a/src/functions/objects.php +++ b/src/functions/objects.php @@ -11,7 +11,7 @@ function set_object($context, $key, $object) (function($key, $object) use($context) { if ($object) { set_value($this, $key, null); - set_value($this, $key, get_values($object)); + set_value($this, $key, get_values($object, false)); $values =& array_get($key, [], $this->values); set_values($object, $values, true); @@ -41,7 +41,7 @@ function set_objects($context, $key, $objects) $objectsValues = []; foreach ($objects as $objectKey => $object) { - array_set($objectKey, get_values($object), $objectsValues); + array_set($objectKey, get_values($object, false), $objectsValues); } set_value($this, $key, $objectsValues); @@ -71,7 +71,7 @@ function set_objects($context, $key, $objects) function add_object($context, $key, $object, $objectKey = null) { (function($key, $object, $objectKey) use ($context) { - $objectValues = get_values($object); + $objectValues = get_values($object, false); $objectKey = add_value($this, $key, $objectValues, $objectKey); diff --git a/src/functions/values.php b/src/functions/values.php index 6ae7f4b..283579c 100644 --- a/src/functions/values.php +++ b/src/functions/values.php @@ -8,7 +8,7 @@ * * @return object */ -function set_values($object, array &$values, $byReference = false) +function set_values($object, array &$values, bool $byReference = false) { $func = (function (array &$values, $byReference) { if ($byReference) { @@ -27,9 +27,11 @@ function set_values($object, array &$values, $byReference = false) return $func($values, $byReference); } -function get_values($object) +function get_values($object, bool $copy = true): array { - return (function () { return $this->values; })->call($object); + $values = (function () { return $this->values; })->call($object); + + return $copy ? array_copy($values) : $values; } function add_value($object, $key, $value, $valueKey = null) @@ -178,7 +180,7 @@ function build_object_ref($classOrCallable = null, array &$values, $context = nu $object = new $class(); //values set in constructor - $defaultValues = get_values($object); + $defaultValues = get_values($object, false); $values = array_replace($defaultValues, $values); set_values($object, $values, true); @@ -209,7 +211,7 @@ function build_object($classOrCallable = null, array $values) function clone_object($object) { - return build_object(get_class($object), get_values($object)); + return build_object(get_class($object), get_values($object, true)); } function register_cast_hooks($objectOrClass = null) { diff --git a/tests/ObjectsTraitTest.php b/tests/ObjectsTraitTest.php index 928823c..da8424b 100644 --- a/tests/ObjectsTraitTest.php +++ b/tests/ObjectsTraitTest.php @@ -691,4 +691,23 @@ public function testClassProvidedByHookShouldTakePriorityOverClassAsArgument() $this->assertInstanceOf($hookClass, $subObj); } + + public function testShouldNotChangeObjectValuesIfGetValuesCopiedTrue() + { + $subObj = new SubObject(); + $subObj->setValue('aSubName.aSubKey', 'aFooVal'); + + $obj = new Object(); + $obj->setObject('aName.aKey', $subObj); + + $values = get_values($obj); // copy must be true by default + + self::assertSame(['aName' => ['aKey' => ['aSubName' => ['aSubKey' => 'aFooVal']]]], get_values($obj)); + self::assertSame(['aSubName' => ['aSubKey' => 'aFooVal']], get_values($subObj)); + + $values['aName']['aKey']['aSubName']['aSubKey'] = 'aBarVal'; + + self::assertSame(['aName' => ['aKey' => ['aSubName' => ['aSubKey' => 'aFooVal']]]], get_values($obj)); + self::assertSame(['aSubName' => ['aSubKey' => 'aFooVal']], get_values($subObj)); + } } \ No newline at end of file