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?

Exception from passing an callable array as a callable parameter

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