JavaScript-style object literals in PHP

March 20th, 2011. Tagged: JavaScript, php

The object literal notation in JavaScript looks like:

var fido = {name: "Fido", barks: true};

or

var fido = {};
fido.name = "Fido";
fido.barks = true;

From assoc arrays to objects

In PHP you would call that an associative array.

$fido = array(
  'name' => "Fido",
  'barks' => true
);

And you can easily make it an object too:

$fido = (object)$fido;
echo gettype($fido); // "object"

Or if you want to start with a blank object and add stuff to it:

$fido = (object)array();

or

$fido = new StdClass();

and then

$fido->name = "Fido";
$fido->barks = true;

A little explanation maybe: objects in JavaScript are hashes, maps, whatever you decide to call them. Objects in PHP were an afterthought in the language and (at least initially) were not much more than "fancy arrays". Fancy associative arrays (hashes, maps, whatever you call them).

Objects in PHP need a class, but the new stdClass() lets you start quickly without the class {...} jazz. Same for casting an array (upgrading it in its fanciness) to an object with (object)array().

So far - so good. What about methods?

Methods anyone?

JavaScript doesn't care about properties versus methods. It's all members of an object (like elements of an assoc array). Only if a member happens to be a function, it's invokable.

fido.say = function () {
  if (this.barks) {
    return "Woof!";
  }
};
 
fido.say(); // "Woof!"

Turns out, since PHP 5.3 there are closures in PHP too. So you can do:

$fido->say = function() {
  if ($this->barks) {
    return "Woof";
  }
};

The difference is that $fido->say() won't work. Two reasons for that:

  1. say is not a method. It's a property. For PHP it matters. You can however assign the property say to a new variable $callme. This variable is now a closure object. As such you can invoke it:
    $callme = $fido->say;
    echo $callme();

    Note the $ in $callme().

  2. the above will also fail because $this is an weird context and doesn't point to the object $fido. But you can use $self and point it to the global object $fido.

So that's a little .... unpretty, but it works:

$fido = (object)array();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function() {
  $self =& $GLOBALS['fido'];
  if ($self->barks) {
    return "Woof";
  }
};
 
$callme = $fido->say;
echo $callme(); // "Woff!"

And a sprinkle of magic

We can make this prettier with the help of a little PHP magic. PHP has some magic methods going on and one of these is the __call() method. If you implement it in a class, then it will be invoked whenever someone tries to call a method that doesn't exist.

In our case $fido->say is not a method. So __call can intercept $fido->say() calls and invoke the $fido->say property as a closure object. Closures are callable and call_user_func() and call_user_func_array() work fine with them. So all in all we should make this work:

$fido = new JSObject();
$fido->name = "Fido";
$fido->barks = true;
 
$fido->say = function($self) {
  if ($self->barks) {
    return "Woof";
  }
};
 
echo $fido->say();

As you can see, very JavaScript-esque. Except that $this is $self and will always be the first argument passed to every method. The secret sauce to make this happen is the JSObject() class.

class JSObject {
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

Nice and easy. Namely:

  1. __call takes the name of the missing method and any arguments.
  2. It checks whether there's a callable property with the same name (a closure object property).
  3. It adds $this to the arguments list and calls the closure.

Yupee! Now you can haz moar class-less JS-like PHP objects :)

(Note that $this->$name is not a typo and should not be $this->name because it's a dynamic property name.)

And one more thing

If we add a constructor to JSObject, it can accept any properties at creation time. So you can be even closer to JavaScript and allow both creating an "empty" object and adding to it later, or creating an object and adding properties simultaneously.

The slightly modified JSObject:

class JSObject {
  function __construct($members = array()) {
    foreach ($members as $name => $value) {
      $this->$name = $value;
    }
  }
  function __call($name, $args) {
    if (is_callable($this->$name)) {
      array_unshift($args, $this);
      return call_user_func_array($this->$name, $args);
    }
  }
}

And example use:

$fido = new JSObject(array(
  'name' => "Fido",
  'barks'=> true,
  'say'  => function($self) {
    if ($self->barks) {
      return "Woof";
    }
  }
));
 
echo $fido->say(); // "Woff"

This is pretty close to what you can have in JavaScript (adding $ and ' even though we can do without them), only changing a few things like -> to . and => to :

$fido = {
  'name' : "Fido",
  'barks': true,
  'say'  : function() {
    if (this.barks) {
      return "Woof";
    }
  }
};
$fido.say(); // Woof

JS and PHP look like twins now don't they.

JS for PHP devs at confoo.ca

This was extracted from a talk I gave at the confoo.ca conference a week or so ago. Below are the slides:

JavaScript for PHP developers
View more presentations from Stoyan Stefanov

Tell your friends about this post: Facebook, Twitter, Google+

31 Responses

  1. [...] See th&#1077 first post: JavaScript-stylishness object literals &#1110n PHP / Stoyan's phpied.com [...]

  2. For older PHP versions (<5.3) I've used the static classes pattern

    class Fido{
    static $name=”Fido”;
    static $barks=true;
    static function say(){
    if(self::$barks){
    return “Woof”;
    }
    }
    }

    echo Fido::say();

    Ain’t so pretty, doesn’t have closures and is not easily mutable but it works :)

  3. This is really interesting, but it seems that I missed the point. Where’s the benefit of forcing PHP to have similar syntax as JS ?

  4. PHP is a truly hideous language. Why not start developing with Node for server-side work and you can forget you ever learnt that horrible mess that is PHP.

  5. I had some fun with the same concepts a couple of months ago.
    You can push the similes with JavaScript even further.

    Here, for instance, I experimented with prototypal inheritance:
    https://gist.github.com/803151#file_prototypal_inheritance.php

    Here is a version of the famous JavaScript module pattern:
    https://gist.github.com/803151#file_module_pattern.php

    I then tried to see if I could apply some of the pattern common to functional programming in PHP.

    I managed to get an example of memoization (https://gist.github.com/803151#file_memoization.php) and curry (https://gist.github.com/803151#file_curry.php)

    It’s all fun, but I honestly haven’t found a single serious usage for all of this stuff! ; )

  6. Another thing to note is that you can easily access the values of specific keys as long as they contain only alphanumerical characters (and don’t start with a number). For example, in JavaScript you can just use obj.foo to access the corresponding value of the foo key in the object literal. In PHP, you would do $obj->foo.

    It gets more complicated when the key contains stuff like hyphens, spaces, or other non-alphanumerical characters. For example, obj.foo bar (JS) obviously won’t work, and the same goes for $obj->foo bar in PHP.

    It’s still possible to access the values for these keys though, and here’s how:

    JavaScript:

    var obj = {
      'foo bar': 42
    };
    obj['foo bar']; // 42

    PHP:

    <?php
    
    $obj = (object) array(
      'foo bar' => 42
    );
    
    $obj->{'foo bar'}; // 42
    
    ?>
  7. I wonder if we could use call_user_func_array(array($this, $this->name), $args) instead. (Not tested)

  8. [...] JavaScript-style object literals in PHP / Stoyan's phpied.com [...]

  9. Your function example is not quite accurate.

    function junction(a, b) {
    b = b || 2;
    return a * b;
    }

    function junction($a, $b = 2) {
    return $a * $b;
    }

    The two calls are not identical when b is a falsy value, like 0. JavaScript will return a * 2. PHP will return $a * 0.

  10. Nevermind me, I missed the slide directly after it! :)

  11. [...] JavaScript-style object literals in PHP / Stoyan's phpied.com [...]

  12. Thank you for sharing this Stoyan.

    I’ve experimented with making my PHP a bit more javascript-like and this will be a very useful snippet of code to play with.

    @Ryan, sometimes Node, or other server-side-javascript solutions, cannot be used. I’ve delved into legacy php code many times and it helps to have simple solutions like this to make the PHP code more familiar/similar to the front-end javascript.

  13. [...] JavaScript-style object literals in PHP / Stoyan's phpied.com [...]

  14. I find the use-this-language-instead-of-that argument silly. This post, for me at least, is about how programming conventions or historic limitations lead you to believe that a concept from one language can’t be applied in another. If you keep comparing languages and try to apply common js/ruby/perl/java/php/whatever concepts in a different language, you’ll often find that it’s well possible, just that the language isn’t seen as being used that way.

    This example shows that (in this context) it’s well possible, in a reasonable and elegant way.

  15. [...] JavaScript-style object literals in PHP [...]

  16. [...] http://www.phpied.com/javascript-style-object-literals-in-php/   If you enjoyed this article, please consider sharing it! [...]

  17. [...] is an obsession for many other developers such Tantek Çelik with his CASSIS or Stoyan Stefanov via JavaScript-style object literals in PHP.As somebody commented already in one or more related posts, “why on earth do not use simply [...]

  18. Curso PHP avanzado…

    JavaScript-style object literals in PHP / Stoyan’s phpied.com…

  19. My shrinks make me impotent. It’s not like I have women to impress.

    God’s watching, so you’re the moron.

  20. Wow great. Now make cophee script for php

  21. I think you can fix the $this reference problem with bindTo

    http://php.net/manual/en/closure.bindto.php

    Nice article, not that I am going to try to do in php what I do in js :)

  22. Would have been awesome if you started your article with something like “PHP has proper classes, here is how not to use them”

  23. Looks very similar to/almost the same of http://lucato.it/php-anonymous-objects

  24. PHP is a truly hideous language. Why not start developing with Node for server-side work and you can forget you ever learnt that horrible mess that is PHP.

    Exactly. Why don’t you throw practicality, libraries, community, and support to the wind, and start using a half-baked project like Node, that changed by the hour, is not even at version 1.0, it’s unsuited for tons of tasks, and lack battle-tested usable first/third party libraries??

    Facebook should drop PHP pronto, and use Node.

  25. FYI, Arrays in PHP are more lightweight than Objects.

  26. [...] In PHP you would call that an associative array. $fido = array ( ' name ' => " Fido " , ' barks ' => true ) ; From assoc arrays to objects And you can easily make it an object too: var fido = { } ; fido . name = " Fido " ; fido . barks = true ; JavaScript-style object literals in PHP / Stoyan's phpied.com – Vimperator [...]

  27. I’m considering Javascript (and notdejs) not just for web development but also for desktop apps on Windows. As Microsoft is serious about html/js on windows, I guess people like me need to catch up with it. Thanks for the tutorial stoyan.

  28. fiverr…

    [...]JavaScript-style object literals in PHP / Stoyan’s phpied.com[...]…

  29. daily affirmations…

    [...]JavaScript-style object literals in PHP / Stoyan’s phpied.com[...]…

  30. Simon Peyote Joints

    “Exactly. Why don’t you throw practicality, libraries, community, and support to the wind, and start using a half-baked project like Node, that changed by the hour, is not even at version 1.0, it’s unsuited for tons of tasks, and lack battle-tested usable first/third party libraries??”

    Oh man, oh boy. Oh boy. Oh man.
    I won’t feed the troll any more than to say grown languages are for speaking, not for developing with. To lack any kind of consistent design, or sensible reasoning for design choices, is unscientific and ultimately destructive.

  31. Stupid PHP…

Leave a Reply