1

I want to call a private class method from trait. If I do $this->privateMethod(); the call is made.

Since I want to only call that method if exists, I've implemented a isCallable() function; but this function is returning false when I was expecting true.

I'm missing something about scopes, why is this happening and how can I get is_callable() to return true?

See this example code:

<?php

function isCallable($obj, $method)
{
    //$test1 = method_exists($obj, $method);
    //var_dump($test1);
    //TRUE

    //$test2 = is_callable([$obj, $method]);
    //var_dump($test2);
    //FALSE

    return (method_exists($obj, $method) && is_callable([$obj, $method]));
}

trait helper
{
    public function show()
    {
        echo "show!";

        if(isCallable($this, "afterShow"))
        {
            $this->afterShow();
        }

        //$this->afterShow();

    }
}

class my_class
{
    use helper;

    private function afterShow()
    {
        echo "...then this.";
    }
}

$objMyClass = new my_class();

$objMyClass->show();
Matías Cánepa
  • 5,770
  • 4
  • 57
  • 97
  • Is there a reason method_exists isn't enough? It looks like is_callable only works for public methods (unless you create a public proxy method for it) – khartnett May 29 '19 at 21:18
  • @khartnett because that helper function is global and it's used to prevent a call to a private method. There is more logic to it but that's the gist – Matías Cánepa May 29 '19 at 21:26

1 Answers1

0

It seems the issue is that your global isCallable function will sometimes want to return false for a private method, but true in this case. It doesn't have a context that it's within the object's scope.

One workaround is to have an isCallable method in your trait:

trait helper
{
    public function show()
    {
        echo "show!";

        if($this->isCallable($this, "afterShow"))
        {
            $this->afterShow();
        }

    }
    private function isCallable($obj, $method)
    {
        return (method_exists($obj, $method) && is_callable([$obj, $method]));
    }
}

If you prefer to use the public function, you can pass an optional parameter into it like so:

function isCallable($obj, $method, $syntax_only = false)
{
    $test1 = method_exists($obj, $method);
    var_dump($test1);
    //TRUE

    $test2 = is_callable([$obj, $method], $syntax_only);
    var_dump($test2);
    //FALSE

    return (method_exists($obj, $method) && is_callable([$obj, $method], $syntax_only));
}

and then change your call to isCallable($this, "afterShow", true)

khartnett
  • 831
  • 4
  • 14
  • Thanks! this is working indeed. But is there a way to use the global function? perhaps passing an optional context parameter? – Matías Cánepa May 29 '19 at 21:44
  • Updated answer to use global function, although I'm not sure if is_callable with $syntax_only true is any different/better than just method_exists. This will at least allow you to keep the original functionality. – khartnett May 29 '19 at 22:22
  • syntax_only is actually worse than method_exists. It literally just checks if the first parameter is something that could conceivably be callable, for example `is_callable([new stdClass(), 'nonexistent'], true)` returns true. – Don't Panic May 29 '19 at 22:35
  • That makes sense. The only other method I can think of is to use a `ReflectionClass` to check private/protected methods. But again would that be any better than just method_exists? – khartnett May 29 '19 at 22:50