'How I can simulate the __callStatic property of PHP in Node.js using Proxy?

I'm trying to create the same behavior of PHP __callStatic magic method in Node.js.

I'm trying to use Proxy to do that, but I don't really know if it's the best option.

class Test {
  constructor() {
    this.num = 0
  }
  
  set(num) {
    this.num = this.num + num

    return this
  }
  
  get() {
    return this.num
  }
}

const TestFacade = new Proxy({}, {
  get: (_, key) => {
    const test = new Test()
        
    return test[key]
  }
})


// Execution method chain ends in get
console.log(TestFacade.set(10).set(20).get())
// expected: 30
// returns: 0

// Start a new execution method chain and should instantiate Test class again in the first set
console.log(TestFacade.set(20).set(20).get())
// expected: 40
// returns: 0

The problem is that the get trap is fired every time that I try to access a property of TestFacade. The behavior that I need is that when the set method is called it will return this of Test class and I can even save the instance for latter usage!

const testInstance = TestFacade.set(10) // set method return this of `Test` not the Proxy

If something isn't clear, please let me know.



Solution 1:[1]

I don't know if it's the best option. But I solved it, returning a new Proxy inside get trap that uses the apply trap to bind the test class instance into the method:

class Facade {
  static #facadeAccessor

  static createFacadeFor(provider) {
    this.#facadeAccessor = provider

    return new Proxy(this, { get: this.__callStatic.bind(this) })
  }

  static __callStatic(facade, key) {
    /**
     * Access methods from the Facade class instead of
     * the provider.
     */
    if (facade[key]) {
      return facade[key]
    }

    const provider = new this.#facadeAccessor()

    const apply = (method, _this, args) => method.bind(provider)(...args)

    if (provider[key] === undefined) {
      return undefined
    }

    /**
     * Access the properties of the class.
     */
    if (typeof provider[key] !== 'function') {
      return provider[key]
    }

    return new Proxy(provider[key], { apply })
  }
}

class Test {
  num = 0

  set(num) {
    this.num = this.num + num

    return this
  }

  get() {
    return this.num
  }
}

const TestFacade = Facade.createFacadeFor(Test)

console.log(TestFacade.set(10).set(20).get()) // 30
console.log(TestFacade.set(5).set(5).get()) // 10

const testInstance = TestFacade.set(10)

console.log(testInstance.num) // 10
console.log(testInstance.get()) // 10
console.log(testInstance.set(10).get()) // 20

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 João Lenon