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?
Answer
Solution:
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)};
Answer
Solution:
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.
Answer
Solution:
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');
Source