Silence "Declaration ... should be compatible" warnings in PHP 7 - php

After upgrade to PHP 7 the logs almost choked on this kind of errors:
PHP Warning: Declaration of Example::do($a, $b, $c) should be compatible with ParentOfExample::do($c = null) in Example.php on line 22548
How do I silence these and only these errors in PHP 7?
Before PHP 7 they were E_STRICT type of warnings which could be easily dealt with. Now they're just plain old warnings. Since I do want to know about other warnings, I can't just turn off all warnings altogether.
I don't have a mental capacity to rewrite these legacy APIs not even mentioning all the software that uses them. Guess what, nobody's going to pay for that too. Neither I develop them in the first place so I'm not the one for blame. (Unit tests? Not in the fashion ten years ago.)
I would like to avoid any trickery with func_get_args and similar as much as possible.
Not really I want to downgrade to PHP 5.
I still want to know about other errors and warnings.
Is there a clean and nice way to accomplish this?

1. Workaround
Since it is not always possible to correct all the code you did not write, especially the legacy one...
if (PHP_MAJOR_VERSION >= 7) {
set_error_handler(function ($errno, $errstr) {
return strpos($errstr, 'Declaration of') === 0;
}, E_WARNING);
}
This error handler returns true for warnings beginning with Declaration of which basically tells PHP that a warning was taken care of. That's why PHP won't report this warning elsewhere.
Plus, this code will only run in PHP 7 or higher.
If you want this to happen only in regard to a specific codebase, then you could check if a file with an error belongs to that codebase or a library of interest:
if (PHP_MAJOR_VERSION >= 7) {
set_error_handler(function ($errno, $errstr, $file) {
return strpos($file, 'path/to/legacy/library') !== false &&
strpos($errstr, 'Declaration of') === 0;
}, E_WARNING);
}
2. Proper solution
As for actually fixing someone else's legacy code, there is a number of cases where this could be done between easy and manageable. In examples below class B is a subclass of A. Note that you do not necessarily will remove any LSP violations by following these examples.
Some cases are pretty easy. If in a subclass there's a missing default argument, just add it and move on. E.g. in this case:
Declaration of B::foo() should be compatible with A::foo($bar = null)
You would do:
- public function foo()
+ public function foo($bar = null)
If you have additional constrains added in a subclass, remove them from the definition, while moving inside the function's body.
Declaration of B::add(Baz $baz) should be compatible with A::add($n)
You may want to use assertions or throw an exception depending on a severity.
- public function add(Baz $baz)
+ public function add($baz)
{
+ assert($baz instanceof Baz);
If you see that the constraints are being used purely for documentation purposes, move them where they belong.
- protected function setValue(Baz $baz)
+ /**
+ * #param Baz $baz
+ */
+ protected function setValue($baz)
{
+ /** #var $baz Baz */
If you subclass has less arguments than a superclass, and you could make them optional in the superclass, just add placeholders in the subclass. Given error string:
Declaration of B::foo($param = '') should be compatible with A::foo($x = 40, $y = '')
You would do:
- public function foo($param = '')
+ public function foo($param = '', $_ = null)
If you see some arguments made required in a subclass, take the matter in your hands.
- protected function foo($bar)
+ protected function foo($bar = null)
{
+ if (empty($bar['key'])) {
+ throw new Exception("Invalid argument");
+ }
Sometimes it may be easier to alter the superclass method to exclude an optional argument altogether, falling back to func_get_args magic. Do not forget to document the missing argument.
/**
+ * #param callable $bar
*/
- public function getFoo($bar = false)
+ public function getFoo()
{
+ if (func_num_args() && $bar = func_get_arg(0)) {
+ // go on with $bar
Sure this can become very tedious if you have to remove more than one argument.
Things get much more interesting if you have serious violations of substitution principle. If you do not have typed arguments, then it is easy. Just make all extra arguments optional, then check for their presence. Given error:
Declaration of B::save($key, $value) should be compatible with A::save($foo = NULL)
You would do:
- public function save($key, $value)
+ public function save($key = null, $value = null)
{
+ if (func_num_args() < 2) {
+ throw new Exception("Required argument missing");
+ }
Note that we couldn't use func_get_args() here because it does not account for default (non-passed) arguments. We are left with only func_num_args().
If you have a whole hierarchies of classes with a diverging interface, it may be easier diverge it even further. Rename a function with conflicting definition in every class. Then add a proxy function in a single intermediary parent for these classes:
function save($arg = null) // conforms to the parent
{
$args = func_get_args();
return $this->saveExtra(...$args); // diverged interface
}
This way LSP would still be violated, although without a warning, but you get to keep all type checks you have in subclasses.

For those who want to actually correct your code so it no longer triggers the warning: I found it useful to learn that you can add additional parameters to overridden methods in subclasses as long as you give them default values. So for example, while this will trigger the warning:
//"Warning: Declaration of B::foo($arg1) should be compatible with A::foo()"
class B extends A {
function foo($arg1) {}
}
class A {
function foo() {}
}
This will not:
class B extends A {
function foo($arg1 = null) {}
}
class A {
function foo() {}
}

If you must silence the error, you can declare the class inside a silenced, immediately-invoked function expression:
<?php
// unsilenced
class Fooable {
public function foo($a, $b, $c) {}
}
// silenced
#(function () {
class ExtendedFooable extends Fooable {
public function foo($d) {}
}
})();
I would strongly recommend against this, though. It is better to fix your code than to silence warnings about how it is broken.
If you need to maintain PHP 5 compatibility, be aware that the above code only works in PHP 7, because PHP 5 did not have uniform syntax for expressions. To make it work with PHP 5, you would need to assign the function to a variable before invoking it (or make it a named function):
$_ = function () {
class ExtendedFooable extends Fooable {
public function foo($d) {}
}
};
#$_();
unset($_);

PHP 7 removes the E_STRICT error level. Info about this can be found in the PHP7 compatibility notes. You might also want to read the proposal document where it was discussed while PHP 7 was being developed.
The simple fact is this: The E_STRICT notices were introduced a number of versions ago, in an attempt to notify developers that they were using bad practice, but initially without trying to force any changes. However recent versions, and PHP 7 in particular, have become more strict about these things.
The error you're experiencing is a classic case:
You have defined a method in your class that overrides a method of the same name in the parent class, but your override method has a different argument signature.
Most modern programming languages would not actually allow this at all. PHP used to allow developers to get away with stuff like this, but the language is becoming more strict with every version, especially now with PHP 7 -- they went with a new major version number specifically so that they could justify making significant changes that break backward compatibility.
The problem you have is because you've already been ignoring the warning messages. Your question implies that this is the solution you want to continue with, but messages like "strict" and "deprecated" should be treated as an explicit warning that your code is likely to break in future versions. By ignoring them for the past number of years, you have effectively placed yourself in the situation you have now. (I know that's not what you want to hear, and doesn't really help the situation now, but it's important to make it clear)
There really isn't a work around of the kind you're looking for. The PHP language is evolving, and if you want to stick with PHP 7 your code will need to evolve too. If you really can't fix the code, then you will either have to suppress all warnings or else live with these warnings cluttering up your logs.
The other thing you need to know if you plan to stick with PHP 7 is that there are a number of other compatibility breaks with this version, including some that are quite subtle. If your code is in a state where it has errors like the one you're reporting, it means that it's probably been around for quite a while, and likely has other issues that will cause you problems in PHP 7. For code like this, I would suggest doing a more thorough audit of the code before committing to PHP 7. If you're not prepared to do that, or not prepared to fix the bugs that are found (and the implication from your question is that you are not), then I'd suggest that PHP 7 is probably an upgrade too far for you.
You do have the option of reverting to PHP 5.6. I know you said you don't want to do that, but as a short-to-medium term solution it will make things easier for you. Frankly, I think it might be your best option.

I agree: the example in the first post is bad practice.
Now what if you have that example :
class AnimalData {
public $shout;
}
class BirdData extends AnimalData {
public $wingNumber;
}
class DogData extends AnimalData {
public $legNumber;
}
class AnimalManager {
public static function displayProperties(AnimalData $animal) {
var_dump($animal->shout);
}
}
class BirdManager extends AnimalManager {
public static function displayProperties(BirdData $bird) {
self::displayProperties($bird);
var_dump($bird->wingNumber);
}
}
class DogManager extends AnimalManager {
public static function displayProperties(DogData $dog) {
self::displayProperties($dog);
var_dump($dog->legNumber);
}
}
I believe this is a legitimate code structure, nevertheless this will raise a warning in my logs because the displayProperties() do not have the same parameters. Moreover I can't make them optional by adding a = null after them...
Am I right thinking this warning is wrong in this specific example please?

I had this issue as well. I have a class that overrides a function of the parent class, but the override has different num of parameters. I can think of a few easy work arounds - but do require minor code change.
change the name of the function in the subclass (so it no longer overrides parent function)
-or-
change the parameters of the parent function, but make the extra parameters optional (e.g., function func($var1, $var2=null) - this may be easiest and require less code changes. But it may not be worth to change this in the parent if its used so many other places. So I went with #1 in my case.
If possible, instead of passing the extra params in the subclass function, use global to pull in the extra params. This is not ideal coding; but a possible band-aid anyway.

You can remove the parent class method definition altogether and intercept it with a magic method.
public function __call($name, $args)
{
if($name == 'do') {
// do things with the unknown # of args
} else {
throw new \Exception("Unknown method $name", 500);
}
}
I just ran into this problem and went this route

If the base class has fewer arguments than the derived class, it is possible to add additional argument(s) to the derived class like this:
$namespace = 'default';
if (func_num_args() > 2) {
$namespace = func_get_arg(2);
}
In this way, you add a 3rd "defaulted" argument, but do not change the signature.
I would only suggest this if you have a substantial amount of code that calls this and are unable to change that code, and want to maintain backward compatibility.
I found this situation in some old Joomla code (v1.5) where JSession::set added a $namespace param, but has JObject as a base class, where JObject::set has no such parameter.

Related

Why does php allow invalid return type declerations it knows it can't allow?

As far as I can tell php has the ability to prevent a return type from being declared where it knows it's problematic.
class Foo {
public function __clone(): Baz {
return new Baz;
}
}
class Baz {
}
$foo = new Foo;
$newFoo = clone $foo;
This results in a Fatal error: Clone method Foo::__clone() cannot declare a return type, which is perfectly sensible.
But then why would php allow things like this:
class Foo {
public function __toString(): float {
return "WAT!?!";
}
}
echo new Foo;
This results in
Fatal error: Uncaught TypeError: Return value of Foo::__toString() must be of the type float, string returned
Which doesn't make sense, because were you to try and return a float:
Fatal error: Uncaught Error: Method Foo::__toString() must return a string value
Wouldn't it make more sense for php to prevent the declared return type of these types of methods rather than give those dubious errors? If not, what is the underlying reason behind this internally? Is there some mechanical barricade that prevents php from doing this where it can do it in cases like clone?
TL;DR: Supporting type inferences on magic methods breaks backwards compatibility.
Example: what does this code output?
$foo = new Foo;
$bar = $foo->__construct();
echo get_class($bar);
If you said Foo, you are incorrect: it's Bar.
PHP has a long, complicated evolution of its return type handling.
Before PHP 7.0, return type hints were a parse error.
In PHP 7.0, we got return type declarations with very simple rules (RFC), and after perhaps the most contentious internal debate ever, we got strict types (RFC).
PHP limped along with some oddities in co- and contra-variance until PHP 7.4, where we got many of these sorted (RFC).
The behavior of today reflects this organic growth, warts and all.
You indicate that the __clone() behavior is sensible, then compare that to the apparently non-sensical __toString() behavior. I challenge that neither of them are sensible, under any rational expectation of type inference.
Here's the __clone engine code:
6642 if (ce->clone) {
6643 if (ce->clone->common.fn_flags & ZEND_ACC_STATIC) {
6644 zend_error_noreturn(E_COMPILE_ERROR, "Clone method %s::%s() cannot be static",
6645 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
6646 } else if (ce->clone->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
6647 zend_error_noreturn(E_COMPILE_ERROR,
6648 "Clone method %s::%s() cannot declare a return type",
6649 ZSTR_VAL(ce->name), ZSTR_VAL(ce->clone->common.function_name));
6650 }
6651 }
Pay careful attention to that wording (emphasis mine):
Clone method ... cannot declare a return type
__clone() gave you an error not because the types were different, but because you gave a type at all! This also is a compile error:
class Foo {
public function __clone(): Foo {
return new Foo;
}
}
"Why?!", you scream.
I believe there are two reasons:
Internals is beholden to a high-bar of backwards compatibility maintenance.
Incremental improvement comes slowly, each improvement building on earlier ones.
Let's talk about #1. Consider this code, which is valid all the way back to PHP 4.0:
<?php
class Amount {
var $amount;
}
class TaxedAmount extends Amount {
var $rate;
function __toString() {
return $this->amount * $this->rate;
}
}
$item = new TaxedAmount;
$item->amount = 242.0;
$item->rate = 1.13;
echo "You owe me $" . $item->__toString() . " for this answer.";
Some poor soul used __toString as their own method, in a perfectly reasonable way. Now preserving its behavior is a top priority, so we can't make changes to the engine that break this code. That's the motivation for strict_types declaration: allowing opt-in changes to parser behavior so as to keep old behavior going while still adding new behavior.
You might ask: why don't we just fix this when declare(strict_types=1) is on? Well, because this code is perfectly valid in strict types mode, too! It even makes sense:
<?php declare(strict_types=1);
class Amount {
var $amount;
}
class TaxedAmount extends Amount {
var $rate;
function __toString(): float {
return $this->amount * $this->rate;
}
}
$item = new TaxedAmount;
$item->amount = 242.0;
$item->rate = 1.13;
echo "You owe me $" . $item->__toString() . " for this answer.";
Nothing about this code smells. It's valid PHP code. If the method were called getTotalAmount instead of __toString, no one would bat an eye. The only odd part: the method name is "reserved".
So the engine can neither (a) enforce __toString returns a string type nor (b) prevent you from setting your own return type. Because to do either would violate backwards compatibility.
What we could do, however, is implement a new affirmative opt-in that says these methods aren't directly callable. Once we do that, then we can add type inference to them. Hypothetically:
<?php declare(strict_magic=1);
class Person {
function __construct(): Person {
}
function __toString(): string {
}
// ... other magic
}
(new Person)->__construct(); // Fatal Error: cannot call magic method on strict_magic object
And this is point #2: once we have a way to protect backwards compatibility, we can add a way to enforce types on magic methods.
In summary, __construct, __destruct, __clone, __toString, etc. are both (a) functions the engine calls in certain circumstances for which it can reasonably infer types and (b) functions that - historically - can be called directly in ways that violate the reasonable type inference from (1).
This is the reason PR 4117 to fix Bug #69718 is blocked.
The only way to break this stale-mate: the developer opts in to a promise that these methods cannot be called directly. Doing that frees the engine to enforce strict type inference rules.

Different number of parameters in function overriding

I want to ask how to enable full error reporting, E_ALL and startup errors in php.ini don't have effect in my case.
My code:
class A
{
function funcA(arg1=null, arg2=null, arg3=false, arg4=null) {}
}
class B extends A
{
function funcB() {}
}
class C extends B
{
function funcA(arg1=null, arg2=null, arg3=false) {}
}
With php 7.0 it was allowed and it was working, after upgrading to php 7.2.15 there is some kind of crash of php, script execution stops, no errors in error logs. With php 7.2 there must be the same number of method parameters like in parent class, that's not a problem for me, but problem is that I don't have any feedback from php about this error.
Do you have any ideas why there is no error or exception? I'm using development php.ini with all errors display enabled.
This code always produces an incompatible signature warning from version 7.0.33 to 7.3.
It can be confirmed here: https://3v4l.org/Ifmbk
Actually, you are unintentionally breaking the L rule of the SOLID, which stands for Liskov's Substitution Principle:
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Instances of C and A in your example are not literally interchangeable, even having optional arguments in the signature.
Still, you have at least two options and both of them requires a design change and the warning's itself confirming the existence of a smell.
Remove the inheritance if there is a really few common functionality and go with the composition:
class B
{
/**
* A
*/
private $a;
public function __construct(A $a) {
$this->a = $a;
}
}
Or split the functionality to different methods and have a more relaxed interface to accept AbcInterface in other places and validate the instance type you got in actual implementation:
interface AbcInterface
{
public function B();
}
class A implements AbcInterface
{
public function funcA($arg1=null, $arg2=null, $arg3=false, $arg4=null)
{
}
public function funcAWithLessArgs($arg1=null, $arg2=null, $arg3=false)
{
}
}
In reality, what you need here is function overloading which is does not exists in PHP ecosystem since the beginning.

How to alias a class method in PHP?

Let's say I have a class in PHP with a method that serialises data. Being English, I use the English spelling of -ISE.
class Foo
{
public function serialise($data)
{
return json_encode($data);
}
}
Let's also say that I have an American developer on my team who tries to "serialize" the data (-IZE).
$foo = new Foo();
$foo->serialize(['one', 'two', 'three']);
// PHP Fatal error: Uncaught Error: Call to undefined method Foo::serialize()
The obvious way to solve this is to alias the method. My instincts are to create another method with the American spelling and simply pass the same parameters.
class Foo
{
// ...
public function serialize($data)
{
return $this->serialise($data);
}
}
But this has a couple of downfalls:
I have to match the parameters and remember to update both methods if there's ever an update (error prone).
Additional function call for American developers means the code is less efficient.
Modifying one in a sub-class doesn't necessarily update the other.
My question is: is there a better way to alias a class method in PHP? A way that can get around the concerns that I have?
One way to reduce the maintenance burden is to use the ... operator to accept any number of arguments, and unpack them in the forwarded call:
public function serialize(...$args)
{
return $this->serialise(...$args);
}
The downside is that the function now no longer has a signature which can be auto-completed in editors; you could list the real arguments in a docblock, but that would defeat the point because you'd need to keep it up to date.
The best solution though is probably to have a naming convention, and have everyone learn it, for the same reason as any other coding convention: it's a lot easier to read code which is consistent. Using a good editor or IDE, the wrong spelling will quickly be highlighted and corrected, and there is no additional complexity of maintaining the aliases.
This is achievable using Interfaces and Traits, like so:
SerializeInterface.php (The name 'Serializable' is already used by PHP, so I'm using an unconventional name for an interface here.):
interface SerializeInterface
{
public function serialize($data);
}
SerializeableTrait.php:
require_once('SerializeInterface.php');
trait SerializableTrait
{
public function serialize($data)
{
return json_encode($data);
}
}
Foo.php
require_once('SerializeInterface.php');
require_once('SerializableTrait.php');
class Foo implements SerializeInterface
{
use SerializableTrait;
use SerializableTrait {
SerializableTrait::serialize as serialise;
}
}
$foo = new Foo();
print $foo->serialize(['one', 'two', 'three']).PHP_EOL;
print $foo->serialise(['four', 'five', 'six']).PHP_EOL;
Output of Foo.php:
["one","two","three"]
["four","five","six"]
Some caveats:
Depending on your preference, you might want to switch whether the 's' or the 'z' version of the word is used in the interface and trait vs. as the alias in the class. You'd probably want whichever spelling is going to be more commonly used to be the name in the interface and trait.
This isn't exactly failproof. There is no contract that ensures that the alias used in Foo.php is required. Therefore, the developer must remember to add the alias in each class that implements this interface.
As others have said, it may be better just to rely on standard naming conventions than to do it this way.
With the above caveats being said, this does allow for both spellings to be used and allows for the function to be updated in only one location (in SerializableTrait.php). And it also has the added benefit (relative to the IMSoP's answer using "...$args") that this should still allow for code completion in most editors.

Typehinting through Zend_Registry::get() - how to make navigation to declaration understandable to PhpStorm?

You can pass anything to Zend_Registry::set('myWidget', $someWidget), so that it's available later on.
However, when you retrieve it elsewhere, PhpStorm IDE has no clues to the type of 'myWidget'.
<?php
class AwesomeWidget {
public function doBazFlurgle() {
// doesn't matter what exactly happens here
return mt_rand();
}
}
?>
<?php
class FooController {
public function init() {
$someWidget = new AwesomeWidget();
Zend_Registry::set('awesome', $someWidget);
}
public function barAction() {
$this->init();
$awesome = Zend_Registry::get('awesome');
$awesomeNumber = $awesome->doBazFlurgle();
}
}
Navigate to declaration on the ->doBazFlurgle() call gets me a "Cannot find declaration to go to".
I could add a /** #var AwesomeWidget $awesome */ annotation, but that would require editing in many places in a sizable codebase
I could also add a return type annotation to Zend_Registry, but that does not look very maintainable (there are many instances of different classes stored this way).
I could search for the string doBazFlurgle through Find In Path..., but that is not entirely convenient (many keystrokes as opposed to a single Ctrl+click)
I noticed that NetBeans is capable of jumping to the method definition in this exact situation; is there a simple way of doing the same in PHPStorm without going through "search the entire codebase for doBazFlurgle"? I have searched available IDE actions, plugins, and fora; all in vain.
There is a way: as pointed out by #LazyOne, making a list of "what is returned from where" helps the IDE make sense of such code; this is somewhat documented on Jetbrains' website:
<?php
/** #link https://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata */
// note that this is not valid PHP code, just a stub for the IDE
namespace PHPSTORM_META {
$STATIC_METHOD_TYPES = [
\Zend_Registry::get('') => [
'awesome' instanceof \AwesomeWidget, // this is the class as seen in the question
'fooSetting' instanceof \Zend_Config, // this came in from application settings
'quuxData' instanceof \ArrayAccess, // an arraylike object
]
];
}
Including this file (named .phpstorm.meta.php by convention) in the project has resolved the issue. This file is not valid PHP - PhpStorm only uses it for typehinting. That way, Zend_Registry::get('awesome')->doBazFlurgle() is correctly resolved as calling the method on an instance of \AwesomeWidget.
There is a workaround:
Position cursor into doBazFlurgle method call (click or cursor movement)
Select word (Ctrl+W)
Navigate to symbol (Ctrl+Alt+Shift+N)
method will be offered in dropdown
Select method (Enter)
Although this is not quite as clumsy as a generic string search, it is still not quite as convenient as the usual navigate to declaration.

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