0

我正在尝试扩展两个原生 PHP5 类(DOMDocument 和 DOMNode)来实现 2 个方法(selectNodes 和 selectSingleNode),以使 XPath 查询更容易。我认为这将是相当直截了当的,但我陷入了一个我认为是 OOP 初学者问题的问题。

class nDOMDocument extends DOMDocument {
    public function selectNodes($xpath){
    $oxpath = new DOMXPath($this);
    return $oxpath->query($xpath);
  }
  public function selectSingleNode($xpath){
    return $this->selectNodes($xpath)->item(0);
  }
}

然后我尝试扩展 DOMNode 以实现相同的方法,这样我就可以直接在节点上执行 XPath 查询:

class nDOMNode extends DOMNode {
    public function selectNodes($xpath){
    $oxpath = new DOMXPath($this->ownerDocument,$this);
    return $oxpath->query($xpath);
  }
  public function selectSingleNode($xpath){
    return $this->selectNodes($xpath)->item(0);
  }
}

现在,如果我执行以下代码(在任意 XMLDocument 上):

$xmlDoc = new nDOMDocument;
$xmlDoc->loadXML(...some XML...);
$node1 = $xmlDoc->selectSingleNode("//item[@id=2]");
$node2 = $node1->selectSingleNode("firstname");

第三行运行并返回一个 DOMNode 对象 $node1。但是,第四行不起作用,因为 selectSingleNode 方法属于 nDOMNode 类,而不是 DOMNode。所以我的问题是:有没有办法将返回的 DOMNode 对象“转换”为 nDOMNode 对象?我觉得我在这里遗漏了一些要点,非常感谢您的帮助。

(对不起,这是对我的问题Extending DOMDocument and DOMNode: problem with return object的重述)

4

5 回答 5

2

您可以通过DOMDocument::registerNodeClass() “告诉” DOMDocument在实例化某个节点类型的对象时应该使用哪个类而不是默认类。例如

$doc->registerNodeClass('DOMElement', 'Foo');

每次 dom 将“通常”实例化 aDOMElement它现在将创建一个Foo.

$doc = new nDOMDocument;
$doc->loadxml('<a><b><c>foo</c></b></a>');

$a = $doc->selectSingleNode('//a');
$c = $a->selectSingleNode('//c');

echo 'content: ', $c->textContent;

class nDOMElement extends DOMElement {
  public function selectNodes($xpath){
    $oxpath = new DOMXPath($this->ownerDocument);
    return $oxpath->query($xpath);
  }
  public function selectSingleNode($xpath){
    return $this->selectNodes($xpath)->item(0);
  }
}


class nDOMDocument extends DOMDocument {
  public function __construct($version=NULL, $encoding=NULL) {
    parent::__construct($version, $encoding);
    $this->registerNodeClass('DOMElement', 'nDOMElement');
  }

  public function selectNodes($xpath){
    $oxpath = new DOMXPath($this);
    return $oxpath->query($xpath);
  }
  public function selectSingleNode($xpath){
    return $this->selectNodes($xpath)->item(0);
  }
}

打印content: foo

但是从现在开始,您不能简单地设置registerNodeClass('DOMNode', 'MyDOMNode')并期望 DOMElement 继承自 MyDOMNode 而不是 DOMNode。即你必须注册你想要覆盖的所有节点类型。

于 2010-04-06T20:49:01.800 回答
1

您需要编写 nDOMDocument::selectSingleNode() 代码以返回 nDOMNode 对象。不可能发生神奇的转变。

我说你应该继续你的实验,因为你会在这个过程中学到一些很好的 OOP 课程(尽管很艰难)。没有错。

于 2010-04-06T15:10:24.627 回答
1

面向对象方法的后果之一是继承是单向的。也就是说,没有办法让父母知道孩子的方法。因此,虽然您可以遍历对象集合,但您只能可靠地调用父类的方法。PHP 确实有办法在运行时测试一个方法是否存在,这可能很有用,但它也会很快变得丑陋。

这意味着当您扩展一个内置类时,您需要完全扩展它,以便您的应用程序永远不会使用内置类的实例——只使用您的版本。在您的情况下,您需要扩展扩展类以覆盖所有返回父类类型的父类方法:

public myClass extends foo {
// override all methods that would normally return foo objects so that they
// return myClass objects.
}
于 2010-04-06T16:22:07.870 回答
1

我认为你最好在这里用包装对象装饰现有的库,而不是直接对它们进行子类化。正如您已经看到的,您不能轻松地将辅助方法附加到DOMDocument并让它返回您的子类对象。

具体来说,这里的问题是DOMXPath::item()不知道返回一个nDOMNode.

如果您尝试执行以下操作,它将失败(许多属性似乎是只读的):

class nDOMDocument extends DOMDocument {

    ...

    public function selectSingleNode($xPath) {
        $node = $this->selectNodes($xPath)->item(0);
        $myNode = new nDOMNode;
        $myNode->set_name($node->get_name()); // fail?
        $myNode->set_content($node->get_content());
        // set namespace
        // can you set the owner document? etc

        return $myNode;
    }

}

如果您包装对象,您可以使用__get()__call() 魔术方法快速公开现有的 DOMNode 接口,并包含您的附加功能并选择返回您自己的包装/自定义类以实现您的原始目标。

例子:

class nDOMNode {

    protected $_node;

    public function __construct(DOMNode $node) {
        $this->_node = $node;
    }

    // your methods

}

class nDOMDocument {

    protected $_doc;

    public function __construct(DOMDocument $doc) {
        $this->_doc = $doc;
    }

    ...

    public function selectNodes($xPath){
        $oxPath = new DOMXPath($this->_doc->ownerDocument, $this->_doc);
        return $oxPath->query($xPath);
    }

    public function selectSingleNode($xPath) {
        return new nDOMNode($this->selectNodes($xPath)->item(0));
    }

}

$doc = new nDOMDocument(new DOMDocument);
$doc->loadXML('<xml>');
$node = $doc->selectSingleNode("//item[@id=2]"); // returns nDOMNode

希望这可以帮助。

于 2010-04-06T16:29:53.190 回答
0

我认为您可能不得不在这里采取很长的路并安装指向向下、向上、向左和向右的指向属性,有点像 DOM 为 JavaScript 所做的那样。这些必须在创建结构时分配。

于 2010-04-06T15:14:24.693 回答