Adding Delegation to PHP

Recently Nikita Popov posted a pull request for PHP named “Add support for “decorator” pattern.” I started to comment on the PR but soon realized I was writing a full-on blog post, so here ya go.

I actually have been thinking about the need for delegation for months now, so I have had plenty of time to develop my thoughts about how delegation could be implemented in PHP from a userland perspective.

Note that I am referring to this feature as “Delegation” vs. “Decoration” because I believe it is a better description of the feature, and because several other people said the same on the PR.

My concept of delegation[1]  is informed both by my Go experience, and my experience in PHP specifically with respect to using PHP traits.

Delegation in Go (aka Type Embedding)

First about delegation/type delegation in Go (you can play with this code in the Go playground to see how it works for yourself):

go-delegation

Basically Go simply allows you to specify the struct[3] name in the declaration of another struct and that embeds an instance of the struct to delegate to into the delegating struct. This example embeds the struct Bar in the struct Foo and automatically instantiates an instance of Bar.[4] 

The automatic instantiation is possible because Go does not have nor require constructors[2]. 

Delegating to multiple classes with conflicting methods names

One of the big questions that always comes up is “How can we deal with naming conflicts?” In Go, they just punt. Go does not allow it, as you can see here (also in playground):

method-name-conflicts

Of note, with Go we can use “object initializers” as you see on lines 16-19 to package struct instantiation and initialization into one expression and retain type safety. The example above uses Go’s object initializers to instantiate Greeting in one expression. 

“My kingdom for object initializers in PHP! But I digress…”

me

Resolving method naming conflicts in Go

Go allows you to call same-named methods by naming their classes explicitly:

g := Greeting{}
g.English.SayHello()
g.French.SayHello()

You can also do this inside the struct itself to solve the method naming conflicts. Create a same-named method in the embedding struct and then by explicit disambiguating the embedded structs in code, for example (which is also in the playground:

resolving-conflicts-go

Using Go’s approach in PHP

If we were to model object delegation/type embedding in PHP it might look like this:

<?php 
class English {
    function say_hello(): string {
        printf( '%s\n', "hello" );
    }
}
class French {
    function say_hello(): string {
        printf( '%s\n', "bonjour" );
    }
}
class Greeting {
    public $language string;
    public English; //allows Greeting to delegate to an instance of English
    public French;  //allows Greeting to delegate to an instance of French
    function say_hello(): string {
        switch ( $this->language ) {
        case "english":
            $g->English->say_hello();
            break;
        case "french":
            $g->French->say_hello();
            break;
        }
        echo "unknown language: {$this->language}";
    }
}
$greet = new Greeting();    
$greet->language = 'english';
$greet->English = new English();
$greet->French = new French();

$greet->say_hello() );          // prints "hello"

$greet->language = "french";
$greet->say_hello();            // prints "bonjour"

$greet->language = "esperanto"
$greet->say_hello();            // prints "unknown language: esperanto"

I think this could work well, but something tells me the developers with a vote on PHP internals won’t go for public <embeddedClassName> inside an embedding class as a syntax to support automatic delegation of methods to embedded classes in PHP.

No worries though, I have another, better proposal that follows and I discuss delegations similarity to PHP’s Traits.

Traits in PHP vs. Delegation?

Looking at the proposed PR I am struck by how similar the PHP trait is to the concept of delegating method calls to an instance of one class to the methods of an instance of another class. 

I am not speaking of internal implementation of traits — I do not actually know how they are implemented — just the concept. After all, a trait is a collection of methods and properties with a symbol name that can be used by a class and have its properties treated as part of that class. That is eerily similar to the proposed delegation solution in the PR. 

Use the syntax for traits, with modifications

Taking what we know from PHP traits, I propose that we extend the use statement with a classmodifier, like so:

<?php 
class Greeting {
    use class English;
    use class French;
    function __construct() {
        $this->English = new English();
        $this->French = new French();
    }
}

The above example would tell PHP that any methods called on Greeting that do not exist in Greetingbut do exist in either English or French will be delegated to the applicable instance automatically, assuming a valid instance has been instantiated to embed in the instance of Greeting.

One thing this does is provide a default name for delegated classes so we do not have to declare them separately as Nikita’s proposal requires. You will see how this benefits us below.

Automatic delegated instance instantiation

Further, if the constructor for the delegated classes have no parameters PHP could automatically instantiate any delegated classes. This is the first benefit of the default property names.

So automated delegated instance creation makes the above example as simple as:

<?php 
class Greeting {
    use class English;
    use class French;
}

When the constructor for any delegated class does have parameters then the developer would need to assign them a valid properly-typed instance before first use, such as in __construct(), in __get()methods, or in get_<property> methods.

A nice optimization would be to assign a closure to the delegate property that would be executed on first access[5] thus instantiating and capturing the instance of the delegated-to class. 

Disambiguating conflicting methods names

The nice part of using the Trait syntax is that traits already handle disambiguation. This would give us the following syntax:

<?php 
class Greeting {
    public $language string;
    use class English, French { 
        English::say_hello as say_english_hello;
        French::say_hello as say_french_hello;
    }
    function say_hello() {
        switch ( $this->Language ) {
        case "english":
            $this->say_english_hello()
            break;
        case "french":
            $this->say_hello_in_french()
            break;
        default:
            echo "unknown language: {$this->language}";
        }
    }
}

And I would assume that the code to parse this type of syntax has already been implemented?

Renaming delegated properties

Another concern is that using the class names as properties won’t work well if the classes are in different namespaces. Or maybe developers just don’t want to use class naming syntax for properties. That’s an easy fix by using as:

<?php 
<?php 
class Greeting {
    use class English as $english;
    use class French as $french;
    function __construct() {
        $this->english = new English();
        $this->french = new French();
    }
}

Including or excluding methods

Yet another concern might be that you want to delegate to some of the methods but not all. If we add include and exclude options, then a developer could use one or the other:

<?php 
class Greeting {
    use class English {
        include cater_to_customers;
    };
    use class French {
        exclude close_on_sundays;
    }
}
class English {
    function cater_to_customers() {}
    function shopping_in_sweatpants() {}
    function order_french_fries() {}
}
class French {
    function close_on_sundays() {}
    function order_nutella() {}
    function order_baguette() {}
}

Including or excluding methods

Some developers might prefer to name each method individually instead of using would want to delegate only specific methods from a large class, and would prefer to detail them individually instead of using include and/or exclude

For this we could again extend the use statement with a method modifier. 

<?php 
class Greeting {
    use method English::cater_to_customers;
    use method French::order_nutella;
    use method French::order_baguette;
}
 

Summary

In summary I am proposing the PHP leverage the use statement like the PHP trait does, but with a class modifier to allow for automatic delegation of method calls to contained instances. 

Also I am proposing we leverage the disambiguation syntax that is already available for traits, and extend it in a few ways that traits do not address such as:

  1. Use as to allow for renaming from the property names for delegate classes,
  2. Add include or exclude to allow including or excluding specific methods, and
  3. Add use method to allow for including specific methods.

Footnotes

[1] This PR feels more like delegation — vs. decoration — as several others have mentioned. [2] In Go structs are its closest equivalent to classes.

[3] Many Go developers creates functions to construct structs that need initialization, For example a developer might create a function named NewFoo() that requires a Bar parameter and returns an initialized Foo.

[4] Since Go supports pointers you can also embed a pointer to another struct and that won’t automatically initialize the pointer, but is out of scope of this comment to explain further.

[5] This is something we cannot currently do in PHP, but would be a useful performance-enhancing feature given typed properties in PHP. For example, if a property is typed to contain an instance of a class it could be assigned a closure that returns an instance of the required class, and PHP would execute the closure first access of the property to create the required instance. But this is probably better in another RFC.

Leave a Reply

Your email address will not be published. Required fields are marked *