php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #52782 DOMDocument subclass forgotten using ->ownerDocument after closure
Submitted: 2010-09-06 15:35 UTC Modified: 2010-11-02 10:38 UTC
Votes:2
Avg. Score:4.0 ± 1.0
Reproduced:2 of 2 (100.0%)
Same Version:2 (100.0%)
Same OS:1 (50.0%)
From: bugs dot php dot net at moesen dot nu Assigned:
Status: Not a bug Package: DOM XML related
PHP Version: 5.3SVN-2010-09-06 (snap) OS: GNU/Linux 2.6.26-2-686 (Debian)
Private report: No CVE-ID: None
 [2010-09-06 15:35 UTC] bugs dot php dot net at moesen dot nu
Description:
------------
We have custom XML document and element classes that extend the original DOMDocument and DOMElement classes for convenience. There is a class that uses an instance of XmlElement obtained via a callback specified at construction time. That XmlElement works fine and stays that way. However, when we get its ownerDocument outside of the closure, the result is not an XmlDocument but a DOMDocument. I cannot see a single reason why.

I tried several options, and it only seems to happen with that closure. I have checked versions 5.3.1, 5.3.2, 5.3.3 and now the latest 5.3.4 snapshot compiled with './configure && make'.

Test script:
---------------
http://codepad.org/hvrNh86K

The original code uses a lot of namespaces and extends XmlDocument, but this is a much more minimal test case. Also try the "Uncomment this" code to see what /does/ work.

Expected result:
----------------
*** Calling the callback directly...
dom-fail.php:110: $container: XmlElement: <div id="content"/>
dom-fail.php:111: $container->ownerDocument: XmlDocument; debug: object(XmlDocument)#2 (0) refcount(1){
}

*** Calling callback from constructor()...
dom-fail.php:110: $container: XmlElement: <div id="content"/>
dom-fail.php:111: $container->ownerDocument: XmlDocument; debug: object(XmlDocument)#2 (0) refcount(1){
}

*** In constructor, after check on $container:
dom-fail.php:95: $container: XmlElement: <div id="content"/>
dom-fail.php:96: $container->ownerDocument: XmlDocument; debug: object(XmlDocument)#2 (0) refcount(1){
}

SUCCESS!

Actual result:
--------------
*** Calling the callback directly...
dom-fail.php:110: $container: XmlElement: <div id="content"/>
dom-fail.php:111: $container->ownerDocument: XmlDocument; debug: object(XmlDocument)#2 (0) refcount(1){
}

*** Calling callback from constructor()...
dom-fail.php:110: $container: XmlElement: <div id="content"/>
dom-fail.php:111: $container->ownerDocument: XmlDocument; debug: object(XmlDocument)#2 (0) refcount(1){
}

*** In constructor, after check on $container:
dom-fail.php:95: $container: XmlElement: <div id="content"/>
dom-fail.php:96: $container->ownerDocument: DOMDocument; debug: object(DOMDocument)#2 (0) refcount(1){
}

PHP Fatal error:  Call to undefined method DOMDocument::append() in /home/jmoe/fuckingpieceofshitfuckfuckfucksocks/dom-fail.php on line 100


Patches

Add a Patch

Pull Requests

Add a Pull Request

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2010-09-06 15:43 UTC] bugs dot php dot net at moesen dot nu
Err, my apologies for not cleaning up the path names. An oversight on my part.
 [2010-09-20 13:19 UTC] bugs dot php dot net at moesen dot nu
Is there anything I can do to get a confirmation on this? I tried #php on several networks, but they referred me to Freenode, where #php was full.
 [2010-10-07 11:32 UTC] bugs dot php dot net at moesen dot nu
(Following up on e-mail correspondance with rrichards at php dot net.)

> It'd most likely due to scoping. If you create an element based on a
> subclass, it will only return the subclass as long as the element's
> object (the DOM wrapper not the underlying XML structure) no longer
> has any references.
>
> There are a couple ways to work around this:
>
> 1 - Always keep the doc object in global scope with at least 1
> reference to it. This really is only needed if you are attaching
> custom data to any custom properties.
This does indeed work around the problem, but I am not keen on polluting the global namespace.

> 2 - The best way is to use: bool DOMDocument::registerNodeClass (
> string $baseclass , string $extendedclass ) 
> http://us2.php.net/manual/en/domdocument.registernodeclass.php Using 
> this mehod, you can register classes to always create specific node 
> types with rather than the default DOM classes so if you have any 
> custom methods they will always be available i.e.
> 
> class myDoc extends DOMDocument { };
> 
> $doc = new myDoc();
> $doc->registerNodeClass("DOMDocument", "myDoc"); 
That works to a certain extent: the class is correct, but its custom properties are lost, as mentioned in work-around 1. The ownerDocument is an instance of MyDocument, but instead of being the original instance, it is a new instance (without calling the constructor) with all of its properties set to their defaults.

It seems to me that as long as there is a DOMElement, there is an implicit reference to the original instance of the owner document (because of $domElement->ownerDocument), so said document should not be garbage collected.

I have uploaded a simplified test case at http://codepad.org/CBsD3eVp
 [2010-10-19 19:36 UTC] rrichards@php.net
-Status: Open +Status: Bogus
 [2010-10-19 19:36 UTC] rrichards@php.net
Thank you for taking the time to write to us, but this is not
a bug. Please double-check the documentation available at
http://www.php.net/manual/ and the instructions on how to report
a bug at http://bugs.php.net/how-to-report.php

This is by design to minimize the system resource impact. The behavior you 
describe was the old behavior used in the domxml extension and purposely not 
duplicated. Objects wrapping the underlying XML tree live only as long as the user 
has a reference to them otherwise the only way to destroy any DOM object would be 
to de-reference every single object that wraps any portion on the underlying xml 
tree.
 [2010-11-02 10:38 UTC] bugs dot php dot net at moesen dot nu
OK, thank you for the rationale.

Contrary to the boilerplate text from the first paragraph, I did check the documentation. :-) I feel the documentation should be updated, because this paragraph is misleading:

| When instantiating a custom DOMDocument the ownerDocument property
| will refer to the instantiated class, meaning there is no need (and
| in fact not possible) to use DOMDocument::registerNodeClass() with
| DOMDocument

This goes against your second advice quoted in the comment from 2010-10-07 09:32 UTC.


For general reference, this is what I do in the function that creates the DOMDocument child instance and returns a DOMElement child of said document:

$GLOBALS['XhtmlDocumentHack-' . uniqid()] = $document = new Xhtml10StrictDocument();

I have not checked if it is possible to override ->ownerDocument in the DOMElement child in order to keep a reference to the extended document alive.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Fri Apr 19 14:01:30 2024 UTC