How strict is undefined index? - php

I've turned on all error reporting to clean up some undefined indexes, just to make the app I'm making more neat. I've noticed a curious behavior:
Let's say I have the following array: $a = array('test' => false, 'foo' => 'bar')
If I do if ($a['nothere']), I properly get a notice of Undefined index: nothere.
However, if I do if ($a['test']['nothere']), I don't get a notice. At all. Despite nothere definitely not being an index in $a['test'].
Now, if I do $a['test'] = array('baz' => 'poof'), then running if ($a['test']['nothere']) does throw a notice.
Does undefined index checking not check for indexes in an empty array? This is on PHP 5.2.8.

it's because of tricky type juggling.
$a['test'] being cast to [empty] string and then 'nowhere' being cast to 0 and then PHP tries to look for 0's symbol in the empty string. It becomes substring operation, not variable lookup, so, no error raised.
Gee, a man says the same: http://php.net/manual/en/language.types.type-juggling.php
A curious example from my experience:
Being requested via hyperlink index.php?sname=p_edit&page=0, a code
if (isset($_GET['sname'])) { $page['current'] = $_GET['sname'].'.php'; };
echo $page['current'];
produces just one letter "p"
I counted 6 steps and 2 type casts which ends up with such a result.
(Hint: a poor fellow who asked this question had register globals on)

Related

Variable exists and has a value and which only replies with a boolean

I've searched around and seen similar discussions in previous posts, but the question has never been answered to my satisfaction. Essentially, I'm looking for a quick, elegant and simple way in PHP to ask the question 'Does this exist, and if it does, does it have a non-zero, non-null value?' that does not throw a notice in "property of non-object" scenarios or undefined variables.
Here's the scenario and a couple of versions that I've tested:
// Setting up
$a = ['key' => 'value', 'otherKey' => 'otherValue', 'sub' => ['key' => 'value']];
$b = 0;
$c = 123;
$d = 'string';
$e = (object) $a;
// my tests:
!! $b
=> false
!! $c
=> true
!! $a['key']
=> true
!! $a['notExist']
=> PHP Notice: Trying to get property of non-object on line 1
!! $e->key
=> true
!! $e->notExist
=> PHP Notice: Trying to get property of non-object on line 1
!! $f
=> PHP Notice: Trying to get property of undefined variable on line 1
I've done the same with isset(), empty() and all kinds of things. I've managed to build a somewhat workable version of the thing I'm trying to do by building a function that checks types, traverses namespaces and does all kinds of voodoo to the argument you're throwing at it, but it feels like it shouldn't have to be that complicated.
Is there really no better way to ask "Does this exist and is it set to a non-zero, non-null value?"
You can still go with empty. As the documentation states:
Determine whether a variable is considered to be empty. A variable is considered empty if it does not exist or if its value equals FALSE. empty() does not generate a warning if the variable does not exist.
Then we can check how PHP converts values to booleans:
When converting to boolean, the following values are considered FALSE:
-- the boolean FALSE itself
-- the integer 0 (zero)
-- the float 0.0 (zero)
-- the empty string, and the string "0"
-- an array with zero elements
-- the special type NULL (including unset variables)
-- SimpleXML objects created from empty tags
Every other value is considered TRUE (including any resource and NAN).
So, you should be good with just !empty, even if any level doesn't exist, such as:
!empty($foo)
!empty($foo->bar->num1)
!empty($foo['bar']['num1'])
None of the above should show a warning.
Now, when it comes to functions, we can't really use empty to check for it's existence. For that, we need to be more specific: function_exists or something alike, such as method_exists.
Generally, my go to is
if ( isset( $var) && $var !== 0 )
{
// condition met
}
The shorthand AND operator will end if the value is not set, and if it is set, then it will check for NULL value. The shorthand checks should prevent errors for variable not set.

Why does accessing array index on boolean value does not raise any kind of error?

When I try to access an array by key which is not exists in this array, php will raise "undefined index" notice error. When I try to do the same on strings, "Illegal string offset " warning is raised. This is an expected behavior and I know how to deal with it.
But when I tried this on boolean or integer values nothing happens:
ini_set('display_errors', 1);
error_reporting(E_ALL);
$var = false;
var_dump($var['test']);
I expect to see some error messages, but $var['test'] just silently sets to NULL.
So why does php permit to access boolean value through an array key without any indication that you are doing something wrong?
The hole "access boolean value through an array key" phrase sounds terribly wierd to me, but you can do it in php.
It's sad, but it's documented behaviour.
http://php.net/manual/en/language.types.string.php
Note:
Accessing variables of other types (not including arrays or objects implementing the appropriate interfaces) using [] or {} silently returns NULL.

using array accessor on integer, float or boolean [duplicate]

Edit 2022: This appears to be fixed as of PHP 7.4 which emits a notice.
In PHP, I have error_reporting set to report everything including notices.
Why does the following not throw any notices, errors or anything else?
$myarray = null;
$myvalue = $myarray['banana'];
Troubleshooting steps:
$myarray = array();
$myvalue = $myarray['banana'];
// throws a notice, as expected ✔
$myarray = (array)null;
$myvalue = $myarray['banana'];
// throws a notice, as expected ✔
$myarray = null;
$myvalue = $myarray['banana'];
// no notice or warning thrown, $myvalue is now NULL. ✘ Why?
It's possible it's a bug in PHP, or I'm just not understanding something about how this works.
There are three types which it might be valid to use the array derefence syntax on:
Arrays
Strings (to access the character at the given position)
Object (objects implementing the ArrayAccess interface)
For all other types, PHP just returns the undefined variable.
Array dereference is handled by the FETCH_DIM_R opcode, which uses zend_fetch_dimension_address_read() to fetch the element.
As you can see, there is a special case for NULLs, and a default case, both returning the undefined variable.
Usually, when you try to use a value of one type as if it were another type, either an error or warning gets thrown or "type juggling" takes place. For example, if you try to concatenate two numbers with ., they'll both get coerced to strings and concatenated.
However, as explained on the manual page about type juggling, this isn't the case when treating a non-array like an array:
The behaviour of an automatic conversion to array is currently undefined.
In practice, the behaviour that happens when this "undefined behaviour" is triggered by dereferencing a non-array is that null gets returned, as you've observed. This doesn't just affect nulls - you'll also get null if you try to dereference a number or a resource.
There is an active bug report started at 2006.
And in documentation it is a notice about this in String section.
As of PHP 7.4, this behavior how emits a Notice.
"Trying to access array offset on value of type null"
See the first item in this 7.4 migration page.
https://www.php.net/manual/en/migration74.incompatible.php
This recently struck one of my colleagues in the butt because he neglected to validate the result of a database query before attempting to access column data from the variable.
$results = $this->dbQuery(...)
if($results['columnName'] == 1)
{
// WHEN $results is null, this Notice will be emitted.
}
And I just noticed #Glen's comment, above, citing the relevant RFC.
https://wiki.php.net/rfc/notice-for-non-valid-array-container

Why does PHP not complain when I treat a null value as an array like this?

Edit 2022: This appears to be fixed as of PHP 7.4 which emits a notice.
In PHP, I have error_reporting set to report everything including notices.
Why does the following not throw any notices, errors or anything else?
$myarray = null;
$myvalue = $myarray['banana'];
Troubleshooting steps:
$myarray = array();
$myvalue = $myarray['banana'];
// throws a notice, as expected ✔
$myarray = (array)null;
$myvalue = $myarray['banana'];
// throws a notice, as expected ✔
$myarray = null;
$myvalue = $myarray['banana'];
// no notice or warning thrown, $myvalue is now NULL. ✘ Why?
It's possible it's a bug in PHP, or I'm just not understanding something about how this works.
There are three types which it might be valid to use the array derefence syntax on:
Arrays
Strings (to access the character at the given position)
Object (objects implementing the ArrayAccess interface)
For all other types, PHP just returns the undefined variable.
Array dereference is handled by the FETCH_DIM_R opcode, which uses zend_fetch_dimension_address_read() to fetch the element.
As you can see, there is a special case for NULLs, and a default case, both returning the undefined variable.
Usually, when you try to use a value of one type as if it were another type, either an error or warning gets thrown or "type juggling" takes place. For example, if you try to concatenate two numbers with ., they'll both get coerced to strings and concatenated.
However, as explained on the manual page about type juggling, this isn't the case when treating a non-array like an array:
The behaviour of an automatic conversion to array is currently undefined.
In practice, the behaviour that happens when this "undefined behaviour" is triggered by dereferencing a non-array is that null gets returned, as you've observed. This doesn't just affect nulls - you'll also get null if you try to dereference a number or a resource.
There is an active bug report started at 2006.
And in documentation it is a notice about this in String section.
As of PHP 7.4, this behavior how emits a Notice.
"Trying to access array offset on value of type null"
See the first item in this 7.4 migration page.
https://www.php.net/manual/en/migration74.incompatible.php
This recently struck one of my colleagues in the butt because he neglected to validate the result of a database query before attempting to access column data from the variable.
$results = $this->dbQuery(...)
if($results['columnName'] == 1)
{
// WHEN $results is null, this Notice will be emitted.
}
And I just noticed #Glen's comment, above, citing the relevant RFC.
https://wiki.php.net/rfc/notice-for-non-valid-array-container

Anyone ever used PHP's (unset) casting?

I just noticed PHP has an type casting to (unset), and I'm wondering what it could possibly be used for. It doesn't even really unset the variable, it just casts it to NULL, which means that (unset)$anything should be exactly the same as simply writing NULL.
# Really unsetting the variable results in a notice when accessing it
nadav#shesek:~$ php -r '$foo = 123; unset($foo); echo $foo;'
PHP Notice: Undefined variable: foo in Command line code on line 1
PHP Stack trace:
PHP 1. {main}() Command line code:0
# (unset) just set it to NULL, and it doesn't result in a notice
nadav#shesek:~$ php -r '$foo = 123; $foo=(unset)$foo; echo $foo;'
Anyone ever used it for anything? I can't think of any possible usage for it...
Added:
Main idea of question is:
What is reason to use (unset)$smth instead of just NULL?
As far as I can tell, there's really no point to using
$x = (unset)$y;
over
$x = NULL;
The (unset)$y always evaluates to null, and unlike calling unset($y), the cast doesn't affect $y at all.
The only difference is that using the cast will still generate an "undefined variable" notice if $y is not defined.
There's a PHP bug about a related issue. The bug is actually about a (in my mind) misleading passage elsewhere in the documentation which says:
Casting a variable to null will remove the variable and unset its value.
And that clearly isn't the case.
I’d guess (knowing PHP and it’s notaribly... interesting choices for different things, I may be completely wrong) that it is so that the value does not need setting to a var. For exact reason to use it for a code, I can’t think of an example, but something like this:
$foo = bar((unset) baz());
There you want or need to have null as argument for bar and still needs to call baz() too. Syntax of function has changed and someone did a duck tape fix, like what seems to be hot with PHP.
So I’d say: no reason to use it in well-thought architecture; might be used for solutions that are so obscure that I’d vote against them in first place.
As of PHP 8.0.X, (unset) casting is now removed and cannot be used.
For example it can be used like this
function fallback()
{
// some stuff here
return 'zoo';
}
var_dump(false ? 'foo' : fallback()); // zoo
var_dump(false ? 'foo' : (unset) fallback()); // null
Even if fallback() returns "zoo" (unset) will clear that value.

Categories