Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 206
DocumentIt
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 16
8190.00
0.00% covered (danger)
0.00%
0 / 206
 document_class
0.00% covered (danger)
0.00%
0 / 1
156.00
0.00% covered (danger)
0.00%
0 / 35
 document_public_methods
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 13
 document_properties
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 12
 document_constants
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 7
 parameters
0.00% covered (danger)
0.00%
0 / 1
42.00
0.00% covered (danger)
0.00%
0 / 14
 parametersInfo
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 19
 defaultValueDisplay
0.00% covered (danger)
0.00%
0 / 1
156.00
0.00% covered (danger)
0.00%
0 / 19
 deduceParameterTypeFromDockBlock
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 6
 deduceReturnTypeFromDockBlock
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 4
 calls
0.00% covered (danger)
0.00%
0 / 1
156.00
0.00% covered (danger)
0.00%
0 / 17
 class_parents
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 4
 class_implements
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
0.00%
0 / 4
 class_uses_deep
0.00% covered (danger)
0.00%
0 / 1
12.00
0.00% covered (danger)
0.00%
0 / 10
 relations
0.00% covered (danger)
0.00%
0 / 1
72.00
0.00% covered (danger)
0.00%
0 / 21
 expandKeyToArray
0.00% covered (danger)
0.00%
0 / 1
30.00
0.00% covered (danger)
0.00%
0 / 16
 methodDocBlockProtected
0.00% covered (danger)
0.00%
0 / 1
6.00
0.00% covered (danger)
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>&#x1f527;</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().' */';
        }
    }
}