[PSR-5] Traits and interfaces

384 views
Skip to first unread message

Larry Garfield

unread,
Apr 10, 2014, 4:01:14 PM4/10/14
to php...@googlegroups.com
Mike asked me to open a discussion on this a while back so I'm finally
doing so while I wait for a batch process to run... :-)

In PHP 5.4, Traits cannot declare that they implement an interface.
They can, however, implement methods that would satisfy an interface (in
whole or in part) if used in a class that implemented an interface. See
http://www.garfieldtech.com/blog/beyond-abstract for some code snippets
of how that works in PSR-3, for example.

There should be some standard way to denote on a trait "this trait
implements / mostly implements interface X" included in PSR-5. How best
to do that, I'm not sure.

A few maybe-not-good options off the top of my head include:

1)

/**
* @implements FooInterface
*/
trait FooTrait {

}

Pros: Very clear what it intends to do.
Cons: Technically inaccurate since Traits don't implement an interface
except by coincidence; doesn't work for partial implementations.

2)

/**
* @see FooInterface
*/
trait FooTrait {

}

Pros: Nice and generic; doesn't imply too much.
Cons: Could mean anything. "I mostly implement" is not always what @see
means.

3)

/**
* @satisfies FooInterface
* @partially-satisfies FooInterface
*/
trait FooTrait {

}

Pros: Not confusing with anything else (I think).
Cons: Invents a new tag, but maybe that's OK; @partially-satisfies is
just super awkward.

4)

[INSERT YOUR SUGGESTION HERE]


Discuss.

--Larry Garfield

Chuck Burgess

unread,
Apr 10, 2014, 4:14:10 PM4/10/14
to php...@googlegroups.com
I haven't needed to solve this use case in my travels yet, but my first instinct would be:

/**
 * @uses FooInterface
 */
trait FooTrait {

}

This can perhaps make a more visible tie between the interface and the trait that with @see, in that the @uses tag here should result in a @used-by tag being listed in the interface's doc. 


--Larry Garfield

--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+unsubscribe@googlegroups.com.
To post to this group, send email to php...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/php-fig/5346F88A.4020008%40garfieldtech.com.
For more options, visit https://groups.google.com/d/optout.

Alexandre Gaigalas

unread,
Apr 11, 2014, 9:14:26 AM4/11/14
to php...@googlegroups.com
Disclaimer: I'm working on a project using traits and Scalable Component Composition. This version of PSR-5 would be really bad for users of this pattern, including me.

My main complaint is that the examples are far from real. We should use real use cases, specially since traits are still uncommon among developers.

The trait mechanism provides the unique feature of decoupling an implementation. Interfaces have a type, classes have a type, even abstract ones are typed.

Interfaces have a type and miss implementation.
Traits have an implementation and miss type.

I like to think of traits as cations and interfaces as anions (as in chemistry). You can combine them into a class and then see if they're stable:

class MyApplicationFactory
implements PDOSetter, PDOGetter
{
    use PDODependencyTrait {
        getPDO as protected getRawPDO;
        setPDO as public    setPDO;
    }
    public function getPDO()
    {
        $pdo = $this->getRawPDO();
        $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }
}

Note that the PDODependencyTrait provides two methods: getPDO() and setPDO(). Both of them are compatible with the PDOSetter and PDOGetter interfaces respectively.

The most important part is that the developer chooses how the implementation works. Traits are about choosing implementations. In the sample above I've chosen to wrap the getPDO() call and set an extra attribute on the implementation provided by the Trait.

Example:

class MyTestingFactory
implements PDOGetter
{
    use MockTrait;

    public function getPDO()
    {
        $this->mockClass("PDO");
    }
}

This time I decided not to implement the PDOSetter interface and use another trait to implement the PDOGetter interface.

These samples are illustrative ideas for scalable components[1], a pattern-based approach for composing traits that was popularized by Scala (which is also the source of inspiration behind PHP traits).

What should be standardized is where PHP is different from other languages:

1 - Traits can declare hard properties that classes cannot override. Traits provided by libraries must not have properties. Traits declared by users should not have properties.

2 - Traits can declare abstract members. These abstract members can be provided by other traits or written implementations by the user. Names can be aliased, so only formatting standards (not semantic) apply.

3 - PHP Traits can have distinct static members. We should research how this works, it seems something that other languages do not provide.

4 - PHP has no standard way of ommiting a method from a trait, but it can change its visibility. Libraries should work with that in mind, and traits should be small and modular.

I'm willing to provide more use cases if needed. I suggest everyone to take a look a the Scalable Components paper as well.
Alexandre Gaigalas

Larry Garfield

unread,
Apr 11, 2014, 3:03:19 PM4/11/14
to php...@googlegroups.com
On 4/11/14, 8:14 AM, Alexandre Gaigalas wrote:
> Disclaimer: I'm working on a project using traits and Scalable Component
> Composition. This version of PSR-5 would be really bad for users of this
> pattern, including me.
>
> My main complaint is that the examples are far from real. We should use
> real use cases, specially since traits are still uncommon among developers.

See PSR-3 for a for-reals example. We're building a few in Drupal 8, as
well, most of which will probably be utility proxies for super-common
services.

*snip*

> What should be standardized is where PHP is different from other languages:
>
> 1 - Traits can declare hard properties that classes cannot override.
> Traits provided by libraries must not have properties. Traits declared
> by users should not have properties.


This thread isn't about standardized use of traits. It's about
standardizing documentation for when a a trait would fulfill an
interface if it were used. It's a much narrower scope.

--Larry Garfield

Alexandre Gaigalas

unread,
Apr 11, 2014, 4:19:51 PM4/11/14
to php...@googlegroups.com
Missed the PSR-3 mention, sorry for that.

On Fri, Apr 11, 2014 at 4:03 PM, Larry Garfield <la...@garfieldtech.com> wrote:

This thread isn't about standardized use of traits.  It's about standardizing documentation for when a a trait would fulfill an interface if it were used.  It's a much narrower scope.

That's a very narrow scope. It does not document the main aspect of traits: aliasing their names, manually resolving the inheritance problems. This mechanism is its core concept, and is only manifested when a class actually uses it.

It's like documenting which traits/classes can be implementations on an interface:

 /**

 * @implementedBy FooTrait
 * @implementedBy BarClass
 */
interface FooInterface {

}

We simply don't need that tag. The very concept of interfaces already tells us that could be infinite possible implementations. The same case applies for traits, there could be infinite interfaces that accept that implementation.

So, the tag can never be normative (does not match traits as a concept), but another existing tag can be used for the same principles:

/**

 * @see FooTrait
 * @see BarClass
 */
interface FooInterface {

}

/**
 * @see FooInterface
 * @see BarClass
 */
trait FooTrait {

}

For improving documentation of traits themselves, I don't believe any extra tag is needed. This use case (merely informative references from Traits to other objects) is covered by @see, which applies already to all structural objects including traits.

--
Alexandre

Christopher Hoult

unread,
Apr 11, 2014, 5:05:34 PM4/11/14
to php...@googlegroups.com
It's probably worth noting the syntax for the @see tag as proposed in PSR-5:

https://github.com/phpDocumentor/fig-standards/blob/master/proposed/phpdoc.md#816-see
    @see [URI | "FQSEN"] [<:type:>] [<description>]

The :type: element would provide ample opportunity to finesse what might well be too generic for the elements that Larry puts forward; it might be that something like the following would fit:

/**
* @see FooInterface :satisfies:
*/
trait FooTrait
{

}

However, this is a little clunky, not particularly elegant and doesn't read particularly well.

Which is where (shameless self-plug) my suggestion of a month ago - introduce the concept of a tag-class element to a standard tag construct - comes in:

https://github.com/phpDocumentor/fig-standards/issues/43
It would therefore read a bit more fluently:

/**
* @see:satisfies FooInterface
*/
trait FooTrait
{

}


Anyway, my two cents...

Chris


--
You received this message because you are subscribed to the Google Groups "PHP Framework Interoperability Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to php-fig+u...@googlegroups.com.

To post to this group, send email to php...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Alexandre Gaigalas

unread,
Apr 11, 2014, 7:53:53 PM4/11/14
to php...@googlegroups.com
Took a minute for me to realize that :type: is not a PHP type. The name conflicts with another definitions of type[2], perhaps :relation: would fit better.

I also believe that `@see:relation Something` is better than `@see Something :relation:`.

I don't like having two @tag extension mechanisms though. There are already `@vendor-prefixes` that act as namespaces for tags.

Perhaps there should be only one extension mechanism that can solve both problems. Like a @context in JSON-LD or a profile="" in HTML, but introducing schemas is a big change.

Mike van Riel

unread,
Apr 12, 2014, 2:25:27 AM4/12/14
to php...@googlegroups.com
I personally see opportunities for specific tags to have specializations (or subtypes) using the colon notation proposed by Chistopher.

I do not consider specializations to be a 'namespace' but a free extension of a predefined tag so that we can standardize a tag but allow the user to have freedom in selecting a specialization for that tag.

Examples:

- `@see:superceded-by OtherClass::OtherMethod()` to indicate the new method to use when a method became deprecated and is marked `@deprecated`
- `@see:alias-for OtherMethod()` to indicate that this element is an alias for another (we had an request to implement aliassing in the issue tracker)
- `@see:implements` to indicate that an element, at least indirectly, implements an interface (or part thereof)

Using this notation we minimize the number of new tags that users would like to introduce as most are variations of @see and @uses.


Larry Garfield

unread,
Apr 13, 2014, 2:01:05 PM4/13/14
to php...@googlegroups.com
I like the idea of essentially "subclassing" a tag this way.  My main question would be if it results in too complex a syntax for IDEs to handle efficiently.  (That may not be relevant in this case, or perhaps it may if they want to have "did you mean to use this new method instead?" functionality.)

--Larry Garfield

Mike van Riel

unread,
Apr 13, 2014, 2:40:01 PM4/13/14
to php...@googlegroups.com
The basic premise is that part of the official format for tags is that they MAY be followed by a colon and then a specialization or subclass. Whether this specialization has any meaning, and what that may be is up to the specific tag.

For example, the `@see` tag defines the type of relation between this item and the target using the specialization.

At this point in time the `@see` tag is the only one that is considered eligible for a specialization; other tags are still to be researched. The ABNF of the PHPDoc Standard for tags will be updated (technically speaking it is by definition backwards compatible and imo it being formally defineable should lower the barrier for implementors)

Christopher Hoult

unread,
Apr 13, 2014, 3:20:24 PM4/13/14
to php...@googlegroups.com

My general thought was that the important, standardized part is the tag; any parser would only be forced to care about the tag itself and discard the remainder unless there's support for a separately defined subclass.

My original motivation behind this suggestion was how best to link from a class to its test(s) - that kind of relational link is well catered for with the well-defined and widespread @see tag but lacks the ability to define the relation.

This mechanism might also be useful in heading off future expansion of the standard in that a large number of use cases would slot nicely into the scheme.

Chris

Chuck Reeves

unread,
Apr 15, 2014, 4:56:07 PM4/15/14
to php...@googlegroups.com
option 3 sounds like the better choice IMO.  I would like to just have @satisfies instead of having @partially-satisfies.  I would like to see that multiple tags are required for each method that it satisfies.

Here would be an example 

interface FooInterface 
{
    public function setFoo($value);

    public function getFoo();

    public function hasFoo();
}

/**
 * @satisifies FooInterface::[hasFoo,getFoo]
trait FooTrait 
{
    protected $foo;

    public function getFoo()
    {
        return $this->foo;
    }

    public function hasFoo()
    {
        return !empty($this->foo);

Chuck Reeves

unread,
Apr 16, 2014, 9:02:20 AM4/16/14
to php...@googlegroups.com
Sorry about that bad post (I was writing it up on my phone).  Here is a better version of what I wanted to say 

Option 3 sounds like the better choice IMO.  However I would like to just have @satisfies instead of having @partially-satisfies.  We can use the array notation to describe which methods of the interface the trait satisfies.  Here is a an example of what I'm thinking:

interface FooInterface 
{
    public function setFoo($value);

    public function getFoo();

    public function hasFoo();
}

/**
 * This trait only satisfies hasFoo and getFoo
 *
 * @satisifies FooInterface::[hasFoo,getFoo]
 */
trait FooTrait 
{
    protected $foo;

    public function getFoo()
    {
        return $this->foo;
    }

    public function hasFoo()
    {
        return !empty($this->foo);
    }
}

/**
 * This trait satisfies the whole interface
 *
 * @satisifies FooInterface
 */
trait BarTrait 
{
    protected $foo;

    public function setFoo($value)
    {
        $this->foo = $value;
Reply all
Reply to author
Forward
0 new messages