...

View Full Version : Extending DOMDocument and setting doctype



Blue_Jeans
12-13-2010, 09:08 PM
My boss and I are sick of dealing with smarty for our templating system, so we have embarked on a journey to create our own way of doing things. We chose to use DOMDocument. An example DOMDocument I'd like to use:


$doctype = DOMImplementation::createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$document = DOMImplementation::createDocument('http://www.w3.org/1999/xhtml',
'html',
$doctype);

The trouble is, we don't want to simply use another class, we want to extend it. I'm having trouble creating a constructor to give me what the above code gives. Consider:

public function __constuct() {
parent::__construct('1.0', 'utf-8');
}
That creates a normal XML doctype declaration. How do I add the XHTML transitional (or any other) doctype? I tried overriding the DOMDocument with the following, but I get the 'Cannot redefine $this' error when I call extendDoctype():

class MyTemplate extends DOMDocument {
public function __constuct() {
parent::__construct('1.0', 'utf-8');
}
public function extendDoctype() {
$doctype = DOMImplementation::createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$this = DOMImplementation::createDocument('http://www.w3.org/1999/xhtml',
'html',
$doctype);
}
}

surreal5335
12-14-2010, 05:11 AM
This probably wont be able to help your error, but I see you are using the DOMDocument for creating objects. W3 officially stated that the best means to use the DOM interface is through the DOMImplementation.

So instead of this:


class MyTemplate extends DOMDocument {
public function __constuct() {
parent::__construct('1.0', 'utf-8');
}

use this:


class MyTemplate extends DOMImplementation {
public function __constuct() {
parent::__construct('1.0', 'utf-8');
}

Source:
http://us3.php.net/manual/en/class.domimplementation.php


As for your $this error I would suggest looking at what $this is calling:

public function extendDoctype() {
$doctype = DOMImplementation::createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$this = DOMImplementation::createDocument('http://www.w3.org/1999/xhtml',
'html',
$doctype);
}

I could be wrong but I would think you are accessing $doctype with the $this, then you have $doctype inside the function which is being saved to $this... May cause a redundancy problem which would explain the key word 'redefine' in the error. Just a thought, I have never worked with a 'redefine' error before, so shooting from the hip there.

Dormilich
12-14-2010, 05:21 PM
I could be wrong but I would think you are accessing $doctype with the $this, then you have $doctype inside the function which is being saved to $this... May cause a redundancy problem which would explain the key word 'redefine' in the error. Just a thought, I have never worked with a 'redefine' error before, so shooting from the hip there.

if I remember you canít redefine $this at all. objects are usually created through a constructor and not by defining $this.

Blue_Jeans
12-15-2010, 03:00 AM
This probably wont be able to help your error, but I see you are using the DOMDocument for creating objects. W3 officially stated that the best means to use the DOM interface is through the DOMImplementation.

So instead of this:


class MyTemplate extends DOMDocument {
public function __constuct() {
parent::__construct('1.0', 'utf-8');
}

use this:


class MyTemplate extends DOMImplementation {
public function __constuct() {
parent::__construct('1.0', 'utf-8');
}

Source:
http://us3.php.net/manual/en/class.domimplementation.php


As for your $this error I would suggest looking at what $this is calling:

public function extendDoctype() {
$doctype = DOMImplementation::createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Transitional//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd');

$this = DOMImplementation::createDocument('http://www.w3.org/1999/xhtml',
'html',
$doctype);
}

I could be wrong but I would think you are accessing $doctype with the $this, then you have $doctype inside the function which is being saved to $this... May cause a redundancy problem which would explain the key word 'redefine' in the error. Just a thought, I have never worked with a 'redefine' error before, so shooting from the hip there.

Extending DOMImplemenation doesn't help, because you have to create a new DOMDocument using it. That means I'd have a DOMDocument inside my class instead of extending my class. That's exactly what I don't want. Typing $myclassname->DOMDocument->Method is just really annoying, especially when all I want is to add a few simple things to the class itself. I like verbose naming schemes because often I see legacy code and, honestly, verbose names are the best documentation for things. So I'd really be typing something that long. Ouch. I'd like to cut out $myclassname->

I'm familiar with the redefine error. There's no conflict in the way it is set up in terms of $doctype and $this, the trouble is that you have to use the __construct (as Dormilich mentioned). I just threw in that information in case anyone tried suggesting it and to prove I actually think for myself before I post :)

Fou-Lu
12-15-2010, 06:21 AM
Come again? I'm a little confused here sorry. For starters, you can't exactly extend the DOMImplementation to do what you are wanting. The DOMImplementation doesn't include a parameterized constructor. DOMImplementation::createDocument and createDocumentType are not actually static methods either, so they should not be treated as static. PHP is far too lenient in this regard though.

Why not simply create a wrapper class with a static creator? You won't be able to do this with the constructor itself.


class MyDOM extends DOMDocument
{
public function __construct($a = null, $b = null)
{
throw new Exception('Cannot construct ' . get_class($this)
. ', use ' . get_class($this) . '::createInstance instead.');
}

public static function createInstance($ns = null, $qn = null)
{
$di = new DOMImplementation();
$dt = $di->createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
return $di->createDocument($ns, $qn, $dt);
}
}


$mydom = MyDOM::createInstance();
print $mydom->saveXML();


There are other alternatives as well, which include passing in the DOMImplementation and DOMDocumentType and constructing from there. I don't like this though as the DOMImplementation will choke if you try to give it more than one creation. On the other hand, pulling out the type for reuse would be aok.
And any special methods or properties and away you go.

Also, been a long long while here, but I believe that $this was previously re-definable. I recall it was a stink when they actually stopped that (which doesn't make any sense to have in the first place). I think it was 5.1 when the plug was pulled on that. I can understand why it was liked though; singleton objects could be created with a call against new, and PHP lacks the scoping ability to demote the constructor from an extended class object. This was also an issue since pre-PHP5.3 there was not late static binding in the classes which caused great havok when trying to create extended classes through a singleton from a parent (why bother rewriting the same code over and over for child classes?).
All of these little problems have been taken care of, and it appears that on Zend's plate is to add a scope demotion for the constructors as well, so that will make it easier too.
You can always trick a class to do what you want though :P

Blue_Jeans
12-15-2010, 06:47 PM
Come again? I'm a little confused here sorry. For starters, you can't exactly extend the DOMImplementation to do what you are wanting. The DOMImplementation doesn't include a parameterized constructor. DOMImplementation::createDocument and createDocumentType are not actually static methods either, so they should not be treated as static. PHP is far too lenient in this regard though.

Why not simply create a wrapper class with a static creator? You won't be able to do this with the constructor itself.


class MyDOM extends DOMDocument
{
public function __construct($a = null, $b = null)
{
throw new Exception('Cannot construct ' . get_class($this)
. ', use ' . get_class($this) . '::createInstance instead.');
}

public static function createInstance($ns = null, $qn = null)
{
$di = new DOMImplementation();
$dt = $di->createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
return $di->createDocument($ns, $qn, $dt);
}
}


$mydom = MyDOM::createInstance();
print $mydom->saveXML();


There are other alternatives as well, which include passing in the DOMImplementation and DOMDocumentType and constructing from there. I don't like this though as the DOMImplementation will choke if you try to give it more than one creation. On the other hand, pulling out the type for reuse would be aok.
And any special methods or properties and away you go.

Also, been a long long while here, but I believe that $this was previously re-definable. I recall it was a stink when they actually stopped that (which doesn't make any sense to have in the first place). I think it was 5.1 when the plug was pulled on that. I can understand why it was liked though; singleton objects could be created with a call against new, and PHP lacks the scoping ability to demote the constructor from an extended class object. This was also an issue since pre-PHP5.3 there was not late static binding in the classes which caused great havok when trying to create extended classes through a singleton from a parent (why bother rewriting the same code over and over for child classes?).
All of these little problems have been taken care of, and it appears that on Zend's plate is to add a scope demotion for the constructors as well, so that will make it easier too.
You can always trick a class to do what you want though :P

Hmm... I'm not sure I understand. We're just all confused. I don't want to extend DOMImplementation, I want to extend a DOMDocument like that you get from using DOMImplementation::createDocumentType(). The code you gave me doesn't achieve this end (though I thank you for it anyway). For example:

class MyDOM extends DOMDocument
{
public $title;

public function __construct($a = null, $b = null)
{
throw new Exception('Cannot construct ' . get_class($this)
. ', use ' . get_class($this) . '::createInstance instead.');
}

public static function createInstance($ns = null, $qn = null)
{
$di = new DOMImplementation();
$dt = $di->createDocumentType('html',
'-//W3C//DTD XHTML 1.0 Strict//EN',
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd');
return $di->createDocument($ns, $qn, $dt);
}
public function buildTemplate() {
$html = $this->createElementNS('http://www.w3.org/1999/xhtml', 'html');
$head = $this->createElement('head');
$title = $this->createElement('title', $this->title);

$head->appendChild($title);
$html->appendChild($head);
$this->appendChild($html);
}
}


$mydom = MyDOM::createInstance();
$mydom->title = 'Hello, World!';

$mydom->buildTemplate();

print $mydom->saveXML();

That throws an error:
Fatal error: Call to undefined method DOMDocument::buildTemplate()

I find it odd though, that setting $mydom->title doesn't throw an error. Is there already a variable in DOMDocument of that name? This all seems crazily complicated for no apparent reason.

Fou-Lu
12-15-2010, 07:45 PM
Oh yes yes yes, I see now what you want. And what was I thinking, the implementation has no reference to what it should create.
I'll need to look into this a little further. The problem is the current setup with the final variables within the dom itself.

Blue_Jeans
12-20-2010, 07:22 PM
Anyone have any luck? I'm still not getting anywhere on this. It seems like they've deliberately made it impossible to extend DOMDocument and use a custom DOCTYPE.

Fou-Lu
12-21-2010, 03:01 AM
I'm in the same boat here as well, I've only tried a couple of things but the API doesn't leave much for movement.
The last idea I had was to essentially create two DOM's, one that is an actual DOMDocument, and one that is a custom extension, and import the entire dom into it (technically that should work since the DOMDocument is a node). Sadly it did not.
And since the DOMImplementation does not include a way of using a custom class, I can't actually see a standard way to implement this.
I'm afraid that you may not be able to implement what you are looking to do.

MECHT4NK
01-05-2011, 10:10 AM
class MyTemplate extends DOMDocument {
private $itsHtmlElement;

public function __constuct($version = "1.0", $encoding = "utf-8") {
parent::__construct($version, $encoding);

$this->appendChild(DOMImplementation::createDocumentType("html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"));
$this->itsHtmlElement = $this->appendChild($this->createElement("html"));
$this->itsHtmlElement->setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
}
}

Works for me!

Fou-Lu
01-05-2011, 02:49 PM
class MyTemplate extends DOMDocument {
private $itsHtmlElement;

public function __constuct($version = "1.0", $encoding = "utf-8") {
// parent::__construct($version, $encoding);

$this->appendChild(DOMImplementation::createDocumentType("html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"));
$this->itsHtmlElement = $this->appendChild($this->createElement("html"));
$this->itsHtmlElement->setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
}
}

Works for me!

Not for me. The Document type is still a plain XML when dumped with the saveXML method. Since these are declared as final properties that cannot be accessed even during construction, they cannot be overridden.
ItsHTMLElement is also null on my test, indicating that the createDocument has failed to append.
I thought I previously indicated that I couldn't merge together a domdocument into another domdocument...

MECHT4NK
01-05-2011, 03:46 PM
Not for me. The Document type is still a plain XML when dumped with the saveXML method. Since these are declared as final properties that cannot be accessed even during construction, they cannot be overridden.
ItsHTMLElement is also null on my test, indicating that the createDocument has failed to append.
I thought I previously indicated that I couldn't merge together a domdocument into another domdocument...

Sorry, I missed an r in __construct. This should work:


class MyTemplate extends DOMDocument {
private $itsHtmlElement;

public function __construct($version = "1.0", $encoding = "utf-8") {
parent::__construct($version, $encoding);

$this->appendChild(DOMImplementation::createDocumentType("html", "-//W3C//DTD XHTML 1.0 Transitional//EN", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"));
$this->itsHtmlElement = $this->appendChild($this->createElement("html"));
$this->itsHtmlElement->setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
}
}

To get rid of '<?xml version="1.0" encoding="utf-8"?>' you could replace saveXML with this:


public function saveXML() {
$output = array();

foreach ($this->childNodes as $node)
$output[] = parent::saveXML($node);

return implode($output);
}

Fou-Lu
01-05-2011, 03:58 PM
I missed the missing r myself!
I'll have to give this a shot when I get home, I realized as well that I tried to merge the documents, but I haven't tried to append a domdocument into an domdocument.

I'm still not convinced just by looking at it that the proper doctype and implementation are passed into the root base of the class (we're not trying to strip out the xml tags, just add the proper doctype's to it). I have a feeling as soon as you call that parent::__construct, that it will cause the domdocument to populate the domimplementation within itself which is not cannot be overridden; however, that saveXML idea may work for what is desired as well. If the domimplementation cannot be overridden, than we should be able to simulate it by overriding the saveXML() and pushing the proper doctype onto the string. It would be a hacky approach, but I'd expect that would probably work.


Alright, so I have no idea why the appending allows you to override the DOMImplementation and DOCType, but this appears to work correctly.
One thing to note is that this does require you to use the documentElement or a known accessible root element for the DOMDocument as appending directly onto the template class will not properly wrap it up. IMO I wouldn't go without accessing documentElement anyway unless its a completely empty tree, so that is kinda a moot point.
Good job Mechtank!



EZ Archive Ads Plugin for vBulletin Copyright 2006 Computer Help Forum