UNPKG

isset-php

Version:

Safe and simple PHP isset() for JavaScript

233 lines (163 loc) 6.57 kB
# isset-php [![Pipeline Status](https://gitlab.com/enom/isset-php/badges/master/pipeline.svg)](https://gitlab.com/enom/isset-php/pipelines/latest) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) Safe and simple implementation of PHP `isset()` for JavaScript. ## Installation This is a zero dependency library that is safe from both *ReferenceError: is not defined* and *TypeError: cannot read property of undefined* exceptions. ```sh npm install --save isset-php ``` ## Description [TypeScript](https://www.typescriptlang.org/) function signature: ```ts isset(accessor: Function, ...accessors: Function[]): boolean; ``` Determine if a variable is considered set, this means if a variable is declared and is different than `null` or `undefined`. If a variable has been unset with the `delete` keyword, it is no longer considered to be set. `isset()` will return `false` when checking a variable that has been assigned to `null` or `undefined`. Also note that a null character ("\0") is not equivalent to the JavaScript `null` constant. If multiple parameters are supplied then `isset()` will return `true` only if all of the parameters are considered set. Evaluation goes from left to right and stops as soon as an unset variable is encountered. ## Parameters **accessor** Accessor function returning the variable to be checked. **accessors** Further accessor functions. ## Return Values Returns `true` if all accessors return a value that is not `null` or `undefined`. `false` otherwise. ## Examples ### Example #1 isset() Examples ```js const isset = require('isset-php') let val = '' // This will evaluate to true so the text will be printed. if (isset(() => val)) { console.log('This val is set so I will print.') } // Less compact but still viable except when trying to use `this` context if (isset(function () { return val })) { console.log('This val is set so I will also print.') } ``` In the next examples we'll use `console.log()` to output the return value of `isset()`. ```js let a = 'test' let b = 'anothertest' console.log(isset(() => a)) // true console.log(isset(() => a, () => b)) // true // This might throw a parsing error: Deleting local variable in strict mode delete a console.log(isset(() => a)) // false console.log(isset(() => a, () => b)) // false let foo = null console.log(isset(() => foo)) // false ``` This also work for elements in objects: ```js let a = { test: 1, hello: null, pie: { a: 'apple' } } console.log(isset(() => a.test)) // true console.log(isset(() => a.foo)) // false console.log(isset(() => a.hello)) // false // The key 'hello' equals null so is considered unset. If you want to check for // null key values then try: console.log(Object.prototype.hasOwnProperty.call(a, 'hello')) // true // Checking deeper object values console.log(isset(() => a.pie.a)) // true console.log(isset(() => a.pie.b)) // false console.log(isset(() => a.cake.a.b)) // false ``` ### Example #2 isset() on String Offsets ```js const expectedArrayGotString = 'somestring' console.log(isset(() => expectedArrayGotString.some_key)) console.log(isset(() => expectedArrayGotString[0])) console.log(isset(() => expectedArrayGotString['0'])) console.log(isset(() => expectedArrayGotString[0.5])) console.log(isset(() => expectedArrayGotString['0.5'])) console.log(isset(() => expectedArrayGotString['0 Mostel'])) ``` Output of the above example in PHP 5.4 and above: ``` false true true true <-- PHP casts 0.5 to 0 for string offsets and throws a notice false false ``` Output of the above example in JavaScript: ``` false true true false <-- JS does not cast 0.5 to 0 false false ``` This is the only caveat with the JavaScript port as this behaviour is not emulated. --- # Explanation Pulled from [StackOverflow](https://stackoverflow.com/questions/2281633/javascript-isset-equivalent/46256973#46256973). ## PHP Note that in PHP you can reference any variable at any depth - even trying to access a non-array as an array will return a simple `true` or `false`: ```js // Referencing an undeclared variable isset($some); // false $some = 'hello'; // Declared but has no depth(not an array) isset($some); // true isset($some['nested']); // false $some = ['nested' => 'hello']; // Declared as an array but not with the depth we're testing for isset($some['nested']); // true isset($some['nested']['deeper']); // false ``` ## JavaScript In JavaScript, we don't have that freedom, we'll always get an error if we do the same because JS is immediately attempting to access the value of `deeper` before we can wrap it in our `isset()` function so... ```js // Common pitfall answer(ES6 arrow function) const isset = (ref) => typeof ref !== 'undefined' // Same as above function isset (ref) { return typeof ref !== 'undefined' } // Referencing an undeclared variable will throw an error, so no luck here isset(some) // Error: some is not defined // Defining a simple object with no properties - so we aren't defining // the property `nested` let some = {} // Simple checking if we have a declared variable isset(some) // true // Now trying to see if we have a top level property, still valid isset(some.nested) // false // But here is where things fall apart: trying to access a deep property // of a complex object; it will throw an error isset(some.nested.deeper) // Error: Cannot read property 'deeper' of undefined // ^^^^^^ undefined ``` More failing alternatives: ```js // Any way we attempt to access the `deeper` property of `nested` will // throw an error some.nested.deeper.hasOwnProperty('value') // Error // ^^^^^^ undefined // Similar to the above but safe from objects overriding `hasOwnProperty` Object.prototype.hasOwnProperty.call(some.nested.deeper, 'value') // Error // ^^^^^^ undefined // Same goes for typeof typeof some.nested.deeper !== 'undefined' // Error // ^^^^^^ undefined ``` And some working alternatives that can get redundant fast: ```js // Wrap everything in try...catch try { if (isset(some.nested.deeper)) { // ... } } catch (e) {} try { if (some.nested.deeper !== undefined && some.nested.deeper !== null) { // ... } } catch (e) {} // Or by chaining all of the isset which can get long isset(some) && isset(some.nested) && isset(some.nested.deeper) // false // ^^^^^^ returns false so the next isset() is never run ```