The constructor did not call the extended PHP DOMElement

When extending a DOMElement in PHP, the constructor of the child class is not called. Nothing jumped out of me in the docs as far as expected, but maybe I missed something. Here is a simple test case ....

class SillyTestClass extends DOMElement{ public $foo=null; public function __construct($name,$value=null,$namespace=null){ echo "calling custom construct...."; $this->foo="bar"; parent::__construct($name,$value,$namespace); } public function sayHello(){ echo "Why, hello there!"; } } $doc=new DOMDocument(); $doc->registerNodeClass('DOMElement','SillyTestClass'); $doc->loadHTML("<div><h1>Sample</h1></div>"); //THIS WORKS! CUSTOM CLASS BEING USED $doc->documentElement->firstChild->sayHello(); //THIS IS STILL NULL:( Never set by construct, no message saying construct was called either echo $doc->documentElement->firstChild->foo; 

Of course, if I create it myself, that's fine ...

 $elm=new SillyTestClass("foo","Hi there"); //WORKS! Outputs "bar"; echo $elm->foo; 

Why, when I register a node class with a DOMDocument, it does not call __construct , although it gives me the correct inheritance in any other way?

UPDATE For really curious people or people who know C

============================================= ====== ====================== Research ....

This is the DOM extension source taken from PHP src on github

If you were to create an element, this is a chain of events that occur:

 document.c :: dom_document_create_element | //uses libxml to generate a new DOMNode | node = xmlNewDocNode(docp, NULL, (xmlChar *) name, (xmlChar *) value); // that node is then sent to php_dom.c :: php_dom_create_object | | //the node type is used to figure out what extension class to use | switch (obj->type) {... | | //that class is used to instance an object | if (domobj && domobj->document) { | ce = dom_get_doc_classmap(domobj->document, ce); | } object_init_ex(return_value, ce); 

It looks like you DO NOT get true inheritance from the DOMNode extension or it is built in extension classes (DOMElement, DOMText) if DOMDocument calls them. In this case, the libxml node is created first, and our class properties are bound to the second.

This is unfortunate and it is impossible to get around this because even when you import a Node into a document, it introduces a new node. Example

 class extendsDE extends DOMElement{ public $constructWasCalled=false; public function __construct($name){ parent::__construct($name); $this->constructWasCalled=true; } } class extendsDD extends DOMDocument{ public function __construct(){ parent::__construct(); $this->registerNodeClass("DOMElement","extendsDE"); } //@override public function createElement($name){ $elm=new extendsDE($name); echo "Element construct called when we create="; echo $elm->constructWasCalled?"true":"false"; return $this->importNode($elm); } } $doc=new extendsDD(); $node=$doc->createElement("div"); echo "<br/>"; echo "But then when we import into document, a new element is created and construct called= "; echo $node->constructWasCalled?"true":"false"; 

Now the discussion is what the developers intended, and the documentation is misleading, or was it supposed to be a mistake and true inheritance?

+5
source share
1 answer

I have figured out a way around this in some circumstances so far. DOMNodes are stored in memory, so as long as you can get it in a document in one piece (your constructor is called), then it will be fine, regardless of what you do with it, or how you get access to it after that .. .

Here is an example where we can get the construction for the call and still be fine with the document

 class extendsDE extends DOMElement{ public $constructWasCalled=false; public function __construct($name){ parent::__construct($name); $this->constructWasCalled=true; } } $doc=new DOMDocument(); $doc->registerNodeClass("DOMElement","extendsDE"); $doc->loadHTML("<div></div>"); //append a node we create manually rather than through createElement $node=$doc->getElementsByTagName('div')->item(0)->appendChild(new extendsDE("p")); $node->nodeValue="Was my construct called?"; echo "<br/>"; echo "A new element was appended and construct called= "; echo $node->constructWasCalled?"true":"false"; echo "<br/>"; echo "Okay but what happens if I retrieve that node some other way.."; echo "<br/>"; echo "what if I get that element through selectors. Custom property still set from constructor="; echo $doc->getElementsByTagName('p')->item(0)->constructWasCalled?"true":"false"; echo "<br/>"; echo "what if I get that element through relationships. Custom property still set from constructor="; echo $doc->getElementsByTagName('div')->item(0)->childNodes->item(0)->constructWasCalled?"true":"false"; 

THE CATCH:

This only works when creating all elements. If you load HTML markup using $ document-> loadHTML ($ html), your extension constructors will not be called. So far, I can only think about how to do this, for example, load markup, and then iterate over each instance of the node instances and insert them. Definitely possible, but slowly ...

+1
source

Source: https://habr.com/ru/post/1263184/


All Articles