In what way is my array index an 'Illegal string offset'? - php

When "future-proofing" code by testing it on PHP 5.4, I get a warning I don't understand.
function __clone() {
$this->changed = TRUE;
foreach ($this->conditions as $key => $condition) {
if (
$condition['field']
instanceOf QueryConditionInterface) {
$this->conditions[$key]['field'] = clone($condition['field']);
}
}
}
I broke out $condition['field'] into its own row to reduce the amount of code to focus on. About that specific line, PHP has this to say
Warning: Illegal string offset 'field' in DatabaseCondition->__clone()
And I just can't see how 'field', is an illegal string offset. I'm guessing that I'm just missing something obvious, but if the community can't find a problem, I'll file a bug report.
I interpret the warning as "Under no circumstances is 'field' a valid key". This error would have made sense if I had tried to us for example an array as a key.

Without more knowledge about the creation of the conditions array/iterator, I can only assume that you should first check if the offset exists.
if(isset($condition['field']) && $condition['field'] instanceOf QueryConditionInterface)
Using isset in this situation is enough and faster than array_key_exists, the only difference is, if $condition['field'] is NULL isset will return falls, array_key_exists will return true, cause the key exists. But because you only want to work on fields that are an instance of QueryConditionInterface, you will running fine with isset.

The warning looks like its saying that $condition is a string. Without any knowledge of the code I don't whether that makes any sense.

Related

PHP - what is the point of using isSet() and checking for truthy value?

I saw this in my codebase today:
if (isset($purchase_data['buyer_tracking_address_id']) && $purchase_data['buyer_tracking_address_id']) {
// do something
}
Why would checking for both conditions be necessary? Isn't using just the first clause enough?
Isn't using just the first clause enough?
Edit I see the context of your question changed while I typed this answer. I think aynber's comment answers your question.
isset is really only there to check for the existence of the array key buyer_tracking_address_id before the the next operation tries to access it. If it doesn't exist, isset will return false and will prevent the next condition from running.
Without isset, if you tried to access the array key buyer_tracking_address_id and it didn't exist, PHP would throw a warning.
Warning: Undefined array key "buyer_tracking_address_id"
The following code will throw a notice in PHP 7, and a warning in PHP 8.
<?php
$purchase_data = [
'buyer_tracking_address_id' => '12345'
];
if ($purchase_data['doesnt_exist']) {
// do something
}
It is also worthwhile reading and understanding the ternary operator and the null coalescing operator as they are often used to access unknown array keys.

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.

Fatal error: Can't use function return value in write context in

Im getting a fatal error in my php code, I'm more of a SQL guy here so a little bit of guidance would be much appreciated.
if (isset(Auth::check() || Auth::attempt())) { $auth_id = Auth::user()->id; }
Fatal error: Can't use function return value in write context in. I thought the code is right but perhaps I am writing it wrong?
This is totally wrong:
(isset(Auth::check() || Auth::attempt()))
isset checks for the existence of a variable. You're not doing that. You're testing for the existence of the result of a logical OR operation.
By definition, a logical operation will ALWAYS produce a result, so there is exactly ZERO point in testing for the existence of that result. You most likely just want to test if that OR operation evalutes to true/false, in which case isset() is utterly useless anyways.
Try
if (Auth::check() || Auth::attempt()) { ... }
instead. Or whatever logic is appropriate for the return values you get from those two calls.

PHP: Illegal string offset because [] binds tighter than ->

I am fairly new to PHP and just had a learning experience that I am sharing here to help others who, like me, may need help to find the cause of this error and also because I still don't know what the solution is and am sure there is simply a syntax that I just haven't found yet to do what I need to do.
So, the problem can be demonstrated with something like this:
class Sample {
protected $foo = array();
public function magicSampleSetFunc($property, $key, $value) {
$this->$property[$key] = $value;
}
}
...
$s = new Sample();
$s->magicSampleSetFunc('foo', 'someKey', 'someVal');
Obviously, this isn't my real code (nor have I run it) this is just a minimal example to explain my situation. Here we have a member variable foo that is clearly an array and a generic function that is going to try to set a key and value into it. I can var_dump $this->$property and see that it is an array, but on the $this->$property[$key] line I get the error message: "Warning: Illegal string offset 'someKey' in ...".
At first I though it was saying that 'someKey' was an illegal string to use as an array offset, which didn't make sense. And, even if I wrap it in an isset it complains. The first thing I learned is that if you have a string in php, you can use the array access operator to get a character out of that string. So the warning message is actually complaining that 'someKey' is not a valid offset into a string (because it is not an integer offset). Okay, but I just var_dumped $this->$property and see that it is an array, so what gives? The second lesson was one of operator precedence. The array operator "[]" binds tighter than the indirection operator "->". So, the binding of that statement is actually something like: ( $this-> ( $property[$key] ) ). So, it is illegally trying to offset the string in $property by the index in $key. What I wanted was to offset the array in $this->$property by the index in $key.
So, now we come to my question. The third lesson I need to learn, and haven't figured out yet is how do I override this operator precedence issue? I tried ($this->$property)[$key] but that appears to be a sytax error. Is there some other syntax I can use to get the interpreter to understand what I meant to do? Or, do I have to assign $this->$property to a temporary variable first? If I do, wouldn't that mean that my actual member variable array is not updated? Do I need a temp reference or something? What's the right syntax for my situation here? Thanks!
this is the way to do it:
Your variable name is basically {$property} so when you do $this->$property[$key] I think PHP parser gets confused. I usually make sure that to explicitly state it to the parser that my variable name is $property which is done by using curly braces around variable.
Curly braces are used to explicitly specify the end of a variable name
class Sample {
protected $foo = array();
public function magicSampleSetFunc($property, $key, $value) {
$this->{$property}[$key] = $value;
}
}
...
$s = new Sample();
$s->magicSampleSetFunc('foo', 'someKey', 'someVal');

In php, should I return false, null, or an empty array in a method that would usually return an array?

I've found several responses to this, but none pertaining to PHP (which is an extremely weak typed language):
With regards to PHP, is it appropriate to return false, null, or an empty array in a method that would usually return an array, but has a failure occur?
In other words, if another developer jumped in on my project, what would they expect to see?
An array is a collection of things. An empty array would signal that "everything went fine, there just isn't anything in that collection". If you actually want to signal an error, you should return false. Since PHP is dynamically typed, it's easy to check the return value either strictly or loosely, depending on what you need:
$result = getCollection();
if (!$result) // $result was false or empty, either way nothing useful
if ($result === false) // an actual error occurred
if ($result) // we have an array with content
There are also exceptions for error reporting in exceptional cases. It really depends on the responsibilities of the function and the severity of errors. If the role of the function allows the response "empty collection" and "nope" equally, the above may be fine. However, if the function by definition must always return a collection (even if that's empty) and in certain circumstances it cannot, throwing an exception may be a lot more appropriate than returning false.
I would strongly discourage to return mixed type return values. I consider it to be so much a problem, that i wrote a small article about not returning mixed typed values.
To answer your question, return an empty array. Below you can find a small example, why returning other values can cause problems:
// This kind of mixed-typed return value (boolean or string),
// can lead to unreliable code!
function precariousCheckEmail($input)
{
if (filter_var($input, FILTER_VALIDATE_EMAIL))
return true;
else
return 'E-Mail address is invalid.';
}
// All this checks will wrongly accept the email as valid!
$result = precariousCheckEmail('nonsense');
if ($result == true)
print('OK'); // -> OK will be given out
if ($result)
print('OK'); // -> OK will be given out
if ($result === false)
print($result);
else
print('OK'); // -> OK will be given out
if ($result == false)
print($result);
else
print('OK'); // -> OK will be given out
Hope this helps preventing some misunderstandings.
Just speaking for myself, I normally prefer to return an empty array, because if the function always returns an array, it's safe to use it with PHP's array functions and foreach (they'll accept empty arrays). If you return null or false, then you'll have to check the type of the result before passing it to an array function.
If you need to distinguish between the case where the method executed correctly but didn't find any results, and the case where an error occurred in the method, then that's where exceptions come in. In the former case it's safe to return an empty array. In the latter simply returning an empty array is insufficient to notify you of the fact an error occurred. However if you return something other than an array then you'll have to deal with that in the calling code. Throwing an exception lets you handle errors elsewhere in an appropriate error handler and lets you attach a message and a code to the exception to describe why the failure happened.
The below pseudo-code will simply return an empty array if we don't find anything of interest. However, if something goes wrong when processing the list of things we got back then an exception is thrown.
method getThings () {
$things = array ();
if (get_things_we_are_interested_in ()) {
$things [] = something_else ();
}
if (!empty ($things)) {
if (!process_things ($things)) {
throw new RuntimeExcpetion ('Things went wrong when I tried to process your things for the things!');
}
}
return $things;
}
Here's a modern answer that's been valid since the 1960's probably.
Some bad design choices in the earliest versions of PHP (before PHP 4) have made many PHP developers exposed to conventions that have always been bad. Fortunately, PHP 5 have come and gone - which helped guide many PHP developers on to the "right path".
PHP 7 is now seeing the benefits of having been through the PHP 5 phase - it is one of the fastest performing script languages in existence.
and this has made it possible to make PHP 7 one of the fastest and most powerful scripting languages in existence.
Since PHP version 4, huge efforts have been made by PHP core developers to gradually improve the PHP language. Many things remain, because we still want to have some backward compatability.
DON'T return false on error
The only time you can return FALSE in case of error, is if your function is named something like isEverythingFine().
false has always been the wrong value to return for errors. The reason you still see it in PHP documentation, is because of backward compatability.
It would be inconsistent. What do you return on error in those cases where your function is supposed to return a boolean true or false?
If your function is supposed to return something other than booleans, then you force yourself to write code to handle type checking. Since many people don't do type checking, the PHP opcode compiler is also forced to write opcodes that does type checking as well. You get double type checking!
You may return null
Most scripting languages have made efficient provisions for the null value in their data types. Ideally, you don't even use that value type - but if you can't throw an exception, then I would prefer null. It is a valid "value" for all data types in PHP - even if it is not a valid value internally in the PC.
Optimally for the computer/CPU is that the entire value is located in a single 1, 2, 4 or 8 byte memory "cell". These value sizes are common for all native value types.
When values are allowed to be null, then this must be encoded in a separate memory cell and whenever the computer needs to pass values to a function or return them, it must return two values. One containing isNull and another for the value.
You may return a special value depending on type
This is not ideal, because
If your function is supposed to return an integer, then return -1.
If your function is supposed to return a string
You should throw exceptions
Exceptions match the inner workings of most CPUs. They have a dedicated internal flag to declare that an exceptional event occurred.
It is highly efficient, and even if it wasn't we have gigantic benefits of not having a lot of extra work in the normal non-erroneous situation.
It depends on the situation and how bad the error is, but a good (and often overlooked) option is to throw an exception:
<?php
function inverse($x) {
if (!$x) {
throw new Exception('Division by zero.');
}
else return 1/$x;
}
try {
echo inverse(5) . "\n";
echo inverse(0) . "\n";
} catch (Exception $e) {
echo 'Caught exception: ', $e->getMessage(), "\n";
}
This will ensure that your function will not fail silently and errors won't go unseen.
I assume that the return type of your method is array, so you should return an empty array only if the execution went fine but no results were found.
In case of an error, you should throw an exception. This should be the preferred way to handle errors.
If there's really a problem then you should raise an error, otherwise if the criteria aren't met etc then return a blank array.
Whichever you prefer, though I suggest an empty array for the for a good reason. You don't have to check the type first!
<?php
function return_empty_array() {
return array();
}
$array = return_empty_array();
// there are no values, thus code within doesn't get executed
foreach($array as $key => $value) {
echo $key . ' => ' . $value . PHP_EOL;
}
?>
In any other case, if you'd return false or null, you'd get an error at the foreach loop.
It's a minuscule difference, though in my opinion a big one. I don't want to check what type of value I got, I want to assume it's an array. If there are no results, then it's an empty array.
Anyway, as far as I'm concerned there are no "defaults" for returning empty values. Native PHP functions keep amazing me with the very different values it returns. Sometimes false, sometimes null, sometimes an empty object.

Categories