This is a very very weird bug that happens randomly. So I will try to explain this as best as I can. I'm trying to diminish the code substantially to pinpoint the problem without having to reference a framework, nor deal with 500 lines of code outside of the framework - so I will try and come back and update this question when I do. OK, so here we go.
So basically say I'm taking a string in a function and preg_replace like so:
$table = '/something';
print $table;
$table = preg_replace("#^(/)#",'prefix_',$table);
print $table;
The output will look something like
...
/something - prefix_something
/something - prefix_something
/something - /something
You have an error in you sql query... blah blah somewhere around SELECT * FROM /something
What happens is that after rolling through the code, this simply stops working and the sql errors out. What's more strange is that if I start commenting out code outside the function it then works. I put that code back in, it breaks.
I tried recoding this by substr($var,0,1) and var_dumping the variable. After a few rounds it returns a bool(false) value even though $var is in fact a string. It's very very strange. Has anybody run into this before? Is this a PHP bug? It's looking like it to me.
-- EDIT --
Example:
foreach ( $user as $id => $user ) {
get_data("/table_name","*", "user_id = '$id'");
}
#in the function $_table_ holds "/table_name"
$_table_ = trim($_table_);
$tmp = explode(',', $_table_);
$tables = array();
foreach ($tmp as $c => $n) {
$n = trim($n);
$a = substr($n, 0, 1);
$b = substr($n, 1);
if ($a == false) {
var_dump($n);
$a = substr($n, 0, 1);
var_dump($b);
var_dump($a);
}
if ($a == '/') { etc...
Output:
string(11) "/table_name" string(10) "table_name" bool(false)
If I switch to the original preg_replace functionality, it simply does not preg_replace and the same thing happens "/table_name" is return rather then "prefix_table_name" and this happens randomly. Meaning it will work on the string it broke on if I comment code out outside of the function that is seemingly unrelated.
--- SOLUTION ----
I found out that it has to do with string to array conversion in another function that caused this strange error.
Basically a function was expecting a null or array() value and instead a number was passed. We have notices turned off, so I turned them on and started fixing all the notices and warnings and that's how I found this strange bug. Now exactly what the inner workings are that created the problem is unknown to me. However I can define the symptom as a string assignment problem that would occur at some other place in the code which we caught when it wouldn't reassign the $table variable with the new preg_replace value which is detailed above. Now you know. The function looks something similar to this:
function format_something($one, $two, $three = array()){
if ($three['something'] == 'Y') {
/** etc... */
}
...
format_something('one','two',3);
After a while in a loop this type of conversion started having problems randomly in the code.
substr doesn't always return a string. If it can't find the substring, it returns false.
Return Values
Returns the extracted part of string, or FALSE on failure or an empty string.
You should test for this condition and var_dump the string you are searching to see what it contains in such cases.
Related
I am running into a "Only variables should be passed by reference" error, because on the code I am using there is a line that does not put the explode() result into a variable. As required when using strict PHP standards.
However because the explode() function is used in a While loop I can't think of a appropriate solution.
My code looks like
function user_exists($username) {
rewind($this->fp);
while(!feof($this->fp) && trim($lusername = array_shift(explode(":",$line = rtrim(fgets($this->fp)))))) {
if($lusername == $username)
return 1;
}
return 0;
}
Any suggestions on how to solve this?
I think maybe you need to sit back and break your code apart a bit and take a look at what is happening.
First, condition is while !feof($this->fp)
From the manual:
feof — Tests for end-of-file on a file pointer
One thing you will notice here is that feof() is only a test which returns true or false. It does not advance the pointer position while looping over, so while using this function, somewhere else in your while loop there needs to be something that advances the pointer or else you will have an infinite loop.
Second condition is:
trim($lusername = array_shift(explode(":",$line = rtrim(fgets($this->fp)))))
First function from left to right is trim(), which returns a string. From our handy dandy comparison table we see that when doing if ((String) $var) it evaluates to false if and only if the string is empty ("") or the number zero as a string ("0"), otherwise it returns true. Personally I tend to really hate using if ((String) $var) (first because it's slightly unclear to newbies unless you know your comparison table well and second because 99% of the time people are doing that they are actually checking for string length, in which case I would want it to return true for the string "0"). So assuming that you don't need it to return false for "0" we could change this to strlen($var) > 0 and then manipulate the variable within the loop. That should greatly simplify things here.
So now we have:
while (!feof($this->fp) && strlen($var) > 0) { /*...*/ }
This will loop over until either we are at the end of the file or $var is an empty line. Everything else can be offloaded into the body of the while loop, so it is much easier to break apart.
So this is what we have now:
$line = rtrim(fgets($this->fp));
$lusername = array_shift(explode(":",$line)));
Uh-oh! There's that "nasty" error:
Strict Standards: Only variables should be passed by reference in /path/to/file.php on line x.
So we can see from here, the part producing the error is not explode(), but array_shift(). See also: Strict Standards: Only variables should be passed by reference
What this means is that since array_shift() modifies the array, it requires it to be by reference. Since you are not passing an actual variable but instead the result of a function, PHP is unable to modify it. It's similar to doing something like function($var) = 3;. Of course you can't do that. Instead you need to save the value to a temporary variable. So now we have:
$line = rtrim(fgets($this->fp));
$split = explode(":",$line);
$lusername = array_shift($split);
Woo hoo! No more warning message.
So putting this together, we now have:
while (!feof($this->fp) && strlen($lusername) > 0) {
$line = rtrim(fgets($this->fp));
$split = explode(":",$line);
$lusername = array_shift($split);
if($lusername == $username) {
return 1;
}
}
Also, as mentioned earlier, the fgets() will advance the pointer, which allows the !feof($this->fp) part in the while statement to vary.
This function works fine outside of a class. Ie simply define the function and call it. Yet when I add it to a class it no longer works - any help is greatly appreciated:
public function recursive_array_search($needle,$haystack) {
foreach($haystack as $key=>$value) {
$current_key=$key;
if($needle===$value || (is_array($value) && $this->recursive_array_search($needle,$value) !== false)) {
return $current_key;
}
}
return false;
}
obviously the $this is removed when not in a class.
Edit:
The error I an getting when using it in a class is:
Invalid argument supplied for foreach()
app\components\GenFun::recursive_array_search('9377907', 9378390)
My sole expectation from the function is that it returns any key (ie identifies that the needle exists in the haystack) - I actually dont care about the actual index.
To be perfectly honest, "it no longer works" isn't a helpful metric by which to assist you in debugging your problem. Nor is "it works fine", since that doesn't tell us your definition of what works means to you. More precisely, these statements don't tell us what you expected the code to do that it's not doing, or what the code is doing that you did not expect.
To me this code is doing exactly what you've told it to do and the result of both a function as well as a class method (using the same code) are identical... See the working 3v4l pastebin here.
However, my guess is that your expectations may be different from what this code actually does. Specifically, this function will return at the very first match of the $needle in the $haystack. Such that the following array, returns 0 (_that is with a needle of 'foo').
$haystack = ['foo', ['foo', 'bar']];
It will also return only the key of the outer-most array in the $haystack. Meaning, the following array returns 0 as the key. Even though the actual match is in $haystack[0][1][2]
$haystack = [['bar',['quix','baz','foo'],'baz'],'quix'];
So depending on what you expected (the inner-most key, or the outer-most key), you may believe this function doesn't work.
So you'll need to clarify exactly what you want the code to do and provide some reproducible example of what didn't work (and that includes the data used or arguments provided to the function).
EDIT:
Hey, I'm glad you figured it out. Here are just a few suggestions to maybe help you refactor this code slightly as well...
So since you're looking for the existence of the needle in any part of the array and don't actually care about the key, you may want to make your intent more obvious in the logic.
So for example, always return a boolean (true on success and false on failure) rather than return false on failure and the key on success. This makes checking the function's result easier and clearer from the caller's perspective. Also, consider naming the function to describe it's intent more clearly (for example: in_array_recursive rather than recursive_array_search since we're not actually intent on searching the array for something, but proving that something is actually in the array). Finally, consider avoiding multiple return points in the same function as this makes debugging harder.
So a cleaner way to write the same code might be something like this:
public function in_array_recursive($needle, $haystack, $strict = false) {
$result = false;
foreach($haystack as $value) {
if(!is_array($value)) {
$result = $strict ? $needle === $value : $needle == $value;
} else {
$result = $this->in_array_recursive($needle, $value, $strict);
}
if ($result) {
break;
}
}
return $result;
}
Now the caller simply does...
$arr = ['bar',['foo']];
if (in_array_recursive('foo', $arr)) {
/* 'foo' is in $arr! */
} else {
/* 'foo' is not in $arr... */
}
Making the code more readable and easier to debug. Notice you also don't have to use exact match if you wanted to add an optional argument for $strict at the end of the function there and also be more inline with in_array.
So the reason this was not working was due to the method by which I was defining $needle.
In my old code it would be input as an integer and in my new code it was a string. The === operator then obviously denied it as being the same. This is why you don't work at 2am :)
So I've got this snippet:
function compare1($s1, $s2)
{
return $s1===$s2;
}
function compare2($s1, $s2)
{
return !strcmp($s1, $s2);
}
function challenge($s1, $s2) //Objective: return TRUE
{
return compare1($s1, $s2) ^ compare2($s1, $s2);
}
What's requested from me is to supply/append/assign values to the $s1 and $s2 variables so as for the last function to return TRUE.
I've tried nearly everything I could think of, like $s1='1' and $s2=1 which does return TRUE:
var_dump(compare1('1', 1) ^ compare2('1', 1)); //output: int 1
Creator of the challenge told me that I shouldn't or rather can't assign integer values to the variables but issue is that no boolean variations worked. Here is the website I'm talking about so as you can see if you could possibly come up with a solution: http://securitytraps.no-ip.org/challs/strcmp/
Thanks in advance :)
How about
$s1= null;
$s2 = "";
This does not work on the tool provided but works from the command line.
also
$s1 = "";
$s2 = false;
Ok so the first give away is that the site is a security trap site so they are looking to show you vulnerabilities in PHP. So while the other answers are valid they don't work when being passed over the internet.
To solve the challenge you actually have to modify the HTML on the page and turn one of the keys into an array like: <input name="s2[]" value="s2" /> and then submit the form. When that happens the strcmp will compare $_POST['s1'] (string) with $_POST['s2'] (array) causing PHP to evaluate the strcmp as true!
i have come across some very strange php behaviour (5.3.2 on ubuntu 10.04). an unset which should occur within local scope is affecting the scope of the caller function. the following snippet is a simplification of my code which displays what i can only assume is a bug:
<?php
function should_not_alter($in)
{
$in_ref =& $in['level1'];
should_only_unset_locally($in);
return $in;
}
function should_only_unset_locally($in)
{
unset($in['level1']['level2_0']);
}
$data = array('level1' => array('level2_0' => 'first value', 'level2_1' => 'second value'));
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>
if you run the above you will see that the value 'first value' has been unset from the $data array in the global scope. however if you comment out test 1 and run test 2 this does not happen.
i can only assume that php does not like referencing an element of an array. in my code i need to alter $in_ref - hence the reason for the $in_ref =& $in['level1']; line in the above code. i realize that removing this line would fix the problem of 'first value' being unset in the global scope, but this is not an option.
can anyone confirm if this is intended behaviour of php?
i suspect it is a bug, rather than a feature, because this behaviour is inconsistent with the way that php handles scopes and references with normal (non-array) variables. for example, using a string rather than an array function should_only_unset_locally() has no effect on the global scope:
<?php
function should_not_alter($in)
{
$in_ref =& $in;
should_only_unset_locally($in);
return $in;
}
function should_only_unset_locally($in)
{
unset($in);
}
$data = 'original';
$data = should_not_alter($data); //test 1
//should_only_unset_locally($data); //test 2
print_r($data);
?>
both test1 or test2 output original as expected. actually, even if $data is an array but $in_ref is referenced to the entire array (ie $in_ref =& $in;) then the buggy behaviour goes away.
update
i have submitted a bug report
Yup, looks like a bug.
As the name of the function implies, should_not_alter should not alter the array since it's passed by value. (I'm of course not basing that just off of the name -- it also should not alter anything based on its definition.)
The fact that commenting $in_ref =& $in['level1']; makes it leave $in alone seems to be further proof that it's a bug. That is quite an odd little quirk. No idea what could be happening internally to cause that.
I'd file a bug report on the PHP bug tracker. For what it's worth, it still exists in 5.4.6.
$data = should_not_alter($data)
This line is overwriting the $data array with the return value of should_not_alter, which is $in. This is normal behavior.
Also, while you're creating a reference $in_ref =& $in['level1']; but you're not doing anything with it. It will have no effect on the program output.
Short answer:
Delete the reference variable via unset($in_ref) before calling the should_only_unset_locally() function.
Long answer:
When a reference to an array element is created, the array element is replaced with a reference. This behavior is weird but it isn't a bug - it's a feature of the language and is by design.
Consider the following PHP program:
<?php
$a = array(
'key1' => 'value1',
'key2' => 'value2',
);
$r = &$a['key1'];
$a['key1'] = 'value3';
var_dump($a['key1']);
var_dump($r);
var_dump($a['key1'] === $r);
Output:
string(6) "value3"
string(6) "value3"
bool(true)
Assigning a value to $a['key1'] changes the value of $r as they both reference the same value. Conversely updating $r will update the array element:
$r = 'value4';
var_dump($a['key1']);
var_dump($r);
Output:
string(6) "value4"
string(6) "value4"
The value doesn't live in $r or $a['key'] - those are just references. It's like they're both referencing some spooky, hidden value. Weird, huh?
For most use cases this is desired and useful behavior.
Now apply this to your program. The following line modifies the local $in array and replaces the 'level1' element with a reference:
$in_ref = &$in['level1'];
$in_ref is not a reference to $in['level1'] - instead they both reference the same spooky value. So when this line comes around:
unset($in['level1']['level2_0']);
PHP sees $in['level1'] as a reference to a spooky value and removes the 'level2_0' element. And since it's a reference the removal is also felt in the scope of the should_not_alter() function.
The solution to your particular problem is to destroy the reference variable which will automagically restore $in['level1'] back to normal behavior:
function should_not_alter($in) {
$in_ref =& $in['level1'];
// Do some stuff with $in_ref
// After you're done with it delete the reference to restore $in['level1']
unset($in_ref);
should_only_unset_locally($in);
return $in;
}
I have question about some behavior I was just debugging, specifically what happens if a variable which is already set is assigned to an undefined value. I just want to check that I'm understanding what happened correctly. If a variable has a value set already, and you try to set it to something undefined, it stays at its old value?
Specifically, I had some PHP code that looked approximately like this - assume that $string is some string of 1's and 2's.
$array = array(1 => 'foo', 2 => 'bar');
for($count=0;$count<len($string);$count++)
{
$newvar = $array[$string[$count]];
if(!empty($newvar))
{
switch($newvar)
{
case 'foo':
// blah blah break;
case 'bar':
// blah blah break;
}
}
}
Now, my code was supposed to set $string to be something like "12212", but an error on my part was sending it something with extra spaces at the end - "12212 ". This caused some aberrant behavior, and I think what happened was this - when $count=5, $string[5] is undefined, so $array[$string[5]] is undefined, and $newvar stays as 2. Thus my if(!empty statement doesn't do its job and case 'bar' happens more times than it should have. Does that all seem like what would happen?
Of course, trimming $string solved my problem, but I want to make sure I understand what was going wrong. Apologies if this is a stupid question - I'm just an amateur here....
Edit: Here's the actual code. $upstr is supposed to be a string of digits.
$len = strlen($upstr);
$cost=0;
$upnames = array(4=>"man", 2=>"raw", 1=>"food", 3=>"fuel",5=>"tech");
for($strloop=0;$strloop<$len; $strloop++)
{
$number = $upstr[$strloop];
if(! empty($number))
{
$name = $upnames[$number];
$cost+= mysql_result($result1,0,$name) +1;
if(mysql_result($result2,0,$name."up")==1)
{
$cost+=100;
}
}
}
What happened when $upstr had some extra spaces at the end was I would see a mysql error, that it couldn't find the column "up" in $result2 . So it was trying to run that block of code in the if() statement with $name being empty or NULL or something. And if I intentionally added 3 or 4 extra spaces, I would see that many mysql errors.
I'm afraid the definition of variable $array is incorrect in your code example, it should read as follows:
$array = array(1 => 'foo', 2 => 'bar');
If you set $newvar to an undefined element of $array (e.g. 3) then $newvar will be set to NULL.
Use array_key_exists($array, $string[$count]) to check if your array has value for your key.
Ok, I've figured out what was causing the behavior I saw. The strings '' and ' ' behave differently under empty(). One of them is considered empty and the other isn't, which was confusing me. Thanks so much for all the help.