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:
say
is not a method. It's a property. For PHP it matters. You can however assign the propertysay
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()
.- 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:
__call
takes the name of the missing method and any arguments.- It checks whether there's a callable property with the same name (a closure object property).
- 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:
Comments? Feedback? Find me on Twitter, Mastodon, Bluesky, LinkedIn, Threads