If all you need is basic traversal based on a /
-separated path, then you can implement it with a simple loop like this:
public function getDescendant($path) {
// Separate the path into an array of components
$path_parts = explode('/', $path);
// Start by pointing at the current object
$var = $this;
// Loop over the parts of the path specified
foreach($path_parts as $property)
{
// Check that it's a valid access
if ( is_object($var) && isset($var->$property) )
{
// Traverse to the specified property,
// overwriting the same variable
$var = $var->$property;
}
else
{
return null;
}
}
// Our variable has now traversed the specified path
return $var;
}
To set a value is similar, but we need one extra trick: to make it possible to assign a value after the loop has exited, we need to assign the variable by reference each time:
public function setDescendant($path, $value) {
// Separate the path into an array of components
$path_parts = explode('/', $path);
// Start by pointing at the current object
$var =& $this;
// Loop over the parts of the path specified
foreach($path_parts as $property)
{
// Traverse to the specified property,
// overwriting the same variable with a *reference*
$var =& $var->$property;
}
// Our variable has now traversed the specified path,
// and is a reference to the variable we want to overwrite
$var = $value;
}
Adding those to a class called Test
, allows us to do something like the following:
$foo = new Test;
$foo->setDescendant('A/B', 42);
$bar = new Test;
$bar->setDescendant('One/Two', $foo);
echo $bar->getDescendant('One/Two/A/B'), ' is the same as ', $bar->One->Two->A->B;
To allow this using array access notation as in your question, you need to make a class that implements the ArrayAccess
interface:
- The above functions can be used directly as
offsetGet
and offsetSet
offsetExists
would be similar to getDescendant
/offsetGet
, except returning false
instead of null
, and true
instead of $var
.
- To implement
offsetUnset
properly is slightly trickier, as you can't use the assign-by-reference trick to actually delete a property from its parent object. Instead, you need to treat the last part of the specified path specially, e.g. by grabbing it with array_pop($path_parts)
- With a bit of care, the 4 methods could probably use a common base.
One other thought is that this might be a good candidate for a Trait
, which basically lets you copy-and-paste the functions into unrelated classes. Note that Traits can't implement Interfaces directly, so each class will need both implements ArrayAccess
and the use
statement for your Trait.
(I may come back and edit in a full example of ArrayAccess
methods when I have time.)