php 7.3 - Class constants as default param values crashing - php

I have some code that has been working for a long time, but is now producing perplexing errors. I have to assume this was caused by my moving to php 7.3, but I can't find any references that explain what is happening.
I have a method in a class that looks something like:
class Foo {
function bar($param = OtherClass::MY_CONSTANT) {
logger(__METHOD__);
logger(OtherClass::MY_CONSTANT);
logger($param);
logger('ready to do stuff');
// does stuff
}
}
Where OtherClass::MY_CONSTANT = 1.
When I call that method from another class:
$foo = new Foo();
$foo->bar();
the output is something like:
DEBUG - Foo::bar
DEBUG - 1
and then php execution stops abruptly, not even calling my registered shutdown function. I am able to use the class constant directly in the body of the method, but I am not able to use the parameter set to the class constant's value by default.
If I change the method to
function bar($param = 1) {
everything works fine.
Also, if I pass the constant when I call the method it works:
$foo = new Foo();
$foo->bar(OtherClass::MY_CONSTANT);
I get the happy
DEBUG - Foo::bar
DEBUG - 1
DEBUG - 1
DEBUG - ready to do stuff
and execution continues normally.
Something about using a class constant as a default is making the variable poisonous, even though I can use the class constant in the method without any trouble.
I tried to create a simple one-file example to recreate this problem, but it worked just fine. Aargh.
Has something changed in php 7.3 that would cause this behavior? I just upgraded to 7.3.1 but the problem persisted. Is there a better practice I should be using?
EDIT TO ADD:
After a few hours trying to make a simple case to reproduce this, I have to move on. Relevant factors include:
running in php-fpm
code running after closing the connection to the browser.
even constants in the same class (self::MY_CONSTANT) create poison variables
My solution was to roll back to php 7.2.14, which is working properly. I can only assume this in a bug in php 7.3.

As a workaround, you can do it like this:
function bar($param = null) {
if ($param === null) {
$param = OtherClass::MY_CONSTANT;
}
logger(__METHOD__);
logger(OtherClass::MY_CONSTANT);
logger($param);
logger('ready to do stuff');
// does stuff
}
This assumes that null is not a valid value for the parameter; replace that with some other invalid value if it is. If the parameter can legitimately be anything, this workaround won't work.

Related

PHP: class variable visibility

I'm just a once-in-a-while PHP developer. Now, working on a legacy application, I just hit upon the following problem, which seems very stupid. But I can't get $someString class variable to hold the right value:
class MyClass{
var $someString;
function doSomething(){
//$this->setString(); //this is effectively not called here, but later in getIframe()
$this->buildIframe();
echo $this->someString; //actually, I need someString here, but it is empty
}
function setString(){
$this->someString = "something";
}
function buildIframe(){
$content .= <iframe....>;
}
function getIframe(){
$this->setString();
}
}
$myClassInstance = new MyClass();
$myClassInstance->doSomething();
$myClassInstance->getIframe();
As far as I can see, doSomething() is called in a class context, as I did show.
What am I doing wrong?
EDIT:
I reviewed the code and I think I found whats causing this. There is an iframe embedded into the html output, which is generated at one part and called later on. So the setString() method is actually not called imediately, what I thought first but when invoking the iframe code. So thats why it's not available where I need the string output.
I guess like the code is now, there is no way to get the $someString output except inside the getIframe() method.
This code is 100% correct and working. I've checked it on PHP 5. It echos "something" in string. And it is proper behavior.
From manual:
Note: The PHP 4 method of declaring a variable with the var keyword is still supported for compatibility reasons (as a synonym for the public keyword). In PHP 5 before 5.1.3, its usage would generate an E_STRICT warning.

call_user_func() or $callback() not working

In a PHP routing file, I want to make a callback to a function called homepage_display().
The function name is in the $callback variable, and when I make the call, it doesn't work:
$callback = "homepage_display";
$callback(); // doesn't work
call_user_func($callback); // doesn't work either
homepage_display(); // works!!
Whereas this specific piece of code just above works in any snippet, the same mechanism doesn't work in one of my functions. Any idea of what can cause such a behavior? I tried removing the _ thinking it might be an encoding problem, but it doesn't solve anything.
EDIT:
To make my point absolutely clear, I added an explicit assignation $callback="homepage_display";, just before calling it as a callback function. You can see on this picture that it just does not work. Whereas calling homepage_display(); directly, does. If somebody understands something, I'm curious :)
Not working:
Working:
The answer, in one word: namespace foo\bar;.
One cannot call $callback(); when in the scope of a non-global namespace.
One has to call call_user_func(__NAMESPACE__.'\\'.$callback); instead.
It can get tricky sometimes ;)
Works fine here:
php > function foo() { echo 'foo!'; }
php > $bar = 'foo';
php > call_user_func($bar);
foo!
php > $bar();
foo!
Did you verify that your testRoute() actually returns true? If it doesn't, then your "autoloader" will never get called.

Class object not working inside ob_start callback

I don't know why, but this code worked for me a month ago... maybe I upgraded the php but can't remember. Tried this with PHP 5.2.17 and 5.3.6
Why is it not possible to use a class object inside the callback of a ob_start function?
<?php
$f=new stdClass();
$f->title="awesome Title";
function callback($buffer)
{
global $f;
$buffer=str_replace("###TITLE###", $f->title, $buffer);
return $buffer;
}
ob_start("callback");
?>
This is the ###TITLE###
Output is:
PHP Notice: Trying to get property of non-object in /Users/qxxx/Sites/test/test.php on line 8
This is the
should be:
This is the awesome Title
This is because the output buffer is being implicitly flushed by the termination of the script.
At this point PHP has already destroyed unreferenced variables, so when it comes to execute your callback function, the variable $f does not exist in the global scope.
You can solve this by explicitly flushing the buffer before shutdown starts destroying objects, by placing the following line somewhere in your script.
register_shutdown_function('ob_end_flush');
Edit:
I'd like to add that even though this is currently the accepted answer that explains the "why", the solution provided here does not address the root cause of the issue; the fact that global is being used.
Many people will tell you that global is evil, without giving a reason why. Here you can see one of the reasons.
The answer provided by Jack gives a more "best practice" solution (using closures to maintain the variable reference), and should be considered as the proper way to avoid using global in new codebases.
The reason for this has been outlined well by Leigh. Using a closure would work better in this case:
ob_start(function($b) use ($f) {
return str_replace('###TITLE###', $f->title, $b);
});
This is because the closure will keep the reference to $f alive by the end of the script so that it won't get garbage collected before running the callback function.
From the php manual page of ob_start, and a bug report I learned that, since 5.2, all objects are destroyed #ob_start
This function's behaviour has been changed in php 5.2.0:
<?
global $AP;
$AP = new ap;
ob_start("ob_end");
function ob_end()
{
global $AP;
$r = $AP->test();
return $r;
}
class ap
{
function test()
{
return "debug";
}
}
?>
In older versions it shows: "debug".
But latest php version causes error: PHP Fatal error: Call to a member function test() on > a non-object.
And this is not a bug: http://bugs.php.net/bug.php?id=40104
from the man pages

In PHP, how to quickly access objects property from a return?

My problem is this.
A function return an object, and I want to access its property. It throws and error when I try to do it like this
$this->FunctionThatReturnsAnObject()->Property;
Right now I'm creating a new variable and taking the property from it, like this:
$newVar=$this->FunctionThatReturnsAnObject();
$property=$newVar->Property;
Is this the right way of doing this?
This works perfectly fine in modern PHP versions.
[mcg#mcg-workstation ~]$ php -a
Interactive shell
php > class Foo { public function functionThatReturnsAnObject() { return new Bar(); } }
php > class Bar { public $property = 'Hello, world.'; }
php > $f = new Foo();
php > echo $f->functionThatReturnsAnObject()->property;
Hello, world.
php >
If you're experiencing an error, we'll need to know what the error is. As mentioned in the comments, you might not be able to do this in PHP4, but you shouldn't be using PHP4 in the modern era to begin with. As long as the instance method you're calling returns an object, you can operate on that object directly. (You can't do this from a constructor right now: new Foo()->functionThatReturnsAnObject() will not work because the PHP internals team is full of people that think this would be confusing in some way. I'm not making this up.)

Unable to call a function stored as string from inside a class

EDIT: Sorry guys, it was a typo =(
I feel embarrassed for wasting your time on this. I'm leaving this thread open in hopes that someone might find the discussed information useful.
To clarify further, the code below will work as expected, I mistyped 'classHandler' in my code, and that's the reason PHP couldn't find it.
Also, the syntax errors noted by some commenters have been corrected
I feel obligated to summarize the discussion on this thread:
#wimvds suggests using inheritance and that my implementation is not a good practice.
#Victor Nicollet disagrees saying extending the behavior of an existing instance is 1. impossible and 2. bad design
#ircmaxell adds: Remember, you should always favor composition over inheritance. And this isn't spaghetti code, it's a pretty standard method of implementing a stripped down notification system (You could build a full blown observer or mediator pattern, but for really simple tasks, this may be a whole lot better since it's easier to maintain).
#Victor asks for a minimal one file example returning this error. This is what helped me solve the issue. When I tried the sample file, it work perfectly leading me to believe that something else indeed was wrong.
#Shakti Singh suggests trying call_user_func( array( $this, $this->handler ), $var);
#Victor Nicollet responds saying This would attempt to call a member function $this->classHandler (which probably doesn't exist) instead of the global classHandler
#abesto gives it a shot, ends up with a very similar implementation as my own, which works without my typo.
#Victor Nicollet answers by claiming that the classHandler needs to be defined prior to the call.
#Blizz responds by saying that PHP parses classes and functions first and then the regular code.
MyClass.php ( singleton )
public $handler;
public function myMethod()
{
$var = "test";
call_user_func( $this->handler, $var );
// PHP Warning: First argument is expected to be a valid callback
}
Script.php
$myClass = new MyClass;
$myClass->handler = "classHandler";
$myClass->myMethod();
function classHandler( $var )
{
echo $var;
}
If this is incorrect, what is the commonly practiced means of invoking handlers / event handlers in php?
Note that this is a simplified version of the actual script
You have to call something like this
call_user_func( array( $this, $this->handler ), $var);
Read your Script.php code again. What it's doing is:
Instantiate MyClass (I'm assuming you forgot the new here).
Define the handler to be classHandler.
Run myMethod(), which attempts to call classHandler.
Define classHandler.
Obviously, if you run 3 before you run 4, it's not going to work. You must define the function first, and then run any code that might want to call it.
In the larger scheme of things, I suspect there is no file like Script.php, and instead file A defines the class, file B instantiates the class and runs myMethodand file C defines classHandler. In such a situation, you need to make sure that file C is loaded before B is run.
First of all: if by storing functions as strings you mean that the actual implementation is in the string, then this is bad practice. You don't have to do it to get what you want.
function afun($param) {}
class BClass { function bfun($param) {} }
call_user_func('afun', 'param'); // Calls function afun
$binstance = new BClass();
call_user_func(array($binstance, 'bfun'), 'param'); // Calls bfun on binstance
// Update according to comment
class CClass {
private $handler;
public function __construct($handler) { $this->handler = $handler; }
public foo() {
// do stuff
call_user_func($this->handler, 'param');
}
}
$cinstance = new CClass('afun');
$cinstance->foo();

Categories