Welcome! Log In Create A New Profile

Advanced

[PHP-DEV] Traits with interfaces

Posted by David Rodrigues 
David Rodrigues
[PHP-DEV] Traits with interfaces
February 28, 2018 10:00PM
Why traits doesn't supports interfaces (via implements) like classes does?

It could be useful when trait implements part of abstract functions from
interface, then the class that uses this traits should implements the
another part.

It could turn it possible (pseudo-code):

interface VisibilityControlContract
- public function isVisible(): bool;

trait VisibilityControlTrait implements VisibilityControlContract
- public function isVisible(): bool { ... }

class UserModel (not need implements VisibilityControlContract directly)
- uses VisibilityControlTrait;

var_dump(new UserModel instanceof VisibilityControlContract); // true

The disvantages that I can see with that is that without an IDE I could not
identify easily if interface was implemented by some trait. But it too
happen when I do implements an abstract class that implements some
interface.

--
David Rodrigues
Marco Pivetta
Re: [PHP-DEV] Traits with interfaces
March 01, 2018 01:40AM
This cannot work for a number of reasons:

- a trait is not a type, and does not practically exist at runtime
- trait defined API can be imported with changed/aliased names, breaking
therefore the contact defined in the interface
- due to the previous point, inheriting a type from a trait becomes a
dangerous BC boundary, easily broken by consumers of the trait when
consumers alias or when the trait implementor adds a new interface
implementation

My general suggestions:
- don't use traits
- don't use traits
- also, don't use traits
- remember to not use traits
- traits: don't
- things you shouldn't use on Betelgeuse and other systems: traits

Besides jokes, inheriting signatures together with implementations
(inheritance, abstract types) is less and less endorsed in the PHP
ecosystem, as it just increases coupling by a huge lot. At least from my
own experience, things are finally moving towards more composition over
inheritance.

On 28 Feb 2018 21:58, "David Rodrigues" <[email protected]> wrote:

> Why traits doesn't supports interfaces (via implements) like classes does?
>
> It could be useful when trait implements part of abstract functions from
> interface, then the class that uses this traits should implements the
> another part.
>
> It could turn it possible (pseudo-code):
>
> interface VisibilityControlContract
> - public function isVisible(): bool;
>
> trait VisibilityControlTrait implements VisibilityControlContract
> - public function isVisible(): bool { ... }
>
> class UserModel (not need implements VisibilityControlContract directly)
> - uses VisibilityControlTrait;
>
> var_dump(new UserModel instanceof VisibilityControlContract); // true
>
> The disvantages that I can see with that is that without an IDE I could not
> identify easily if interface was implemented by some trait. But it too
> happen when I do implements an abstract class that implements some
> interface.
>
> --
> David Rodrigues
>
Andreas Hennings
Re: [PHP-DEV] Traits with interfaces
March 01, 2018 02:50PM
> don't use traits

Yes, in general, yes.
Composition is great, and I use it most of the time.
I still do have some cases where I think that base classes (with
private properties) are justified, and then some cases where either
traits or multiple inheritance would be justified.
We could agree to set this debate aside, and focus on the original
proposal, assuming it is for those people who have a good reason to
use traits.
But I have a feeling that this part of the discussion ("traits are
bad") isn't over. I will get back to it further below.


> due to the previous point, inheriting a type from a trait becomes a dangerous BC boundary, easily broken by consumers of the trait when consumers alias or when the trait implementor adds a new interface implementation

Yes.
On the other hand, many traits out there really try to comply with an
existing interface, and are meant as a replacement for additional base
classes.
The renaming is technically possible, but how often does it occur in reality?

So what about this modified proposal:
- A trait can "promise" to comply with an interface. We could use the
"implements" keyword for this, but maybe we should rather use
something else.
- Using the trait in a class does NOT automatically add that interface
to the class.

I personally don't have a strong desire for this functionality. But at
least this would circumvent the problems you pointed out.


In my personal experience, in all the cases where I did use traits, I
would have rather used multiple inheritance.
Or let's say: In many cases where I did use traits, or base classes, I
did later find a better solution with composition.

A good base class, for me, has
- private properties
- abstract protected methods
- possibly final public methods

In which cases would I use (single) base classes instead of composition?
- If composition would require awkward one-off interfaces.
- If the to-be-implemented methods have parameter constraints that
cannot be expressed in the signature.

E.g. a base class could implement a public method, do some sanity
checks and preparation on the parameters, then call the abstract
protected method with the processed parameters.

In which cases would I use multiple base classes instead of
composition or single base classes?
- If I want to provide an object with a rich verbose interface to a
consumer, possibly extending multiple smaller interfaces.
(- If I am implementing someone else's interface, which happens to
have more methods than I want to fit in one class.)

Base classes allow to implement partial functionality in isolated
(encapsulated) pockets of an object.
Often each of these pockets is nothing more than a decorator of the
partial interface, and the actual functionality happens in the
injected object.
I would prefer to implement those decorator pockets separately and
then extend them one by one. But this is not possible, because we
don't have multiple inheritance in PHP.

Traits are quite useless for this purpose. They encapsulate nothing.
Therefore I don't like them.

Sometimes if I started with the assumption that I want to provide a
rich one-object interface at least in some places in the architecture,
I later regret it, and split it up again.
But I do think that some cases are justified.






On 1 March 2018 at 01:34, Marco Pivetta <[email protected]> wrote:
> This cannot work for a number of reasons:
>
> - a trait is not a type, and does not practically exist at runtime
> - trait defined API can be imported with changed/aliased names, breaking
> therefore the contact defined in the interface
> - due to the previous point, inheriting a type from a trait becomes a
> dangerous BC boundary, easily broken by consumers of the trait when
> consumers alias or when the trait implementor adds a new interface
> implementation
>
> My general suggestions:
> - don't use traits
> - don't use traits
> - also, don't use traits
> - remember to not use traits
> - traits: don't
> - things you shouldn't use on Betelgeuse and other systems: traits
>
> Besides jokes, inheriting signatures together with implementations
> (inheritance, abstract types) is less and less endorsed in the PHP
> ecosystem, as it just increases coupling by a huge lot. At least from my
> own experience, things are finally moving towards more composition over
> inheritance.
>
> On 28 Feb 2018 21:58, "David Rodrigues" <[email protected]> wrote:
>
>> Why traits doesn't supports interfaces (via implements) like classes does?
>>
>> It could be useful when trait implements part of abstract functions from
>> interface, then the class that uses this traits should implements the
>> another part.
>>
>> It could turn it possible (pseudo-code):
>>
>> interface VisibilityControlContract
>> - public function isVisible(): bool;
>>
>> trait VisibilityControlTrait implements VisibilityControlContract
>> - public function isVisible(): bool { ... }
>>
>> class UserModel (not need implements VisibilityControlContract directly)
>> - uses VisibilityControlTrait;
>>
>> var_dump(new UserModel instanceof VisibilityControlContract); // true
>>
>> The disvantages that I can see with that is that without an IDE I could not
>> identify easily if interface was implemented by some trait. But it too
>> happen when I do implements an abstract class that implements some
>> interface.
>>
>> --
>> David Rodrigues
>>

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Rowan Collins
Re: [PHP-DEV] Traits with interfaces
March 02, 2018 12:10AM
Hi all,

On 01/03/2018 00:34, Marco Pivetta wrote:
> - trait defined API can be imported with changed/aliased names, breaking
> therefore the contact defined in the interface

This isn't actually true, because trait method aliases create an *extra*
copy of the pasted method, rather than actually renaming it. See example
at the end of this e-mail for a demonstration.

I'm not sure why this is; the original traits RFC
[https://wiki.php.net/rfc/horizontalreuse#renaming] says that it is
because of "the dynamic nature of PHP", but the example it gives doesn't
actually show anything breaking.

What *can* break an interface's contract is changing the *visibility* of
the pasted method using "as protected" or "as private". This would need
to be captured somehow while composing the class, probably producing a
compile-time error, just as an explicit "implements" declaration would.

While looking for the above RFC, I came across this draft by Kevin
Gessner from 2 years ago for exactly the feature discussed here:
https://wiki.php.net/rfc/traits-with-interfaces It includes references
to potential uses of this feature, and equivalents in other languages.

This appears to be the discussion in the archives, to avoid us repeating
ourselves: https://marc.info/?t=145571923500003&r=1&w=2 and
https://marc.info/?t=145573610000001&r=1&w=2


Appendix: Example of a trait providing implementation for an interface,
even though the method was aliased:

# https://3v4l.org/DMAoY

interface Bobbable {
    public function bob();
}
trait Bobber {
    public function bob() {
        echo "Bobbity";
    }
}
class Bibble implements Bobbable {
    use Bobber { bob as bib; }
}
$b = new Bibble;
$b->bob();
$b->bib();


Regards,

--
Rowan Collins
[IMSoP]


--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Pedro Magalhães
Re: [PHP-DEV] Traits with interfaces
March 02, 2018 02:50AM
On Thu, Mar 1, 2018 at 11:04 PM, Rowan Collins <[email protected]>
wrote:
>
> What *can* break an interface's contract is changing the *visibility* of
> the pasted method using "as protected" or "as private". This would need to
> be captured somehow while composing the class, probably producing a
> compile-time error, just as an explicit "implements" declaration would.


To add to that, the receiving class can simply replace the trait method. So
picking up from the same example:
<?php

interface Bobbable {
public function bob();
}

trait Bobber {
public function bob() {
echo "Bobbity";
}
}

class Bibble {
use Bobber { bob as bib; }
public function bob($arg) {
return $arg;
}
}

$b = new Bibble;
$b->bob();
$b->bib();

Bibble is no longer Bobbable.

Currently, not even an abstract method on a trait enforces anything on the
receiving class. See: https://bugs.php.net/bug.php?id=75449

With that said, I have started some work on "Implicit Interfaces". It
allows a trait to implement an interface in the sense that as long as any
class actually implements a given interface, that class passes the type
check (and instance_of() and ReflectionClass::implementsInterface(),
is_a(), ...). Basically allowing duck typing. It has some similarities to
https://wiki.php.net/rfc/protocol_type_hinting, you can check the current
implementation on
https://github.com/pmmaga/php-src/compare/implicit-interfaces

Regards,
Pedro
Rowan Collins
Re: [PHP-DEV] Traits with interfaces
March 04, 2018 11:40PM
On 2 March 2018 01:38:37 GMT+00:00, "Pedro Magalhães" <[email protected]> wrote:
>On Thu, Mar 1, 2018 at 11:04 PM, Rowan Collins
><[email protected]>
>wrote:
>>
>> What *can* break an interface's contract is changing the *visibility*
>of
>> the pasted method using "as protected" or "as private". This would
>need to
>> be captured somehow while composing the class, probably producing a
>> compile-time error, just as an explicit "implements" declaration
>would.
>
>
>To add to that, the receiving class can simply replace the trait
>method.

True. This feels closer to normal interface / inheritance checks though: you've explicitly written a method that violates the contract not used a trait-specific syntax or feature to do so.


>Currently, not even an abstract method on a trait enforces anything on
>the
>receiving class. See: https://bugs.php.net/bug.php?id=75449

The way I see it, the Trait in this proposal isn't enforcing anything, it's just pasting in the "implements Foo" clause along with the extra methods. Whether the result is valid is up to the receiving class, and there's a completely separate requirement that the Trait itself matches the contract.


>With that said, I have started some work on "Implicit Interfaces".

I'm not sure I like the sound of this, but I'll leave that to its own thread.

Regards,

--
Rowan Collins
[IMSoP]

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php
Sorry, only registered users may post in this forum.

Click here to login