DOMNode __destruct method broken?

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

DOMNode __destruct method broken?

Post by Kieran Huggins »

I've been beating my head against the wall for 2 days about this... hopefully someone out there can help me out!

Here's My sample PHP:

Code: Select all

class someNode extends DOMNode{

    function __destruct() {

        echo "now I have ".$this->attributes->length." attribute(s)";

    }

}

$x = new someNode('nodename');

$x->setAttribute('bob','frank');

echo "I have ".$x->attributes->length." attributes"; // outputs "I have 1 attribute(s)"

unset($x); // outputs "now I have  attribute(s)"
The DOMNode class is part of the DOM functions...not XML-DOM, btw. Also, I've tried against php 5.1.2 and 5.2.0, with the same result :-(

The behaviour is the same for DOMNode's __construct method as well... is this a bug? or am I missing something???

Any help is GREATLY appreciated!!!!

Cheers,

Kieran
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

I get:

Fatal error: Call to undefined method someNode::setAttribute()

Code: Select all

$x->setAttribute('bob','frank');
Notice: Undefined property: someNode::$attributes AND Notice: Trying to get property of non-object

Code: Select all

echo "now I have ".$this->attributes->length." attribute(s)";
now I have attribute(s)
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

My apologies, In slimming down the code for the post, I got a little carried away:

This code fails as expected:

Code: Select all

class element extends DOMElement{

	function __destruct() {
		echo "<br/>now I have ".$this->attributes->length." attribute(s)<br/>";
	}

}

$doc = new DOMDocument;
$x = new element('nodename');

$doc->appendChild($x);

$x->setAttribute('bob','frank');

echo "<br/>I have ".$x->attributes->length." attribute(s)"; // outputs "I have 1 attribute(s)"

unset($x); // outputs "now I have  attribute(s)"
Cheers,
Kieran
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Code: Select all

I have 1 attribute(s)

Notice: Undefined property: element::$attributes in /osis/x/test/www/index.php on line 8

Notice: Trying to get property of non-object in /osis/x/test/www/index.php on line 8

now I have attribute(s)
line 8 wrote:echo "<br/>now I have ".$this->attributes->length." attribute(s)<br/>";
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

That only leads me to believe that the parent class ceases to exist before the destructor is run... but that makes no sense to me at all, since we're manually unset()-ing the class!

ARGGGGGHHHH *head-hits-wall-again*

Cheers,
Kieran
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

You might want to research the support for extending DOM classes.

Why do you need this code anyway? What problem is this piece of code part of the solution for?
I may be able to suggest another approach.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

Thanks for your help ole :-)

What I'm trying to do is store a DOM tree in a database.

I've created the following table:

Code: Select all

CREATE TABLE [nodes] (
[id] INTEGER  NULL,
[parent] INTEGER  NULL,
[name] TEXT  NULL,
[value] TEXT  NULL,
[type] TEXT  NULL,
[ns] TEXT  NULL
)
to store each node in a row.

Then as I create a "new Element(id)" I pull the row at "id" out of the database, attach it to it's "parent" element on the DOM tree, and attach all the nodes (attributes, children) that have it listed as it's parent.

I want to register a database save on the __destruct function so I can autosave the state of each node.

Does that make sense?

I'll look for more documentation on extending DOM classes as per your suggestion, unless you have a better idea? (I hope!!!)

Thanks!
Kieran :-)
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Oh wow.. yes that makes a lot of sense.

Just one thing tho, why store DOM in a database in the first place?
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Code: Select all

class element extends DOMElement{

        function peek()
        {
            echo count((array)$this);
            foreach ($this as $k => $v) {
                $type = gettype($v);
                echo "$k: $type$v<br />";
            }
        }

}

$doc = new DOMDocument;
$x = new element('nodename');

$doc->appendChild($x);

$x->setAttribute('bob','frank');
$x->peek();
Outputs:

Code: Select all

0
Disappointing, I know.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

I'm trying to create a better data layer for a rapid development environment. XML has the right structure, but I want to be able to create special element types that are data-aware... so in the DOM I can create an element of a custom type "PhoneNumber" (for instance) and give it custom methods like getAreaCode() etc...

Ideally, when I'm writing a controller I'll be able to string together nodes like this:

Code: Select all

$person = getPersonByToken("Someone's Unique Name");
$areacode = $person->phoneNumber->getAreaCode();

$neighbours = getPeopleByAttribute('phoneNumber',"/^$areacode/");
and then I can attach whichever trees I want to the DOM and pass it to XSLT for presentation.

The other value of doing it this way is that I only load contextual information into memory, VS one-mega-XML file.

I'm open to suggestions though!!!

Cheers,
Kieran
Last edited by Kieran Huggins on Wed Dec 06, 2006 9:12 pm, edited 2 times in total.
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

8O wow...just wow.
pass it to XSLT for presentation
Snap! I use that too, I like it very much :)
The other value of doing it this way is that I only load contextual information into memory, VS one-mega-XML file.
Hmm yes that is a big plus, I am slightly worried about the complexity of the data in the database and the number of queries required to pull a single node. but this is something you can only know having done it.

So you want to bind classes (behaviour) to specific nodes names? For instance:

Code: Select all

<wood>
<tree type="oak" />
<weed type="kettle" />
</wood

Code: Select all

class Wood extends XmlDataType
{
    public function removeWeeds()
    {
         // necessary database interactions
    }
}
only bolster that kind of stuff on top of DOM.

You could of course rewrite a DOM that interacts with your database directly, this would be even better performance wise because you then only need to query exactly what you need. So if you just want to get the first child in the wood (1000s of trees and weeds) you wouldn't pull the entire wood and construct loads of DOM objects to do so.

Another technique (really tricky tho):
Compile your XML files somehow. So that the byte offsets certain things are stored in a binary file and you can find things really quickly / get portions of XML files without loading the entire thing first. Perhaps someone has already done something like this (doubtful in PHP but the Java world maybe). This does remove all the issue of database interactions for you though. Again you would need to code your own DOM.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

I originally rejected rewriting the DOM classes myself because I was worried that php code would be FAR slower than what they're likely written (and compiled) in.... C would be my guess.

I don't really want to tie element names to classes... I would like to keep that separate if possible. It would be the job of the controller developer to follow the output XML model, giving XSLT template developers a concrete model to work with.

I've found that attaching the node to it's parent node in the constructor suddenly makes $this work, so I can at least auto-fetch the nodes. I guess at this point I may just have to explicitly "save" each node as I need to... avoiding the destructor altogether :-(

The solution so far is to create a custom method for creating elements that include telling it which node to attach itself to.

I'm eager to hear any other thoughts you have on the subject - they've all been excellent so far!

Cheers,
Kieran
User avatar
Ollie Saunders
DevNet Master
Posts: 3179
Joined: Tue May 24, 2005 6:01 pm
Location: UK

Post by Ollie Saunders »

Code: Select all

class element extends DOMElement{

        function peek()
        {
            $reflection = new ReflectionClass($this);
            foreach ($reflection->getMethods() as $method) {
                if (($name = $method->getName()) == __FUNCTION__) {
                    continue;
                }
                echo $name, '<br />';
            }
        }

}
may be of interest.
I originally rejected rewriting the DOM classes myself because I was worried that php code would be FAR slower than what they're likely written (and compiled) in.... C would be my guess.
Yes

Other thoughts:
I've thought about an XML data server (this would have to be written from the ground up in C or C++ ) that would read DTD and work out the most efficient way to store the XML, all indexed of course.

Lastly have you considered storing straight blocks of XML in the database. So you can take advantage of the database's performance on any aspect that you choose so long as you structure the tables correctly and then the power of XML when you need to. Only problem with that is if you need to search the data for something in the XML rather than in the database tables themselves you'll have to query very large portions of tables and run XPath on each row. The idea is best explained with this:

Code: Select all

<person name="Bob" age="10">
 <occupation sector="education" title="pupil" />
</person>
<person name="Frank" age="42">
 <occupation sector="retail" title="sales assistant" />
</person>
<person name="Deadry" age="54">
 <occupation sector="unemployed" />
</person>
You have to imagine there is a lot more data and more complex stuff under each person. Say you need to search by for specific people frequently:

Code: Select all

table Person (
   id int,
   age int,
   name text,
   xmlcontent text
)
so you can still use XML under each person. Problem here is what if you want to find all people working in retail.

I'm off to bed now.
User avatar
volka
DevNet Evangelist
Posts: 8391
Joined: Tue May 07, 2002 9:48 am
Location: Berlin, ger

Post by volka »

Two reasons why you shouldn't put any persistance code into the destructor:
- You don't know when the code is executed and if you have related data objects you don't know in which order the destructors are called.
- The destructor should only support freeing resources or similar, keep it as simple as possible. Error and resource handling in destructors can be (depending on the language) tricky.
Kieran Huggins wrote:I don't really want to tie element names to classes... I would like to keep that separate if possible. It would be the job of the controller developer to follow the output XML model, giving XSLT template developers a concrete model to work with.
But you can provide a default mechanism that has a mapping tagname->classname.
With .net you can overwrite CreateElement and it will be used while parsing, see http://msdn2.microsoft.com/en-us/library/e3x60fe9.aspx
Unfortunately this doesn't work in php 5.2 (or maybe I'm doing somethign wrong, it's 4:40am here ;))

Code: Select all

class MyDOMDocument extends DOMDocument {
	public function __construct() {
		parent::__construct();
		echo "<div>MyDOMDocument::__construct</div>\n";
	}
    
	public function createElement($name, $value=null) {
		echo "MyDOMDocument::createElement($name, $value)";
		return parent::createElement($name, $value);
	}
	
	public function createElementNS($namespaceURI, $qualifiedName, $value=null) {
		echo "MyDOMDocument::createElementNS($namespaceURI, $qualifiedName, $value)";
		return parent::createElementNS($namespaceURI, $qualifiedName, $value);
	}
}

$xml = <<< eox
<de>
	<yadda>1</yadda>
	<yadda>2</yadda>
	<yadda>3</yadda>
</de>
eox;

$dom = new MyDOMDocument;
$dom->loadxml($xml);
echo $dom->savexml();
Maybe you're interested in articles about container based/container managed persistence.
User avatar
Kieran Huggins
DevNet Master
Posts: 3635
Joined: Wed Dec 06, 2006 4:14 pm
Location: Toronto, Canada
Contact:

Post by Kieran Huggins »

Volka wrote:you shouldn't put any persistance code into the destructor
Excellent points - now I'll definitely use the manual save() funcitons.
Volka wrote:you can provide a default mechanism that has a mapping tagname->classname
A default container element name - not a bad idea either!

I'll search out the articles you mentioned - they sound like good reading :-)
ole wrote:have you considered storing straight blocks of XML in the database
Sounds like it would make one hell of an optimization in a write once/read many operation... but I'll wait until the system works before I test that... "Premature optimization is the root of all evil" ;-)

Another idea I had that I wanted to bounce off you guys was using a separate table for each node type, as each have slightly different needs: Like attributes always have a name/value pair while elements never have a value, but always have a name... and cdata sections never ave a name, but always a value... etc...

Seems logical to me, but I'm a little too deep in it to be completely objective ;-)

Cheers,
Kieran
Post Reply