I need a function that can set its deepest value on an instance, whether it is an object or an array.
For example, I have a function like this that I wrote and played with, but the setValue
function does not work, my silly placing of links here and there did not help me((
functions:
function setValue($name, &$initial, $value) {
// Set function not work
$names = explode('.', $name);
$lastIndex = (count($names) - 1);
$getter = (function ($name) {
if (property_exists($this, $name) === false) {
return null;
}
$value = &$this->{$name};
return $value;
})(...);
$setter = (function ($name, $value) {
if (property_exists($this, $name) === true) {
$this->{$name} = $value;
}
})(...);
$reference = &$initial;
foreach ($names as $index => $keyOrProperty) {
if (is_array($reference) === true) {
if ($lastIndex === $index) {
$reference[$keyOrProperty] = $value;
break;
}
$nextReference = &$reference[$keyOrProperty];
if ($nextReference === null) {
break;
}
$reference = &$nextReference;
continue;
}
if (is_object($reference) === true) {
if ($lastIndex === $index) {
$setter->call($reference, $keyOrProperty, $value);
break;
}
$nextReference = $getter->call($reference, $keyOrProperty);
if ($nextReference === null) {
break;
}
$reference = &$nextReference;
continue;
}
break;
}
}
function getValue($name, $initial) {
$names = explode('.', $name);
$getter = (function ($name) {
if (property_exists($this, $name) === false) {
return $this;
}
return $this->{$name};
})(...);
$accumulator = static function (mixed $curr, string $name) use ($getter): mixed {
if ($curr === null) {
return null;
}
if (is_array($curr) === true) {
return (array_key_exists($name, $curr) === true) ? $curr[$name] : null;
}
if (is_object($curr) === true) {
return $getter->call($curr, $name);
}
return $curr;
};
return array_reduce($names, $accumulator, $initial);
}
test:
class MyFoo
{
private MyBar $bar;
private array $array;
public function setBar(MyBar $bar): static
{
$this->bar = $bar;
return $this;
}
public function setArray(array $array): static
{
$this->array = $array;
return $this;
}
}
class MyBar
{
private MyFoo $foo;
private array $array;
public function setFoo(MyFoo $foo): static
{
$this->foo = $foo;
return $this;
}
public function setArray(array $array): static
{
$this->array = $array;
return $this;
}
}
$instance = (new MyFoo())
->setBar(
(new MyBar())
->setFoo(
(new MyFoo())
->setArray(
[
'myBar' => (new MyBar())
->setArray(
[
'deep' => [
'myFoo' => (new MyFoo())
->setBar(
(new MyBar())
->setArray(['test' => 'is Test'])
)
]
]
)
]
)
)
);
echo getValue('bar.foo.array.myBar.array.deep.myFoo.bar.array.test', $instance);
setValue('bar.foo.array.myBar.array.deep.myFoo.bar.array.test', $instance, 'Set Test');
echo getValue('bar.foo.array.myBar.array.deep.myFoo.bar.array.test', $instance);
Is it even possible to write such a function, or does anyone have any ideas?
The main problem is that when you want to get an array from a property, the return must also be a reference. But you cannot use Closure::call
, because its declaration return type is not a reference. You need to bind the closure first, then directly call the closure.
# $nextReference = $getter->call($reference, $keyOrProperty);
$cl = $getter->bindTo($reference, $reference);
$nextReference =& $cl($keyOrProperty);
Besides, anonymous functions are already closures, the first class callable syntax is not necessary here. Note the anonymous function has an ampersand to return a reference instead of a value.
# $getter = (function ($name) {
# blabla
# })(...);
$getter = function &($name) {
if (property_exists($this, $name) === false) {
return null;
}
return $this->{$name};
};