PHP Type hinting callable function - php

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');

Related

How to test if Callable parameter will return a string with Reflection?

I have a function which expects a Callable parameter. I want to determine that this callable returns a string and if it doesn't an exception should be thrown.
I tried searching for this, but no luck. Does the PHP reflection API provide functionality like this? I don't want to run the method and see if it actually returns a string.
Example of what I need:
class MyClass
{
protected static $overrider = null;
public static function setOverrider(Callable $callback)
{
// Pseudo code start
if (!$callback returns string) {
throw new \Exception('Wasnt a string!');
}
// Pseudo code end
self::$overrider = $callback;
}
}
Maybe you need something like this:
class MyClass
{
protected static $overrider = null;
public static function setOverrider(Callable $callback)
{
$reflection = new ReflectionFunction($callback);
if ('string' != $reflection->getReturnType()) {
throw new \Exception('Wasnt a string!');
}
self::$overrider = $callback;
}
}
So, as I mentioned previously in comments: You need to declare returning type of your callable (which is a PHP7+ feature). It is a MUST, otherwise, it will not work
Like this:
function my_function(): string
{
return 'hello';
}
or like this if you prefer anonymous functions (Closure):
$my_callable = function(): string {
return 'hello';
}
It is as simple as this:
The interpreter cannot know the returning data type of a function without invoking it if you don't first tell the interpreter what should return the function in question.

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.

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.

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