isset-php
Version:
Safe and simple PHP isset() for JavaScript
233 lines (163 loc) • 6.57 kB
Markdown
# isset-php
[](https://gitlab.com/enom/isset-php/pipelines/latest)
[](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
```