Hi Cups 
I’ll give that a shot.
[color=darkred]empty()[/color]
and [color=darkred]isset()[/color]
are treated in very much the same way by PHP. They’re both language constructs, which means they operate at a much lower level than other things like functions. Because of this, the process is much simpler than with a function.
During the stage where the PHP script is parsed and converted into a list of operations for the zend vm to execute, the parser can spot the [color=darkred]empty(…)[/color]
statement (the token sequence that the parser recognises is [color=darkred]T_EMPTY '(' variable ')'[/color]
). The [color=darkred]variable[/color]
token there is pretty forgiving in what it will accept, including things like function/method calls as well as variables in their many guises. Due to this forgiving statement (which is a good thing!), code like your [color=darkred]empty(trim(…))[/color]
will happily get consumed as (at this stage) a valid call to [color=darkred]empty()[/color]
.
Upon seeing the [color=darkred]empty(…)[/color]
statement, the parser calls the engine function [color=darkred]zend_do_isset_or_isempty()[/color]
. This function examines the statement and decides which opcodes need to be recorded for later (there are different opcodes for checking whether variables/array items/object properties are set or empty). But before that decision is made, a call to a function called [color=darkred]zend_check_writable_variable()[/color]
is made. This is the part of the whole procedure which is most relevant to the thread here. Its job is simply to check that whatever was seen in place of the [color=darkred]variable[/color]
token earlier is a “writable variable”, or not.
The source code for the function is very simple so, rather than describe it verbosely, here it is:
zend_check_writable_variable(const znode *variable) /* {{{ */
{
zend_uint type = variable->EA;
if (type & ZEND_PARSED_METHOD_CALL) {
zend_error(E_COMPILE_ERROR, "Can't use method return value in write context");
}
if (type == ZEND_PARSED_FUNCTION_CALL) {
zend_error(E_COMPILE_ERROR, "Can't use function return value in write context");
}
}
If you don’t read C, don’t be too put off. All that the function does is trigger a familiar error message if the [color=darkred]variable[/color]
was a function/method call.
That’s pretty much it for how/when the “Can’t use function return value in write context” error message gets triggered with [color=darkred]empty()[/color]
. As for why it needs to be a variable, well as the manual succinctly puts it, [color=darkred]empty()[/color]
's purpose is to (emphasis mine) “determine whether a variable is considered to be empty.” It is really that simple a reason.
Not functions, however the same “writable variable” check is made…
(might not be a complete list, emphasis denotes what is passed to [color=darkred]zend_check_writable_variable()[/color]
)
- with
[color=darkred]isset()[/color]
isset([color=darkgreen][b]pi()[/b][/color])
- with
[color=darkred]unset()[/color]
unset([color=darkgreen][b]pi()[/b][/color])
- on the left side of an assignment
[color=darkgreen][b]pi()[/b][/color] = "cake"
- unary operations
[color=darkgreen][b]pi()[/b][/color]++
- array/function references
trim( &[color=darkgreen][b]pi()[/b][/color] )
array( "apple" => &[color=darkgreen][b]pi()[/b][/color] )
- with
[color=darkred]list()[/color]
list([color=darkgreen][b]pi()[/b][/color]) = array('apple')
- with
[color=darkred]foreach()[/color]
foreach ([color=darkgreen][b]pi()[/b][/color] as $a)
foreach ($a as [color=darkgreen][b]pi()[/b][/color])
foreach ($a as $b => [color=darkgreen][b]pi()[/b][/color])
As you can see, all of those occasions are in situations where PHP, for one reason or another, expects to be able to write to (i.e. manipulate) a variable.
Hopefully that helps to clarify things a little. (: