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; | |
| } | |
| } |