PHP in Action Rotating Header Image

Real programming with PHP 5.3 (part 2): JavaScript-style classes

In part one of this series, we looked at the ability to use lambda functions or closures to process arrays. In this part, we will see how closures can be used to build classes in a completely new way.

After I did my own research, I discovered that someone had beat me to it. As early as September last year Fredrik Holmström wrote about Javascript-OO & Python-DuckTyping in PHP5.3.

I’ve done the same thing independently and somewhat differently, so it’s interesting to compare. Unlike Holmström, I’m defining the closures inside the constructor for the class that’s using them. That makes the code that uses the class a little more similar to what we’re used to.

Let’s start with a “Hello world” example.

class HelloWorldWithClosures {
    private $hello;
    private $goodbye;

    public function __construct() {
        // If we try using $this inside the closure, we get a syntax error.
        // So we need another variable name, which is basically arbitrary.
        $self = $this;

        // $self is not actually used inside this method/closure, but we want to be consistent.
        $this->hello = function() use ($self) {
            echo "hello world...\n";
        };

        $this->goodbye = function() use ($self) {
            $self->hello();
            echo "...and goodbye for now\n";
        };
    }

    public function __call($method,$args) {
        return call_user_func_array($this->$method,$args);
    }
}

The closures are instance variables in the class, and the __call() method is set up to call the correct closure when calling the method in the usual way in PHP.

Now we can call a plain method that uses nothing else from the class:

$obj = new HelloWorldWithClosures;
$obj->hello(); // prints "hello world..."

And we can call the goodbye() method that starts by calling hello():

$obj = new HelloWorldWithClosures;
$obj->goodbye();
// prints "hello world...
//...and goodbye for now"

We can also add new methods to object that’s already instantiated without changing the class:

$obj->helloAgain = function() use($obj) {
    $obj->goodbye();
    echo "...and hello again\n";
};

This last example exploits the somewhat vulgar fact that you can still add public instance variables on the fly to an object, as in PHP 4.

A different approach that avoids this, but is a bit more verbose, would be to use an array to store the closures in the object:

class HelloWorldWithClosures2 {
    public $methods = array();

    public function __construct() {
        $self = $this;

        $this->methods['hello'] = function() use ($self) {
            echo "hello world...\n";
        };

        $this->methods['goodbye'] = function() use ($self) {
            $self->hello();
            echo "...and goodbye for now\n";
        };
    }

    public function __call($method,$args) {
        return call_user_func_array($this->methods[$method],$args);
    }
}

In addition, we would normally need to throw an execption when a method does not exist. The blog post I linked to at the beginning has this feature.

Those are the basics of how to write JavaScript-classes in PHP 5.3. Whether you actually want do it this way depends on your needs. There’s no point unless you actually want to use the flexibility this style of programmer affords.

Share/Save/Bookmark

16 Comments

  1. Jiri Fornous says:

    Nice brain exercise :) But except its much more dynamic than usual “static” class definition I wouldn’t recommend to use it in serious project. Actually I everytime disliked the bottom-up class construction in Javascript.

  2. Mike says:

    You could just chuck an exception in __set if you don’t want to allow setting new vars on an instance.

    I played with this shortly after reading Fredricks post and came to the conclusion that predicting state in PHP was hard enough without making class definitions variable too.

  3. Andrei says:

    real chaos with php 5.3 …

  4. dagfinn says:

    I think it depends on what you’re trying to do. Sometimes this kind of freedom can help you decouple classes without resorting to complex design patterns. On the other, if you don’t know what you’re trying to achieve with it, it’s better to leave it alone. And I haven’t tried it in a real PHP application.

  5. weltraumschaf says:

    I know these concept sfrom JavaScript. But I wonder for what this is godd to? For me its looks like an alternative Syntax. The hello() example is the same as you wrote it like:

    public function hello() { … }

    The feature of closures makes only sense, whenn you cann pass them into a function as parameter and when you can return it as a parameter and finaly when nyou can execute the closure with an different object context.

    Can I do in PHP the following things?

    // returning a closure:
    public function foo() {
    return function() {…};
    }

    // passing closure around (e.g. for anonymous callbacks):
    $this->bar($param, function() use ($foo) { …});

    public function bar($p, $callback) {
    // …
    $callback($someValueFromBar);
    }

    // can I invoke the closure with different object contexts ($this):

    public function bar($p, $callback) {
    $someOtherObject = new …;
    // …
    $callback->applay($someOtherObject);
    }

  6. dagfinn says:

    @weltraumschaf: You should be able to do the first two, but not the last one. The closure is an object belonging to the Closure class, but the Closure class has no methods.

  7. Tarjei says:

    Is it possible to overwrite existing functions?

    In python I find DuckTyping indispensable for testing as I can easily just stub out one function from an object and still test the rest of the object.

  8. dagfinn says:

    @Tarjei: Since a method in this style is simply the content in a instance variable, you can overwrite it by whatever means you would otherwise use to change an instance variable. *How* you would want to do that, considering visibility restrictions and so on, is another matter.

  9. I had this idea back in August 2008, but at that point nobody seemed to care about it.
    I’ve made it public in a thread on sitepoint.com[1], where apparently just one person found it to be interesting.

    Yeah, Amenthes is me.

    [1] http://www.sitepoint.com/forums/showthread.php?t=565576

  10. dagfinn says:

    @Ionut: I might have noticed it if you had posted it in the PHP Application Design forum. On the other hand, perhaps not. Anyway, it’s interesting that we seem to have four different independent implementations of this idea.

  11. derosion says:

    Hey,

    I might (probably am) be totally confused here but in reference to your first “hello world” example where you pass in $self as a lexical value to your hello and goodbye functions.

    Reading http://wiki.php.net/rfc/closures it says:

    “By default, all imported variables are copied as values into the closure.”

    So with this said, is $self a copy of $this rather than a reference?

  12. dagfinn says:

    @derosion: I admit I haven’t considered that. I’ll have to investigate. I have other examples that I believe would not work if it were a copy.

  13. [...] Real programming with PHP 5.3 (part 2): JavaScript-style classes [...]

  14. Dellorbo Giulio says:

    Hi,

    the problem while using closures to add methods is that the closure cannot access private or protected obj props nor they can call private/protected method’s…

  15. tania says:

    This is a nice article..
    Its very easy to understand ..
    And this article is using to learn something about it..

    c#, dot.net, php tutorial

    Thanks a lot..!

Leave a Reply