7

What's the equivalent of the __call magic method from PHP ?

I was under the impression that Proxy can do this, but it can't.

class MyClass{
  constructor(){
    return new Proxy(this, {
      apply: function(target, thisArg, args){
        console.log('call', thisArg, args);
        return 'test';
      },

      get: function(target, prop){
        console.log('get', prop, arguments);
      }


    });

  }

}

var inst = new MyClass();
console.log(inst.foo(123));

get seems to work because I see "get foo", but apply does not. I get is not a function error.

user3840170
  • 26,597
  • 4
  • 30
  • 62
Alex
  • 66,732
  • 177
  • 439
  • 641
  • The `apply` trap on the proxy if for calling the object itself, i.e. `inst(…)`. However you cannot create a callable proxy from a non-callable target. – Bergi Jan 13 '19 at 18:58
  • Your `get` trap on the `.foo` property must return a function so that you can call it. – Bergi Jan 13 '19 at 18:59
  • https://stackoverflow.com/questions/50842477/mixing-constructor-and-apply-traps-on-the-javascript-proxy-object, https://stackoverflow.com/questions/50027225/in-the-proxy-handler-how-to-distiguish-getting-a-property-var-vs-calling-a-me – Bergi Jan 13 '19 at 19:04

3 Answers3

8

apply actually handles a function call to the object itself, i.e. if you do new Proxy(someFunction, { apply: ... }), apply would be called before someFunction is called.

There is nothing for trapping a call to a property, because this would be superfluous – get already handles when a property is returned. You can simply return a function that then produces some debug output when called.

class MyClass{
  constructor(){
    return new Proxy(this, {
      get: function(target, prop) {
        return function() {
          console.log('function call', prop, arguments);
          return 42;
        };
      }
    });
  }
}

var inst = new MyClass();
console.log(inst.foo(123));
Aurel Bílý
  • 7,068
  • 1
  • 21
  • 34
  • I don't think this is a complete solution. you have returned a static value. you need to use `apply` inside `get` to calculate result of original function. – Badis Merabet Jan 13 '19 at 19:40
  • 1
    @BadisMerabet I'm sure OP can modify the function to suit their needs. Nevertheless the question was an analogue to `__call` in PHP, which is used to resolve non-existent functions of an object, i.e. there is no "original function" to call. The default / intended behaviour of `Proxy` is [well-documented already](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy). – Aurel Bílý Jan 13 '19 at 20:01
  • thank you for clarifying this, It makes sense. Still I am interested in what's the goal or benefits of intercepting calls to non existing functions in a piece of code or software ? – Badis Merabet Jan 16 '19 at 13:30
  • @BadisMerabet I know that this sort of "magic" (comparable to macros in some languages, and runtime reflection in others) can be used to implement e.g. ORM systems, where methods may be generated dynamically for one-to-many relations. Some other uses I can think of – a library with lazily loaded modules/functions, libraries which have (too) many functions that all have a given pattern (e.g. a geometry library – `addPoint2D`, `addPoint3D`, …). There are lots of applications, and perhaps they are not indicative of "good" code always, but this *is* Javascript after all. :) – Aurel Bílý Jan 16 '19 at 20:17
4

This another way of achieving what you have requested.

class MyClass{
  constructor(){
     return new Proxy(this, {
        get(target, propKey, receiver) {
            const origMethod = target[propKey];
            return function (...args) {
                let result = origMethod.apply(this, args);
                console.log(propKey + JSON.stringify(args)
                    + ' -> ' + JSON.stringify(result));
                return result;
            };
        }
    });
  }
  
foo = (x) => {
  return x + 1;
};

}

var inst = new MyClass();
console.log(inst.foo(123));
Badis Merabet
  • 13,970
  • 9
  • 40
  • 55
2

Yeah Proxy can do that, but even when trapping methods you have to use get of Proxy.

Then here I also executes your real method, but I don't know if you want to mock it.

class MyClass {
  constructor() {
    return new Proxy(this, {
      get(target, prop, receiver) {
        if (typeof target[prop] !== "function") {
          return "etcetcetc";
        }
        return function(...args) {
          console.log('call', args);
          return target[prop]();
        };
      }
    });
  }

  foo() {
    console.log('I am foo!');
  }
}

var inst = new MyClass();
inst.foo(123);

As you can see, if you are calling a method of your instance, I will intercept it, and then return your original method execution.

If you are accessing an attribute of your instance, I will return always a mocked string.

Then of course change it with the behavior that you want to.

quirimmo
  • 9,800
  • 3
  • 30
  • 45