1

哦,是的,标题很棒,我知道,我知道。抱歉,不是英语母语,而且,标题可能没有反映问题,但我会尽力而为。

我正在对 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.js 的踪迹。

我将如何扩展 DOMNode,从该扩展 DOMDocument 并从中创建我自己的文档扩展?

4

3 回答 3

4

有趣的多重继承很有趣。

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

问题的根源是因为registerNodeClass()(见下面的原始答案)是一个实例方法,它只能在 a 的实例上调用DOMDocument- 并且在创建实例时,已经确定了继承 - 你可以' t 在创建对象后更改其基类。

(据我所知)没有办法静态地将 PHP 设置为始终基于您的扩展基类创建实例new Document,这是为了让您的自定义文档继承您的自定义节点。无论如何,您可能都不希望这样做,因为它是隐藏的全局状态,可能会影响超出您的库范围的消费应用程序。

我仍然在玩一个有点丑陋的想法,其中涉及促进装饰器模式的特性,但我不确定我的想法是否真的可能,或者我是否应该只是坐在角落里,向后摇摆走过去,悄悄地自言自语。

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


原答案:

幸运的是,在一次愉快的打破常规的过程中,PHP 开发人员预见到了这种可能性,并以一种(合理的)明智的方式为您提供了便利:

DOMDocument::registerNodeClass()

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

<?php

namespace DOMWorks;

use \DOMDocument;

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

    // methods...
}
于 2013-06-26T10:33:51.763 回答
2

我将如何扩展 DOMNode,从该扩展 DOMDocument 并从中创建我自己的文档扩展?

在 PHP 中,我们没有多重继承。这意味着,您尝试做的事情在 PHP 中是不可能的。原因是 DOMDocument 是从 DOMNode 扩展而来的(就像 DOMElement、DOMAttr、DOMText 等一样),所以这个继承路径已经完成(DaveRandom在他的回答中解释得更冗长,可能措辞更好,如果你想知道,我的意思是一样的)。

PHP 5.4 使情况稍微好一点,因为您可以将在所有子类型之间共享的代码(例如,您将放入DOMNode的代码)放入traits中。

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

如果您还想让它们都成为您的 DOMNode类型,您还可以定义一个空接口,然后使用您的所有子类型实现该接口。

以下是示例爬虫库中该技术的示例:

class ScraperDoc extends DOMDocument implements ScraperNodeType
{
    use ScraperNodeTrait;

    ...

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

于是就有了界面:

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

并且有特点;如果您不熟悉 trait,这里有一些示例代码,一个为实现该 trait 的所有节点提供字符串上下文的单一方法 trait(只是为了给出一个想法,它是从原始库中缩短的):

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

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

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

这是一个继承图,显示了DOMNode顶部,然后是 PHP DOM 扩展的扩展类型,然后是这些扩展的用户级扩展以及它们与 trait(左下)和接口(右下)的关系。

右侧的 cluser 与迭代器和 simplexml 相关,这不是此答案的一部分,因此没有直接兴趣。尽管它例如表明您不能DOMNodeList在 PHP 中重载。SimpleXML 可能有一些疯狂的举动,这就是为什么该库也将它作为整个图片的一部分。

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

在此处输入图像描述

基于 DOMDocument 继承图的示例抓取库(全尺寸

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

于 2013-06-26T12:24:38.363 回答
-1

使用带有特征的“代理模式”怎么样?这个想法是在“特征”中声明通用方法,以便扩展和注册的节点类即使不是扩展 DOMNode 的派生/子类也可以访问......</p>

这是发布在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;
        /* [...] */
    }
于 2014-11-07T21:36:15.083 回答