Extension methods for PHP

Edit on GitHub

PHP Extension Methods The concept of “extension” methods will be nothing new to this blog’s .NET-related audience. For the PHP-related audience, this is probably something new. Let’s start with the official definition for extension methods: Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type. Extension methods are a special kind of static method, but they are called as if they were instance methods on the extended type.

Let’s visualize this. Imagine having the following class:

[code:c#]

class HelloWorld
{
    public function sayHello($to = 'World', $from = null)
    {
        $helloString = "Hello, " . $to . "!";
        if (!is_null($from)) {
            $helloString .= " From " . $from;
        }

        echo $helloString . "\r\n";
    }
}

[/code]

Not too difficult. There’s a sayHello() method that allows you to optionally pass who should be greeted and who the greeting is from. Imagine you would like to have a sayHelloTo() method as well, a shortcut to sayHello($to). And some other helper methods that use sayHello() under the hood. Wouldn’t it be nice to have the HelloWorld class concentrating on the real logic and defining the helper/utility functions somewhere else? How about a class named HelloExtensions:

[code:c#]

class HelloWorldExtensions
{
    public static function sayHelloTo(HelloWorld $subject, $name = '')
    {
        $subject->sayHello($name);
    }

    public static function sayHelloFrom(HelloWorld $subject, $name = '')
    {
        $subject->sayHello('World', $name);
    }
}

[/code]

Ok, nice. But this means I should call HelloWorldExtensions::sayHelloTo(‘Maarten’); to use the utility functions, right? Well: no! Not if we implement the concept of extension methods. Let’s see what we can do to HelloWorld in order to make our life easier.

[code:c#]

class HelloWorld
{
    public function sayHello($to = 'World', $from = null)
    {
        $helloString = "Hello, " . $to . "!";
        if (!is_null($from)) {
            $helloString .= " From " . $from;
        }

        echo $helloString . "\r\n";
    }

    public function __call($functionName, $arguments = array())
    {
        // Add an extra parameter
        array_unshift($arguments, $this);

        // Current reflected class
        $reflectedClass = new reflectionObject($this);

        // Find suitable class and function
        $availableClasses = get_declared_classes();
        foreach ($availableClasses as $class) {
            $classDefinition = new ReflectionClass($class);
            $availableMethods = $classDefinition->getMethods();
            foreach ($availableMethods as $method) {
                if ($method->isStatic() && $method->getName() == $functionName) {
                    $availableParameters = $method->getParameters();
                    if ($availableParameters[0]->getClass()->getName() == $reflectedClass->getName()) {
                        $method->invokeArgs(null, $arguments);
                    }
                }
            }
        }
    }
}

[/code]

The magic method __call() is used to look for a class that defines a static method with the first parameter of our type, HelloWorld. This now allows us to write very clean calls to code, as well as simplify our HelloWorld class:

[code:c#]

$helloWorld = new HelloWorld();
$helloWorld->sayHello();
$helloWorld->sayHelloTo('Maarten');
$helloWorld->sayHelloFrom('Maarten');

[/code]

Sweet, no? Can someone get this into the PHP core? I would greatly appreciate it: my classes can focus on functionality and all utility functions that just pass around variables can be defined in a separate class.

This is an imported post. It was imported from my old blog using an automated tool and may contain formatting errors and/or broken images.

Leave a Comment

avatar

14 responses

  1. Avatar for Eric
    Eric May 18th, 2010

    Thank you for posting this sample - it seems conceptually similar to mixins, in that is is a good way to implement utility methods. The main difference I can see is that methods must a) be declared statically, and b) type hint the caller as the first parameter. This seems like a solution with a smaller code footprint than mixins.

    I am curious though about performance. Both PHP magic methods and the Reflection API have a reputation for not being the best methods for a PHP production environment. Have you tested calling these as extension methods vs. a typical inheritance structure? Is the difference negligible?

    thanks again,
    - Eric

  2. Avatar for Ralph Schindler
    Ralph Schindler May 18th, 2010

    Just a few words of caution about the above post.

    From a technical standpoint, this is not very performant for a number of reasons. First, __call() itself is not very fast- none of the magic methods are. It is always faster to call a method who's signature is defined by a class already. Second, Reflection is even less performant. Reflection should be used in development time code and if it must, in very low trafficed production code. Even then, if you are using Reflection, the conclusions of the reflection should be cached somewhere as to not incure the cost of reflection more than a single request in a production environment. Third, after the __call slowdown, and the reflection slowdown, this code is iterating every known class for all its known methods. In some cases, this loop might iterate over 1000's of items.. and if this technique is used heavily in an application, this means that any dynamic calls (stuff proxied through __call()) would incure a loop that iterates at least a couple of 1000 times.

    From a best practices standpoint- this attempts to bypass some of the fundamental coding best practices that C#, Java and PHP strive for in their implementation. By this I mean polymorphic code via class based inheritance and interfaces. By leveraging "extends" and "implements" you are adhereing to a "Design by contact" methodology, which is generally accepted as a "good thing". Languages such as python and ruby don't follow this implementation in their object model and opt for things like "monkey patching" and "duck typing" which, why interesting techniques, generally mean your API's are documentation driven, as opposed to code driven. For example, these extension methods would never pop up in code completion in an IDE.

    I guess I could make a few more comments, but I think that hits all the high notes. :) In any case, it is an interesting bit of code!

    Cheers,
    Ralph

  3. Avatar for maartenba
    maartenba May 18th, 2010

    Check the next comment :-)

  4. Avatar for maartenba
    maartenba May 18th, 2010

    Hello Ralph,

    This is indeed bad code, performance hits: a lot!

    What I wanted to do is intentionally be vague on some of the "why" and "how". basically, in .NET, this technique is uded to extend classes that are final and can not be inherited from. It's also a widely used solution to making clean code wth 1 full blown method and then adding extension methods for those who need it.

    I hope this post will give some more discussion :-)

    Maarten

  5. Avatar for Steve Hollis
    Steve Hollis May 19th, 2010

    Interesting post. I blogged on a similar topic recently, albeit with a slightly different approach.

    http://www.stevehollis.com/...

    It still uses reflection and the __call magic method, but employs a broker to add specific functionality to objects.

    Ralph's points on performance are all clearly valid, but I wonder how significant they might be in a real life app compared to say, too many or poorly optimised DB queries, remote calls, excessive file system access, lack of caching etc. etc. Presumably we're only talking about milliseconds even for hundreds of magic method and reflection calls.

    Regarding best practices, what about DRY? The approach that both Maarten and I have blogged about address an un-addressed problem in PHP... a means of so-called "horizontal" re-use. There are other approaches such as code generation that are "code completion friendly", but surely they offer a means of making code duplication easier rather than eliminating.

  6. Avatar for maartenba
    maartenba May 19th, 2010

    It is indeed essentially the same problem. Again, this post was not to give a best solution to the problem, just a starting point of something that would be a great addition to PHP as a language.

  7. Avatar for Hodicska Gergely
    Hodicska Gergely May 19th, 2010

    Hello Ralph,

    I would argue a little as we are using reflection API in a very high trafficed environment (ustream.tv was already among alexa top 200), and it makes the controllers and the templates much cleaner. When I introduced this feature I thought that I would cache the result of the reflection API into APC (as it was always stated the reflection API is very slow), but I was surprised as getting the needed information from the API and from APC was the same.

    Of course using the reflection API was noticeable on the performance side but even with it our framework outperforms all the popular (and of course general) PHP frameworks (over 1000 req/sec from a hello world page). Actually for us this cost worth the benefits we get on development side. And if we want we can avoid the slowdown by generating a static cache as part of the build/code deployment process.

    Regards,
    Felhő

  8. Avatar for Pádraic Brady
    Pádraic Brady May 19th, 2010

    The sample code coincides with something I do myself (as a sort of hackish way of getting some of Ruby's module benefits in certain scenarios - not many of those since I restrict it to times when I just have to be able to throw methods at a class without actually editing it too much). There are performance hits, but it really depends on where they're used, how often, and whether you can optimise a bit. One optimisation, that's obvious, is from relying on get_declared_classes(). Is it possible to be more specific about classes to reflect? For example, use a specific namespace or a class prefix - then filter the results just for those types of classes. That would cut the potential 1000s of class lookups down to a minimal count.

  9. Avatar for Artem
    Artem May 19th, 2010

    Why would you extend a class that's final? You're not supposed to.

  10. Avatar for maartenba
    maartenba May 19th, 2010

    Ever had the situation where you hard to do this:

    You get: final class Foo { }
    You need a helper method, lik&#235: FooHelper::bar( $myInstanceOfFoo )

    Using extension methods, you can just wire up this helper method at runtime.

  11. Avatar for maartenba
    maartenba May 20th, 2010

    Probably possible, yes.

  12. Avatar for Artem
    Artem May 20th, 2010

    No, I've never had such a situation. If a class is a final, it should stay final. That's the point of making it final in the first place.

  13. Avatar for maartenba
    maartenba May 20th, 2010

    While it is called extension method, this technique does not give access to the class internals. Which makes it essentially a shorthand for the helper classes I described above.

  14. Avatar for Chris Henry
    Chris Henry June 16th, 2010

    Interesting concept. Why would someone choose to do this over simply extending the class and adding the methods that way? It seems as though it would be difficult to leverage and extensions you write and lead to a mess of randomly extended objects without any helpful organization.