哦,是的,标题很棒,我知道,我知道。对不起,不是母语,而且标题可能不能反映问题,但我会尽我所能。
我正在开发一个相对较大的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扩展?
答案 0 :(得分:4)
有趣的多重继承很有趣。
经过多一些游戏后,我得出结论,单凭继承本身就无法以有意义的方式做到这一点。唯一可行的方法是通过装饰(即将本机类包装在您自己的类中而不进行扩展,并使用魔术方法来访问内部对象的属性)。显然这不是理想的,它很快就会变得混乱,你会失去真正的继承提供的明显好处,比如与instanceof
,is_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至少分层到代码库中。
基于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;
/* [...] */
}