扩展DOMNode,然后扩展DOMDocument,继承扩展的DOMNode

时间:2013-06-26 10:27:04

标签: php oop domdocument extend

哦,是的,标题很棒,我知道,我知道。对不起,不是母语,而且标题可能不能反映问题,但我会尽我所能。

我正在开发一个相对较大的DOM库扩展。最近,我正在考虑重写它以扩展标准库。尽管如此,由于遗产,我偶然发现了问题。

DOM的核心元素之一是DOMNode,所以我开始扩展那个:

<?php namespace DOMWorks;

use \DOMNode;

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

然后,我继续尝试使用DOMDocument,默认情况下,扩展DOMNode。

<?php namespace DOMWorks;

use \DOMDocument;

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

但是,这会丢失以前扩展的Node。

我如何扩展DOMNode,从DOMDocument扩展DOMNode和创建我自己的Document扩展?

3 个答案:

答案 0 :(得分:4)

有趣的多重继承很有趣。

经过多一些游戏后,我得出结论,单凭继承本身就无法以有意义的方式做到这一点。唯一可行的方法是通过装饰(即将本机类包装在您自己的类中而不进行扩展,并使用魔术方法来访问内部对象的属性)。显然这不是理想的,它很快就会变得混乱,你会失去真正的继承提供的明显好处,比如与instanceofis_a()等人玩得很好。

问题的根源是因为registerNodeClass()(参见下面的原始答案)是一个实例方法,它只能在DOMDocument的实例上调用 - 并且当实例具有时已创建,继承已经确定 - 在创建对象后,您无法更改对象的基类。

有(据我所知)无法静态设置PHP以始终在new Document点基于扩展基类创建实例,这时需要按顺序完成用于继承自定义节点的自定义文档。你可能不会想要这个,因为它是隐藏的全局状态,它可能会影响到库之外的消费应用程序。

我还在玩一个稍微丑陋的想法,涉及促进装饰者模式的特征,但我不确定我的想法是否真的可行,或者我是否应该只是坐在角落里,来回摇摆,悄悄地喃喃自语。

如果我想出更有用/更具体的东西或者通常不像脑屁那样,我可能会在某些时候对此进行扩展。下面的第一句话让我想哭: - (


原始答案:

幸运的是,在与常规的愉快休息中,PHP开发人员预见到了这种可能性并以一种(合理的)明智的方式满足您的需求:

DOMDocument::registerNodeClass()

您可以在扩展文档类的构造函数中将其构建到lib中(因此消费者不必这样做):

<?php

namespace DOMWorks;

use \DOMDocument;

class Document extends DOMDocument
{
    public function __construct($version = null, $encoding = null)
    {
        $this->registerNodeClass('DOMNode', __NAMESPACE__ . '\Node');
    }

    // methods...
}

答案 1 :(得分:2)

  

我如何扩展DOMNode,从DOMDocument扩展DOMNode和创建我自己的Document扩展?

在PHP中,我们没有多重继承。这意味着,你想要做的事情在PHP中是不可能的。原因是DOMDocument从DOMNode扩展(如DOMElement,DOMAttr,DOMText等),所以这个继承路径已经完成了( DaveRandom解释这个更冗长,可能更好措辞in his answer,如果你想知道,我意思是相同的)。

PHP 5.4使情况变得更好,因为您可以将所有子类型之间共享的代码(例如,您将放入您的DOMNode 中的代码)放入{{3 }}

您创建的每个子类型都可以使用这些特征(您很快就会看到一个示例)。

如果您还希望将它们设置为您的DOMNode 类型,您还可以定义一个空的接口,然后使用所有子类型实现该接口。

以下是来自示例性刮刀库的该技术的示例:

class ScraperDoc extends DOMDocument implements ScraperNodeType
{
    use ScraperNodeTrait;

    ...

如图所示,它实现了一个接口(ScraperNodeType)和一个特征(ScraperNodeTrait)。

所以有界面:

/**
 * Empty Interface for typing reasons (instanceof operator and traits
 * work not well, this interface cures that limitation a bit)
 */
interface ScraperNodeType
{
}

有特质;如果你是traits的新手,这里有一些示例代码,一个单方法特性,为实现特征的所有节点提供字符串上下文(只是为了给出一个想法,它缩短了原始库):

Trait ScraperNodeTrait
{
    public function __toString()
    {
        /* @var $this DOMNode  */
        return trim($this->textContent);
    }
}

这不像Ruby中的traits / mixins那样流畅,但是在PHP中尽可能接近(到目前为止使用非动态代码)。

这仍然无法解决创建自己的层次结构中的所有问题,但我认为您应该了解这种技术(特征+空接口)。

这是一个继承图,它显示了顶部的DOMNode,然后是PHP DOM扩展中的扩展类型,然后是这些的用户范围扩展以及它们与特征(左下方)和接口的关系(正好在下面)。

右边部分的cluser与迭代器和simplexml有关,这不是这个答案的一部分,因此不是直接感兴趣的。虽然它显示了例如你不能在PHP中重载DOMNodeList。使用SimpleXML可能会有一些疯狂的动作,这就是为什么该库也将它作为整个画面的一部分。

然后在左下角找到对Net_URL2的引用,这是迄今为止最好的PHP URL类恕我直言。库从它扩展到拥有它自己的URL类型,外部lib至少分层到代码库中。

enter image description here

  

基于DOMDocument继承图(traits

的示例刮取库

我希望这会有所帮助,也会给你一些灵感。上次我回答有关扩展DOMDocument的问题是关于DOMDocument是模型的现象,而不是你的模型:

答案 2 :(得分:0)

如何使用具有特征的“代理模式”? 这个想法是在“特征”中声明常用方法,以便扩展和注册的Node类具有访问权限,即使不是扩展DOMNode的派生/子代...

这是PHP.net上发布的一小段代码:

    namespace my;

    trait tNode
    {    // We need the magic method __get in order to add properties such as DOMNode->parentElement
        public function __get($name)
        {    if(property_exists($this, $name)){return $this->$name;}
            if(method_exists($this, $name)){return $this->$name();}
            throw new \ErrorException('my\\Node property \''.(string) $name.'\' not found…', 42, E_USER_WARNING);
        }

        // parentElement property definition
        private function parentElement()
        {    if($this->parentNode === null){return null;}
            if($this->parentNode->nodeType === XML_ELEMENT_NODE){return $this->parentNode;}
            return $this->parentNode->parentElement();
        }

        // JavaScript equivalent
        public function isEqualNode(\DOMNode $node){return $this->isSameNode($node);}
        public function compareDocumentPosition(\DOMNode $otherNode)
        {    if($this->ownerDocument !== $otherNode->ownerDocument){return DOCUMENT_POSITION_DISCONNECTED;}
            $c = strcmp($this->getNodePath(), $otherNode->getNodePath());
            if($c === 0){return 0;}
            else if($c < 0){return DOCUMENT_POSITION_FOLLOWING | ($c < -1 ? DOCUMENT_POSITION_CONTAINED_BY : 0);}
            return DOCUMENT_POSITION_PRECEDING | ($c > 1 ? DOCUMENT_POSITION_CONTAINS : 0);
        }
        public function contains(\DOMNode $otherNode){return ($this->compareDocumentPosition($otherNode) >= DOCUMENT_POSITION_CONTAINED_BY);}
    }

    class Document extends \DomDocument
    {    public function __construct($version=null, $encoding=null)
        {    parent::__construct($version, $encoding);
            $this->registerNodeClass('DOMNode', 'my\Node');
            $this->registerNodeClass('DOMElement', 'my\Element');
            $this->registerNodeClass('DOMDocument', 'my\Document');
            /* [...] */
        }
    }

    class Element extends \DOMElement
    {    use tNode;
        /* [...] */
    }

    class Node extends \DOMNode
    {    use tNode;
        /* [...] */
    }