Passing an instance method as argument in PHP - 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

PHP Type hinting callable function

Ok, so I have this class method defined to take a callback and I've type hinted it as callable as per the type hinting documentation.
protected function AddTransformData(array $definition,array $where,callable $callback){
$this->transforms[]=[$definition,$where,$callback];
}
Here is an example of where I might call this function. Using the array syntax for passing a method and object as a callback.
public function __construct(PostalZoneMapping $pzm){
$this->pzm=$pzm;
$this->AddTransformData(Countries::region,['filter'],[$this,'TransformId']);
$this->AddTransformData(PostalZones::id,['filter'],[$this,'TransformId']);
$this->ReceiveData();
}
This throws an error, image below, complaining about argument 3 not being callable, but an array. Logically, I guess this makes sense, as it is array, but it's an array of a callable function - surely it must detect that it's a callback?
Is this a PHP quirk or am I doing something wrong?
public function __construct(PostalZoneMapping $pzm){
$this->pzm=$pzm;
$method = 'TransformId';
$callable = fn() => $this->$method();
$this->AddTransformData(Countries::region,['filter'], $callable);
$this->AddTransformData(PostalZones::id,['filter'], $callable);
$this->ReceiveData();
}
if you have PHP version below 7.4 then instead of this:
$callable = fn() => $this->$method();
do this:
$callable = function() use ($method) { $this->$method() };
You also can receive an arguments:
$callable = fn($param) => $this->$method($param);
or
$callable = function($param) use ($method) { $this->$method($param)};
Looks like TransformId is not a method on that class. Maybe its a typo, maybe its a property but not a method.
In order for array to be a valid callback it has to be: A method of an instantiated object is passed as an array containing an object at index 0 and the method name at index 1.
This works:
class A {
function __construct() {
$this->asd2([$this, 'asd']);
}
private function asd() {}
public function asd2(callable $c) {}
}
$a = new A();
This doesnt:
class A {
function __construct() {
$this->asd2([$this, 'somethingElse']);
}
private function asd() {}
public function asd2(callable $c) {}
}
Fatal error: Uncaught TypeError: Argument 1 passed to A::asd2() must be callable, array given, called in
If I'm wrong - paste whole class code including TransformId method.
I create some test code to reproduce the error, having noticed that I declared the callback as private! This code won't work, but will work if you change the TransformId method to protected or public.
<?php
abstract class CommonDataInterface{
private $transforms=[];
/**
* Adds a callback that transform data
*
* #param array $definition Definition, where, if matches, callback is called.
* #param array $where Where to transform data, array values one or more of 'set','insert' or'filter'
* #param callable $callback Function that takes value as parameter, and returns transformed value.
* #return void
*/
protected function AddTransformData(array $definition,array $where,callable $callback){
$this->transforms[]=[$definition,$where,$callback];
}
}
class Api_PostalZoneMapping extends CommonDataInterface{
private $pzm;
public function __construct($pzm){
$this->pzm=$pzm;
$this->AddTransformData(['blah'],['filter'],[$this,'TransformId']);
$this->AddTransformData(['blah2'],['filter'],[$this,'TransformId']);
//$this->ReceiveData();
}
private function TransformId($data){
if($data==-1)
return null;
return $data;
}
}
$p=new Api_PostalZoneMapping('not relevant for test');

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.

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

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.

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,

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