Расширение DOMNode, после чего расширение DOMDocument, который наследует расширенный DOMNode

О да, название потрясающее, я знаю, я знаю. Извините, английский не родной, и, возможно, название не отражает проблему, но я постараюсь.

Я работаю над относительно большим расширением библиотеки 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 и из него создать собственное расширение документа?


person tomsseisums    schedule 26.06.2013    source источник


Ответы (3)


Множественное наследование — это весело.

Немного поигравшись, я пришел к выводу, что просто невозможно сделать это осмысленно, используя только наследование. Единственный практический способ сделать это - декорировать (т.е. обернуть собственные классы в ваши собственные классы без расширения и использовать магические методы для доступа к свойствам внутреннего объекта). Очевидно, что это не идеально, это очень быстро начинает запутываться, и вы теряете очевидные преимущества, которые дает истинное наследование, такие как хорошая игра с instanceof, is_a() и др.

Корень проблемы в том, что, поскольку registerNodeClass() (см. первоначальный ответ ниже) является методом экземпляра, его можно вызвать только для экземпляра DOMDocument, и к моменту создания экземпляра наследование уже определено - вы не может изменить базовый класс объекта после его создания.

Не существует (насколько я могу судить) способа статически настроить 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...
}
person DaveRandom    schedule 26.06.2013
comment
Я обнаружил, что registerNodeClass работает только при расширении DOMElement. Тем не менее, применимо то, что вы сказали - документ не наследует расширенный. Таким образом, расширенный класс работает только для элементов, созданных после создания экземпляра Document. Грустно, грустно, грустно... - person tomsseisums; 26.06.2013
comment
Чтобы использовать nDOMDocument, а также расширенный DOMElement или DOMNode, вы можете рассмотреть возможность использования признаков для создания ваших пользовательских методов и использования их в соответствии с его наследием класса Node... php.net/manual/en/class.domimplementation.php - person llange; 08.11.2014

Как мне расширить DOMNode, из этого расширения DOMDocument и из него создать собственное расширение документа?

В PHP у нас нет множественного наследования. Это означает, что то, что вы пытаетесь сделать, невозможно в PHP. Причина в том, что DOMDocument расширяется от DOMNode (как и DOMElement, DOMAttr, DOMText и т. д.), поэтому этот путь наследования уже завершен (DaveRandom объяснил это более подробно и, вероятно, лучше сформулировано в своем ответе, если вам интересно, я имею в виду то же самое).

В PHP 5.4 ситуация немного улучшилась, так как вы можете поместить код, общий для всех ваших подтипов (например, код, который вы должны были поместить в ваш DOMNode) в черты.

Затем каждый создаваемый вами подтип может использовать эти черты (вы скоро увидите пример).

Если вы дополнительно хотите сделать их даже типами вашего 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
{
}

И есть черта; Если вы новичок в трейтах, вот пример кода, трейта с одним методом, который предоставляет строковый контекст для всех узлов, реализующих трейт (просто для того, чтобы дать представление, он сокращен из оригинальной библиотеки):

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

Это не так плавно, как с трейтами/миксинами в Ruby, но настолько близко, насколько это возможно (пока с нединамическим кодом) в PHP.

Это еще не решает всех проблем в создании собственной иерархии, но я думаю, вы должны знать об этой технике (трейты + пустой интерфейс).

Вот диаграмма наследования, которая показывает DOMNode сверху, затем расширенные типы из расширения PHP DOM, затем их расширения пользователя и их отношения к трейту (слева внизу) и интерфейсу (справа внизу).

Cluser в правой части связан с итераторами и simplexml, которые не являются частью этого ответа, поэтому не представляют прямого интереса. Хотя это показывает, например, что вы не можете перегружать DOMNodeList в PHP. С SimpleXML возможны некоторые безумные ходы, поэтому в этой библиотеке он также является частью общей картины.

Затем в левом нижнем углу вы найдете ссылку на Net_URL2, который на данный момент является лучшим классом PHP URL. Библиотека расширяется от него, чтобы иметь собственный тип URL, а внешняя библиотека, по крайней мере, наложена на кодовую базу.

введите здесь описание изображения

Пример библиотеки парсинга на основе диаграммы наследования DOMDocument (полный размер)

Я надеюсь, что это поможет, а также даст некоторое вдохновение. В прошлый раз я отвечал на вопрос о расширении DOMDocument о том, что DOMDocument является моделью, а не вашей моделью:

person hakre    schedule 26.06.2013

Как насчет использования «прокси-шаблона» с чертами? Идея состоит в том, чтобы объявить общие методы внутри «черты», чтобы расширенные и зарегистрированные классы узлов имели доступ, даже если они не являются производными/дочерними элементами расширенного 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;
        /* [...] */
    }
person llange    schedule 07.11.2014
comment
Функция compareDocumentPosition здесь некорректна — она должна сравнивать порядок узлов, но на самом деле сравнивает строку XPath по алфавиту. - person Greg; 02.03.2021