PHP Shorthand Addition Operator - Undefined Offset - php

I'm using the PHP shorthand addition operator to tally the number of times a specific id occurs within a multidimensional array:
$source['tally'] = array();
foreach ($items as $item) {
$source['tally'][$item->getId()] += 1;
}
The first time it hits a new id, it sets its 'tally' value to 1 and then increments it each time it's found thereafter.
The code works perfectly (I get the correct totals), but PHP gives me an "Undefined Offset" notice each time it finds a new id.
I know I can just turn off notices in php.ini, but figured there must be a reason why PHP doesn't approve of my technique.
Is it considered bad practice to create a new key/offset dynamically like this, and is there a better approach I should take instead?
Please Note: To help clarify following initial feedback, I do understand why the notice is being given. My question is whether I should do anything about it or just live with the notice. Apologies if my question didn't make that clear enough.

You have to understand that PHP notices are a tool. They exist so you have additional help when writing code and you are able to detect potential bugs easily. Uninitialized variables are the typical example. Many developers ask: if it's not mandatory to initialize variables, why is PHP complaining? Because it's trying to help:
$item_count = 0;
while( do_some_stuff() ){
$iten_count++; // Notice: Undefined variable: iten_count
}
echo $item_count . ' items found';
Oops, I mistyped the variable name.
$res = mysql_query('SELECT * FROM foo WHERE foo_id=' . (int)$_GET['foo_id']);
// Notice: Undefined index: foo_id
Oops, I haven't provided a default value.
Yours is just another example of the same situation. If you're incrementing the wrong array element, you'd like to know.

If you simply want to hide the notice, you can use the error control operator:
$source['tally'] = array();
foreach ($items as $item) {
#$source['tally'][$item->getId()]++;
}
However, you should generally initialize your variables, in this case by adding the following code inside the loop:
if (!isset( $source['tally'][$item->getId()] ))
{
$source['tally'][$item->getId()] = 0;
}

Using += (or any of the other augmented assignment operators) assumes that a value already exists for that key. Since this is not the case the first time the ID is encountered, a notice is emitted and 0 is assumed.

It's caused because you don't initialize your array to contain the initial value of 0. Note that the code would probably work, however it is considered good practice to initialize all the variable you are about to preform actions upon. So the following code is an example of what you should probably have:
<?php
$source['tally'] = array();
foreach ($items as $item) {
//For each $item in $items,
//check if that item doesn't exist and create it (0 times).
//Then, regardless of the previous statement, increase it by one.
if (!isset($source['tally'][$item->getID()]) $source['tally'][$item->getID()] = 0;
$source['tally'][$item->getId()] += 1;
}
?>
The actual reason why PHP cares about it, is mainly to warn you about that empty value (much like it would if you try to read it). It is a kind of an error, not a fatal, script-killing one, but a more subtle quiet one. You should still fix it though.

Related

Illegal string offset PHP 5.6

We have an old version of CakePHP that we've moved to a new server running PHP 5.6 and we've started to recieve this error when adding a product to the basket:
Warning (2): Illegal string offset 'AddBasketData'
[APP/controllers/personalisation_controller.php, line 848]
Here is line 848 within the file:
if (is_array($this->data['AddBasketData'])) {
$personalisation_data['Personalise'] = $this->data['AddBasketData'];
}else {
$personalisation_data['Personalise'] = array();
}
Could anyone shed any light on this, I think it's down to the specific PHP version we're running now but any help would be great.
Thanks
Transforming my comments into an answer :
The problem here seems to be that $this->data is a string and not an array.
You should test this first, then check if the offset AddBasketData exists, and finally if the offset AddBasketData is an array :
if (is_array($this->data) && isset($this->data['AddBasketData']) && is_array($this->data['AddBasketData'])) {
$personalisation_data['Personalise'] = $this->data['AddBasketData'];
} else {
$personalisation_data['Personalise'] = array();
}
Of course, that will only correct the symptoms (which is the raised warning), you might have some code debugging to do to find out why $this->data is a string instead of an array.
As noted by #roberto06 in the comments to your question, the reason you're getting the error is because you're trying to treat a string value as an array.
The reason for that specific error message is because you can use the array-offset notation to fetch a single character from the string. Just like you'd do in C's string arrays. But this only support numerical indices, and not a string index as shown in the code you posted.
Now, the easy way to stop the error from occurring is to simply test the type of the data, and whether or not the given index actually exists.Like so:
if (is_array ($this->data) && !empty ($this->data['AddBasketData'])) {
$personalisation_data['Personalise'] = $this->data['AddBasketData'];
} else {
$personalisation_data['Personalise'] = array();
}
However, seeing as you're not suddenly getting this error after update hints towards something else being the issue. Especially since the code you posted expects the value stored in the Personalise index to be an array. I'd trace where the $this->data member gets set, and changed, to see if you can find the underlying reason for why this apparent change in behavior. This might be the side-effect of a more nefarious subtle bug, after all.

No E_NOTICE for undefined variables in array?

So.. I'm still confused by this, when creating an array with $array = array(); and then manually setting variables like:
<?php
$array[] = 1;
$array['type'] = 2;
$array['number'] = 3;
I know, this is OK for PHP to do, but then when I echo something like $array['none'] it won't show a E_NOTICE for undefined variables.
Can someone explain me, why?
It will. If you have turned on error reporting, it should display a warning similar to the one below:
Notice: Undefined index: none in /path/to/script.php line X.
To check, try the following:
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
$array = array();
echo $array['none'];
And, if you want to actually make sure they exist before trying to use them in your code, use isset():
if(isset($array['none'])) {
// do stuff ...
}
See it live!
All this is explained on the config doc page, too:
Enabling E_NOTICE during development has some benefits.
For debugging purposes: NOTICE messages will warn you about possible bugs in your code. For example, use of unassigned values is warned. It is extremely useful to find typos and to save time for debugging.
NOTICE messages will warn you about bad style. For example, $arr[item] is better to be written as $arr['item'] since PHP tries to treat "item" as constant. If it is not a constant, PHP assumes it is a string index for the array.
It's quite simple: When you access a key that doesn't exist, and assign it a new value, PHP will create that key, and add it to the list (or array). But when you try to access a non-existing key, and attempt to echo it's value, PHP won't crash, but it'll let you know that your code contains a possible bug:
$arr = array('foo' => 'bar');
echo $arr['fo'];
This issues a notice because my code may contain a typo. I may expect the key fo to exist, while clearly it doesn't, so I need to work on my code some more.
Another reason why this notice is issued is because lookups for non existing properties/keys are "slow". In order for PHP to know for a fact that the key doesn't exist, the entire array has to be scanned. That, too, is not ideal, though inevitable at times. If you have code that issues tons of E_NOTICE's, chances are that some simple if's, like:
if (!isset($arr['fo']))
{
$arr['fo'] = '';
}
echo $arr['fo'];
Will, though adding more code, effectively speed up your code. Not in the least because issueing notices isn't free (it's not that expensive, but not free either).
Other benefits:
Notices also let you know when you forgot to quote array keys, for example
echo $arr[foo];
echo $arr['foo'];
Initially, both will echo bar, but let's add 1 line of code to this:
define('foo', 'bar');
echo $arr[foo];
echo $arr['foo'];
This won't, because foo is now a constant, so $arr[foo] amounts to $arr['bar'];, which is an undefined index. Turning off notices, will just echo the string representation of NULL, which is an empty string.
Basically, notices help you. Use them, listen to them, and fix them. If your site is broken, fix it. If you get into the habbit of ignoring these notices, you'll probably set your ini files to a more "forgiving" setting, and grow lazy.
As time progresses, your code will become ever more messy/smelly, until such time you actually have a difficult to trace bug. You'll decide to turn your error reporting to E_STRICT | E_ALL, and won't be able to see the actual notice/warning that points out where your bug actually is, because your screen will be cluttered with E_NOTICE undefined index/variable...

PHP: with an associative array of counters, is it more idiomatic to explicitly initialize each value to 0 before the first increment?

I'm writing some code that builds up associative array of counters. When it encounters a new item for the first time it creates a new key and initializes it to zero. I.e.:
if (!array_key_exists($item, $counters)) {
$counters[$item] = 0;
}
$counters[$item]++;
However, PHP actually does that first part implicitly. If I just do...
$counters[$item]++;
... then $counters[$item] will evaluate to NULL and be converted to 0 before it's incremented. Obviously the second way is simpler and more concise, but it feels a little sleazy because it's not obvious that $counters[$item] might not exist yet. Is one way or the other preferred in PHP?
For comparison, in Python the idiomatic approach would be to use collections.Counter when you want keys that initialize themselves to 0, and a regular dictionary when you want to initialize them yourself. In PHP you only have the first option.
Incrementing an uninitialized key will generate a PHP Notice, and is a bad idea. You should always initialize first.
However, the use of array_key_exists is not very idiomatic. I know coming from Python it may seem natural, but if you know that $counter has no meaningful NULL values it's more idiomatic to use isset() to test for array membership. (It's also much faster for no reason I can discern!)
This is how I would write a counter in PHP:
$counters = array();
foreach ($thingtobecounted as $item) {
if (isset($counters[$item])) {
$counters[$item]++;
} else {
$counters[$item] = 1;
}
}
Unfortunately unlike Python PHP does not provide any way to do this without performing two key lookups.
the first is preferred. the second option will generate a Notice in your logs that $counters[$item] is undefined. it still works but if you change display_errors = On; and error_reporting = E_ALL. in your php.ini file you will see these notices in your browser.
The first way is generally how you do it, if for nothing other than simpler maintenance. Remember, you may not be the one maintaining the code. You don't want error logs riddled with correctly operating code. Even worse, you may need to transfer methods to other languages (or earlier versions of PHP) where implicit initialization might not occur.
If you don't really need a check on each array index - or know that most of the indexes will be undefinded - why not suppress errors like: ?
(this way you save some performance on initializing [useless] indexes)
if (#!array_key_exists($item, $counters)) {

Why php does not complain when referencing a non existing variable?

I was wondering why php does not complain when we reference a non existing variable (being it a plain variable or array), is this just the way it is, or there is something else I am missing?
For example this code
<?php
$t = &$r["er"];
var_dump($r);
?>
throws no warning about a non existing variable.
Besides that the var_dump show this:
array(1) { ["er"]=> &NULL }
that &NULL is something I didn't really expected, I thought I would get a plain NULL.
Thanks in advance!
If memory of the PHP interpreter reference var allocation serves me right, PHP will create a null element in the hash table with a key like the one you sent and reference it. This is visible by running the following test:
<?php
$i = 0;
$arr = [];
$arrS = null;
$v = memory_get_peak_usage();
for ($i = 0; $i < 150; $i++) {
$arrS = &$arr[rand()];
}
$v = memory_get_peak_usage() - $v;
echo $v;
Until the default heap size, PHP will return exactly an extra 0 memory used - as it is still allocating already "prepared" array items (PHP keeps a few extra hash table elements empty but allocated for performance purposes). You can see this by setting it from 0 to 16 (which is the heap size!).
When you get over 16, PHP will have to allocate extra items, and will do so on i=17, i=18 etc..., creating null items in order to reference them.
P.S: contrary to what people said, this does NOT throw an error, warning or notice. Recalling an empty item without reference would - referencing an inexistent item does not. Big big BIG difference.
throws no warning about a non existing variable.
This is how references work. $a = &$b; creates $b if it does not exist yet, "for future reference", if you will. It's the same as in parameters that are passed by reference. Maybe this looks familiar to you:
preg_match($pattern, $string, $matches);
Here the third parameter is a reference parameter, thus $matches does not have to exist at time of the method invocation, but it will be created.
that &NULL is something I didn't really expected, I thought I would get a plain NULL.
Why didn't you expect it? $r['er'] is a reference "to/from" $t. Note that references are not pointers, $r['er'] and $t are equal references to the same value, there is no direction from one to the other (hence the quotation marks in the last sentence).

PHP, an odd variable scope?

This more a question about the why then 'how-to', yet it has been annoying me for some days now. Currently I am doing some work with CodeIgniter and going back to PHP temporarily from Ruby, bugs me about the following scoping magic.
<?php $query = $this->db->get('articles', 2);
if ($query->num_rows() > 0)
{
foreach ($query->result_array() as $row)
{
$data[] = $row; # <-- first appearance here
}
return $data; # <--- :S what?!
}
As you can see, I am not exactly a PHP guru, yet the idea of local scope bugs me that outside the foreach loop the variable is 'available'. So I tried this out inside a view:
<?php
if($a==1)
{
$b = 2;
}
echo $b;
?>
Which result in an error message:
Message: Undefined variable: b
The PHP manual tells about the local scoping, yet I am still wondering why this happens and if there are special rules I do not know about. And it scares me :)
Thanks for sharing ideas,
Only functions create a new local scope. Curly braces by themselves do not. Curly braces are just an auxillary construct for other language structures (if, while or foreach).
And whereever you access any variable in a local scope doesn't matter. The local scope is an implicit dictionary behind the scenes (see get_defined_vars). You might get a debug notice by accessing previously undefined variables, but that's about it.
In your specific example it seems, you are even just operating in the global scope.
foreach does not create any variable scope in PHP so it is natural if variable is available outside foreach
for the second question the $a is not equal to the 1 hence $b is not initialized and throw notice when you access outside. If you assign value 1 to $a and test it you will wonder the notices will gone.
Here is nothing like scope.
See: http://php.net/manual/en/language.variables.scope.php
In php curly braces don't necessarily define a new scope for variables. (your first example)
In your 2nd example, $b is only set on a specific condition. So it is possible to be 'undefined' if this condition is not met.
Shyam, you are using a scripting language, not C++. It is typical for scripting languages like PHP or JavaScript not to have different scopes for each code block. Instead there is one scope for the whole function. This is actually quite handy if you consider your first example, but you obviously need to be careful as can be seen in your second one.
is $a equals to 1? If not $b=2 will never be evaluated!
Actually your first method should be giving you an error too.
You're using a variable that hasn't been declared as an array. I can't understand why you didn't get an error for that.
PHP doesn't have block scope, so whether it's inside IF or FOREACH is irrelevant. If it's available inside the method, you can use it inside the method.

Categories