isset-php

Safe and simple PHP isset() for JavaScript

Usage no npm install needed!

<script type="module">
  import issetPhp from 'https://cdn.skypack.dev/isset-php';
</script>

README

isset-php

Pipeline Status JavaScript Style Guide

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.

npm install --save isset-php

Description

TypeScript function signature:

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

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().

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:

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

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.

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:

// 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...

// 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:

// 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:

// 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