I am trying to use a variable to get a function in a extended class, this is what I want to do but I can't get it to work, Thanks for your help.
class topclass {
function mode() {
$mode = 'function()';
$class = new extendclass;
$class->$mode;
}
}
Don't include the brackets "()" in the $mode variable.
class topclass {
function mode() {
$mode = 'functionx';
$class = new extendclass;
$class->$mode();
}
}
You can also use a callback, which is an array tuple of the instance and a string naming the function. If the intended call is $foo->bar(), then the callback would be:
$callback = array($foo, 'bar');
Regular functions (not a method) and static methods are stored as plain strings:
// Function bar
$callback = 'bar';
// Static method 'bar' in class Foo
$callback = 'Foo::bar';
It is called with call_user_func or call_user_func_array, the second allowing parameters to be passed to the callback function:
// No parameters
call_user_func($callback);
// Parameters 'baz' and 'bat'
call_user_func_array($callback, array('baz', 'bat');
It may seem like this is unnecessary complication, but in many instances, you may want to be able to programmatically build a function call, or you may not know ahead of time how many parameters you will be passing to a function (some functions, such as array_merge and sprintf allow a variable number of parameters).
Related
I have this code snippet that is supposed to find the differences between two arrays of feed items:
protected function execute()
{
$existingFeedItems = $feed->getItems();
$newFeedItems = $feed->loadItems();
function compareFeedItemIds($feedItem1, $feedItem2)
{
return $feedItem1->getFeedItemId() == $feedItem2->getFeedItemId() ? 0 : -1;
}
$feedItemsAdded = array_udiff($newFeedItems, $existingFeedItems, "compareFeedItemIds");
$feedItemsRemoved = array_udiff($existingFeedItems, $newFeedItems, "compareFeedItemIds");
$unchangedFeedItems = array_uintersect($newFeedItems, $existingFeedItems, "compareFeedItemIds");
}
This will throw the error:
Warning: array_udiff() expects parameter 3 to be a valid callback,
function 'compareFeedItemIds' not found or invalid function name
Even though I have defined that function above. What is the reason for PHP throwing this error? I have to add I am executing this from an object's method context.
If your callback function is defined within a namespace, then you need to indicate that namespace when you make the udiff() call.
$feedItemsAdded = array_udiff($newFeedItems, $existingFeedItems, "namespace\\compareFeedItemIds");
Otherwise PHP will search for the callback function in the global namespace
Let's assume having a local function definition and/or lambda function really is a good idea (it get's way less attractive if you have the same function definiton a couple of times scattered over your project ....).
You can define a function within another function/method and this defintion only takes place when execution of the script(s) reaches this code. But: The function definition isn't local; it bubbles up - outside of the function/class. And because of that you will get a "cannot redeclare function compareFeedItemIds" error if you execute execute() more than once.
There are several options to "fix" that.
You can assign the function to a local variable and then pass that variable as the third parameter to the array_* functions.
$compareFeedItemIds = function($feedItem1, $feedItem2) {
return $feedItem1->getFeedItemId() == $feedItem2->getFeedItemId() ? 0 : -1;
};
$feedItemsAdded = array_udiff($newFeedItems, $existingFeedItems, $compareFeedItemIds);
$feedItemsRemoved = array_diff($existingFeedItems, $newFeedItems, $compareFeedItemIds);
....
You can also store that function in an instance variable ...or a static class member.
Or... to avoid the namspace problem, just create a static method in your class and then reference that method via self::methodname or static::methodname like e.g.
<?php
class Foo {
public function bar() {
$a = [1,2,3];
$b = [2,3,4];
var_export( array_udiff($a, $b, 'self::moo') );
var_export( array_udiff($b, $a, 'self::moo') );
var_export( array_uintersect($a, $b, 'self::moo') );
}
protected static function moo($a,$b) {
return $a<=>$b;
}
}
$foo = new Foo;
$foo->bar();
I'm working on a logging class, and I'd like to add a convenience function which would log the arguments of the function from which it is called. A little convoluted, so here's some code:
function MyFunc($arg1, $arg2) {
global $oLog;
$oLog->logArgs();
}
So, I can get the name of the calling function (MyFunc) using debug_backtrace(). I'd be interested in getting the names and values of the arguments, preferably without having to add func_get_args() in the call to logArgs().
I know it's a tall order, but PHP continues to surprise me, so I'm putting it out there, just in case.
Thanks.
You can do this with reflection:
function logger()
{
$bt = debug_backtrace();
$previous = $bt[1];
if(empty($previous['class'])) {
$fn = new ReflectionFunction($previous['function']);
} else {
$class = new ReflectionClass($previous['class']);
$fn = $class->getMethod($previous['function']);
}
$parameters = $fn->getParameters();
//Get a parameter name with $parameters[$paramNum]->getName()
//Get the value from $previous['args'][$paramNum]
}
This particular implementation won't work with closures, but it will work with both global functions and class methods.
Consider the following code, which is a scheme of storing a callback function as a member, and then using it:
class MyClass {
function __construct($callback) {
$this->callback = $callback;
}
function makeCall() {
return $this->callback();
}
}
function myFunc() {
return 'myFunc was here';
}
$o = new MyClass(myFunc);
echo $o->makeCall();
I would expect myFunc was here to be echoed, but instead I get:
Call to undefined method MyClass::callback()
Can anyone explain what's wrong here, and what I can do in order to get the desired behaviour?
In case it matters, I am using PHP 5.3.13.
You can change your makeCall method to this:
function makeCall() {
$func = $this->callback;
return $func();
}
Pass it as a string and call it by call_user_func.
class MyClass {
function __construct($callback) {
$this->callback = $callback;
}
function makeCall() {
return call_user_func($this->callback);
}
}
function myFunc() {
return 'myFunc was here';
}
$o = new MyClass("myFunc");
echo $o->makeCall();
One important thing about PHP is that it recognises the type of a symbol with the syntax rather than the contents of it, so you need to state explicitly what you refer to.
In many languages you just write:
myVariable
myFunction
myConstant
myClass
myClass.myStaticMethod
myObject.myMethod
And the parser/compiler knows what each of the symbols means, because it's aware of what they refer to simply by knowing what's assigned to them.
In PHP, however, you need to use the syntax to let the parser know what "symbol namespace" you refer to, so normally you write:
$myVariable
myFunction()
myConstant
new myClass
myClass::myStaticMethod()
$myObject->method()
However, as you can see these are calls rather than references. To pass a reference to a function, class or method in PHP, combined string and array syntax is used:
'myFunction'
array('myClass', 'myStaticMethod')
array($myObject, 'myMethod')
In your case, you need to use 'myFunc' in place of myFunc to let PHP know that you're passing a reference to a function and not retrieving the value the myFunc constant.
Another ramification is that when you write $myObject->callback(), PHP assumes callback is a method because of the parentheses and it does not attempt to loop up a property.
To achieve the expected result, you need to either store a copy of/reference to the property callback in a local variable and use the following syntax:
$callback = $this->callback;
return $callback();
which identifies it as a closure, because of the dollar sign and the parentheses; or call it with the call_user_func function:
call_user_func($this->callback);
which, on the other hand, is a built-in function that expects callback.
Let's say I have a class with 10 methods, each method has different parameters.
I want to log input parameters of all methods of said class without having to do edit each method to insert that logging code. Is there away to do that ?
Just wrap it with decorator with magic __call http://ideone.com/n9ZUD
class TargetClass
{
public function A($a, $b) {}
public function B($c, $d) {}
public function C($e, $f) {}
}
class LoggingDecorator
{
private $_target;
public function __construct($target)
{
$this->_target = $target;
}
public function __call($name, $params)
{
$this->_log($name, $params);
return call_user_func_array(array($this->_target, $name), $params);
}
private function _log($name, $params)
{
echo $name . ' has been called with params: ' . implode(', ', $params) . '<br>';
}
}
$target = new TargetClass();
$logger = new LoggingDecorator($target);
$logger->A(1, 2);
$logger->A(3, 4);
The only disadvantage of this approach is that you will lose the type of the decorated class, e.g. you won't be able to satisfy type hints with it. If that is a concern, distill the interface of TargetClass and implement it in the LoggingDecorator.
Not directly, no.
You could rename all your methods to have an underscore suffix, e.g.:
myFunction() -> _myFunction()
Then, write add the magic __call() method to intercept calls to the previous (unprefixed) methods. Then you would log the request and pass on all arguments to the original method.
It's kind of ugly and still requires a change to all your method names.
I might be over simplifying this - but you can retrieve all the arguments of a function using
func_get_args();
Returns an array comprising a function's argument list
http://www.php.net/manual/en/function.func-get-args.php
<?php
function foo() {
$args = func_get_args();
var_export($args);
}
foo('arg1', 'arg1');
?>
That would output something like this -
array (
0 => 'arg1',
1 => 'arg2',
)
There are a few notes to be added here - you should read the documentation link I provided - one "limitation" is -
Note:
This function returns a copy of the passed arguments only, and does not account for default (non-passed) arguments.
This question already has answers here:
How to use class methods as callbacks
(5 answers)
Closed last year.
How can I dynamically invoke a class method in PHP? The class method is not static. It appears that
call_user_func(...)
only works with static functions?
Thanks.
It works both ways - you need to use the right syntax
// Non static call
call_user_func( array( $obj, 'method' ) );
// Static calls
call_user_func( array( 'ClassName', 'method' ) );
call_user_func( 'ClassName::method' ); // (As of PHP 5.2.3)
Option 1
// invoke an instance method
$instance = new Instance();
$instanceMethod = 'bar';
$instance->$instanceMethod();
// invoke a static method
$class = 'NameOfTheClass';
$staticMethod = 'blah';
$class::$staticMethod();
Option 2
// invoke an instance method
$instance = new Instance();
call_user_func( array( $instance, 'method' ) );
// invoke a static method
$class = 'NameOfTheClass';
call_user_func( array( $class, 'nameOfStaticMethod' ) );
call_user_func( 'NameOfTheClass::nameOfStaticMethod' ); // (As of PHP 5.2.3)
Option 1 is faster than Option 2 so try to use them unless you don't know how many arguments your going to be passing to the method.
Edit: Previous editor did great job of cleaning up my answer but removed mention of call_user_func_array which is different then call_user_func.
PHP has
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
http://php.net/manual/en/function.call-user-func.php
AND
mixed call_user_func_array ( callable $callback , array $param_arr )
http://php.net/manual/en/function.call-user-func-array.php
Using call_user_func_array is orders of magnitude slower then using either option listed above.
You mean like this?
<?php
class A {
function test() {
print 'test';
}
}
$function = 'test';
// method 1
A::$function();
// method 2
$a = new A;
$a->$function();
?>
As of PHP7, you use an array-like way:
// Static call only
[TestClass::class, $methodName](...$args);
// Dynamic call, static or non-static doesn't matter
$instance = new TestClass();
[$instance, $methodName](...$args);
Just replace the class name with TestClass, the method name with $methodName and the method arguments with ...$args. Note that, in the later case, it doesn't matter that the method is static or non-static.
One advantage is you can pass the array as a callable to a function.
call_user_func(array($object, 'methodName'));
For more details, see the php callback documentation.
EDIT: I just worked out what you were trying to ask... ah well.. will leave my comments in anyway. You can substitute names of classes and methods with variables if you like..(but you are crazy) - nick
To call a function from within a class you can do it one of two ways...
Either you can create an instance of the class, and then call it.
e.g.:
$bla = new Blahh_class();
$bla->do_something();
or... you can call the function statically.. i.e. with no instance of the class.
e.g.:
Blahh_class::do_something()
of course you do need to declare that your function is static:
class Blahh_class {
public static function do_something(){
echo 'I am doing something';
}
}
If a class is not defined as static, then you must create an instance of the object.. (so the object needs a constructor)
e.g.:
class Blahh_class {
$some_value;
public function __construct($data) {
$this->$some_value = $data;
}
public function do_something() {
echo $this->some_value;
}
}
The important thing to remember is that static class functions can not use $this as there is no instance of the class. (this is one of the reasons why they go much faster.)
This may be useful as a substitute
class ReferenceContainer {
function __construct(CallbackContainer $callbackContainer) {
//Alternatively you can have no parameters in this constructor and create a new instance of CallbackContainer and invoke the callback in the same manner
//var_dump($this->callbackContainer);
$data = 'This is how you parse a class by reference';
$callbackContainer->myCallback($data);
}
}
class CallbackContainer {
function __construct() {}
function myCallback($data) {
echo $data."\n";
}
}
$callbackContainer = new CallbackContainer();
$doItContainer = new ReferenceContainer($callbackContainer);