PHP - Pass a function into another function and call it [duplicate] - php

I would like to create a Listener class
class Listener {
var $listeners = array();
public function add(callable $function) {
$this->listeners[] = $function;
}
public function fire() {
foreach($this->listeners as $function) {
call_user_func($function);
}
}
}
class Foo {
public function __construct($listener) {
$listener->add($this->bar);
}
public function bar() {
echo 'bar';
}
}
$listener = new Listener();
$foo = new Foo($listener);
But this code fails with this error:
Notice: Undefined property: Foo::$bar in index.php on line 18
Catchable fatal error: Argument 1 passed to Listener::add() must be callable, null given, called in index.php on line 18 and defined index.php on line 5
What am I doing wrong?

Before PHP 5.4, there was no type named callable, so if you use it as a type hint, it means "the class named callable". If you use PHP >= 5.4, callable is a valid hint.
A callable is specified by a string describing the name of the callable (a function name or a class method name for example) or an array where the first element is an instance of an object and the second element is the name of the method to be called.
For PHP < 5.4, replace
public function add(callable $function)
with:
public function add($function)
Call it with:
$listener->add(array($this, 'bar'));

Methods and properties have separate namespaces in PHP, which is why $this->bar evaluates to null: You're accessing an undefined property.
The correct way to create an array in the form of array($object, "methodName"):
Passing the callback correctly:
$listener->add(array($this, 'bar'));
The type hint you have given is okay—as of PHP 5.4, that is.

I don't think you can specify a callable this way...
Try
$listener->add(array($this, 'bar'));
And see http://php.net/manual/en/language.types.callable.php too.

Related

Argument passed to function must be callable, array given

I'm trying to run a method on each element inside a collection. It's an object method residing in the same class:
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each([$this, 'doSomethingElse']);
}
protected function doSomethingElse($element)
{
$element->bar();
// And some more
}
If I precede the call on Collection::each with the check is_callable([$this, 'doSomethingElse']) it returns true, so apparently it is callable. The call itself however throws an exception:
Type error: Argument 1 passed to Illuminate\Support\Collection::each() must
be callable, array given, called in ---.php on line 46
The method trying to be called can be found here.
I'm bypassing this by just passing a closure that itself simply calls that function, but this would definitely a much cleaner solution and I can't find out why it throws the error.
Change the visibility of your callback method to public.
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each([$this, 'doSomethingElse']);
}
public function doSomethingElse($element)
{
$element->bar();
// And some more
}
Since PHP 7.1 you can leave your function protected. Now you can write:
protected function doSomething()
{
$discoveries = $this->findSomething();
$discoveries->each(\Closure::fromCallable([$this, 'doSomethingElse']));
}
protected function doSomethingElse($element)
{
$element->bar();
// And some more
}
Source
function with callback
public function foo(callable $callback){
$callback('something')
}
named function for callback
public function do($something) {
//
}
result
foo(function ($something) {
//
})
or call named function
foo(\Closure::fromCallable([$this, 'do']))
or
foo(this->do(...))
PHP >= 5.4
I wasn't able to reproduce your error, but my guess is that you should use $discoveries instead of $this in the callback array, like so:
$discoveries->each([$discoveries, 'doSomethingElse']);
Even though $discoveries and $this are of the same class, and therefore can access each other's protected and private methods, the type-hinting functionality may not check that the object in the callback array is the same class as the current class. However, the is_callable() method will check for this, which may explain why it returns true when you call it from inside the each() method.
PHP < 5.4
There is no type named callable, so when you use it as a type hint, it is referring to a class named callable. See this answer.

Type hinting – Difference between `Closure` and `Callable`

I noticed that I can use either of Closure or Callable as type hint if we expected some callback function to run. For example:
function callFunc1(Closure $closure) {
$closure();
}
function callFunc2(Callable $callback) {
$callback();
}
$function = function() {
echo 'Hello, World!';
};
callFunc1($function); // Hello, World!
callFunc2($function); // Hello, World!
Question
What's the difference here? In other words when to use Closure and when to use Callable or do they serve the same purpose?
The difference is, that a Closure must be an anonymous function, where callable also can be a normal function.
You can see/test this with the example below and you will see that you will get an error for the first one:
function callFunc1(Closure $closure) {
$closure();
}
function callFunc2(Callable $callback) {
$callback();
}
function xy() {
echo 'Hello, World!';
}
callFunc1("xy"); // Catchable fatal error: Argument 1 passed to callFunc1() must be an instance of Closure, string given
callFunc2("xy"); // Hello, World!
So if you only want to type hint anonymous function use: Closure and if you want also to allow normal functions use callable as type hint.
The main difference between them is that a closure is a class and callable a type.
The callable type accepts anything that can be called:
var_dump(
is_callable('functionName'),
is_callable([$myClass, 'methodName']),
is_callable(function(){})
); // all true
Where a closure will only accept an anonymous function. Note that in PHP version 7.1 you can convert functions to a closure like so:
Closure::fromCallable('functionName').
Example:
namespace foo{
class bar{
private $baz = 10;
function myCallable(callable $cb){$cb()}
function myClosure(\Closure $cb){$cb()} // type hint must refer to global namespace
}
function func(){}
$cb = function(){};
$fb = new bar;
$fb->myCallable(function(){});
$fb->myCallable($cb);
$fb->myCallable('func');
$fb->myClosure(function(){});
$fb->myClosure($cb);
$fb->myClosure(\Closure::fromCallable('func'));
$fb->myClosure('func'); # TypeError
}
So why use a closure over callable?
Strictness because a closure is an object that has some additional methods: call(), bind() and bindto(). They allow you to use a function declared outside of a class and execute it as if it was inside a class:
$inject = function($i){return $this->baz * $i;};
$cb1 = \Closure::bind($inject, $fb);
$cb2 = $inject->bindTo($fb);
echo $cb1->call($fb, 2); // 20
echo $cb2(3); // 30
You would not like to call methods on a normal function as that will raise fatal errors. So in order to circumvent that you would have to write something like:
if($cb instanceof \Closure){}
To do this check every time is pointless. So if you want to use those methods state that the argument is a closure. Otherwise just use a normal callback. This way; An error is raised on function call instead of your code causing it making it much easier to diagnose.
On a side note: The closure class cannot be extended as its final.
It's worth mentioning that this won't work for PHP versions 5.3.21 to 5.3.29.
In any of those versions you will get an output like:
Hello, World!
Catchable fatal error: Argument 1 passed to callFunc2() must be an instance of > Callable, instance of Closure given, called in /in/kqeYD on line 16 and defined in /in/kqeYD on line 7
Process exited with code 255.
One can try that out using https://3v4l.org/kqeYD#v5321
Best regards,

Passing an instance method as argument in PHP

I would like to create a Listener class
class Listener {
var $listeners = array();
public function add(callable $function) {
$this->listeners[] = $function;
}
public function fire() {
foreach($this->listeners as $function) {
call_user_func($function);
}
}
}
class Foo {
public function __construct($listener) {
$listener->add($this->bar);
}
public function bar() {
echo 'bar';
}
}
$listener = new Listener();
$foo = new Foo($listener);
But this code fails with this error:
Notice: Undefined property: Foo::$bar in index.php on line 18
Catchable fatal error: Argument 1 passed to Listener::add() must be callable, null given, called in index.php on line 18 and defined index.php on line 5
What am I doing wrong?
Before PHP 5.4, there was no type named callable, so if you use it as a type hint, it means "the class named callable". If you use PHP >= 5.4, callable is a valid hint.
A callable is specified by a string describing the name of the callable (a function name or a class method name for example) or an array where the first element is an instance of an object and the second element is the name of the method to be called.
For PHP < 5.4, replace
public function add(callable $function)
with:
public function add($function)
Call it with:
$listener->add(array($this, 'bar'));
Methods and properties have separate namespaces in PHP, which is why $this->bar evaluates to null: You're accessing an undefined property.
The correct way to create an array in the form of array($object, "methodName"):
Passing the callback correctly:
$listener->add(array($this, 'bar'));
The type hint you have given is okay—as of PHP 5.4, that is.
I don't think you can specify a callable this way...
Try
$listener->add(array($this, 'bar'));
And see http://php.net/manual/en/language.types.callable.php too.

Does a PHP static anonymous function really work?

I'm trying to learn PHP, and now I'm stuck in 'static anonymous function'.
I found this in a tutorial (http://www.slideshare.net/melechi/php-53-part-2-lambda-functions-closures-presentation)
"Object Orientation
Lambda Functions are Closures because they automatically get bound to the scope of the class that they are created in.
'$this' is not always needed in the scope.
Removing '$this' can save on memory.
You can block this behaviour by declaring the Lambda Function as static."
What is wrong with this code?
I get this error:
Parse error: parse error, expecting `T_PAAMAYIM_NEKUDOTAYIM' in C:\wamp\www\z-final\a.php on line 11
Why this code line doesn't work "return static function(){var_dump($this);};" ?
class foo
{
public function getLambda()
{
return function(){var_dump($this);};
}
public function getStaticLambda()
{
return static function(){var_dump($this);};
}
}
$foo = new foo();
$lambda = $foo->getLambda();
$staticLambda = $foo->getStaticLambda();
$lambda();
$staticLambda();
Yes, that is perfectly valid syntax in 5.4+.
Basically, it prevents auto-binding of the current class to the closure (in fact, it prevents all binding, but more on that later).
class Foo {
public function bar() {
return static function() { var_dump($this); };
}
public function baz() {
return function() { var_dump($this); };
}
}
If we instantiate that on 5.4+, the closure bar() returns will have $this set to null. Just as if you made a static call to it. But baz() would have $this set to the foo instance you called baz() on.
So:
$bar = $f->bar();
$bar();
Results in:
Notice: Undefined variable: this in /in/Bpd3d on line 5
NULL
And
$baz = $f->baz();
$baz();
Results in
object(Foo)#1 (0) {
}
Make sense? Great.
Now, what happens if we take closures defined outside of a function:
$a = function() { var_dump($this); };
$a();
We get null (and a notice)
$c = $a->bindTo(new StdClass());
$c();
We get StdClass, just as you'd expect
$b = static function() { var_dump($this); };
$b();
We get null (and a notice)
$d = $b->bindTo(new StdClass());
$d();
This is where things get interesting. Now, we get a warning, a notice, and null:
Warning: Cannot bind an instance to a static closure in /in/h63iF on line 12
Notice: Undefined variable: this in /in/h63iF on line 9
NULL
So in 5.4+, you can declare a static closure, which results in it never getting $this bound to it, nor can you ever bind an object to it...
There should be no need to define it with the static keyword.
<?php
class House
{
public function paint($color)
{
return function() use ($color) { return "Painting the house $color..."; };
}
}
$house = new House();
$callback = $house->paint('red');
var_dump($callback); // object(Closure)#2 (2) {..}
var_dump($callback()); // "Painting the house red..."

What happens in this code block during php oop?

Could someone please explain the third line where Request and $request is used. It would be great if you could provide me a link having an explanation of the same? I just want to know what is happening there.
<?php
class xyz {
public function foo(Request $request){
//some code
}
}
Type hinting:
http://php.net/manual/en/language.oop5.typehinting.php
<?php
// An example class
class MyClass
{
/**
* A test function
*
* First parameter must be an object of type OtherClass
*/
public function test(OtherClass $otherclass) {
echo $otherclass->var;
}
/**
* Another test function
*
* First parameter must be an array
*/
public function test_array(array $input_array) {
print_r($input_array);
}
}
// Another example class
class OtherClass {
public $var = 'Hello World';
}
It throws an error if the argument is not of the type specified:
<?php
// An instance of each class
$myclass = new MyClass;
$otherclass = new OtherClass;
// Fatal Error: Argument 1 must be an object of class OtherClass
$myclass->test('hello');
// Fatal Error: Argument 1 must be an instance of OtherClass
$foo = new stdClass;
$myclass->test($foo);
// Fatal Error: Argument 1 must not be null
$myclass->test(null);
// Works: Prints Hello World
$myclass->test($otherclass);
// Fatal Error: Argument 1 must be an array
$myclass->test_array('a string');
// Works: Prints the array
$myclass->test_array(array('a', 'b', 'c'));
?>
Arguments:
http://php.net/manual/en/functions.arguments.php
Type hints:
http://php.net/manual/en/language.oop5.typehinting.php
An Object of the type Request is being passed to function foo.
It is made available to the function foo in a private variable named $request.
This is a type hint, to tell php to expect an object wich has
$request instanceof Request == true
Please note that this will actually not ensure anything. If $request is null or another object, there will most likely only a catchable fatal error be thrown, and so you have to test for a valid value anyway.
The third line defines a class method called foo that can get a $request argument of type "Request".
This is a security measure for the class developer. Determine that
<?php
class User
{
private $username;
public function get_username()
{
return $this->username;
}
}
class xyz()
{
public function foo(User $currentUser)
{
$currentUser->get_username();
}
}
$x = new xyz();
$u = new User();
$x->foo($u); // That will not produce any error because we pass an Object argument of type User
$name = "my_name";
$x->foo($name); // This will produce an error because we pass a wrong type of argument
?>

Categories