PHP: Chained '__invoke's on '$this' Cause a Parsing Error - php

I'm running PHP 5.5.9 and I'm getting a parsing error that I haven't a clue how to resolve. Here's an extremely contrieved example of the technique I'm trying to employ:-
<?php
class NumberDisplayer {
var $numbers = [];
function __invoke($n) {
array_push($this->numbers, $n);
return $this;
}
function display() {
foreach ($this->numbers as $number) {
echo "$number, ";
}
}
}
((new NumberDisplayer())
(5)
(10)
(14)
(20)
(11)
->display());
?>
That yields:-
Parse error: syntax error, unexpected '(' in <documents>/index.php on line 18
Such a pointless example could obviously be solved via better means, but my question is how to make this technique work in situations where it's a good fit.
My reasoning is as follows: new NumberEchoer() evaluates to an instance of NumberEchoer, which itself is callable due to __invoke. Because of this, I should be able to invoke the expression, which itself returns $this which is also invokable, and so on and so forth. At the end of the expression, I invoke the display method which does the displaying.
I also tried to pack the whole expression onto a single line, but the elimination of the newlines didn't fix anything.
I seem to be able to store NumberDisplayer into a variable and invoke the variable manually on every line, but it makes the code far less readable.
How can this idiom of chaining magic methods be done in a readable fashion?

1.. Remove the var because php variables uses $, example $numbers = " ";
2.. Use only one underscore for the invoke name like __invoke.
3.. Edit echo "$number,"; to echo $numbers; as ur empty array in the loop array

Related

PHP functions: passing variables by reference (and if not possible) by value

I am trying to build an "escape" function (as an exercise). The objective of this function is to transform "dangerous" values into safe values to be inserted in a database. The content of this function is not important.
function escape(&$value){
//some code
return $value;
}
Here's the problem: I want to make this function very handy to use, therefore it should be able to support 2 possible scenarios:
1) returning a safe value:
$safe_val = escape($unsafe_val);
2) changing a variable "by reference":
escape($value);
At the moment, my function does its job, however...if I pass something like:
$safe_val = escape(php_native_change_string_to_something($value));
PHP gets angry and says:
Notice: Only variables should be passed by reference
How can I make PHP accept that if something can't be passed by reference it does not matter and it should just ignore the error and continue the execution?
PHP is complaining because the value being passed into escape by escape(php_native_change_string_to_something($value)) is a temporary value (rvalue). The argument has no permanent memory address so it does not make sense to modify the value.
However, despite this not making sense, PHP will still do what you want. You are receiving a notice, not an error. Your code should still produce the output you are expecting. This short program models your setup:
<?php
function escape (&$s) {
return $s;
}
$s = 'TEXT TO ESCAPE';
$new_s = escape( strtolower( $s ) );
echo "$s\n";
echo "$new_s\n";
and produces the following results:
s: TEXT TO ESCAPE
new_s: text to escape
If you would like to get rid of the notice you will need to use the error control operator (#), #escape(php_native_change_string_to_something($value)).
Despite this being something that will work in PHP I would suggest avoiding this type of usage as it will decrease code readability and is not suggested by PHP (as the notice indicates).

How Do I Retrieve a Static Property From a Dynamic Object in a Single Expression?

After swapping some of my non-static vars to static vars, I ended up with some expressions similar to the one here. This throws a syntax error, but I can't figure out why.
Class Bar {
public static $name = "bar";
}
Class Foo {
public function getParent(){
$this->parentClass = new Bar();
return $this;
}
}
$foo = (new Foo())->getParent();
echo ($foo->parentClass)::$name; //this line is throwing a syntax error
//output:
PHP Parse error: syntax error, unexpected '::' (T_PAAMAYIM_NEKUDOTAYIM)
If I assign the object to a variable, then it doesn't throw the error:
$class = $foo->parentClass;
echo $class::$name;
//outputs "bar";
I could imagine possibly running into some unintended order of operations issues, but can't figure out why it's a syntax error, and I'm wondering if there is a way to do this in a single one line expression. (Since this issue was caused by a mass find/replace, it would be nice to keep it in one line)
It's kinda ugly, but if you really need a one-liner:
echo get_class_vars(get_class($foo->parentClass))["name"];
Inspired by this answer
DEMO
In fact this is only possible as of PHP 7.0. The changed behaviour is not well documentated. I think it's more of a bugfix than a new feature.
However the closest solution to a "one-liner" (working in 5.6) seems to be this one:
$bar = (new Foo())->getParent()->parentClass;
echo $bar::$name;
Maybe that is not what you tried to achieve.
The important thing is that the static class is only accessable by putting it into a single variable first.
I recommend a migration to PHP7 urgently.

Avoid a "PHP Strict standards" warning with parentheses? [duplicate]

It was noted in another question that wrapping the result of a PHP function call in parentheses can somehow convert the result into a fully-fledged expression, such that the following works:
<?php
error_reporting(E_ALL | E_STRICT);
function get_array() {
return array();
}
function foo() {
// return reset(get_array());
// ^ error: "Only variables should be passed by reference"
return reset((get_array()));
// ^ OK
}
foo();
I'm trying to find anything in the documentation to explicitly and unambiguously explain what is happening here. Unlike in C++, I don't know enough about the PHP grammar and its treatment of statements/expressions to derive it myself.
Is there anything hidden in the documentation regarding this behaviour? If not, can somebody else explain it without resorting to supposition?
Update
I first found this EBNF purporting to represent the PHP grammar, and tried to decode my scripts myself, but eventually gave up.
Then, using phc to generate a .dot file of the two foo() variants, I produced AST images for both scripts using the following commands:
$ yum install phc graphviz
$ phc --dump-ast-dot test1.php > test1.dot
$ dot -Tpng test1.dot > test1.png
$ phc --dump-ast-dot test2.php > test2.dot
$ dot -Tpng test2.dot > test2.png
In both cases the result was exactly the same:
This behavior could be classified as bug, so you should definitely not rely on it.
The (simplified) conditions for the message not to be thrown on a function call are as follows (see the definition of the opcode ZEND_SEND_VAR_NO_REF):
the argument is not a function call (or if it is, it returns by reference), and
the argument is either a reference or it has reference count 1 (if it has reference count 1, it's turned into a reference).
Let's analyze these in more detail.
First point is true (not a function call)
Due to the additional parentheses, PHP no longer detects that the argument is a function call.
When parsing a non empty function argument list there are three possibilities for PHP:
An expr_without_variable
A variable
(A & followed by a variable, for the removed call-time pass by reference feature)
When writing just get_array() PHP sees this as a variable.
(get_array()) on the other hand does not qualify as a variable. It is an expr_without_variable.
This ultimately affects the way the code compiles, namely the extended value of the opcode SEND_VAR_NO_REF will no longer include the flag ZEND_ARG_SEND_FUNCTION, which is the way the function call is detected in the opcode implementation.
Second point is true (the reference count is 1)
At several points, the Zend Engine allows non-references with reference count 1 where references are expected. These details should not be exposed to the user, but unfortunately they are here.
In your example you're returning an array that's not referenced from anywhere else. If it were, you would still get the message, i.e. this second point would not be true.
So the following very similar example does not work:
<?php
$a = array();
function get_array() {
return $GLOBALS['a'];
}
return reset((get_array()));
A) To understand what's happening here, one needs to understand PHP's handling of values/variables and references (PDF, 1.2MB). As stated throughout the documentation: "references are not pointers"; and you can only return variables by reference from a function - nothing else.
In my opinion, that means, any function in PHP will return a reference. But some functions (built in PHP) require values/variables as arguments. Now, if you are nesting function-calls, the inner one returns a reference, while the outer one expects a value. This leads to the 'famous' E_STRICT-error "Only variables should be passed by reference".
$fileName = 'example.txt';
$fileExtension = array_pop(explode('.', $fileName));
// will result in Error 2048: Only variables should be passed by reference in…
B) I found a line in the PHP-syntax description linked in the question.
expr_without_variable = "(" expr ")"
In combination with this sentence from the documentation: "In PHP, almost anything you write is an expression. The simplest yet most accurate way to define an expression is 'anything that has a value'.", this leads me to the conclusion that even (5) is an expression in PHP, which evaluates to an integer with the value 5.
(As $a = 5 is not only an assignment but also an expression, which evalutes to 5.)
Conclusion
If you pass a reference to the expression (...), this expression will return a value, which then may be passed as argument to the outer function. If that (my line of thought) is true, the following two lines should work equivalently:
// what I've used over years: (spaces only added for readability)
$fileExtension = array_pop( ( explode('.', $fileName) ) );
// vs
$fileExtension = array_pop( $tmp = explode('.', $fileName) );
See also PHP 5.0.5: Fatal error: Only variables can be passed by reference; 13.09.2005

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

I have a pretty nasty error I can't get rid of. Here's the function causing the issue:
function get_info_by_WatIAM($WatIAM, $info) {
$users_info = array();
exec("uwdir -v userid={$WatIAM}", $users_info);
foreach ($users_info as $user_info) {
$exploded_info = explode(":", $user_info);
if (isset($exploded_info[1])){
$infoArray[$exploded_info[0]] = $exploded_info[1];
}
}
return $infoArray[$info]; }
Here's what's calling the function:
} elseif ( empty(get_info_by_WatIAM($_POST['ownerId'])) ) { ...
I would really appreciate any suggestion. Thanks very much!
If the code doesn't make sense, here's a further explanation: exec uses a program that stores information on all the users in a school. These include things like faculty, name, userid, etc. The $_POST['ownerId'] is a username -- the idea is that, upon entering a username, all of the user's information is automatically filled in
You do not need empty around function calls, in fact empty only works with variables and not functions (as you see). You only need empty if you want to test a variable that may not be set for thruthiness. It is pointless around a function call, since that function call must exist. Instead simply use:
} else if (!get_info_by_WatIAM($_POST['ownerId'])) { ...
It does the same thing. For an in-depth explanation, read The Definitive Guide To PHP's isset And empty.
empty can only be used on variables, not on expressions (such as the result of calling a function). There's a warning on the documentation page:
Note:
empty() only checks variables as anything else will result in a parse
error. In other words, the following will not work: empty(trim($name)).
Just one of PHP's best-left-alone quirks.
One workaround is to store the result in a variable and call empty on that, although it's clunky. In this specific case, you can also use
if (!get_info_by_WatIAM(...))
...although in general, if (empty($a)) and if(!$a) are not equivalent.
get the value of this
$a = get_info_by_WatIAM($_POST['ownerId'])
then chack
empty($a)
it will work

PHP class constant string variable spanning over multiple lines

I want to have a string variable for a PHP class, which would be available to all methods.
However, this variable is quite long, so I want to separate it into multiple lines.
For example,
$variable = "line 1" .
"line 2" .
"line 3";
But above doesn't work.
I tried EOD, but EOD is not allowed within class. And when I declare it outside the class, I can't access the variable from within the class.
What is the best way?
If you are using PHP >= 5.3, you could use HEREDOC syntax to declare your string :
class MyClass {
public $str = <<<STR
this is
a long
string
STR;
}
$a = new MyClass();
var_dump($a->str);
But this :
is only possible with PHP >= 5.3
and the string must not contain any variable
this is because the string's value must be known at compile-time
which, btw, explains why the concatenation, with the ., will not work : it's done at execution time.
And another drawback is that this will put newlines in the string -- which might, or not, be a bad thing.
If you are using PHP <= 5.2 :
You can't do that ; a solution could be to initialize the string in your class' constructor :
class MyClass {
public $str;
public function __construct() {
$this->str = <<<STR
this is
a long
string
STR;
}
}
(same not with newlines)
Or, here, you can do strings concatenations :
class MyClass {
public $str;
public function __construct() {
$this->str = 'this is' .
'a long' .
'string';
}
}
(this way, no newlines)
Else, you can have a string that's surrounded by either single or double quotes, and put it on several lines :
class MyClass {
public $str = "this is
a long
string";
}
(Here, again, you'll have newlines in the resulting string)
$var = "this is a really long variable and I'd rather have it " .
"span over multiple lines for readability sake. God it's so hot in here " .
"can someone turn on the A/C?";
echo $var;
Which outputs:
this is a really long variable and I'd rather have it span over multiple lines for readability sake. God it's so hot in here can someone turn on the A/C?
What you have now works using the string concatenation operator. If you can post more information regarding your issue, some code or perhaps a further explanation of how it doesn't work. More information will lead you to a better answer.
I'm using PHP 5.5.9 and have come across a similar issue only with class constants. I want to use a somewhat long string as a constant but I don't want:
really long lines of code
newlines showing up in the text at the break points
the property to be mutable
the property to be inaccessible outside the class
I think the solution here is something that is done a lot in the Laravel 5 scaffolding and why they kept doing this had me baffled until now. What they do is something like:
public static function getLongPropertyString()
{
return 'A a string that can be arbitrarily long and contain as ' .
'many breaks in the code as you want without line breaks ' .
'appearing in the resulting string.';
}
This method provides an immutable string. You don't strictly get that by creating a protected/private variable with getters since its still mutable internally. Only changing the code or overriding can change this string. Another pro is that making it static allows a single "instance" per class.
Unfortunately, now your code would be Class::getProperty() rather than just Class::property. Another downside is that the concatenation would be done every time you call the function but depending on how you use this, that cost is typically negligible.
It would be cool if the PHP compiler were able to recognize that the concatenation operation on only values already known at compile time can be run at compile time and the result substituted (beings more knowledgeable than myself know why this is so).

Categories