Page 1 of 2
DOMNode __destruct method broken?
Posted: Wed Dec 06, 2006 4:26 pm
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
Posted: Wed Dec 06, 2006 4:51 pm
by Ollie Saunders
I get:
Fatal error: Call to undefined method someNode::setAttribute()
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)
Posted: Wed Dec 06, 2006 5:16 pm
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
Posted: Wed Dec 06, 2006 5:21 pm
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/>";
Posted: Wed Dec 06, 2006 5:32 pm
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
Posted: Wed Dec 06, 2006 5:36 pm
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.
Posted: Wed Dec 06, 2006 5:51 pm
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

Posted: Wed Dec 06, 2006 5:58 pm
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?
Posted: Wed Dec 06, 2006 6:01 pm
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:
Disappointing, I know.
Posted: Wed Dec 06, 2006 6:14 pm
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
Posted: Wed Dec 06, 2006 6:37 pm
by Ollie Saunders

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.
Posted: Wed Dec 06, 2006 6:53 pm
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
Posted: Wed Dec 06, 2006 7:24 pm
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.
Posted: Wed Dec 06, 2006 9:40 pm
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.
Posted: Wed Dec 06, 2006 10:53 pm
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