safely get array element value for defined and undefined indexes - php

I'm getting tired of writing ternary expressions to sanitize the data, things like:
$x = isset($array['idx']) ? $array['idx'] : null;
// and
$x = !empty($array['idx']) ? $array['idx'] : null;
Is there a native way or ZF accessor/filter to get array element value for some given array without:
disabling error_reporting
ternary isset/empty check
error control operator #
creating my own global function or application accessor/filter
Something like:
$x = get_if_set($array['idx']);
// or
$x = Zend_XXX($array, 'idx')

PHP7 introduced the null coalesce operator ??. Assuming you're lucky enough to be running it, you can just do
$x = $array['idx'] ?? null;

Since PHP 7.0
Things are much more easy - thanks to Andrea Faulds and Nikita Popov for - the Null Coalescing Operator ??Docs, Migration, RFC:
$x = $array['idx'] ?? NULL;
or with a $default:
$x = $array['idx'] ?? $default ?? NULL;
Like isset or empty it gives no warning and the expression falls through to the right (if unset). if set and not NULL the value is taken. This is also how $default in the previous example works, always, even if undefined.
Since PHP 7.4
Thanks to Midori Kocak - is the Null Coalescing Assignment Operator ??=Docs, RFC (which was one of the things I missed after ??) allows to assign default values directly:
$array['idx'] ??= null;
$x = $array['idx'];
I don't use it by far as often as ??, but it is good to know about it, especially if while break up data-handling logic and want to have defaults early.
Original old answer
As far as you only need NULL as "default" value, you can make use of the error suppression operator:
$x = #$array['idx'];
Criticism: Using the error suppression operator has some downsides. First it uses the error suppression operator, so you can not easily recover issues if that part of the code has some. Additionally the standard error situation if undefined does pollute looking for screams. Your code is not as expressing itself as precise as it could be. Another potential issue is to make use of an invalid index value, e.g. injecting objects for indexes etc.. This would get unnoticed.
It will prevent warnings. However if you like to allow other default values as well, you can encapsulate the access to an array offset via the ArrayAccess interface:
class PigArray implements ArrayAccess
{
private $array;
private $default;
public function __construct(array $array, $default = NULL)
{
$this->array = $array;
$this->default = $default;
}
public function offsetExists($offset)
{
return isset($this->array[$offset]);
}
public function offsetGet($offset)
{
return isset($this->array[$offset])
? $this->array[$offset]
: $this->default
;
}
public function offsetSet($offset, $value)
{
$this->array[$offset] = $value;
}
public function offsetUnset($offset)
{
unset($this->array[$offset]);
}
}
Usage:
$array = array_fill_keys(range('A', 'C'), 'value');
$array = new PigArray($array, 'default');
$a = $array['A']; # string(13) "value"
$idx = $array['IDX']; # NULL "default"
var_dump($a, $idx);
Demo: https://eval.in/80896

I would say, use a helper function to interface with your array.
function getValue($key, $arr, $default=null) {
$pieces = explode('.', $key);
$array = $arr;
foreach($pieces as $array_key) {
if(!is_null($array) && is_array($array) && array_key_exists($array_key, $array)) {
$array = $array[$array_key];
}
else {
$array = null;
break;
}
}
return is_null($array) ? $default : $array;
}
$testarr = [
['foobar' => 'baz'],
['active' => false]
];
$output = getValue('0.foobar',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('0',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('1.active',$testarr,'NOT FOUND');
var_dump($output);
$output = getValue('i.do.not.exist',$testarr,'NOT FOUND');
var_dump($output);
This way you can provide a default value instead of null to your liking, you don't need to reinstate the array as another object and you can request any value as deeply nested as you wish without having to check the "parent" arrays.
https://ideone.com/11jtzj

Declare your variables and give them some initial value.
$x = NULL;
$y = 'something other than NULL';
now if you have an array $myArray that has x and y keys the extract function will overwrite the initial values (you can also configure it not to)
$myArray['x'] = 'newX';
extract($myArray);
//$x is now newX
if no keys are present the initial values for variables remain. It will also put other array keys in respective variables.

Related

How to make a PHP structure, that doesn't complain about requests of undefined properties?

I have a bunch of optional settings and I'm sick of checking for isset and property_exists.
In Laravel, if I ask for a property that does not exist on a model or request, I get null and no complaints (errors). How can I do the same for my data structure.
If I try array, I can't do simple $settings['setting13'], I have to either pre-fill it all with nulls or do isset($settings['setting13']) ? $settings['setting13'] : '' or $settings['setting13'] ?? null. If I try an object (new \stdClass()), $settings->setting13 still gives me a warning of Undefined property.
How can I make a class such that it responds null or an empty string whenever it is asked for a property that it doesn't have?
Simply do what Laravel does, create a class that deals with your data structure which returns a value if key exists, and something else if it doesn't.
I'll illustrate with an example class (this class supports the "dot notation" of accessing array keys):
class MyConfigClass
{
protected $data;
public function __construct(array $data)
{
$this->data = $data;
}
public function get($path = '', $default = null)
{
if(!is_string($path))
{
return $default;
}
// There's a dot in the path, traverse the array
if(false !== strpos('.', $path))
{
// Find the segments delimited by dot
$segments = explode('.', $path);
$result = $this->data;
foreach($segments as $segment)
{
if(isset($result[$segment]))
{
// We have the segment
$result = $result[$segment];
}
else
{
// The segment isn't there, return default value
return $default;
}
}
return $result;
}
// The above didn't yield a result, check if the key exists in the array and if not - return default
return isset($this->data[$path]) ? $this->data[$path] : $default;
}
}
Use:
$my_structure = [
'url' => 'www.stackoverflow.com',
'questions' => [
'title' => 'this is test title'
]
];
$config = new MyConfigClass($my_structure);
echo $config->get('url'); // echoes www.stackoverflow.com
echo $config->get('questions.title'); // echoes this is test title
echo $config->get('bad key that is not there'); // returns null
There is also a possibility to create wrapper as Jon Stirling mentioned in a comments. This approach will allow to keep code clean and also add functionality via inheritance.
<?php
class myArray implements ArrayAccess {
private $container;
function __construct($myArray){
$this->container = $myArray;
}
public function offsetSet($offset, $value) {
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
public function offsetExists($offset) {
return isset($this->container[$offset]);
}
public function offsetUnset($offset) {
unset($this->container[$offset]);
}
public function offsetGet($offset) {
return isset($this->container[$offset]) ? $this->container[$offset] : null;
}
}
$settings = array("setting1"=>1,"setting2"=>2,"setting3"=>3);
$arr = new myArray($settings);
echo $arr['setting1'];
echo "<br>";
echo $arr['setting3'];
echo "<br>";
echo $arr['setting2'];
echo "<br>";
echo "------";
echo "<br>";
echo $arr['setting4'] ?:"Value is null";
!empty($settings['setting13']) ? $settings['setting13'] : ''
can be replaced with
$settings['setting13'] ?: ''
as long as whatever you want to print and whatever you want to check exists is the same expression. It's not the cleanest thing ever - which would be to check the existence of anything - but it's reasonably clear and can be chained :
echo ($a ?: $b ?: $c ? $default ?: '');
However, you are not the first who are "sick of checking for isset and property_exists, it's just that we still have to do it, or else we get unexpected results when we expect it the least.
It's not about saving time typing code, it's about saving time not debugging.
EDIT : As pointed in the comments, I wrote the first line with isset() instead of !empty(). Since ?: returns the left operand if it's equal to true, it's of course uncompatible with unchecked variables, you have at least to check for existence beforehand. It's emptiness that can be tested.
The operator that returns its left operand if it exists and is different from NULL is ??, which can be chained the same way ?: does.
Admittedly not the best way to do this, but you can use the error suppressor in php like this:
$value = #$settings['setting13'];
This will quitely set$value to NULL if $settings['setting13'] is not set and not report the undefined variable notice.
As for objects, you should just calling for attributes that are not defined in class.

PHP - Is there a way to get a variable value if it is set?

I always find myself writing something like:
if(isset($var))
{
DoSomethingWith($var);
}
else
{
DoSomethingWith(null);
}
or
if(isset($arr["key"]))
{
DoSomethingWith($arr["key"];
}
else
{
DoSomethingWith(null);
}
My question is exacly this:
Is there a way to write a get_var_if_set() function so that you can simply write...
DoSomethingWith(get_var_if_set($var));
/// and
DoSomethingWith(get_var_if_set($arr["key"]));
....WITHOUT notifying if $var doesn't exists or that $arr doesn't have a set value for "key"?
I guess it should be something like this:
function get_var_if_set($a)
{
return (isset($a) ? $a : null);
}
But that doesn't work because calling get_var_if_set() with unset variables will always generate a Notice, so it might need a little magic.
Thanks all folks.
Edit
A user who deleted his answer suggested to pass variables by reference, as PHP will pass null if the $variable doesn't exist.
So that would be perfect, take a look at these solutions (which might probably be equivalent):
function get_var_if_set(&$a) { return (isset($a) ? $a : null); }
function get_var_if_set(&$a) { return $a ?? null; } // Only in PHP 7.0+ (Coalescing operator)
Note: Coalescing operator suggested by Koray Küpe
The problem as you can see is that they initialize the passed variables somehow in the return statement.
We don't want that.
If you use PHP 7.0+ you can use null coalescing operator.
return $a ?? null;
Note: The operator checkes whether the variable exist and not empty. So, if the variable is already set but empty, it will return the null variable already.
The problem is not the var itself, but the key of the array key in this:
DoSomethingWith(get_var_if_set($arr["key"]))
So the only solution is to check for the array and the key you're looking for.
function get_var_if_set($a, $key = null)
{
if(is_array($a) && array_key_exists($key, $array)) {
return $a[$key];
} else {
return (isset($a) ? $a : null);
}
}

Is there a shortcut for the "isset construct"?

I'm writing quite often this line of code:
$myParam = isset($params['myParam']) ? $params['myParam'] : 'defaultValue';
Typically, it makes the line very long for nested arrays.
Can I make it shorter?
function getOr(&$var, $default) {
if (isset($var)) {
return $var;
} else {
return $default;
}
}
$myParam = getOr($params['myParam'], 'defaultValue');
Be sure to pass the variable by reference though, otherwise the code will produce a E_NOTICE. Also the use of if/else instead of a ternary operator is intentional here, so the zval can be shared if you are using PHP < 5.4.0RC1.
PHP 7 will contain ?? operator that does exactly that.
See https://wiki.php.net/rfc/isset_ternary, example:
// Fetches the request parameter user and results in 'nobody' if it doesn't exist
$username = $_GET['user'] ?? 'nobody';
// equivalent to: $username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
Yes, by making a proxy function, but is it really worth it?
Also, isset is a language construct, so wrapping it in a proxy function will degrade performance, although the degradation will likely be less than trivial (not even really worth mentioning.)
This is what I use:
function getindex($arr, $index, $default = null) {
return isset($arr[$index]) ? $arr[$index] : $default;
}
As of PHP 5.3 you can use:
$myParam = $params['myParam'] ?: 'defaultValue';
Note, however, that $params['myParam'] and isset($params['myParam']) are not 100% the same.
I'm using little this little magic class which works as variable
class Post() {
private $post = Array();
public function __construct() {
$this->post = $_POST;
}
public function __get($name) {
return #$this->post[$name];
}
public function __set($name, $value) {
return $this->post[$name] = $value;
}
public function __call($function, $params) {
if(isset($this->post[$function])) {
return $this->post[$function];
} else {
$this->post[$function] = $params[0];
return $params[0];
}
}
}
$post = new Post();
then in document you can use it easily as any other variable so for example $post->name $post->somelist[2] or with default value $post->name("John Doe") and after that you got it returned as well as stored.
I know this doesn't shorten anything up for you but thought I'd just share this, I use this alot in my applications to make sure something is set and has a value.
function is_blank($var = NULL){
return empty($var) && !is_numeric($var) && !is_bool($var);
}
function chk_var($var = NULL){
return (isset($var) && !is_null($var) && !is_blank($var));
}
Then...
if(chk_var($myvar)){ ... }
No. Unfortunately, you can't. Not in a decent way. You'll at least have to give in on performance.
Update: since PHP7, ?? will do just that. See https://wiki.php.net/rfc/isset_ternary
You if you have to do it often, you are probably missing the point.
In fact, variables should be defined before use.
So, there oughtn't be a case when you have your param undefined.
Just create a default params file, and initialize every your variable.
$params['myParam'] = 'defaultValue';
later it can be changed under some circunstances but it never be undefined.
Got the idea?

Is there a better way to check POSTed variables in PHP?

I find in my PHP pages I end up with lines and lines of code that look like this:
$my_id = isset($_REQUEST['my_id']) ? $_REQUEST['my_id'] : '';
$another_var = isset($_REQUEST['another_var']) ? $_REQUEST['another_var'] : 42;
...
Is there a better, more concise, or more readable way to check this array and assign them to a local variable if they exist or apply a default if they don't?
EDIT: I don't want to use register_globals() - I'd still have the isset problem anyway.
How about wrapping it in a function?
<?php
function getPost($name, $default = null) {
return isset($_POST[$name]) ? $_POST[$name] : $default;
}
a better method might be to create a singleton/static class to abstract away the details of checking the request data.
Something like:
class Request {
private $defaults = array();
private static $_instance = false;
function getInstance () {
if (!self::$_instance) {
$c = __CLASS__;
self::$_instance = new $c;
}
return self::$_instance;
}
function setDefaults($defaults) {
$this->defaults = $defaults;
}
public function __get($field) {
if (isset($_REQUEST[$field]) && !empty($_REQUEST[$field])) {
return $_REQUEST['field'];
} elseif (isset($this->defaults[$field])) {
return $this->defaults[$field];
} else {
return ''; # define a default value here.
}
}
}
you can then do:
# get an instance of the request
$request = Request::getInstance();
# pass in defaults.
$request->setDefaults(array('name'=>'Please Specify'));
# access properties
echo $request->name;
echo $request->email;
I think this makes your individual scripts loads cleaner and abstracts away the validation etc. Plus loads of scope with this design to extend it/add alternate behaviours, add more complicated default handling etc etc.
First, use $_POST for POSTed variables. $_REQUEST is a mashup of many different incoming variables, not just $_POST and could cause problems.
One solution for your question would be to create a function that handles the isset() logic.
function ForceIncomingValue($Key, $Default) {
if (!isset($_POST[$Key]))
return $Default;
else return $_POST[$Key];
}
first of all, NEVER use the $_REQUEST variable, it'll lead to bugs and other problems during development
function getPOST($key) {
if(isset($_POST[$key])) {
return $_POST[$key];
}
}
note that this code leaves the variable empty when $_POST[$key] was not set
you could also adapt that code to enable you to instead provide you with a (sensible) default when the value could not be loaded.
function getPOST($key, $default = NULL) {
if(isset($_POST[$key])) {
return $_POST[$key];
} else {
return $default;
}
}
Is the set of variables you're expecting known at the time of the script's writing, or do you want to do this for an arbitrary set of values? If the former is true, you could do something like this:
# This array would hold the names of all the variables you're expecting
# and a default value for that variable name
$variableNames = array (...);
foreach ($variableNames as $key => $default) {
if (isset ($_REQUEST[$key])) $$key = $_REQUEST[$key];
else $$key = $default;
}
Basically, this takes advantage of PHP's ability to evaluate variables to create other variables (hence the double-dollar for $$key--this means create a new variable whose name is the value of $key).
I haven't yet come up with a good solution to the latter situation.
PHP's null coalescing operator!
$username = $_GET['user'] ?? 'nobody';
For a lot of variables, with a requirement check, anyone is free to use my expect function.

Coalesce function for PHP?

Many programming languages have a coalesce function (returns the first non-NULL value, example). PHP, sadly in 2009, does not.
What would be a good way to implement one in PHP until PHP itself gets a coalesce function?
There is a new operator in php 5.3 which does this: ?:
// A
echo 'A' ?: 'B';
// B
echo '' ?: 'B';
// B
echo false ?: 'B';
// B
echo null ?: 'B';
Source: http://www.php.net/ChangeLog-5.php#5.3.0
PHP 7 introduced a real coalesce operator:
echo $_GET['doesNotExist'] ?? 'fallback'; // prints 'fallback'
If the value before the ?? does not exists or is null the value after the ?? is taken.
The improvement over the mentioned ?: operator is, that the ?? also handles undefined variables without throwing an E_NOTICE.
First hit for "php coalesce" on google.
function coalesce() {
$args = func_get_args();
foreach ($args as $arg) {
if (!empty($arg)) {
return $arg;
}
}
return NULL;
}
http://drupial.com/content/php-coalesce
I really like the ?: operator. Unfortunately, it is not yet implemented on my production environment. So I use the equivalent of this:
function coalesce() {
return array_shift(array_filter(func_get_args()));
}
It is worth noting that due to PHP's treatment of uninitalised variables and array indices, any kind of coalesce function is of limited use. I would love to be able to do this:
$id = coalesce($_GET['id'], $_SESSION['id'], null);
But this will, in most cases, cause PHP to error with an E_NOTICE. The only safe way to test the existence of a variable before using it is to use it directly in empty() or isset(). The ternary operator suggested by Kevin is the best option if you know that all the options in your coalesce are known to be initialised.
Make sure you identify exactly how you want this function to work with certain types. PHP has a wide variety of type-checking or similar functions, so make sure you know how they work. This is an example comparison of is_null() and empty()
$testData = array(
'FALSE' => FALSE
,'0' => 0
,'"0"' => "0"
,'NULL' => NULL
,'array()'=> array()
,'new stdClass()' => new stdClass()
,'$undef' => $undef
);
foreach ( $testData as $key => $var )
{
echo "$key " . (( empty( $var ) ) ? 'is' : 'is not') . " empty<br>";
echo "$key " . (( is_null( $var ) ) ? 'is' : 'is not') . " null<br>";
echo '<hr>';
}
As you can see, empty() returns true for all of these, but is_null() only does so for 2 of them.
I'm expanding on the answer posted by Ethan Kent. That answer will discard non-null arguments that evaluate to false due to the inner workings of array_filter, which isn't what a coalesce function typically does. For example:
echo 42 === coalesce(null, 0, 42) ? 'Oops' : 'Hooray';
Oops
To overcome this, a second argument and function definition are required. The callable function is responsible for telling array_filter whether or not to add the current array value to the result array:
// "callable"
function not_null($i){
return !is_null($i); // strictly non-null, 'isset' possibly not as much
}
function coalesce(){
// pass callable to array_filter
return array_shift(array_filter(func_get_args(), 'not_null'));
}
It would be nice if you could simply pass isset or 'isset' as the 2nd argument to array_filter, but no such luck.
I'm currently using this, but I wonder if it couldn't be improved with some of the new features in PHP 5.
function coalesce() {
$args = func_get_args();
foreach ($args as $arg) {
if (!empty($arg)) {
return $arg;
}
}
return $args[0];
}
PHP 5.3+, with closures:
function coalesce()
{
return array_shift(array_filter(func_get_args(), function ($value) {
return !is_null($value);
}));
}
Demo: https://eval.in/187365
A function to return the first non-NULL value:
function coalesce(&...$args) { // ... as of PHP 5.6
foreach ($args as $arg) {
if (isset($arg)) return $arg;
}
}
Equivalent to $var1 ?? $var2 ?? null in PHP 7+.
A function to return the first non-empty value:
function coalesce(&...$args) {
foreach ($args as $arg) {
if (!empty($arg)) return $arg;
}
}
Equivalent to (isset($var1) ? $var1 : null) ?: (isset($var2) ? $var2 : null) ?: null in PHP 5.3+.
The above will consider a numerical string of "0.00" as non-empty. Typically coming from a HTTP GET, HTTP POST, browser cookie or the MySQL driver passing a float or decimal as numerical string.
A function to return the first variable that is not undefined, null, (bool)false, (int)0, (float)0.00, (string)"", (string)"0", (string)"0.00", (array)[]:
function coalesce(&...$args) {
foreach ($args as $arg) {
if (!empty($arg) && (!is_numeric($arg) || (float)$arg!= 0)) return $arg;
}
}
To be used like:
$myvar = coalesce($var1, $var2, $var3, ...);

Categories