knot22
knot22

Reputation: 2768

programmatically identify methods that override trait methods

I'm trying to generate an array of methods in a class that override trait methods. Here is a simplified example that shows the structure of methods of a typical class and trait in my app:

class demo
{
    public function MethodA()
    {
        return 'method A from class';
    }

    public function MethodC()
    {
        return 'method C from class';
    }

    public function MethodE()
    {
        return 'method E from class';
    }

    use tGeneric;
}

trait tGeneric
{
    public function MethodA()
    {
        return 'method A from trait';
    }

    public function MethodB()
    {
        return 'method B from trait';
    }

    public function MethodD()
    {
        return 'method D from trait';
    }

    public function MethodE()
    {
        return 'method E from trait';
    }
}

Per the precedence rules listed in the PHP manual:

The precedence order is that methods from the current class override Trait methods, which in turn override methods from the base class.

this is behaving as expected because the output from this code:

$object = new demo();
$array = [
    $object->MethodA(),
    $object->MethodB(),
    $object->MethodC(),
    $object->MethodD(),
    $object->MethodE()
];

$br = '<br>';
$msg = '';

foreach ($array as $value):
    $msg .= $value . $br;
endforeach;
echo $msg . $br;

is:
enter image description here

The methods in the class demo that override the trait methods from tGeneric are MethodA() and MethodE(). Is there a way to programmatically generate an array of just these methods in the class that override the methods from the trait?

I have experimented with reflection but the GetMethods() method retrieves all methods for a class, regardless of whether they originated in the class or were obtained via the use of a trait.

This code:

$rc = new ReflectionClass('demo');
$d = $rc->GetMethods();

$traits = class_uses('demo');
foreach ($traits as $trait):
    $reflection = new ReflectionClass($trait);
    $t = $reflection->GetMethods();
endforeach;

DisplayInfo($d);
DisplayInfo($t);

function DisplayInfo($array)
{
    $br = '<br>';
    echo '<b>' . $array[0]->class . '</b>' .  $br;
    foreach ($array as $value):
        echo $value->name . $br;
    endforeach; 
    echo $br;
}

gives this output:
enter image description here

Upvotes: 5

Views: 236

Answers (2)

Jeto
Jeto

Reputation: 14927

You could make almost sure a method overrides a trait method by comparing:

Condition

if ($class_method->getFileName() !== $trait_method->getFileName() 
    || $class_method->getStartLine() !== $trait_method->getStartLine()) {
  $methods_overridden[] = $class_method->getName();
}

(of course, they also need to have the same name)

Full function

/**
 * Given a class name, retrieves the corresponding class' methods that override
 * trait methods.
 *
 * @param string $class_name
 * @return \ReflectionMethod[]
 * @throws \ReflectionException
 */
function getMethodsOverriddenFromTraits(string $class_name): array
{
  $class = new \ReflectionClass($class_name);

  // Retrieve trait methods

  $trait_methods = [];
  foreach ($class->getTraits() as $trait) {
    foreach ($trait->getMethods() as $trait_method) {
      $trait_methods[$trait_method->getName()] = $trait_method;
    }
  }

  // Compute class methods that override them

  $methods_overridden = [];
  foreach ($class->getMethods() as $class_method) {
    if (array_key_exists($class_method->getName(), $trait_methods)) {
      $trait_method = $trait_methods[$class_method->getName()];
      if ($class_method->getFileName() !== $trait_method->getFileName() 
          || $class_method->getStartLine() !== $trait_method->getStartLine()) {
        $methods_overridden[] = $class_method->getName();
      }
    }
  }

  return $methods_overridden;
}

Demo here: https://3v4l.org/EcFIC

Upvotes: 3

Nigel Ren
Nigel Ren

Reputation: 57121

One possible solution would be more applicable if it was more a real code scenario where your traits and classes where in individual files - which should be how you code anyway (IMHO)...

So I put the trait code in a file (I've called it TraitTest.php). Then in my main script is the rest of the code you have, but of course using require_once 'TraitTest.php';. Then in the DisplayInfo() function, I've just added the value of getFileName(), which shows the filename where the method is defined...

function DisplayInfo($array)
{
    $br = PHP_EOL;
    echo '<b>' . $array[0]->class . '</b>' .  $br;
    foreach ($array as $value)  {
        echo $value->getFileName()."->".$value->name . $br;
    }
    echo $br;
}

which shows...

<b>demo</b>
/home/nigel/workspace2/Test/t1.php->MethodA
/home/nigel/workspace2/Test/t1.php->MethodC
/home/nigel/workspace2/Test/t1.php->MethodE
/home/nigel/workspace2/Test/TraitTest.php->MethodB
/home/nigel/workspace2/Test/TraitTest.php->MethodD

<b>TraitTest</b>
/home/nigel/workspace2/Test/TraitTest.php->MethodA
/home/nigel/workspace2/Test/TraitTest.php->MethodB
/home/nigel/workspace2/Test/TraitTest.php->MethodD
/home/nigel/workspace2/Test/TraitTest.php->MethodE

As you can see, MethodB is showing up as being defined in TraitTest.php.

Upvotes: 1

Related Questions