How do I obtain an iterative object reference to t

2019-06-16 16:23发布

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.

2条回答
做个烂人
2楼-- · 2019-06-16 17:07

Given the huge push by the answer by Lars Ebert and not before considering a number of crazy (and unworkable) ideas I came upon Extend Class with Final Constructor on PHP which led me to understand that the design pattern I need was that of a delegation class.

As I might not be done with adding specialist functions to the SimpleXMLElement extension I used magic methods to create a delegation wrapper. The downside is that this does not play so well in my IDE but it does at initial testing fully work. I have pulled out stuff that relates only to my own project but otherwise here is the solution that I am using.

It requires the XPage object to implement some interface elements the same (I shall probably define an interface) and it passes the SimpleXML element using my extensions and a pointer to itself into this delegation wrapper which behaves like SimpleXML but is truly an object and can pass debug messages and self references back up to XPage.

<?php

/**
 * A Delegation extension of SimpleXMLElement
 * 
 * Designed to act like SimpleXML but builds a back traceable tree that expects
 * XPage (or some other parent factory) to catch references and debug messages.
 * 
 * @author Lord Matt 
 * 
 * @license
 *  Copyright (C) 2015 Matthew Brown
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

class AEWrapper {

    protected $mother = null;
    protected $MyAE = null;

    public function __construct($MyAE,$mother) {
        $this->MyAE = $MyAE;
        $this->mother = $mother;
        return $this;
    }

    public function __call($name, $arguments) {
        try {
            $result = call_user_method_array($name, $this->MyAE, $arguments);
        } catch (Exception $e) {
            $this->log_error('AEWrapper caught exception: ',  $e->getMessage());
            return FALSE;
        }
        if(is_object($result)){
            return new AEWrapper($result,$this);
        }
        return $result;
    }

    public function __get($name) {
        try {
            $result = $this->MyAE->{$name};
        } catch (Exception $e) {
            $this->log_error('AEWrapper caught exception: ',  $e->getMessage());
            return FALSE;
        }
        if(is_object($result)){
            return new AEWrapper($result,$this);
        }
        return $result;
    }

    public function &actual_simpleXML_obj_please(){
        return $this->MyAE;
    }

    // methods exists in XPage
    public function register_as($name,$what=null){
        if($what===null){
            $what = $this;
        }
        $this->mother->register_as($name,$what);
    }

    // [SNIP] 
    // Other project specific stuff here
    // [/SNIP]

    // methods exists in XPage    
    protected function log_error($error){
        $this->mother->log_error('/'.$error);
    }
}
查看更多
Evening l夕情丶
3楼-- · 2019-06-16 17:16

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);
查看更多
登录 后发表回答