Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ in a consistent and safe way and can be included as a composer dependency.
* [`SuperGlobals::get_sanitized_superglobal( string $superglobal )`](#superglobalsget_sanitized_superglobal-string-superglobal-)
* [`SuperGlobals::get_server_var( $var, $default = null )`](#superglobalsget_server_var-var-default--null-)
* [`SuperGlobals::get_var( $var, $default = null )`](#superglobalsget_var-var-default--null-)
* [`SuperGlobals::sanitize_deep( &$value )`](#superglobalssanitize_deep-value-)
* [`SuperGlobals::sanitize_deep( $value )`](#superglobalssanitize_deep-value-)

## Installation

Expand Down Expand Up @@ -51,7 +51,7 @@ use StellarWP\SuperGlobals\SuperGlobals;
// Get $_GET['post_id']
$var = SuperGlobals::get_get_var( 'post_id' );

// Provide a default value if the variable is not set.
// Provide a default value if the variable is not set or fails to sanitize.
$var = SuperGlobals::get_get_var( 'post_id', 12 );
```

Expand All @@ -67,7 +67,7 @@ use StellarWP\SuperGlobals\SuperGlobals;
// Get $_POST['post_id']
$var = SuperGlobals::get_post_var( 'post_id' );

// Provide a default value if the variable is not set.
// Provide a default value if the variable is not set or fails to sanitize.
$var = SuperGlobals::get_post_var( 'post_id', 12 );
```

Expand Down Expand Up @@ -133,7 +133,7 @@ use StellarWP\SuperGlobals\SuperGlobals;
// Get $_SERVER['REQUEST_URI']
$var = SuperGlobals::get_server_var( 'REQUEST_URI' );

// Provide a default value if the variable is not set.
// Provide a default value if the variable is not set or fails to sanitize.
$var = SuperGlobals::get_server_var( 'REQUEST_URI', 'http://example.com' );
```

Expand All @@ -146,17 +146,21 @@ Gets a value from `$_REQUEST`, `$_POST`, or `$_GET` and recursively sanitizes it
```php
use StellarWP\SuperGlobals\SuperGlobals;

// Get $_REQUEST['post_id'] or $_POST['post_id'] or $_GET['post_id'], wherever it lives
// Get $_REQUEST['post_id'] or $_POST['post_id'] or $_GET['post_id'], wherever it lives.
$var = SuperGlobals::get_var( 'post_id' );

// Provide a default value if the variable is not set.
// Provide a default value if the variable is not set or fails to sanitize.
$var = SuperGlobals::get_var( 'post_id', 12 );
```

### `SuperGlobals::sanitize_deep( &$value )`
### `SuperGlobals::sanitize_deep( $value )`

Sanitizes a value recursively using appropriate sanitization functions depending on the type of the value.

> [!IMPORTANT]
> During deep sanitization, any nested array value that cannot be safely sanitized is **removed entirely** (its key is unset).
> This is intentional: missing data is safer and more predictable than retaining invalid or corrupted values.

#### Example

```php
Expand Down
64 changes: 49 additions & 15 deletions src/SuperGlobals/SuperGlobals.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ public static function get_server_var( $var, $default = null ) {
}

$unsafe = Arr::get_in_any( $data, $var, $default );
return static::sanitize_deep( $unsafe );
$safe = static::sanitize_deep( $unsafe );

return $safe === null ? $default : $safe;
}

/**
Expand All @@ -47,7 +49,9 @@ public static function get_server_var( $var, $default = null ) {
*/
public static function get_get_var( string $var, $default = null ) {
$unsafe = Arr::get( (array) $_GET, $var, $default );
return static::sanitize_deep( $unsafe );
$safe = static::sanitize_deep( $unsafe );

return $safe === null ? $default : $safe;
Copy link

@jonwaldstein jonwaldstein Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@defunctl I'm not sure returning the default value makes sense. It's really more philosophical though 😄 "why should the default value be returned if a value is set?", a default value is typically used when a value is not set - hence the word default. If the value is set then the default value doesn't really mean anything anymore.

It looks like the goal here is really to get the return value to match the intended type when validation fails to prevent type errors.

If that's the case, then how about either adding a param for $type or use gettype($value). Then, if validation fails you would return the empty version of that type instead of the value itself.

get_get_var('foo', 'bar', 'string')

// some kind of get empty $type function
return $safe === null ? '' : $safe;

Copy link
Contributor Author

@defunctl defunctl Feb 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonwaldstein That would get very complicated once you start involving arrays and nested values of different types.

I agree $default may no longer be the best variable name though, but I think that just came from how Laravel methods have similar functionality.

I am happy to rename it to $fallback if that helps?

The goal here is to sanitize values (based on the initial way the library was built), and values that can't be sanitized should not be included in sanitized output.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@defunctl yeah I agree that approach would take some time.

Would you consider a sanitization validation fail as a value not being set since it will ultimately be null?
That could be enough from a philosophical standpoint to persuade my stance on the intention of $default since a value is technically not being set and thus should return the default. I imagine in most cases that default value will be used as an empty placeholder anyway 😏

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jonwaldstein sanitization and validation are not the same thing though. This is just sanitizing and not validating.

But yes, I would said a failure or missing item is both null 😄

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, it's just trying to sanitize the value which could ultimately return null if it's not able to. I was just using the word validation for that scenario which is not technically correct lol. I think your approach makes sense then - carry on 😏

}

/**
Expand All @@ -64,7 +68,9 @@ public static function get_get_var( string $var, $default = null ) {
*/
public static function get_post_var( string $var, $default = null ) {
$unsafe = Arr::get( (array) $_POST, $var, $default );
return static::sanitize_deep( $unsafe );
$safe = static::sanitize_deep( $unsafe );

return $safe === null ? $default : $safe;
}

/**
Expand All @@ -81,7 +87,9 @@ public static function get_post_var( string $var, $default = null ) {
*/
public static function get_env_var( string $var, $default = null ) {
$unsafe = Arr::get( (array) $_ENV, $var, $default );
return static::sanitize_deep( $unsafe );
$safe = static::sanitize_deep( $unsafe );

return $safe === null ? $default : $safe;
}

/**
Expand Down Expand Up @@ -135,6 +143,7 @@ public static function get_raw_superglobal( string $superglobal ) {
*/
public static function get_sanitized_superglobal( string $superglobal ) {
$var = static::get_raw_superglobal( $superglobal );

return static::sanitize_deep( $var );
}

Expand Down Expand Up @@ -177,7 +186,9 @@ public static function get_var( $var, $default = null ) {
}

$unsafe = Arr::get_in_any( $requests, $var, $default );
return static::sanitize_deep( $unsafe );
$safe = static::sanitize_deep( $unsafe );

return $safe === null ? $default : $safe;
}

/**
Expand All @@ -190,27 +201,50 @@ public static function get_var( $var, $default = null ) {
* @param mixed $value The value, or values, to sanitize.
*
* @return mixed|null Either the sanitized version of the value, or `null` if the value is not a string, number or
* array.
* array. If the nested value can't be sanitized, it's removed.
*/
public static function sanitize_deep( &$value ) {
public static function sanitize_deep( $value ) {
if ( is_bool( $value ) ) {
return $value;
}

if ( is_string( $value ) ) {
$value = htmlspecialchars( $value );
return $value;
return htmlspecialchars(
$value,
ENT_QUOTES | ENT_SUBSTITUTE,
'UTF-8'
);
}

if ( is_int( $value ) ) {
$value = filter_var( $value, FILTER_VALIDATE_INT );
return $value;
return filter_var( $value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE );
}

if ( is_float( $value ) ) {
$value = filter_var( $value, FILTER_VALIDATE_FLOAT );
return $value;

// Drop NAN and INF.
if ( ! is_finite( $value ) ) {
return null;
}

return filter_var( $value, FILTER_VALIDATE_FLOAT, FILTER_NULL_ON_FAILURE );
}

if ( is_array( $value ) ) {
array_walk( $value, [ __CLASS__, 'sanitize_deep' ] );
return $value;
$sanitized = [];

foreach ( $value as $key => $item ) {
$clean = self::sanitize_deep( $item );

// Remove nested values that can't be sanitized.
if ( $clean === null ) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a controllable behavior. It's fine for this to have a default of dropping, but I would add a bool $drop_on_invalid = true parameter to control this. There are cases where it could make sense to not continue at all if any value is invalid.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lucatume as in throw an exception if we failed to sanitize a value?

continue;
}

$sanitized[ $key ] = $clean;
}

return $sanitized;
}

return null;
Expand Down
Loading