Get details about function call - php

In PHP, I'd like to get the details about a function call inside the function itself. The behavior I want (without doing this) is to have debug_backtrace() passed as an argument to the function.
I want that done automatically, for every call to a function.
I need this so I can have pre-defined errors for a fairly sizable project I'm working on, but I obviously want the line number of a central trigger_error() call, as that's not very useful for tracking down the problem. I also don't want to count on future developers to remember a debug_backtrace() argument.

You do realize that you can call debug_backtrace() yourself, and it would be pretty much the same. For instance:
function error(..params)
{
$backtrace = debug_backtrace();
array_shift($backtrace);
}
If you array_shift the given backtrace, if will be as if it were passed into the function.
If you aren't familiar with it:
array_shift() shifts the first value of the array off and returns it, shortening the array by one element and moving everything down.
Thus, because debug_backtrace() is numerically indexed, it will act the exact same.

Related

Array being mysteriously converted to object immediately before usort() sees it

I'm getting a good laugh out of this situation. It's in a giant application, so I can't just upload everything, but here is the local, relevant part of my code:
```
public function reallyCustomSort($thing, $options='')
{
die('dying. gettype results for our param: '.gettype($thing));
//Gives us "array." So why does usort complain about "object given?"
die('dying. get_class results for our param: '.get_class($thing)); // Gives us "get_class() expects parameter 1 to be object, array given.
usort($thing, 'AppBundle\Twig\CustomSort::customCompare'); // Gives us "Warning: usort() expects parameter 1 to be array, object given"
die('We are done here.');
return($thing);
}
public function someRandomFunction($thing){
die('dying in someRandomFunction. gettype results for our param: '.gettype($thing)); //Gives us "array."
}
```
In other words, two function calls that analyze my variable both agree that it's an array, and then a line later, my usort() function thinks that the variable contains an object.
Has anyone ever encountered this behavior before? If so, how did you get around it?
A few things I've already tried:
Inserting a sleep() call to see if race conditions are involved. (See
gist below.) No luck.
Passing the $thing variable to a different function just to see if
for some strange reason the mere passing of the $thing to a
function was causing a conversion. No luck.
Clearing various caches in my app. No luck.
A gist with some more (commented out) code showing things I've tried:
https://gist.github.com/patrickmaynard/7c3c7f0695e223e6903fd729172b4c21
I got around the issue by just writing a custom function, based heavily on an SO answer elsewhere. Here's the gist:
https://gist.github.com/patrickmaynard/922cf7cfa7533c655ec1c5eb4303345a
(The array I'm sorting is an array of objects, each of which has a getName() method. So the logic in this gist works relatively well for my use case. It will probably require some adaptation for others.)
====
Edit: Someone I respect also noticed that my method was being called more than once, which meant that a standalone die() function was not the best way to debug it. It was receiving an array the first time around, then receiving an object the second or third time. So while the workaround above "works," we were able to find a more elegant solution by observing the object type that was being passed during those later method calls, then using that object's toArray() method.

Objects are passed by reference. Parameters to call_user_func aren't. What gives?

In PHP, objects are effectively passed by reference (what’s going on under the hood is a bit more complicated). Meanwhile, parameters to call_user_func() not passed by reference.
So what happens with a piece of code like this?
class Example {
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $k => $v) {
//call_user_func($v, &$this);
// The above line is working code on PHP 5.3.3, but
// throws a parse error on PHP 5.5.3.
call_user_func($v, $this);
}
}
}
}
$e = new Example;
$e->events['example'][] = 'with_ref';
$e->events['example'][] = 'without_ref';
$e->RunEvent('example');
function with_ref(&$e) {
$e->with_ref = true;
}
function without_ref($e) {
$e->without_ref = true;
}
header('Content-Type: text/plain');
print_r($e);
Output:
Example Object
(
[events] => Array
(
[example] => Array
(
[0] => with_ref
[1] => without_ref
)
)
[without_ref] => 1
)
Note: Adding error_reporting(E_ALL); or error_reporting(-1); to the top of the file makes no difference. I’m seeing no errors or warnings, and of course php -l on the command line shows no errors.
I was actually expecting it to work both with and without references in the callback functions. I thought that removing the ampersand before $this in call_user_func() would be enough to fix this for the latest version of PHP. Obviously, the version with the reference doesn’t work, but equally it doesn’t throw any linting errors, so it’s hard to track down such situations (which may occur many times in the codebase I’m working with).
I’ve got a practical question here: Is there any way to make the with_ref() function work? I’d like to modify only the RunEvent() code, not every single function which uses it (the majority of which do use references).
I’ve also got a curiosity question, as the behaviour I see here makes no sense to me. The opposite would make more sense. What’s actually going on here? It seems startlingly counter-intuitive that a function call without an ampersand can modify the object, while one with the ampersand cannot.
Parameters passing
The main issue is - that parameters, passed to call_user_func() will be passed as values - so they will be copy of actual data. This behavior overrides the fact, that
objects are passed by reference. Note:
Note that the parameters for call_user_func() are not passed by
reference.
Tracking error
You're not fully correct about "silent agreement" in such cases. You will see error with level E_WARNING in such cases:
Warning: Parameter 1 to with_ref() expected to be a reference, value given in
So - you will be able to figure out that you're mixing reference and values passing
Fixing the issue
Fortunately, it's not too hard to avoid this problem. Simply create reference to desired value:
class Example {
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $k => $v) {
$obj = &$this;
call_user_func($v, $obj);
}
}
}
}
-then result will be quite as expected:
object(Example)#1 (3) {
["events"]=>
array(1) {
["example"]=>
array(2) {
[0]=>
string(8) "with_ref"
[1]=>
string(11) "without_ref"
}
}
["with_ref"]=>
bool(true)
["without_ref"]=>
bool(true)
}
Obviously, the version with the reference doesn’t work, but equally it doesn’t throw any linting errors, so it’s hard to track down such situations (which may occur many times in the codebase I’m working with).
It throws an error: Warning: Parameter 1 to with_ref() expected to be a reference....
Error_reporting while developing should be error_reporting(-1);.
I’ve got a practical question here: Is there any way to make the with_ref() function work? I’d like to modify only the RunEvent() code, not every single function which uses it (the majority of which do use references).
You can replace call_user_func($v, $this); with $v($this);.
I’ve also got a curiosity question, as the behaviour I see here makes no sense to me. The opposite would make more sense. What’s actually going on here?
call_user_func can only pass parameters by value, not by reference.
Why does the error "expected to be a reference, value given" appear?
Here’s the modified code (taken from the accepted answer) and comment (taken from the other answers and a comment on the accepted answer). The answer to part 2 of this question is buried in a comment on the accepted answer. If the whole answer were in one place I’d just accept it and have done with it, but since I’ve stitched it together I’m throwing it in here.
function RunEvent($event) {
if (isset($this->events[$event])) {
foreach ($this->events[$event] as $v) {
$obj = &$this;
call_user_func($v, $obj);
// The user func *should* receive the object by value, not
// by reference. What's *actually* passed is a pointer to
// the location of the object, so modifications to the object
// in that func will actually be applied to the real object.
// In that case, a simple call_user_func($v, $this) will
// work.
//
// However, some of the existing user funcs receive the
// object by reference. That can and should be changed,
// but there are quite a lot to track down, and they don't
// throw linting errors. In that case, call_user_func will
// pass by value, and they're expecting to recive it by
// reference, so you get a run-time warning. (In theory,
// anyway. When I was practising on a standalone script
// I saw no warnings at all.) That's not good.
//
// One way around this would be to use $v($this) instead
// of call_user_func, but the user func may sometimes be
// a class method, so that's not going to work either. Instead,
// the above compromise method seems to work without problems.
//
// We may at some future point switch to call_user_func($v, $this),
// and track down the remaining warnings as we hit them.
//
// https://stackoverflow.com/q/20683779/209139
}
}
}
First of all, "objects" are not "passed by reference" or "effectively passed by reference" (that's a made-up term). "Objects" are not values in PHP5, and cannot be "passed" at all. The value of $e, $this, etc., is a pointer to an object.
In PHP, things are passed by reference when there is a &, and passed by value otherwise. Always.
call_by_func is just a function. In its declaration, its parameter does not have a &. Therefore, that parameter is pass-by-value. Therefore, what you passed to call_by_func is always passed by value.
You were using "call-time pass-by-reference" in PHP 5.3, which overrode a pass-by-value parameter into pass-by-reference, and was really bad practice and was removed in PHP 5.4.
Neither the functions with_ref and without_ref assign to their parameter ($e), so actually there was no point to pass it by reference. But since you declared the parameter to with_ref as pass-by-reference, there is a problem when using it with call_user_func, because as we discussed before, call_user_func takes its parameter by value, so it already lost the "reference", so there's no way it can pass-by-reference to your function. The documentation of call_user_func says it results in a warning and the call returns FALSE.
One solution, of course, it just to use $v($this);. i.e. not use call_user_func at all -- just use the name to call it directly. There is rarely a need to use call_user_func anyway.
If you must use the call_user_func* family of functions, you can use call_user_func_array with an array with elements that are by reference (Remember that you can put references into an array). That preserves the "reference" so that it can be passed to the pass-by-reference function:
call_user_func_array($v, array(&$this));
Note: Before PHP 5.4, this used to do call-time pass-by-reference. However, since PHP 5.4, this is not a call-time pass-by-reference. When we use it to call by reference a function that was meant to be called by reference, it works. When we use it to call a pass-by-value function, it works as pass-by-value.

Why does PHP's call_user_func() function not support passing by reference?

Why don't the function handling functions like call_user_func() support passing parameters by reference?
The docs say terse things like "Note that the parameters for call_user_func() are not passed by reference." I assume the PHP devs had some kind of reason for disabling that capability in this case.
Were they facing a technical limitation? Was it a language design choice? How did this come about?
EDIT:
In order to clarify this, here is an example.
<?php
function more(&$var){ $var++; }
$count = 0;
print "The count is $count.\n";
more($count);
print "The count is $count.\n";
call_user_func('more', $count);
print "The count is $count.\n";
// Output:
// The count is 0.
// The count is 1.
// The count is 1.
This is functioning normally; call_user_func does not pass $count by reference, even though more() declared it as a referenced variable. The call_user_func documentation clearly says that this is the way it's supposed to work.
I am well aware that I can get the effect I need by using call_user_func_array('more', array(&$count)).
The question is: why was call_user_func designed to work this way? The passing by reference documentation says that "Function definitions alone are enough to correctly pass the argument by reference." The behavior of call_user_func is an exception to that. Why?
The answer is embedded deep down in the way references work in PHP's model - not necessarily the implementation, because that can vary a lot, particularly in the 5.x versions. I'm sure you've heard the lines, they're not like C pointers, or C++ references, etc etc... Basically when a variable is assigned or bound, it can happen in two ways - either by value (in which case the new variable is bound to a new 'box' containing a copy of the old value), or by reference (in which case the new variable is bound to the same value box as the old value). This is true whether we're talking about variables, or function arguments, or cells in arrays.
Things start to get a bit hairy when you start passing references into functions - obviously the intent is to be able to modify the original variables. Quite some time ago, call-time pass-by-reference (the ability to pass a reference into a function that wasn't expecting one) got deprecated, because a function that wasn't aware it was dealing with a reference might 'accidentally' modify the input. Taking it to another level, if that function calls a second function, that itself wasn't expecting a reference... then everything ends up getting disconnected. It might work, but it's not guaranteed, and may break in some PHP version.
This is where call_user_func() comes in. Suppose you pass a reference into it (and get the associated the call-time pass-by-reference warning). Then your reference gets bound to a new variable - the parameters of call_user_func() itself. Then when your target function is called, its parameters are not bound where you expect. They're not bound to the original parameters at all. They're bound to the local variables that are in the call_user_func() declaration. call_user_func_array() requires caution too. Putting a reference in an array cell could be trouble - since PHP passes that array with "copy-on-write" semantics, you can't be sure if the array won't get modified underneath you, and the copy won't get detached from the original reference.
The most insightful explanation I've seen (which helped me get my head around references) was in a comment on the PHP 'passing by reference' manual:
http://ca.php.net/manual/en/language.references.pass.php#99549
Basically the logic goes like this. How would you write your own version of call_user_func() ? - and then explain how that breaks with references, and how it fails when you avoid call-time pass-by-reference. In other words, the right way to call functions (specify the value, and let PHP decide from the function declaration whether to pass value or reference) isn't going to work when you use call_user_func() - you're calling two functions deep, the first by value, and the second by reference to the values in the first.
Get your head around this, and you'll have a much deeper understanding of PHP references (and a much greater motivation to steer clear if you can).
See this:
http://hakre.wordpress.com/2011/03/09/call_user_func_array-php-5-3-and-passing-by-reference/
Is it possible to pass parameters by reference using call_user_func_array()?
http://bugs.php.net/bug.php?id=17309&edit=1
Passing references in an array works correctly.
Updated Answer:
You can use:
call_user_func('more', &$count)
to achieve the same effect as:
call_user_func_array('more', array(&$count))
For this reason I believe (unfoundedly) that call_user_func is just a compiler time short cut. (i.e. it gets replaced with the later at compile time)
To give my view on you actual question "Why was call_user_func designed to work this way?":
It probably falls under the same lines as "Why is some methods strstr and other str_replace?, why is array functions haystack, needle and string functions needle, haystack?
Its because PHP was designed, by many different people, over a long period of time, and with no strict standards in place at the time.
Original Answer:
You must make sure you set the variable inside the array to a reference as well.
Try this and take note of the array(&$t) part:
function test(&$t) {
$t++;
echo '$t is '.$t.' inside function'.PHP_EOL;
}
$t = 0;
echo '$t is '.$t.' in global scope'.PHP_EOL;
test($t);
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
call_user_func_array('test', array(&$t));
$t++;
echo '$t is '.$t.' in global scope'.PHP_EOL;
Should output:
$t is 0 in global scope
$t is 1 inside function
$t is 2 in global scope
$t is 3 inside function
$t is 4 in global scope
Another possible way - the by-reference syntax stays the 'right' way:
$data = 'some data';
$func = 'more';
$func($more);
function more(&$data) {
// Do something with $data here...
}

when do we need to create pass/call by reference function

I will always be in confusion whether to create pass/call by reference functions. It would be great if someone could explain when exactly I should use it and some realistic examples.
A common reason for calling by reference (or pointers) in other languages is to save on space - but PHP is smart enough to implement copy-on-write for arguments which are declared as passed-by-value (copies). There are also some hidden semantic oddities - although PHP5 introduced the practice of always passing objects by reference, array values are always stored as references, call_user_func() always calls by value - never by reference (because it itself is a function - not a construct).
But this is additional to the original question asked.
In general its good practice to always declare your code as passing by value (copy) unless you explicitly want the value to be different after the invoked functionality returns. The reason being that you should know how the invoked functionality changes the state of the code you are currently writing. These concepts are generally referred to as isolation and separation of concerns.
Since PHP 5 there is no real reason to pass values by reference.
One exception is if you want to modify arrays in-place. Take for example the sort function. You can see that the array is passed by reference, which means that the array is sorted in place (no new array is returned).
Or consider a recursive function where each call needs to have access to the same datum (which is often an array too).
In php4 it was used for large variables. If you passed an array in a function the array was copied for use in the function, using a lot of memory and cpu. The solution was this:
function foo(&$arr)
{
echo $arr['value'];
}
$arr = new array();
foo($arr);
This way you only passed the reference, a link to the array and save memory and cpu. Since php5 every object and array (not sure of scalars like int) are passed by reference internally so there isn't any need to do it yourself.
This is best when your function will always return a modified version of the variable that is passed to it to the same variable
$var = modify($var);
function modify($var)
{
return $var.'ret';
}
If you will always return to the passed variable, using reference is great.
Also, when dealing with large variables and especially arrays, it is good to pass by reference wherever feasible. This helps save on memory.
Usually, I pass by reference when dealing with arrays since I usually return to the modified array to the original array.

Find what functions were called (from a variable's perspective)

I'm trying to figure out how to know what has been done to a variable.
Here's an example:
function a($hello) {
$out .= strtoupper(ucwords(strtolower($hello)));
return $out;
}
echo function_trace('$hello') // returns array(strtoupper,ucwords,strtolower)
Thanks!
Matt
There's not really an easy way to do this, because variables don't store "state" or "history". Stack traces (where you probably got your inspiration from) are possible because they're generated from the existing execution stack, which is stored out of necessity to be able to properly unwind chains of function calls.
In addition, your example is trying to trace a function parameter - but that parameter variable is only defined within the scope of the function. Attempting to reference it outside of the function would result in the interpreter not knowing what variable you're trying to indicate - it'd think you're looking for a globally-scoped $hello, not the one used as an argument in the function.
There's no hook in PHP that does exactly what you want, but you can get a call stack with debug_backtrace():
http://php.net/manual/en/function.debug-backtrace.php
It's not possible to do exactly what you're asking for, but perhaps if you gave a bit more context about what you were hoping to do with that function trace, we could give some suggestions?

Categories