phpv8 / v8js

V8 Javascript Engine for PHP — This PHP extension embeds the Google V8 Javascript Engine
http://pecl.php.net/package/v8js
MIT License
1.83k stars 200 forks source link

Modifications to an array that is accessed via passed PHP object are not persisted #277

Closed mikemherron closed 7 years ago

mikemherron commented 7 years ago

This is probably easier to show than explain. See below test script. The final example adds an item to the end of an array returned from a php object passed to v8. The item added however does not appear when iterating through the same array. The return of the push call is 3, as if the item has been added. Appending on to the array DOES work if the array is passed directly from PHP.

<?php

class Provider {

    public function getItems()
    {
        return ["one", "two"];
    }

    public function getItemsInObject()
    {
        return (object) [
            "items" => ["one", "two"]
        ];
    }
}

$v8 = new V8Js('Test');
$v8->provider = new Provider();

$v8->executeString("

    var items = Test.provider.getItems();

    print('\\nItems from php as array, initial state:' + '\\n');
    for( var i=0;i!=items.length;i++) {
        print('Item :' + items[i] + '\\n');
    }

    items.push( 'three' );
    print('\\nItems from php as array, added new items, appears on end as expected:' + '\\n');
    for( var i=0;i!=items.length;i++) {
        print('Item :' + items[i] + '\\n');
    }

    print('\\nItems from php via array on objected, added new item, DOES NOT appear on end of array:' + '\\n');
    var obj = Test.provider.getItemsInObject();
    var result = obj.items.push('three');
    print ('Result of push:' + result + '\\n');
    for( var i=0;i!=obj.items.length;i++) {
        print('Item :' + obj.items[i] + '\\n');
    }
");

This outputs:

Items from php as array, initial state:
Item :one
Item :two

Items from php as array, added new items, appears on end as expected:
Item :one
Item :two
Item :three

Items from php via array on objected, added new item, DOES NOT appear on end of array:
Result of push:3
Item :one
Item :two

Is this expected behaviour?

stesie commented 7 years ago

ugh, Github where's my answer gone to!? Actually I've answered this issue yesterday already, but seems to have been gone :-(

So yes, this is actually expected behaviour, ... so I agree that it might not seem obvious (and probably is unwanted even).

The point is that objects are exposed to JavaScript by an small object simply delegating to the real PHP object. This is if you access one of the properties from JavaScript you're actually "just in time" accessing the PHP object. If you're writing to the object from JavaScript it's actually persisted on the PHP object and you are able to see the change on PHP side:

root@8f794daab180:/# php -a
Interactive mode enabled

php > $v8 = new V8Js();
php > $foo = new stdClass();
php > $foo->blar = 42;
php > $v8->foo = $foo;
php > $v8->executeString('PHP.foo.blub = 23;');
php > var_dump($foo);
object(stdClass)#2 (2) {
  ["blar"]=>
  int(42)
  ["blub"]=>
  int(23)
}

However this isn't true for arrays. An array is exported to a real JavaScript array, and all elements are immediately exported. If you change the JavaScript array later on it doesn't affect the PHP array.

root@8f794daab180:/# php -a
Interactive mode enabled

php > $v8 = new V8Js();
php > $foo = [ 'blar' => 42 ];
php > $v8->foo = $foo;
php > $v8->executeString('PHP.foo.blub = 23; ');
php > var_dump($foo);
array(1) {
  ["blar"]=>
  int(42)
}
php > $v8->executeString('var_dump(PHP.foo); ');
array (2) {
  ["blar"] =>
  int(42)
  ["blub"] =>
  int(23)
}

... the var_dump on JavaScript side reflects the change; while the PHP side doesn't.

Now applying this knowledge to your code sample:

    var obj = Test.provider.getItemsInObject();
    var result = obj.items.push('three');
    print ('Result of push:' + result + '\\n');
    for( var i=0;i!=obj.items.length;i++) {
        print('Item :' + obj.items[i] + '\\n');
    }

... however as the PHP array is unmodified you don't see the modification by the push call

Hope that at least makes clear what's going on inside V8Js :)

mikemherron commented 7 years ago

@stesie Thanks for the detailed answer and explanation. I had a hunch that the problem was going to be somewhere in the mapping process between JavaScript and PHP. Your explanation makes sense, and I can workaround the behaviour. Happy to have the issue closed and thanks again for taking the time to explain is such detail.