-
Notifications
You must be signed in to change notification settings - Fork 202
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Compromise between FORCE_ARRAY and standard setup #511
Comments
Hej Mark, I.e. in your example I like that it makes explicit what's going on and doesn't involve any magic. But YMMV. So to put it differently, is it just "preference" that you'd like to get rid of the explicit cast (by means of convention) or do I miss something? |
Perhaps there is a preference of sorts, but there are thousands of typehinted functions that would need to be un-typehinted and casting added. I always prefer (where possible) PHP typehints, though it's heavily documented too so static analysis isn't an issue. Partly also, I've simplified things greatly :-p The use cases and requirements from the 'userCode' I speak of above are massively varied. I'd like to migrate to using the standard approach without FORCE_ARRAY. But mostly, it feels like that given PHP is the 'processor' of logic for the most part, the data is likely wanted in regular PHP forms, rather than coming back in a different form to how it was passed. The rest is just kind of an extension on that, really :) I guess it depends on the complexity to implement. FORCE_ARRAY is used twice, from what I can see - once to declare it as a flag, the other to check if enabled/disabled. I think - like #454 - it's sort of just trying to deal with the preservation of things as they go back and forth between PHP and JS. Having said that, it almost feels like a PHP thing. With an object having a __toString(), you can: function testString(string $string) { return $string; }
$cast = (string)$myObject; // works
$cast = testString($myObject); // works
// the latter doesn't fatal because of the typehint, either, because __toString() kicks in! but with an array object: function testArray(array $array) { return $array; }
$myObject = new ArrayObject(['foo' => 'bar']);
$cast = (array)$myObject; // works
$cast = testArray($myObject); // fatal because of typehint |
I don't quite get that. You need not do both. More like just one or the other. If you have the explicit type cast you can perfectly pass the (then) array to the function with an array type parameter. And regarding the ... and php-v8js cannot do much about that. Regarding your example function someThingInPHP(array $array) {
}
someThingInPHP($result->another()); ... this extensions involvement ends when So to summarize, you initially invoke What about wrapping the hook object on PHP side? Hence leaving it to the facade to "guess" if it should be cast to an array? class FancyHeuristicsCastingWrapper {
private $wrapped;
function __construct($wrapped) {
$this->wrapped = $wrapped;
}
function __call(string $name, array $args) {
$result = $this->wrapped->$name(...$args);
if ($result instanceof \V8Object) {
return (array) $result; // plain V8Object, convert to array
}
return $result; // retain date object
}
}
// the main runner...
$v8js = new V8Js('cs');
$userCode = getUserCode();
$result = new FancyHeuristicsCastingWrapper($v8js->executeString('
function userCode() {
' . $userCode . '
}
result = userCode();
'));
// hook result processor
function someThingInPHP(array $array) {
}
// invoke hook
$tmp = $result->another(); // variable could be inlined, just for verbosity
// if another returned a plain JS object, the wrapper will have cast this to array
var_dump( is_array($tmp) ); // true
someThingInPHP( $tmp ); // passes, since matches array typehint |
Ok, so consider this: class V8JsWrapper extends V8Js
{
public function __construct()
{
parent::__construct('cs');
}
public function getArray(): array
{
return ['foo', 'bar'];
}
public function explicitFunction(array $params)
{
}
public function _wrappedFunction(array ($params)
{
}
public function __call(string $method, array $args)
{
if (method_exists($this, '_' . $method) {
// convert inner $args to array and call the method
}
}
}
$v = new V8JsWrapper();
$result = $v->executeString('
const myArray = cs.getArray();
cs.wrappedFunction(myArray); // this is ok
cs.explicitFunction(myArray); // this is not ok - Fatal error
ret = myArray;
');
// i can also work with and convert $result if I needed to, before passing elsewhere. Not one of our use-cases, but just pointing out for completeness. All of our functions are currently set up like the So my options, if I'm to move to removing FORCE_ARRAY are below. I'll call the option
// just an example
var thing = { "foo": "bar" }
cs.doSomething(thing); // would send an array to PHP land. currently V8Object
thing = new V8ProtectThing(thing);
cs.doSomethingElse(thing); // would send a V8Object or whatever. Apologies if it's not making sense, maybe I just over-simplified things :/ |
well, after all this is due to tight coupling introduced by extending directly from Generally I'd prefer to use composition over inheritance. If your code would allow for composition you could easily add and remove conversion layers between your class and V8Js class. This is, instead of extending However given your codebase, I'd propose to change inheritance from That way you only have to change inheritance once, everything else can remain the same. This is what I think of: class V8JsShim {
private $v8;
public function __construct() {
$this->v8 = new V8Js();
$ref = new ReflectionClass( $this );
foreach( $ref->getMethods( ReflectionMethod::IS_PUBLIC ) as $method ) {
if ($method->name === '__construct') {
continue;
}
$this->v8->{$method->name} = function( ...$args ) use ($method) {
// you could go fully fancy here and even inspect the typehint of
// the called method (and convert only if it has 'array' typehint)
if ( count($args) === 1 && $args[0] instanceof \V8Object ) {
$args[0] = (array) $args[0];
}
return $this->{$method->name}( ...$args );
};
}
}
// forward executeString etc.
public function __call(string $method, $args) {
return $this->v8->$method( ...$args );
}
}
class Blarg extends V8JsShim {
public function moep( array $data ) { // array typehint here
var_dump($data);
}
}
$blarg = new Blarg();
$blarg->executeString('
print("hello there :)\\n");
PHP.moep( { blarg: 42 } ); // without auto conversion it would fail due to the typehint
');
|
I'd accept that as workable. I actually experimented a bit by seeing if I could return a PHP Callable instance from V8Js in place of a V8Function (so at least it can be bound via the whole I still think having some kind of control (within the extension itself) over the conversion (rather than just FORCE_ARRAY or not for the entire execution) would be a good option to have at one's disposal, but will close this one out for now :) Thanks for the suggestions, as always! |
IMHO everything that can be done in PHP itself should be done in PHP and not be pushed into an extension. The latter is way more inflexible, and intransparent to people not knowing the C++ side. That said I don't even think that FORCE_ARRAY is a great feature to have. After all it's pretty straight forward to do the same completely in PHP code. But no worries, I'm not going to remove it 🙂 |
Quite true, as does For me, the only time I want to use objects in PHP is if there are methods, getters, setters, etc. if it’s just representing structures of non-contextual data, an array is always my preference. |
Hey!
I wonder if there is medium that could be added between the standard setup and the setup with FORCE_ARRAY?
We use FORCE_ARRAY mostly for legacy reasons (many years ago, it was easy to segfault standard mode from user code, whereas FORCE_ARRAY was mostly protected), but also because 99% of the time when passing back objects, associative arrays are want we want in PHP.
However, the benefit of standard mode is the use of 'this'. A basic example of how we sort of implement things:
and some example 'userCode':
This means the user can write 'hooks' that are called on the
$result
, e.g.$result['main']($someval, $otherVal);
at various points. This works well, but as userCode gets more complex, you often want to usethis
to organise things better:or something like that. The problem though, is FORCE_ARRAY returns, well, an array and not a V8Object, so the concept of
this
doesn't exist.On the flipside, if we use it as standard without FORCE_ARRAY, then (using the last example):
This will result in a fatal error because
$result->another()
will return a V8Object, not an array so the typehint will fatal.So I guess I'm wondering if there is a halfway house, where BOTH could be used somewhat. MAYBE something like:
$v8js->someFunc = function (array $settings) { };
)TLDR: Introduce an option almost entirely like FORCE_ARRAY, but with a handful of exceptions and the ability to skip conversion at runtime for certain objects.
Any thoughts? @stesie
The text was updated successfully, but these errors were encountered: