Last active
November 20, 2025 15:59
-
-
Save loilo/4d385d64e2b8552dcc12a0f5126b6df8 to your computer and use it in GitHub Desktop.
Revisions
-
loilo revised this gist
Oct 30, 2024 . 2 changed files with 2 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,4 +1,4 @@ export function magicMethods (clazz) { // A toggle switch for the __isset method // Needed to control "prop in instance" inside of getters let issetEnabled = true This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -19,7 +19,7 @@ foo.bar // "Bar" foo.baz // "[[baz]]" ``` If you're using a JavaScript transpiler like Babel or TypeScript with decorators enabled, you can also use the `magicMethods` function as a decorator: ```javascript @magicMethods -
loilo revised this gist
Sep 22, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -72,7 +72,7 @@ They are either not necessary or not practical: Yes: ```javascript // `Bar` inherits magic methods from `Foo` class Bar extends Foo {} ``` -
loilo revised this gist
Sep 21, 2020 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -59,7 +59,7 @@ They are either not necessary or not practical: * `__construct()` is not needed, there's JavaScript's `constructor` already. * `__destruct()`: There is no mechanism in JavaScript to hook into object destruction. * `__call()`: Functions are first-class objects in JavaScript. That means that (as opposed to PHP) an object's methods are just regular properties in JavaScript and must first be obtained via `__get()` to be invoked subsequently. So to implement `__call()` in JavaScript, you'd simply have to implement `__get()` and return a function from there. * `__callStatic()`: As in `__call()`, but with `__getStatic()`. * `__sleep()`, `__wakeup()`: There's no builtin serialization/unserialization in JavaScript. You could use `JSON.stringify()`/`JSON.parse()`, but there's no mechanism to automatically trigger any methods with that. * `__toString()` is already present in JavaScript's `toString()` -
loilo revised this gist
Sep 21, 2020 . 2 changed files with 14 additions and 31 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,9 +6,9 @@ function magicMethods (clazz) { const classHandler = Object.create(null) // Trap for class instantiation classHandler.construct = (target, args, receiver) => { // Wrapped class instance const instance = Reflect.construct(target, args, receiver) // Instance traps const instanceHandler = Object.create(null) @@ -17,15 +17,15 @@ function magicMethods (clazz) { // Catches "instance.property" const get = Object.getOwnPropertyDescriptor(clazz.prototype, '__get') if (get) { instanceHandler.get = (target, name, receiver) => { // We need to turn off the __isset() trap for the moment to establish compatibility with PHP behaviour // PHP's __get() method doesn't care about its own __isset() method, so neither should we issetEnabled = false const exists = Reflect.has(target, name) issetEnabled = true if (exists) { return Reflect.get(target, name, receiver) } else { return get.value.call(target, name) } @@ -36,9 +36,9 @@ function magicMethods (clazz) { // Catches "instance.property = ..." const set = Object.getOwnPropertyDescriptor(clazz.prototype, '__set') if (set) { instanceHandler.set = (target, name, value, receiver) => { if (name in target) { Reflect.set(target, name, value, receiver) } else { return target.__set.call(target, name, value) } @@ -50,7 +50,7 @@ function magicMethods (clazz) { const isset = Object.getOwnPropertyDescriptor(clazz.prototype, '__isset') if (isset) { instanceHandler.has = (target, name) => { if (!issetEnabled) return Reflect.has(target, name) return isset.value.call(target, name) } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -69,33 +69,16 @@ They are either not necessary or not practical: * `__debugInfo()`: There's no way to hook into `console.log()` output. ## Can I extend a class with Magic Methods on it? Yes: ```javascript // `Bar` instances will have the same magic methods as `Foo` instances class Bar extends Foo {} ``` Or, if class `Bar` contains magic methods itself: ``` const Bar = magicMethods(class Bar extends Foo { // You may define `Bar`'s magic methods here }) ``` -
loilo revised this gist
Apr 1, 2019 . 2 changed files with 24 additions and 5 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -71,23 +71,23 @@ function magicMethods (clazz) { // __getStatic() // Catches "class.property" if (Object.getOwnPropertyDescriptor(clazz, '__getStatic')) { classHandler.get = (target, name, receiver) => { if (name in target) { return target[name] } else { return target.__getStatic.call(receiver, name) } } } // __setStatic() // Catches "class.property = ..." if (Object.getOwnPropertyDescriptor(clazz, '__setStatic')) { classHandler.set = (target, name, value, receiver) => { if (name in target) { return target[name] } else { return target.__setStatic.call(receiver, name, value) } } } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -69,7 +69,7 @@ They are either not necessary or not practical: * `__debugInfo()`: There's no way to hook into `console.log()` output. ## Can I extend a class with Magic Methods on it? Yes, to a certain extent: ```javascript class Bar extends Foo {} @@ -79,4 +79,23 @@ class Bar extends Foo {} const Bar = magicMethods(class Bar extends Foo { // ... }) ``` Unfortunately though, you cannot access properties from the child class in the parent class: ```javascript const Foo = magicMethods(class Foo { __get() { return this.bar() } }) class Bar extends Foo { bar() { return 'value' } } // This will *not* call B's bar() method but instead throw a TypeError: (new Bar).something ``` -
loilo revised this gist
Jul 22, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -32,7 +32,7 @@ class Foo { Given a class `Class` and an `instance` of it, the following are the magic methods supported by this script: ### `__get(name)` Called when trying to access `instance[name]` where `name` is not an existing property of `instance`. **Attention:** As in PHP, the check if `name` exists in `instance` does not use any custom `__isset()` methods. -
loilo revised this gist
Mar 1, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,7 +6,7 @@ function magicMethods (clazz) { const classHandler = Object.create(null) // Trap for class instantiation classHandler.construct = (target, args) => { // Wrapped class instance const instance = new clazz(...args) -
loilo revised this gist
Feb 28, 2018 . 1 changed file with 1 addition and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,6 +1,6 @@ function magicMethods (clazz) { // A toggle switch for the __isset method // Needed to control "prop in instance" inside of getters let issetEnabled = true const classHandler = Object.create(null) -
loilo revised this gist
Feb 28, 2018 . 1 changed file with 11 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -69,4 +69,14 @@ They are either not necessary or not practical: * `__debugInfo()`: There's no way to hook into `console.log()` output. ## Can I extend a class with Magic Methods on it? Yes. ```javascript class Bar extends Foo {} // Or, if class Bar contains Magic Methods itself: const Bar = magicMethods(class Bar extends Foo { // ... }) ``` -
loilo revised this gist
Feb 28, 2018 . 1 changed file with 4 additions and 1 deletion.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -66,4 +66,7 @@ They are either not necessary or not practical: * `__invoke()`: JavaScript will throw an error if you'll try to invoke a non-function object, no way to avoid that. * `__set_state()`: There's nothing like `var_export()` in JavaScript. * `__clone()`: There's no builtin cloning functionality in JavaScript that can be hooked into. * `__debugInfo()`: There's no way to hook into `console.log()` output. ## Can I extend a class with Magic Methods on it? Yes. -
loilo renamed this gist
Feb 28, 2018 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
loilo created this gist
Feb 28, 2018 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,96 @@ function magicMethods (clazz) { // A toggle switch for the __isset method // Needed to be control "prop in instance" inside of getters let issetEnabled = true const classHandler = Object.create(null) // Trap for class instantiation classHandler.construct = (...args) => { // Wrapped class instance const instance = new clazz(...args) // Instance traps const instanceHandler = Object.create(null) // __get() // Catches "instance.property" const get = Object.getOwnPropertyDescriptor(clazz.prototype, '__get') if (get) { instanceHandler.get = (target, name) => { // We need to turn off the __isset() trap for the moment to establish compatibility with PHP behaviour // PHP's __get() method doesn't care about its own __isset() method, so neither should we issetEnabled = false const exists = name in target issetEnabled = true if (exists) { return target[name] } else { return get.value.call(target, name) } } } // __set() // Catches "instance.property = ..." const set = Object.getOwnPropertyDescriptor(clazz.prototype, '__set') if (set) { instanceHandler.set = (target, name, value) => { if (name in target) { target[name] = value } else { return target.__set.call(target, name, value) } } } // __isset() // Catches "'property' in instance" const isset = Object.getOwnPropertyDescriptor(clazz.prototype, '__isset') if (isset) { instanceHandler.has = (target, name) => { if (!issetEnabled) return name in target return isset.value.call(target, name) } } // __unset() // Catches "delete instance.property" const unset = Object.getOwnPropertyDescriptor(clazz.prototype, '__unset') if (unset) { instanceHandler.deleteProperty = (target, name) => { return unset.value.call(target, name) } } return new Proxy(instance, instanceHandler) } // __getStatic() // Catches "class.property" if (Object.getOwnPropertyDescriptor(clazz, '__getStatic')) { classHandler.get = (target, name) => { if (name in target) { return target[name] } else { return target.__getStatic(name) } } } // __setStatic() // Catches "class.property = ..." if (Object.getOwnPropertyDescriptor(clazz, '__setStatic')) { classHandler.set = (target, name, value) => { if (name in target) { return target[name] } else { return target.__setStatic(name, value) } } } return new Proxy(clazz, classHandler) } This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,69 @@ # JavaScript Magic Methods This script implements some of PHP's magic methods for JavaScript classes, using a [Proxy](https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Proxy). ## Example You can use it like this: ```javascript const Foo = magicMethods(class Foo { constructor () { this.bar = 'Bar' } __get (name) { return `[[${name}]]` } }) const foo = new Foo foo.bar // "Bar" foo.baz // "[[baz]]" ``` If you're using a JavaScript transpiler like Babel with decorators enabled, you can also use the `magicMethods` function as a decorator: ```javascript @magicMethods class Foo { // ... } ``` ## Supported Magic Methods Given a class `Class` and an `instance` of it, the following are the magic methods supported by this script: ### `__get(name)` Called when trying to access `instance[name]` where `name` is neither set as a property of `instance`. **Attention:** As in PHP, the check if `name` exists in `instance` does not use any custom `__isset()` methods. ### `__set(name, value)` Called when trying to do `instance[name] = ...` where `name` is neither set as a property of `instance`. ### `__isset(name)` Called when trying to check existance of `name` by calling `name in instance`. ### `__unset(name)` Called when trying to unset property `name` by calling `delete instance[name]`. ## Additional Methods The following magic methods are made available by this script, but are not supported in PHP: ### `static __getStatic(name)` Like `__get()`, but in the `Class` instead of the `instance`. ### `static __setStatic(name, value)` Like `__set()`, but in the `Class` instead of the `instance`. ## Why is Magic Method `X` not supported? They are either not necessary or not practical: * `__construct()` is not needed, there's JavaScript's `constructor` already. * `__destruct()`: There is no mechanism in JavaScript to hook into object destruction. * `__call()`: As opposed to PHP, methods are just like properties in JavaScript and are first obtained via `__get()`. To implement `__call()`, you simply return a function from `__get()`. * `__callStatic()`: As in `__call()`, but with `__getStatic()`. * `__sleep()`, `__wakeup()`: There's no builtin serialization/unserialization in JavaScript. You could use `JSON.stringify()`/`JSON.parse()`, but there's no mechanism to automatically trigger any methods with that. * `__toString()` is already present in JavaScript's `toString()` * `__invoke()`: JavaScript will throw an error if you'll try to invoke a non-function object, no way to avoid that. * `__set_state()`: There's nothing like `var_export()` in JavaScript. * `__clone()`: There's no builtin cloning functionality in JavaScript that can be hooked into. * `__debugInfo()`: There's no way to hook into `console.log()` output.