1

そうそう、タイトルは素晴らしいです、私は知っています。申し訳ありませんが、ネイティブの英語ではありません。タイトルが問題を反映していない可能性がありますが、最善を尽くします。

私は DOM ライブラリの比較的大きな拡張に取り組んでいます。最近、標準ライブラリを拡張するように書き直そうと考えていました。しかし、私は継承のために問題に遭遇しました。

DOM のコア要素の 1 つは DOMNode であるため、これを拡張することから始めました。

<?php namespace DOMWorks;

use \DOMNode;

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

次に、デフォルトで DOMNode を拡張する DOMDocument の作業に取り掛かりました。

<?php namespace DOMWorks;

use \DOMDocument;

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

しかし、これは以前に拡張された Node.js を追跡できなくなります。

DOMDocument を拡張し、独自の Document 拡張機能を作成して、DOMNode を拡張するにはどうすればよいですか?

4

3 に答える 3

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...
}
于 2013-06-26T10:33:51.763 に答える
2

DOMDocument を拡張し、独自の Document 拡張機能を作成して、DOMNode を拡張するにはどうすればよいですか?

PHP では、多重継承はありません。つまり、あなたがやろうとしていることは PHP では不可能です。その理由は、DOMDocument が DOMNode (DOMElement、DOMAttr、DOMText などと同様) から拡張されているため、この継承パスは既に終了しているためです ( DaveRandom はこれをより長く説明し、おそらく彼の答えより適切に表現しました。

PHP 5.4 では、すべてのサブタイプ間で共有されるコード (たとえば、 DOMNodeに入れるコード) をtraitsに入れることができるため、状況が少し改善されました。

作成した各サブタイプは、これらの特性を使用できます(すぐに例を示します)。

さらにそれらをすべて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
{
}

そして、特徴があります。トレイトを初めて使用する場合は、コードの例を次に示します。これは、トレイトを実装するすべてのノードに文字列コンテキストを提供する 1 つのメソッドのトレイトです (アイデアを示すために、元のライブラリから短縮されています)。

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

これは、Ruby の traits/mixins ほど流動的ではありませんが、PHP で (これまでのところ非動的コードで) 可能な限り流動的です。

これはまだ、独自の階層を作成する際のすべての問題を解決するわけではありませんが、この手法 (特性 + 空のインターフェイス) について知っておく必要があると思います。

これは継承図で、DOMNode上に 、次に PHP DOM 拡張から拡張された型、次にこれらのユーザーランド拡張と、トレイト (左下) およびインターフェース (右下) との関係を示しています。

右側のクラスターは、この回答の一部ではないイテレーターと simplexml に関連しているため、直接の関心事ではありません。DOMNodeListたとえば、PHPでオーバーロードできないことを示していますが。SimpleXML で可能な非常識な動きがいくつかあります。これが、そのライブラリが全体像の一部としてそれを持っている理由です。

次に、左下に、これまでで最高の PHP URL クラスである Net_URL2 への参照があります。ライブラリはそれから拡張され、独自の URL タイプを持ち、外部ライブラリは少なくともコードベースに階層化されます。

ここに画像の説明を入力

DOMDocument 継承図に基づくスクレイピング ライブラリの例 (フルサイズ)

これが役に立ち、インスピレーションを与えてくれることを願っています。前回、DOMDocument の拡張に関する質問に答えたのは、DOMDocument はモデルですが、あなたのモデルではないという現象に関するものでした:

于 2013-06-26T12:24:38.363 に答える
-1

特性で「プロキシパターン」を使用するのはどうですか? 拡張 DOMNode の派生/子でなくても、拡張および登録された Node Classes がアクセスできるようにするために、「trait」内で共通メソッドを宣言するというアイデア…</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 に答える