Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
15 / 15 |
CRAP | |
100.00% |
79 / 79 |
ReflectionContext | |
100.00% |
1 / 1 |
|
100.00% |
15 / 15 |
37 | |
100.00% |
79 / 79 |
__construct() | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
resolveSymbol() | |
100.00% |
1 / 1 |
1 | |
100.00% |
3 / 3 |
|||
resolveMethodDeclaration() | |
100.00% |
1 / 1 |
4 | |
100.00% |
10 / 10 |
|||
recursivDeclaration($current, $m) | |
100.00% |
1 / 1 |
7 | |
100.00% |
15 / 15 |
|||
resolveTraitUse() | |
100.00% |
1 / 1 |
5 | |
100.00% |
11 / 11 |
|||
findMethodInInheritanceTree($cls, $method) | |
100.00% |
1 / 1 |
4 | |
100.00% |
7 / 7 |
|||
initSymbol($name, $symbolType) | |
100.00% |
1 / 1 |
3 | |
100.00% |
9 / 9 |
|||
pushParentClass($cls, $parent) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
pushUseTrait($cls, $useTrait) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
addMethodToClass($cls, $method) | |
100.00% |
1 / 1 |
1 | |
100.00% |
2 / 2 |
|||
hasDeclaringClass($cls) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getDeclaringClass($cls, $meth) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
getClassesUsingTraitForDeclaringMethod($fqcn, $methodName) | |
100.00% |
1 / 1 |
5 | |
100.00% |
11 / 11 |
|||
isInterface($cls) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
|||
isTrait($cls) | |
100.00% |
1 / 1 |
1 | |
100.00% |
1 / 1 |
<?php | |
/* | |
* Mondrian | |
*/ | |
namespace Trismegiste\Mondrian\Transform; | |
use Trismegiste\Mondrian\Graph\Graph; | |
/** | |
* ReflectionContext is a context for Reflection on types | |
* | |
* Responsible for maintaining a list of methods, traits, classes and interfaces used | |
* for building inheritance links in a digraph | |
*/ | |
class ReflectionContext | |
{ | |
const SYMBOL_TRAIT = 't'; | |
const SYMBOL_INTERFACE = 'i'; | |
const SYMBOL_CLASS = 'c'; | |
/** | |
* @var $inheritanceMap array the symbol map | |
*/ | |
protected $inheritanceMap; | |
/** | |
* @var array List of three types : trait, class, interface | |
*/ | |
private $symbolTypes = []; | |
/** | |
* Build the context | |
* | |
* @param Graph $g | |
*/ | |
public function __construct() | |
{ | |
$this->inheritanceMap = array(); | |
$this->symbolTypes = [self::SYMBOL_CLASS, self::SYMBOL_INTERFACE, self::SYMBOL_TRAIT]; | |
} | |
/** | |
* Resolve all methods inheritance, use by traits and declared | |
*/ | |
public function resolveSymbol() | |
{ | |
$this->resolveTraitUse(); | |
$this->resolveMethodDeclaration(); | |
} | |
/** | |
* Construct the inheritanceMap of method by resolving which class or interface | |
* first declares a method | |
* | |
* (not vey efficient algo, I admit), it sux, it's redundent, I don't like it | |
*/ | |
protected function resolveMethodDeclaration() | |
{ | |
foreach ($this->inheritanceMap as $className => $info) { | |
$method = $info['method']; | |
foreach ($method as $methodName => $declaringClass) { | |
$upper = $this->recursivDeclaration($declaringClass, $methodName); | |
if (!is_null($upper)) { | |
$this->inheritanceMap[$className]['method'][$methodName] = $upper; | |
} | |
} | |
} | |
} | |
private function recursivDeclaration($current, $m) | |
{ | |
$higher = null; | |
if (array_key_exists($m, $this->inheritanceMap[$current]['method'])) { | |
// default declarer : | |
$higher = $this->inheritanceMap[$current]['method'][$m]; | |
} elseif (interface_exists($current) || class_exists($current)) { | |
if (method_exists($current, $m)) { | |
$higher = $current; | |
} | |
} | |
// higher parent ? | |
foreach ($this->inheritanceMap[$current]['parent'] as $mother) { | |
$tmp = $this->recursivDeclaration($mother, $m); | |
if (!is_null($tmp)) { | |
$higher = $tmp; | |
break; | |
} | |
} | |
return $higher; | |
} | |
protected function resolveTraitUse() | |
{ | |
// @todo recursion for use trait in trait | |
foreach ($this->inheritanceMap as $className => $info) { | |
if ($info['type'] === self::SYMBOL_CLASS) { // @todo for trait, we need a recursion | |
foreach ($info['use'] as $traitName) { | |
$imported = $this->inheritanceMap[$traitName]['method']; | |
// in fact this all method is a recursion with a getImportedMethod() | |
foreach ($imported as $methodName => $declaringTrait) { | |
// @todo alias ! Because of Alias, a trait does not own its | |
// declaration. An existing trait in a class does not give | |
// you any information about its contract since the class | |
// could rename each trait's method | |
$this->addMethodToClass($className, $methodName); | |
} | |
} | |
} | |
} | |
} | |
/** | |
* Find if method is declared in superclass. | |
* | |
* Note1: Algo is DFS | |
* Note2: Must be called AFTER resolveSymbol | |
* Note3: this one is kewl, I don't know why it works at the first try | |
* | |
* @param string $cls | |
* @param string $method | |
* | |
* @return string the class which first declares the method (or null) | |
*/ | |
public function findMethodInInheritanceTree($cls, $method) | |
{ | |
if (array_key_exists($method, $this->inheritanceMap[$cls]['method'])) { | |
return $this->inheritanceMap[$cls]['method'][$method]; | |
} else { | |
// higher parent ? | |
foreach ($this->inheritanceMap[$cls]['parent'] as $mother) { | |
if (!is_null($found = $this->findMethodInInheritanceTree($mother, $method))) { | |
return $found; | |
} | |
} | |
} | |
return null; | |
} | |
/** | |
* Initialize a new symbol | |
* | |
* @param string $name class or interface name | |
* @param string $symbolType one of SYMBOL_ const | |
*/ | |
public function initSymbol($name, $symbolType) | |
{ | |
if (!in_array($symbolType, $this->symbolTypes)) { | |
// this is a security since I'm changing the API | |
throw new \InvalidArgumentException($symbolType . ' is unknown'); | |
} | |
if (!array_key_exists($name, $this->inheritanceMap)) { | |
$this->inheritanceMap[$name]['type'] = $symbolType; | |
$this->inheritanceMap[$name]['parent'] = array(); | |
$this->inheritanceMap[$name]['method'] = array(); | |
$this->inheritanceMap[$name]['use'] = []; | |
} | |
} | |
/** | |
* Stacks a parent type for a type | |
* | |
* @param string $cls the type | |
* @param string $parent the parent type of $cls | |
*/ | |
public function pushParentClass($cls, $parent) | |
{ | |
$this->inheritanceMap[$cls]['parent'][] = $parent; | |
} | |
public function pushUseTrait($cls, $useTrait) | |
{ | |
$this->inheritanceMap[$cls]['use'][] = $useTrait; | |
} | |
/** | |
* Add a method to its type with the current type | |
* for its default declaring type (after resolveSymbol, it changes) | |
* | |
* @param string $cls | |
* @param string $method | |
*/ | |
public function addMethodToClass($cls, $method) | |
{ | |
$this->inheritanceMap[$cls]['method'][$method] = $cls; | |
} | |
/** | |
* Search if a type (trait, class or interface) exists in the inheritanceMap | |
* | |
* @param string $cls | |
* | |
* @return bool | |
*/ | |
public function hasDeclaringClass($cls) | |
{ | |
return array_key_exists($cls, $this->inheritanceMap); | |
} | |
/** | |
* Finds the FQCN of the first declaring class/interface of a method | |
* | |
* @param string $cls subclass name | |
* @param string $meth method name | |
* | |
* @return string | |
*/ | |
public function getDeclaringClass($cls, $meth) | |
{ | |
return $this->inheritanceMap[$cls]['method'][$meth]; | |
} | |
/** | |
* Returns a list of all classes using a trait for declaring a given method | |
* | |
* @param string $fqcn FQCN of trait | |
* @param string $methodName the imported method | |
* | |
* @return array | |
*/ | |
public function getClassesUsingTraitForDeclaringMethod($fqcn, $methodName) | |
{ | |
$user = []; | |
foreach ($this->inheritanceMap as $classname => $info) { | |
if ($info['type'] === self::SYMBOL_CLASS) { | |
if (in_array($fqcn, $info['use'])) { | |
// class $classname is using the trait $fqcn, now | |
// is the method first declared in this class ? | |
if ($info['method'][$methodName] === $classname) { | |
// ok we can add $classname to the returned list | |
$user[] = $classname; | |
} | |
} | |
} | |
} | |
return $user; | |
} | |
/** | |
* Is FQCN an interface ? | |
* | |
* @param string $cls FQCN | |
* | |
* @return bool | |
*/ | |
public function isInterface($cls) | |
{ | |
return $this->inheritanceMap[$cls]['type'] === self::SYMBOL_INTERFACE; | |
} | |
/** | |
* Is FQCN an interface ? | |
* | |
* @param string $cls FQCN | |
* | |
* @return bool | |
*/ | |
public function isTrait($cls) | |
{ | |
return $this->inheritanceMap[$cls]['type'] === self::SYMBOL_TRAIT; | |
} | |
} |