I have been working on a class to use Reflection to interrogate other PHP classes and interfaces, what I want to know from anyone with more experience of this is, is there anything else I can add, or is there a better way of doing the things I am trying to do.
In essence I want to be able to use the interrogator to tell me everything it can about a class I give it, interfaces, parents, methods etc etc. When working on large multi-file projects this would give you an idea of the structure of the code and where to find things you need or want to change, eg you change a method in a parent only to find it was overridden by a child class, this should tell you if methods etc are overridden.
thoughts / comments etc welcome.
<?php
/* Test / Dummy classes and interfaces for testing */
interface i1
{
const interface_i1_version = '1.0.0';
public function fred();
}
interface i2 extends i1
{
const interface_i2_version = '2.0.0';
}
interface i3
{
const interface_i3_version = '3.0.0';
}
class c1
{
const class_c1_version = '1.0.0';
private $test1;
var $test2;
var $test3 = 'fred';
public function __construct()
{
}
public function foo()
{
}
}
class c2 extends c1 implements i3
{
const class_c2_version = '2.0.0';
public function bar()
{
}
}
class c3 extends c2 implements i1,i2
{
const class_c3_version = '3.0.0';
public static $Test = "sub";
public function foo($bob = 1)
{
}
public function fred()
{
}
}
class c4 extends c3
{
const class_c4_version = '3.0.0';
public static $Test = "bob";
public function foo($bob = 1)
{
}
}
/* The real class to do the work */
class interogate
{
private $constants = Array();
private function get_type($ro)
{
return implode(' ', Reflection::getModifierNames($ro->getModifiers()));
}
private function in_array_r($needle, $haystack, $strict = true)
{
foreach ($haystack as $item)
{
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && $this->in_array_r($needle, $item, $strict)))
{
return true;
}
}
return false;
}
private function check_parent($name, $parent)
{
foreach ($parent as $p)
{
if ($name == $p['name'])
{
return $p;
}
}
return false;
}
private function update_global_constants($name, $location)
{
foreach ($this->constants as $constant)
{
if ($constant['name'] === $name)
{
return;
}
}
$constant = Array('name' => $name, 'location' => $location);
$this->constants[] = $constant;
}
private function get_global_constants($name)
{
foreach ($this->constants as $constant)
{
if ($constant['name'] === $name)
{
return ($constant['location']);
}
}
return (false);
}
private function constants_from_interfaces($interface_name)
{
$results = Array();
try
{
$ro = new ReflectionClass($interface_name);
}
catch (ReflectionException $re)
{
return($results);
}
foreach ($ro->getConstants() as $name => $value)
{
$this->update_global_constants($name, $interface_name);
$location = $this->get_global_constants($name);
$results[] = Array('name' => $name, 'value' => $value, 'location' => $location);
}
return ($results);
}
private function interogate_interfaces($ro)
{
$results = Array();
foreach ($ro->getInterfaceNames() as $in)
{
if (!($this->in_array_r($in, $results)))
{
$constants = $this->constants_from_interfaces($in);
$results[] = Array('name' => $in, 'constants' => $constants);
}
}
asort($results);
return ($results);
}
private function interogate_statics($ro)
{
$results = Array();
foreach ($ro->getStaticProperties() as $name => $value)
{
$results[] = Array('name' => $name, 'value' => $value);
}
return ($results);
}
private function interogate_constants($ro, $class_name)
{
$results = Array();
foreach ($ro->getConstants() as $name => $value)
{
$this->update_global_constants($name, $class_name);
$location = $this->get_global_constants($name);
$results[] = Array('name' => $name, 'value' => $value, 'location' => $location);
}
return ($results);
}
private function interogate_properties($ro)
{
$results = Array();
foreach ($ro->getProperties() as $p)
{
$name = $p->name;
$results[] = Array('name' => $p->name, 'value' => 'todo');
}
return ($results);
}
public function interogate_methods($ro, $name, $parent, $interface)
{
$results = Array();
$results['local'] = Array();
$results['inherited'] = Array();
foreach ($ro->getMethods() as $m)
{
$local = $this->check_parent($m->name, (isset($parent['methods']['local']))?$parent['methods']['local']:Array());
$inherited = $this->check_parent($m->name, (isset($parent['methods']['inherited']))?$parent['methods']['inherited']:Array());
$overridden = 0;
$overridden_from = 0;
if ((($local !== false) || ($inherited !== false)) && ($m->class == $name))
{
$overridden = 1;
if ($local !== false)
{
$overridden_from = $local['class'];
}
elseif ($inherited !== false)
{
$overridden_from = $inherited['class'];
}
}
if ($interface)
{
$location = 'local';
}
elseif ($m->class == $name)
{
$location = 'local';
}
else
{
$location = 'inherited';
}
$type = $this->get_type($m);
$inheritable = 0;
if (($m->isProtected()) || ($m->isPublic()))
{
$inheritable = 1;
}
if (($inheritable == 0) && ($location == 'inherited'))
{
// Skip ??
}
else
{
$parameters = Array();
foreach ($m->getParameters() as $p)
{
if ($p->isOptional())
{
$optional = 'Yes';
try
{
$default = $p->getDefaultValue();
}
catch (ReflectionException $re)
{
$default = 'Built In';
}
}
else
{
$optional = 'No';
$default = 'none';
}
$position = $p->getPosition();
$parameters[] = Array('name' => $p->name, 'optional' => $optional, 'default' => $default, 'position' => $position);
}
$results[$location][] = Array('name' => $m->name, 'class' => $m->class, 'overridden' => $overridden, 'overridden_from' => $overridden_from, 'inheritable' => $inheritable, 'modifier' => $type, 'parameters' => $parameters);
}
}
return ($results);
}
public function interogate_object($name)
{
$results = Array();
try
{
$rc1 = new ReflectionClass($name);
}
catch (ReflectionException $re)
{
return($results);
}
if ($rc1->isInterface())
{
$type = 'interface';
$interface = 1;
}
else
{
$type = 'class';
$interface = 0;
}
$results['name'] = $name;
$results['type'] = $type;
$filename = $rc1->getFileName();
$start_line = $rc1->getStartLine();
$end_line = $rc1->getEndLine();
$results['filename'] = ($filename == FALSE)?'Unknown':$filename;
$results['details'] = (($start_line == FALSE) || ($end_line == FALSE))?'Unknown':'Between lines: ' . $start_line . ' and ' . $end_line;
$parent = (array) $rc1->getParentClass();
if (array_key_exists('name', $parent))
{
$parent = $parent['name'];
$results['parent'] = $this->interogate_object($parent);
}
$results['interfaces'] = $this->interogate_interfaces($rc1);
$results['methods'] = $this->interogate_methods($rc1, $name, (isset($results['parent']))?$results['parent']:Array(), $interface);
$results['statics'] = $this->interogate_statics($rc1);
$results['constants'] = $this->interogate_constants($rc1, $name);
$results['properties'] = $this->interogate_properties($rc1);
return ($results);
}
}
$i = new interogate();
//$results = $i->interogate_object('ReflectionClass');
$results = $i->interogate_object('c4');
print_r($results);
?>
I am not sure if anyone else has done this already, or if I am making some bad assumptions, but this is my first play with Reflection so this is as much a learning exercise as anything else.
The completed class (once complete of course), will be available for free (release GPL v3) for anyone to make use of.