Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I obtain an iterative object reference to the root object in PHP?

I have a class that takes some basic HTML5 and with some DOM magic turns it into a class which is an extension simpleXMLElement. This all starts at a "factory" (I might be abusing that term slightly) class called XPage.

Thus we might have

<?php
$MyPage = new XPage($HTML);

So far so good. What I now need to do is select some parts of the XML doc so that they can be "bookmarked" (I am sure that there is a better name for it) so one can go like this

$MyPage->ThatBit("foo")->addChild('p',$MyParagraph);

Ideally I would like to be able to go

$MyPage->page()->body->header->RememberThisBitAs("foo");

Or something intuitively similar. What should then happen is that the object $MyPage (of type XPage) should be passed the reference for, in this case html/body/header (that is probably not quite the right XPath notation but hopefully you can follow).

The idea being that the most commonly access areas can be gotten to more smoothly. This is where the SimpleXML class (even with my extension) runs into something it cannot do without another factory and also making XPage a singleton.

However it is the XPage class ($MyPage) that holds the array of "bookmarks". As far as I know there is no way of passing a reference down the iterations of SimpleXML so that a child or a child of a child could not tell the factory "bookmark me please".

This would leave me doing nasty things like:

$MyPage->addBookmark("foo", 
$MyPage->page()->body->header->getChildren() );

Which just does not seem right.

or

$MyPage->page()->body->header->RememberThisBitAs("foo",$MyPage);

which is not so bad but still "feels" wrong.

How do I approach this without excessive object coupling or ugly self referencing?

Update

I wondered if it was possible to reverse traverse a class using debug_backtrace but the answer appears to be no.

In PHP is it possible to reverse traverse a Traversable class to find the root object?

Other similar test have shown me that setting the value of a class variable on an element is not accessible at any other element in the traversable stack.

I am going to focus on the wrapper suggestion.

Update

SimpleXML is not actually a true object but a system resource. This could explain where my pain was coming from. Say the notes: http://php.net/manual/en/class.simplexmlelement.php

So for all the objects feel like a chain of objects calling each other this is not actually the case.

like image 626
Matthew Brown aka Lord Matt Avatar asked Feb 25 '15 22:02

Matthew Brown aka Lord Matt


1 Answers

Option 1: Reference to root

You could modify the methods of your XPage class to return XPage objects instead of simpleXMLElements (or another class extending simpleXMLElement). That class could then have an additional property holding a reference to the "root" element.

So basically your "wrapper class" would look something like this (pseudo-code ahead!):

class Wrap extends simpleXMLElement
{
    private $root = null;
    private $refs = array();

    public function addReference($name, $element = null) {
        if($element === null) {
            $element = $this;
        }
        if($this->root === null) {
            $this->refs[$name] = $this;
        } else {
            $this->root->addReference($name, $this);
        }
    }

    public function getReference($name) {
        if($this->root === null) {
            return $this->refs[$name];
        } else {
            return $this->root->getReference($name);
        }
    }
}

So your use-case from above would look like this:

$MyPage->page()->body->header->addReference("foo");

$MyPage->getReference("foo")->addChild('p',$MyParagraph);

Option 2: Add an id

Alternatively, you might just add an id attribute to the element and retrieve it by that:

$MyPage->page()->body->header->addAttribute("id", "foo");

$MyPage->xpath("//*[@id='foo']")->addChild('p',$MyParagraph);
like image 137
Lars Ebert Avatar answered Oct 22 '22 01:10

Lars Ebert