Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
| Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 206 |
| DocumentIt | |
0.00% |
0 / 1 |
|
0.00% |
0 / 16 |
8190.00 | |
0.00% |
0 / 206 |
| document_class | |
0.00% |
0 / 1 |
156.00 | |
0.00% |
0 / 35 |
|||
| document_public_methods | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 13 |
|||
| document_properties | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 12 |
|||
| document_constants | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 7 |
|||
| parameters | |
0.00% |
0 / 1 |
42.00 | |
0.00% |
0 / 14 |
|||
| parametersInfo | |
0.00% |
0 / 1 |
72.00 | |
0.00% |
0 / 19 |
|||
| defaultValueDisplay | |
0.00% |
0 / 1 |
156.00 | |
0.00% |
0 / 19 |
|||
| deduceParameterTypeFromDockBlock | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 6 |
|||
| deduceReturnTypeFromDockBlock | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
| calls | |
0.00% |
0 / 1 |
156.00 | |
0.00% |
0 / 17 |
|||
| class_parents | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
| class_implements | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 4 |
|||
| class_uses_deep | |
0.00% |
0 / 1 |
12.00 | |
0.00% |
0 / 10 |
|||
| relations | |
0.00% |
0 / 1 |
72.00 | |
0.00% |
0 / 21 |
|||
| expandKeyToArray | |
0.00% |
0 / 1 |
30.00 | |
0.00% |
0 / 16 |
|||
| methodDocBlockProtected | |
0.00% |
0 / 1 |
6.00 | |
0.00% |
0 / 5 |
|||
| <?php | |
| namespace ia\DocumentIt; | |
| use DateTime; | |
| use DateTimeImmutable; | |
| use Exception; | |
| use ReflectionClass; | |
| use ReflectionException; | |
| use ReflectionFunction; | |
| use ReflectionMethod; | |
| use ReflectionParameter; | |
| //use ReflectionParameter; | |
| class DocumentIt { | |
| /** | |
| * Genertes documentation for $classNam | |
| * | |
| * @param string $className | |
| * @return string | |
| */ | |
| public static function document_class($className) | |
| { | |
| try { | |
| /** @var ReflectionClass $ref */ | |
| $ref = new ReflectionClass($className); | |
| } catch (ReflectionException $e) { | |
| return "\Reflection error, correct \$ia_example['title'], $className"; | |
| } | |
| $final = $ref->isFinal() ? 'final' : ''; | |
| $abstract = $ref->isAbstract() ? 'abstract' : ''; | |
| $es = 'class'; | |
| if ($ref->isInterface()) { | |
| $es = 'interface'; | |
| } | |
| if ($ref->isTrait()) { | |
| $es = 'trait'; | |
| } | |
| $ret = "<h2>" . trim("$final $abstract $es $className") . "</h2>"; | |
| $ret .= "<ul>"; | |
| $classDocBlock = $ref->getDocComment(); | |
| if (!empty($classDocBlock)) { | |
| $ret .= "<li><code>$classDocBlock</code>"; | |
| } | |
| if ($ref->getNamespaceName()) | |
| $ret .= "<li><b>namespace</b>: " . $ref->getNamespaceName(); | |
| // extends, parents | |
| $extends = self::class_parents($ref, false); | |
| if (!empty($extends)) { | |
| $ret .= "<li><b>Extends</b>: " . implode(", ", $extends); | |
| } | |
| // interfaces | |
| $interfaces = self::class_implements($ref, false); | |
| if (!empty($interfaces)) { | |
| $ret .= "<li><b>Implements</b>: " . implode(', ' , $interfaces); | |
| } | |
| // traits | |
| $traits = self::class_uses_deep($ref, false); | |
| if (!empty($traits)) { | |
| $ret .= "<li><b>Traits</b>: " . implode(', ' , $traits); | |
| } | |
| $ret .= self::document_public_methods($ref); | |
| $ret .= self::document_properties($ref); | |
| $ret .= self::document_constants($ref); | |
| $relations = self::calls($ref->getFileName()); | |
| if(!empty($relations)) { | |
| asort($relations, SORT_NATURAL); | |
| $ret .= "<li><b>Calls</b>:<ol><li>".implode('<li>', $relations)."</ol>"; | |
| } | |
| $ret .= "</ul>"; | |
| return $ret; | |
| } | |
| /** | |
| * @param ReflectionClass $ref | |
| * @return string | |
| */ | |
| private static function document_public_methods($ref) { | |
| $ret = "<p><li><b>Public Methods</b><ol>"; | |
| foreach ($ref->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { | |
| try { | |
| $functionDocBlock = $ref->getMethod($method->name)->getDocComment(); | |
| } catch (ReflectionException $e) { | |
| $functionDocBlock = "/** Error retrieving docblock */"; | |
| } | |
| $parameters = implode(', ', self::parameters($method, $functionDocBlock) ); | |
| // $parameters = implode(', ', array_column($method->getParameters(), 'name')); | |
| $returnType = '<span class="type"> : ' . | |
| ($method->hasReturnType() ? | |
| $method->getReturnType() : | |
| self::deduceReturnTypeFromDockBlock($functionDocBlock) | |
| ) . '</span>'; | |
| $ret .= "<li><b>" . | |
| ($method->isStatic() ? 'static ' : '') . | |
| "$method->name( $parameters ) $returnType</b><br><code>$functionDocBlock</code><hr class='light'>"; | |
| } | |
| return $ret . "</ol>"; | |
| } | |
| /** | |
| * @param ReflectionClass $ref | |
| * @return string | |
| */ | |
| private static function document_properties($ref) { | |
| $ret = ''; | |
| /** @var ReflectionParameter $properties */ | |
| $properties = $ref->getProperties(ReflectionMethod::IS_PUBLIC); | |
| if (!empty($properties)) { | |
| $ret .= "<p><li><b>Public Properties</b><ol>"; | |
| foreach ($properties as $property) { | |
| try { | |
| $functionDocBlock = $ref->getProperty($property->name)->getDocComment(); | |
| } catch (ReflectionException $e) { | |
| $functionDocBlock = "/** Error retrieving docblock */"; | |
| } | |
| $ret .= "<li><b>" . ($property->isStatic() ? 'static ' : '') . | |
| "$property->name</b><br><code>$functionDocBlock</code>"; | |
| } | |
| $ret .= "</ol>"; | |
| } | |
| return $ret; | |
| } | |
| /** | |
| * @param ReflectionClass $ref | |
| * @return string | |
| */ | |
| private static function document_constants($ref) { | |
| if(empty($ref->getConstants())) { | |
| return ''; | |
| } | |
| $ret = "<li><b>Constants</b><ol>"; | |
| foreach ($ref->getConstants() as $constant => $value) { | |
| $ret .= "<li><b>$constant</b> = $value"; | |
| } | |
| $ret .= "</ol>"; | |
| return $ret; | |
| } | |
| /** | |
| * | |
| * @param ReflectionFunction|ReflectionMethod $function | |
| * @param $functionDocBlock string docBlock for the function or method | |
| * @return array | |
| */ | |
| public static function parameters($function, $functionDocBlock) { | |
| $parameters = []; | |
| foreach($function->getParameters() as $p) { | |
| try { | |
| $defaultType = ($p->isOptional() ? | |
| ' <span class="default">= ' . self::defaultValueDisplay($p->getDefaultValue()) . '</span>' : '' | |
| ); | |
| } catch(ReflectionException $e) { | |
| $defaultType = '?'; | |
| } | |
| $param = '<span class="type">' . | |
| ($p->hasType() ? | |
| $p->getType() : | |
| self::deduceParameterTypeFromDockBlock($p->getName(), $functionDocBlock) | |
| ) . '</span> ' . | |
| ($p->isPassedByReference() ? '&' : '') . | |
| '$' . $p->getName() . $defaultType; | |
| $parameters[] = $param; | |
| } | |
| return $parameters; | |
| } | |
| /** | |
| * | |
| * @param ReflectionFunction|ReflectionMethod $function | |
| * @param $functionDocBlock string docBlock for the function or method | |
| * @return array | |
| * @throws ReflectionException | |
| */ | |
| public static function parametersInfo($function, $functionDocBlock) { | |
| $parameters = []; | |
| foreach($function->getParameters() as $p) { | |
| try { | |
| $defaultType = $p->isOptional() ? | |
| ' <span class="default">= ' . self::defaultValueDisplay($p->getDefaultValue()) . '</span>' : '' | |
| ; | |
| } catch(ReflectionException $e) { | |
| $defaultType = '?'; | |
| } | |
| $param = '<span class="type">' . | |
| ($p->hasType() ? | |
| $p->getType() : | |
| self::deduceParameterTypeFromDockBlock($p->getName(), $functionDocBlock) | |
| ) . '</span> ' . | |
| ($p->isPassedByReference() ? '&' : '') . | |
| '$' . $p->getName() . $defaultType; | |
| $parameters[] = [ | |
| 'name'=>$p->getName(), | |
| 'type'=> $p->hasType() ? $p->getType() : self::deduceParameterTypeFromDockBlock($p->getName(), $functionDocBlock), | |
| 'isOptional' => $p->isOptional(), | |
| 'default' => $p->isOptional() ? self::defaultValueDisplay($p->getDefaultValue()) : '', | |
| 'prototype' => $param | |
| ]; | |
| } | |
| return $parameters; | |
| } | |
| /** | |
| * @param $d | |
| * @return string | |
| */ | |
| private static function defaultValueDisplay($d) { | |
| if($d === true) return 'true'; | |
| if($d === false) return 'false'; | |
| if($d === null) return 'null'; | |
| if(is_string($d)) return "'$d'"; | |
| if(is_numeric($d)) return $d; | |
| if(is_array($d)) { | |
| if(empty($d)) { | |
| return '[]'; | |
| } | |
| $array = str_ireplace(['Array (','Array('], '[', print_r($d, true)); | |
| if(substr($array, -1) === ')') { | |
| $array = substr($array, 0, -1).'['; | |
| } | |
| return $array; | |
| } | |
| if($d instanceof DateTimeImmutable) { | |
| return "new DateTimeImmutable()"; | |
| } | |
| if($d instanceof DateTime) { | |
| return "new DateTime()"; | |
| } | |
| if(is_object($d)) { | |
| return "new ".get_class($d)."()"; | |
| } | |
| return print_r($d, true); | |
| } | |
| /** | |
| * @param string $paramName | |
| * @param string $functionDocBlock | |
| * @return string | |
| */ | |
| private static function deduceParameterTypeFromDockBlock($paramName, $functionDocBlock) { | |
| $regExp = '/\@param\s+(.*)\s+\${0,1}'.$paramName.'[\s\.\;\:]/miu'; | |
| if(preg_match_all($regExp, $functionDocBlock, $matches, PREG_PATTERN_ORDER, 0) == 0) { | |
| $regExp = '/\@param\s+\${0,1}'.$paramName.'[\s\.\;\:]+(.*)/miu'; | |
| if(preg_match_all($regExp, $functionDocBlock, $matches, PREG_PATTERN_ORDER, 0) == 0) { | |
| return ''; | |
| } | |
| } | |
| return $matches[1][0]; | |
| } | |
| /** | |
| * @param string $functionDocBlock | |
| * @return string | |
| */ | |
| public static function deduceReturnTypeFromDockBlock($functionDocBlock) { | |
| $regExp = '/\@return\s+([a-z0-9_|\\\\]*)[\s\.\;\:\"\'\-]/miu'; | |
| if(preg_match_all($regExp, $functionDocBlock, $matches, PREG_PATTERN_ORDER, 0) == 0) { | |
| return ''; | |
| } | |
| return $matches[1][0]; | |
| } | |
| /** | |
| * @param string $fileName | |
| * @return array string names of use ..., and classes used with new or :: (statically) | |
| */ | |
| private static function calls($fileName) { | |
| // https://stackoverflow.com/questions/30308137/get-use-statement-from-class | |
| // https://github.com/Kdyby/ParseUseStatements | |
| $code = file_get_contents($fileName); | |
| $replaced = preg_replace('/(\/\*[\s\S]*\*\/)/miU', " ", $code); | |
| if($replaced !== null) { | |
| $code = $replaced; | |
| } | |
| $replaced = preg_replace('/(\/\/.*$)/miU', " ", $code); | |
| if($replaced !== null) { | |
| $code = $replaced; | |
| } | |
| $return = []; | |
| if(preg_match_all('/\bnew\s+([a-z0-9_\\\\]+)/miu', $code, $matches,PREG_PATTERN_ORDER) > 0) { | |
| foreach($matches[1] as $u) { | |
| if($u !== 'this' && $u !== 'parent') { | |
| $return[$u] = $u; | |
| } | |
| } | |
| } | |
| if(preg_match_all('/([a-z0-9_\\\\]+)::/miu', $code, $matches,PREG_PATTERN_ORDER) > 0) { | |
| foreach($matches[1] as $u) { | |
| if($u !== 'self' && $u !== 'static' && $u !== 'parent') { | |
| $return[$u] = $u; | |
| } | |
| } | |
| } | |
| return $return; | |
| } | |
| private static function class_parents($reflectionClass, $autoload = false) { | |
| $parents = class_parents($reflectionClass, $autoload); | |
| if(!empty($parents['Reflector'])) { | |
| unset($parents['Reflector']); | |
| } | |
| return $parents; | |
| } | |
| private static function class_implements($reflectionClass, $autoload = false) { | |
| $parents = class_implements($reflectionClass, $autoload); | |
| if(!empty($parents['Reflector'])) { | |
| unset($parents['Reflector']); | |
| } | |
| return $parents; | |
| } | |
| /** | |
| * @param ReflectionClass $reflectionClass | |
| * @return array | |
| */ | |
| private static function class_uses_deep($class, $autoload = false) { | |
| // https://www.php.net/manual/en/function.class-uses.php | |
| $traits = []; | |
| do { | |
| $traits = array_merge(class_uses($class, $autoload), $traits); | |
| } while($class = get_parent_class($class)); | |
| // Get traits of all parent traits | |
| $traits_to_search = $traits; | |
| while (!empty($traits_to_search)) { | |
| $new_traits = class_uses(array_pop($traits_to_search), $autoload); | |
| $traits = array_merge($new_traits, $traits); | |
| $traits_to_search = array_merge($new_traits, $traits_to_search); | |
| }; | |
| ksort($traits, SORT_NATURAL | SORT_FLAG_CASE); | |
| return array_unique($traits); | |
| } | |
| public static function relations($classes) { | |
| $classUses = []; | |
| $classUsedBy = []; | |
| foreach($classes as $className => $fileName) { | |
| try { | |
| $reflectionClass = new ReflectionClass($className); | |
| $classStructure = array_merge( | |
| self::class_parents($className, false), | |
| self::class_implements($className, false), | |
| //self::class_uses_deep($className, false), | |
| self::calls($reflectionClass->getFilename()) | |
| ); | |
| if(!empty($classStructure)) { | |
| ksort($classStructure, SORT_NATURAL | SORT_FLAG_CASE); | |
| $classUses[$className] = $classStructure; | |
| foreach($classUses[$className] as $usedBy) { | |
| if($usedBy !== 'Reflector' && $usedBy !== 'static') { | |
| $classUsedBy[$usedBy][$className] = $className; | |
| } | |
| } | |
| if(!empty($classUsedBy[$usedBy])) { | |
| ksort($classUsedBy[$usedBy], SORT_NATURAL | SORT_FLAG_CASE); | |
| } | |
| } | |
| } catch (Exception $e) { $classUses[$className][] = "<span class='error'>reflection error</span>"; } | |
| } | |
| ksort($classUses, SORT_NATURAL | SORT_FLAG_CASE); | |
| ksort($classUsedBy, SORT_NATURAL | SORT_FLAG_CASE); | |
| return [ | |
| 'uses' => $classUses, | |
| 'usedBy' => $classUsedBy | |
| ]; | |
| } | |
| /** | |
| * @param array $array | |
| * @param string $separator | |
| * @param string $baseWebPath | |
| * @return array | |
| */ | |
| public static function expandKeyToArray($array, $separator, $baseWebPath) { | |
| $ret = []; | |
| ksort($array, SORT_NATURAL | SORT_FLAG_CASE); | |
| foreach($array as $k => $d) { | |
| $pointer = &$ret; | |
| $path = explode($separator, $k ); | |
| $len = count($path); | |
| $i=0; | |
| foreach($path as $p) { | |
| ++$i; | |
| if($i === $len) { | |
| $urlencode = urlencode($k); | |
| $pointer[] = "<a href='$baseWebPath/showDoc.php?fqn=$urlencode' >$p</a> <a class=nodecoration href='$baseWebPath/showToolbox.php?fqn=$urlencode' ><sup>🔧</sup></a>"; | |
| } else { | |
| if(!array_key_exists($p, $pointer)) { | |
| $pointer[$p] = []; | |
| } | |
| $pointer = &$pointer[$p]; | |
| } | |
| } | |
| } | |
| return $ret; | |
| } | |
| public static function methodDocBlockProtected($className, $methodName) { | |
| try { | |
| $ref = new ReflectionClass($className); | |
| $method = $ref->getMethod($methodName); | |
| return str_replace('$',"\\$", $method->getDocComment()); | |
| } catch(Exception $e) { | |
| return '/** Exception finding docblock '.$e->getMessage().' */'; | |
| } | |
| } | |
| } |