Search code examples
phpoopdomdocumentextend

Extending DOMNode, afterwards extending DOMDocument, that inherits extended DOMNode


Oh yes, the title is awesome, I know, I know. Sorry, not native English, and, it's possible that the title doesn't reflect the problem, but I'll do my best.

I'm working on a relatively large extension to DOM library. Recently, I was thinking of rewriting it to extend upon standard library. Though, I have stumbled into problems due to inheritance.

One of the core elements of DOM is DOMNode, so I started by extending that one:

<?php namespace DOMWorks;

use \DOMNode;

class Node extends DOMNode
{
    // methods...
}

Then, I went forward to try to work on DOMDocument, that, by default, extends DOMNode.

<?php namespace DOMWorks;

use \DOMDocument;

class Document extends DOMDocument
{
    // methods...
}

But, this loses track of previously extended Node.

How would I extend DOMNode, from that extend DOMDocument and from that create my own Document extension?


Solution

  • Fun multiple inheritance is fun.

    After a bit more playing around, I have concluded it simply isn't possible to do this in a meaningful way using inheritance alone. The only practical way to do this is by decoration (i.e. wrapping the native classes in your own classes without extending, and using magic methods to access the properties of the inner object). Obviously this is not ideal, it very quickly starts to get messy and you lose obvious benefits that true inheritance provides, things like playing nice with instanceof, is_a() et al.

    The root of the problem is that because registerNodeClass() (see original answer below) is an instance method, it can only be called on an instance of a DOMDocument - and by the time the instance has been created, the inheritance has already been determined - you can't alter the base class of an object after it has been created.

    There is (as far as I can tell) no way to statically set PHP to always create instances based on your extended base class at the point of new Document, which is when it would need to be done in order for your custom document to inherit your custom node. You probably wouldn't want this anyway, as it's hidden global state that could affect the consuming application beyond the reaches of your library.

    I'm still playing with a slightly ugly idea involving traits to facilitate the decorator pattern, but I'm not sure yet if what I'm thinking is actually possible or whether I should simply go but to sitting in the corner, rocking back and forth and quietly muttering to myself.

    I may expand on this at some point if I come up with anything more useful/concrete or generally less like a brain-fart. The first sentence below makes me want to cry :-(


    Original answer:

    Luckily, and in a pleasant break from the norm, the PHP devs foresaw this possibility and accommodated you in a (reasonably) sensible way:

    DOMDocument::registerNodeClass()

    You can build this into your lib (so the consumer doesn't have to do it) in the constructor for your extended document class:

    <?php
    
    namespace DOMWorks;
    
    use \DOMDocument;
    
    class Document extends DOMDocument
    {
        public function __construct($version = null, $encoding = null)
        {
            $this->registerNodeClass('DOMNode', __NAMESPACE__ . '\Node');
        }
    
        // methods...
    }