I'm working in Magento, but this is more of a general PHP question. The situation is that within Magento, there are classes that extend classes that extend classes that extend classes. I want to be able to quickly find which class actually contains the definition for a method and/or if that method is actually magic.
So for instance, if I'm 10 levels deep into classes extending others, and 4 of those 10 classes have a method called getHtml
, I want to be able to find out which one of those methods is actually being called when I call $this->getHtml()
. I would also like to be able to tell if getHtml
is actually a magic method.
How can I do this with the PHP Reflection Class, or any other programmatic means?
(untested code below — if you find any bugs I'd appreciate an update in the comments)
The best you'll be able to do with the Reflection API is find the classes in the hierarchy where the method is not defined. The ReflectionClass
's hasMethod
feature will take parent classes into account — a better name for it might be aClassInTheHierarchyHasThisMethod
. Consider this (quick top-my-head) inline function
$getClassesWithMethod = function($classOrObject, $method, $return=false) use(&$getClassesWithMethod)
{
$return = $return ? $return : new ArrayObject;
$r = new ReflectionClass($classOrObject);
if($r->hasMethod($method))
{
$return['has ' . $method . ' method'][] = $r->getName();
}
else
{
$return['no '. $method . ' method'][] = $r->getName();
}
$parent = $r->getParentClass();
if($parent)
{
$getClassesWithMethod($parent->getName(), $method, $return);
}
return $return;
};
$product = Mage::getModel('catalog/product');
$classesWithMethod = $getClassesWithMethod($product, 'load');
var_dump((array)$classesWithMethod);
Run the above, and you'll get
array (size=2)
'has load method' =>
array (size=4)
0 => string 'Mage_Catalog_Model_Product' (length=26)
1 => string 'Mage_Catalog_Model_Abstract' (length=27)
2 => string 'Mage_Core_Model_Abstract' (length=24)
'no load method' =>
array (size=1)
0 => string 'Varien_Object' (length=13)
So you know Varien_Object
doesn't have the method load
defined, which means it shows up first in Mage_Core_Model_Abstract
. However, you won't know if there's also a definition in Mage_Catalog_Model_Abstract
or Mage_Catalog_Model_Product
. The Reflection API won't get you this.
What can get you this using the token_get_all
method. This method can break a PHP file down into it's component PHP/Zend tokens. Once you have that, you can write a small parser in PHP that identifies method/function definitions in a specific class file. You can use this to recursively check the hierarchy. Again, an inline function.
$getClassesWithConcreteDefinition = function($classOrObject,$method,$return=false) use(&$getClassesWithConcreteDefinition)
{
$return = $return ? $return : new ArrayObject;
$r = new ReflectionClass($classOrObject);
$tokens = token_get_all(file_get_contents($r->getFilename()));
$is_function_context = false;
foreach($tokens as $token)
{
if(!is_array($token)){continue;}
$token['name'] = token_name($token[0]);
if($token['name'] == 'T_WHITESPACE'){ continue; }
if($token['name'] == 'T_FUNCTION')
{
$is_function_context = true;
continue;
}
if($is_function_context)
{
if($token[1] == $method)
{
$return[] = $r->getName();
}
$is_function_context = false;
continue;
}
}
$parent = $r->getParentClass();
if($parent)
{
$getClassesWithConcreteDefinition($parent->getName(),$method,$return);
}
return $return;
};
$product = Mage::getModel('catalog/product');
$hasActualDefinition = $getClassesWithConcreteDefinition($product, 'setData');
var_dump((array)$hasActualDefinition);
Here we're checking for the setData
method. The above will return
array (size=2)
0 => string 'Mage_Catalog_Model_Abstract' (length=27)
1 => string 'Varien_Object' (length=13)
Because setData
is defined in both the Mage_Catalog_Model_Abstract
class and the Varien_Object
class. You should be able to modify these functions to fit your own needs. Good luck!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With