PhpGt/Dom

Declare interfaces for stronger types and composition

pactole opened this issue · 4 comments

Hi everyone,

Sorry in advance for my poor english, I hope I will be understandable :)

First of all, I want to thank you for this library !!!

I’ve struggled a bit with strong typing with PhpGt/Dom and have a suggestion :

« While you use trait, you may propose meaningful Interfaces to help people use strong typing and be more functional minded. »

Struggling with types

Here a quick implementation with current GtDom Traits :

class WhateverInMyProject
{
    public function doSomethingUsingParentNodeTrait(Document|DocumentFragment|Element $somethingUsingParentNodeTrait): void
    {
        if (0 === $somethingUsingParentNodeTrait->children->count()) {
            //do something
        } else {
            //do something else...
    }
}
  • We need to declare multiple types
  • The meaning of Document|DocumentFragment|Element is not obvious.
  • The real intention is to have « something » that is a ParentNode

Proposition

Combining Interface with Traits is great because it offers you composition and more meaningfull purpose.

A class using the ParentNode trait should implement a dedicated interface.

This interface may have a classic name like ParentNodeInterface or a more meaningfull name like IsAParentNode or ParentNode (the trait name should change then)

class Document ... implements ..., IsAParentNode
class DocumentFragment ... implements ..., IsAParentNode
class Element ... implements ..., IsAParentNode
class WhateverInMyProject
{
    public function doSomethingUsingParentNodeTrait(IsAParentNode $somethingUsingParentNodeTrait): void
    {
        if (0 === $somethingUsingParentNodeTrait->children->count()) {
            //do something
        } else {
            //do something else...
    }
}

And sometimes, you need to use more than one Trait (for exemple ParentNode and ChildNode)

You can then declare a new interface :

interface CanBeFullyManipulated extends ParentNode, ChildNode
class Element ... implements ..., CanBeFullyManipulated // IsAParentNode and IsAChildNode interfaces do not need to be declared here but can be used if needed.
class WhateverInMyProject
{
    public function doSomethingUsingParentNodeAndChildNodeTraits(CanBeFullyManipulated $somethingUsingParentNodeAndChildNodeTraits, string|Node...$replaceNodes): void
    {
        if (0 === $somethingUsingParentNodeAndChildNodeTraits->children->count()) {
            $somethingUsingParentNodeAndChildNodeTraits->replaceWith($replaceNodes);
        } else {
            //do something else...
    }
}

Hope it looks enjoyable,

Kind regards,

Jacques

g105b commented

Hi @pactole ,

Thank you for your message. I will give this some proper thought before getting back to you with more information or ideas.

I'm currently in the process of improving the library by introducing a Facade to the native DOMDocument. This is available in PR #266 on the facade branch. Please could you check this out, as it might answer some of your questions.

I'll get back to you with more detail after I've given your points some thought.

Cheers,
Greg.

Hi @g105b,

Thanks for your answer

The Facade is a real great improvement.
I could manage typing with \DOMNode|\DOMText ...

My points are a bit more on « enhancing Traits » and « be more meaningful ».

When you add a method to many objects with a Trait, you give them a new power.
This new Power may be the name of a new interface.

Then if a client needs to use an object for this new Power, he only needs to use the Interface.

Cheers,
Jacques

g105b commented

I agree with you completely @mediaparttech. One of the core concepts in this repository is to remain fully compliant with the W3C specification. I haven't yet introduced any interfaces that don't exist in the W3C spec - can you think of a way we can introduce ParentNode/ChildNode/etc as Interfaces and still remain compatible?

g105b commented

Hi all,

I've held a meeting discussing this with some developers who use this library. Feel free to join us - it's an open group of freelancers, not linked directly by PHP.Gt, but this is where we discussed it.

I've ran these ideas by current developers who use this library, and here are our shared thoughts:

  1. It's important to remain on-spec with the W3C current living standard (https://dom.spec.whatwg.org/) and wherever possible, we should not introduce any functionality that is not defined within this standard.
  2. The W3C DOM Standard specifies "ParentNode" as a "mixin" and not part of the inheritance model. PHP traits are the way to implement mixins within the PHP language.
  3. The example public function doSomethingUsingParentNodeTrait(Document|DocumentFragment|Element $somethingUsingParentNodeTrait): void may be verbose, but verbosity is required if this functionality is desired. The Document and Element types have significantly different functionality, and most of the time the developer would be better off passing Document::documentElement to the function, as this will strictly define what functionality is expected, and no type juggling will be required.

I agree that using Traits adds some strange behaviour, and I'd love to see more inheritance with the interfaces that you've suggested, but the reality is that the DOM Standard was written a long time ago and has a great legacy to adhere to. The main goal of this library is to provide accurate representation of this standard within PHP, so I will close this issue for now.

If you'd like to follow up with any further points, or disagree with my decision, please feel free to continue discussion on this issue and we can discuss further.

Thanks for your time,
Greg.