Location: PHPKode > scripts > Hierarchy > hierarchy/doc.php
<?php
/**
 * This file is part of Hierarchy Class
 * It provides basic documentation.
 *
 * @autor Rubens Takiguti Ribeiro
 * @date 2008-06-19
 * @version 0.52 2008-06-26
 * @license http://www.gnu.org/licenses/lgpl-3.0.html LGPLv3 (LICENSE.TXT)
 * @copyright Copyright (C) 2008  Rubens Takiguti Ribeiro
 */
require_once(dirname(__FILE__).'/hierarchy.class.php');

/* BEGIN */
global $DOC;
start_doc();
print_doc_page();
end_doc();
/* END */


/**
 * Start the doc script. Data used in this script are saved in session.
 *
 * @return void
 */
function start_doc() {
    global $DOC;
    
    if (!extension_loaded('reflection')) {
        die('Reflection extension is need to access this page.');
    }
    $DOC = new stdClass();

    session_start();
    if (isset($_SESSION['DOC_CLASS'])) {
        $DOC = unserialize($_SESSION['DOC_CLASS']);
    }
    
    // Get filename
    if (!isset($DOC->file)) {
        $DOC->file = basename(__FILE__);
    }
    
    // Get action
    if (isset($_GET['action'])) {
        $DOC->action = $_GET['action'];
    } elseif (!isset($DOC->action)) {
        $DOC->action = 'main';
    }
    
    // Get data
    if (!isset($DOC->data)) {
        $DOC->data = array();
    }
    if (isset($_GET['data'])) {
        foreach ($_GET['data'] as $key => $value) {
            if ($value != 'null') {
                $DOC->data[$key] = $value;
            } else {
                unset($DOC->data[$key]);
            }
        }
    }
}


/**
 * End the doc script (save session).
 *
 * @return void
 */
function end_doc() {
    global $DOC;
    $_SESSION['DOC_CLASS'] = serialize($DOC);
}


/**
 * Print page based on user action.
 *
 * @return void
 */
function print_doc_page() {
    global $DOC;
    $last = 0;
    foreach (get_classes(false) as $class) {
        try {
            $ref_class = new ReflectionClass($class);
        } catch (Exception $e) {
            continue;
        }
        $data = parse_comment($ref_class->getDocComment());
        list($version, $date) = explode(' ', $data['@version']);
        $date = date_parse($date);
        $last = max($last, mktime(0, 0, 0, $date['month'], $date['day'], $date['year']));
    }
    
    header('Content-Type: text/html; charset=UTF-8');
    echo "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">\n";
    echo '<html lang="en">';
    echo '<head>';
    echo '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">';
    echo '<title>Class Documentation</title>';
    echo '<link rel="index" href="'.$DOC->file.'" title="Main" >';
    echo '</head>';
    echo '<body>';
    echo '<h1 id="header">Class Documentation</h1>';
    echo '<div id="content">';
    echo '<hr />';
    check_actions(); // Script Controller
    echo '<hr />';
    echo '</div>';
    echo '<p id="footer">';
    echo 'Copyright &copy; 2008&nbsp;&nbsp;Rubens Takiguti Ribeiro<br />';
    echo "Last update: \n";
    echo '$Date '.gmstrftime('%Y/%m/%d %H:%M:%S', $last)." $\n";
    echo '</p>';
    echo '</body>';
    echo '</html>';
}


/**
 * Check and invoke action requests.
 *
 * @return void
 */
function check_actions() {
    global $DOC;
    switch ($DOC->action) {
    case 'open_class':
    case 'class_description':
        print_class_description();
        break;
    case 'class_structure':
        print_class_structure();
        break;
    case 'class_constants':
        print_class_constants();
        break;
    case 'class_properties':
        print_class_properties();
        break;
    case 'class_methods':
        print_class_methods();
        break;
    case 'main':
    default:
        $DOC->data = array(); // Clear data
        print_main();
        break;
    }
}


/**
 * Print the main page.
 *
 * @return void
 */
function print_main() {
    global $DOC;

    echo '<h2>Main Page</h2>';
    echo '<p>Select a class below.</p>';
    echo '<ol>';
    foreach (get_classes() as $class => $name) {
        echo '<li><a href="'.$DOC->file.'?action=open_class&amp;data[class]='.$class.'">'.$name.'</a></li>';
    }
    echo '</ol>';
}


/**
 * Print class menu.
 *
 * @return void
 */
function print_class_menu() {
    global $DOC;
    $file  = &$DOC->file;
    $actions = array('main'              => 'Main',
                     'class_description' => 'Description',
                     'class_structure'   => 'Structure',
                     'class_constants'   => 'Constants',
                     'class_properties'  => 'Properties',
                     'class_methods'     => 'Methods');
    
    
    echo '<h2>'.$DOC->data['class'].'</h2>';
    echo '<ul class="navbar">';
    foreach ($actions as $action => $name) {
        $class = $DOC->action == $action ? ' class="active"' : '';
        echo '<li><a href="'.$file.'?action='.$action.'"'.$class.'>'.$name.'</a></li>';
    }
    echo '</ul>';
}


/**
 * Print class description.
 *
 * @return void;
 */
function print_class_description() {
    global $DOC;
    if (!isset($DOC->data['class'])) {
        echo '<p class="error">Class not defined</p>';
        print_main();
        return;
    }
    
    try {
        $ref_class = new ReflectionClass($DOC->data['class']);
    } catch (Exception $e) {
        echo '<p class="error">Class not defined: '.$e->getMessage().'</p>';
        print_main();
        return;
    }
    $data = parse_comment($ref_class->getDocComment());
    
    $extension = $ref_class->getExtensionName();
    if (empty($extension)) {
        $extension = null;
    }
    $interfaces = array();
    foreach ($ref_class->getInterfaces() as $interface) {
        $interfaces[] = $interface->getName();
    }
    
    $modifiers = implode(' ', Reflection::getModifierNames($ref_class->getModifiers()));
    if (empty($modifiers)) {
        $modifiers = null;
    }
    
    $data['@extension']    = $extension;
    $data['@class']        = $ref_class->getName();
    $data['@file']         = $ref_class->getFileName();
    $data['@lines']        = $ref_class->getStartLine().'-'.$ref_class->getEndLine();
    $data['@implements']   = $interfaces;
    $data['@instantiable'] = (bool)$ref_class->isInstantiable();
    $data['@interface']    = (bool)$ref_class->isInterface();
    $data['@abstract']     = (bool)$ref_class->isAbstract();
    $data['@final']        = (bool)$ref_class->isFinal();
    $data['@iterateable']  = (bool)$ref_class->isIterateable();
    $data['@modifiers']    = $modifiers;
    
    print_class_menu();
    echo '<h3>Basic Class Description</h3>';
    print_table($data, 'Basic Class Description');
}


/**
 * Print class structure.
 *
 * @return void
 */
function print_class_structure() {
    global $DOC;
    if (!isset($DOC->data['class'])) {
        echo '<p class="error">Class not defined</p>';
        print_main();
        return;
    }
    
    $ref_class = new ReflectionClass($DOC->data['class']);
    $comments = get_category_comments($ref_class->getFileName());
    print_class_menu();
    echo '<h3>Class Structure</h3>';
    if (count($comments)) {
        print_list($comments);
    } else {
        echo '<p>No structure specified in this file.</p>';
        return;
    }
}


/**
 * Print class constants.
 *
 * @return void
 */
function print_class_constants() {
    global $DOC;
    if (!isset($DOC->data['class'])) {
        echo '<p class="error">Class not defined</p>';
        print_main();
        return;
    }
    $ref_class = new ReflectionClass($DOC->data['class']);
    $constants = $ref_class->getConstants();
    
    print_class_menu();
    echo '<h3>Class Constants</h3>';
    if (!count($constants)) {
        echo '<p>No constants avaliable.</p>';
        return;
    }
    echo '<table>';
    echo '<caption>Class Constants</caption>';
    echo '<thead>';
    echo '  <tr>';
    echo '    <th scope="col">Constant</th>';
    echo '    <th scope="col">Value</th>';
    echo '    <th scope="col">Type</th>';
    echo '  </tr>';
    echo '</thead>';
    echo '<tbody>';
    foreach ($constants as $constant => $value) {
        echo '<tr>';
        echo '  <th scope="row">'.$DOC->data['class'].'::'.$constant.'</th>';
        echo '  <td>'.convert_php_value($value).'</td>';
        echo '  <td>'.gettype($value).'</td>';
        echo '</tr>';
    }
    echo '</tbody>';
    echo '</table>';
}


/**
 * Print class properties.
 *
 * @return void
 */
function print_class_properties() {
    global $DOC;
    if (!isset($DOC->data['class'])) {
        echo '<p class="error">Class not defined</p>';
        print_main();
        return;
    }
    $ref_class = new ReflectionClass($DOC->data['class']);
    $default_properties = $ref_class->getDefaultProperties();
    
    print_class_menu();
    if (!count($ref_class->getProperties())) {
        echo '<p>No properties avaliable.</p>';
        return;
    }
    
    $properties = array();
    echo '<h3>Class Properties</h3>';
    echo '<ol>';
    foreach ($ref_class->getProperties() as $property) {
        $data = parse_comment($property->getDocComment());
        $data['@name'] = $property->getName();
        if ($property->isPublic()) {
            $data['@visibility'] = 'public';
        } elseif ($property->isProtected()) {
            $data['@visibility'] = 'protected';
        } elseif ($property->isPrivate()) {
            $data['@visibility'] = 'private';
        } else {
            $data['@visibility'] = 'undefined';
        }
        $data['@static'] = $property->isStatic();
        $default = $default_properties[$data['@name']];
        $data['@default'] = convert_php_value($default).' ('.gettype($default).')';
                
        echo '  <li><a href="#prop_'.$data['@name'].'">'.$data['@name'].'</a></li>';
        $properties[] = $data;
    }
    echo '</ol>';
    echo '<hr />';
    echo '<h3>Properties Details</h3>';
    foreach ($properties as $property) {
        echo '<div>';
        echo '<h4><a id="prop_'.$property['@name'].'">'.$property['@name'].'</a></h4>';
        print_table($property);
        echo '</div>';
    }
}


/**
 * Print class methods.
 *
 * @return void
 */
function print_class_methods() {
    global $DOC;
    if (!isset($DOC->data['class'])) {
        echo '<p class="error">Class not defined</p>';
        print_main();
        return;
    }

    $ref_class = new ReflectionClass($DOC->data['class']);
    $comments = get_category_comments($ref_class->getFileName());
    $first_method_line = false;
    
    foreach ($ref_class->getMethods() as $method) {
        $data = parse_comment($method->getDocComment());
        $data['@name'] = $method->getName();
        if ($method->isPublic()) {
            $data['@visibility'] = 'public';
        } elseif ($method->isProtected()) {
            $data['@visibility'] = 'protected';
        } elseif ($method->isPrivate()) {
            $data['@visibility'] = 'private';
        } else {
            $data['@visibility'] = 'undefined';
        }
        $data['@static'] = $method->isStatic();
        $data['@final'] = $method->isFinal();
        $data['@abstract'] = $method->isAbstract();
        $data['@returns reference'] = $method->returnsReference();
        $data['@lines'] = $method->getStartLine().'-'.$method->getEndLine();
        
        $parameters = array();
        foreach ($method->getParameters() as $parameter) {
            $p = '$'.$parameter->getName();
            if ($parameter->isPassedByReference()) {
                $p = '&'.$p;
            }
            if ($parameter->isDefaultValueAvailable()) {
                $p .= ' = '.convert_php_value($parameter->getDefaultValue());
            }
            $parameters[] = $p; 
        }
        $data['@parameters'] = $parameters;
        if ($comments) {
            $data['@category'] = get_category($comments, $method->getStartLine());
        }
        $methods[$data['@name']] = $data;
    }

    print_class_menu();
    echo '<h3>Class Methods</h3>';
    if (!count($methods)) {
        echo '<p>No methods avaliable.</p>';
        return;
    }
    echo '<ul>';
    if (!count($comments)) {
        foreach ($methods as $method) {
            echo '<li><a href="#method_'.$method['@name'].'">'.$method['@name'].'</a></li>';
        }
    } else {
        $category = array();
        foreach ($methods as $method) {
            if (!isset($category[$method['@category']])) {
                if (count($category)) {
                    echo '</ul>';
                }
                echo '<li><strong>'.$method['@category'].'</strong><ul>';
                $category[$method['@category']] = true;
            }
            echo '<li><a href="#method_'.$method['@name'].'">'.$method['@name'].'</a></li>';
        }
        echo '</ul>';
    }
    echo '</ul>';
    echo '<hr />';

    echo '<h3>Methods Details</h3>';
    foreach ($methods as $method) {
        echo '<div>';
        echo '<h4<a id="method_'.$method['@name'].'">'.$method['@name'].'</a></h4>';
        print_table($method);
        echo '</div>';
    }
}


/**
 * Print a list hierarchically
 *
 * @param array $list
 * @return void
 */
function print_list($list) {
    echo '<ul>';
    foreach ($list as $key => $value) {
        if (is_array($value)) {
            echo '<li>'.$key.print_list($value).'</li>';
        } else {
            echo '<li>'.$value.'</li>';
        }
    }
    echo '</ul>';
}


/**
 * Print an associative array as an HTML table.
 *
 * @param array[string => string] $data
 * @param string $caption
 * @return void
 */
function print_table($data, $caption = '') {
    static $id = 0;
    $id++;
    $id_key = 't_key_'.$id;
    $id_value = 't_value_'.$id;
    
    if (!isset($data['description'])) {
        $data['description'] = '[No description]';
    }
    
    echo '<table>';
    if ($caption) {
        echo '<caption>'.$caption.'</caption>';
    }
    echo '<thead>';
    echo '  <tr>';
    echo '    <th id="'.$id_key.'" scope="col">Key</th>';
    echo '    <th id="'.$id_value.'" scope="col">Value</th>';
    echo '  </tr>';
    echo '</thead>';
    echo '<tbody>';
    
    $id_line = 't_desc_'.$id;
    echo '  <tr>';
    echo '    <th id="'.$id_line.'" scope="row" headers="'.$id_key.'">Description</th>';
    echo '    <td headers="'.$id_value.' '.$id_line.'">'.nl2br($data['description']).'</td>';
    echo '  </tr>';
    foreach ($data as $key => $value) {
        if ($key[0] == '@') {
            $key = substr($key, 1);
            list($key, $value) = convert_key_value($key, $value);
            $id_line = 't_'.strtolower($key).'_'.$id;
            echo '  <tr>';
            echo '    <th id="'.$id_line.'" scope="row" headers="'.$id_key.'">'.ucfirst($key).'</th>';
            echo '    <td headers="'.$id_value.' '.$id_line.'">'.$value.'</td>';
            echo '  </tr>';
        }
    }
    echo '</tbody>';
    echo '</table>';
}

/**
 * Convert a key value to be shown.
 *
 * @param string $key
 * @param mixed $value
 * @return array
 */
function convert_key_value($key, $value) {
    if (is_array($value)) {
        if (!count($value)) {
            return array($key, '[empty]');
        }
        $first_key = current(array_keys($value));
        $list_type = is_int($first_key) ? 'ol' : 'ul';
        
        $return = '<'.$list_type.'>';
        foreach ($value as $k => $v) {
            list($k, $v) = convert_key_value($k, $v);
            $return .= '<li>'.($list_type == 'ul' ? $k.': ' : '').$v.'</li>';
        }
        $return .= '</'.$list_type.'>';
        return array($key, $return);
    }

    switch ($key) {
    case 'license':
    case 'see':
        if (preg_match('/^(http[s]?:\/\/[^\040]+) (.+)$/', $value, $match)) {
            $value = '<a href="'.$match[1].'">'.convert_value($match[2]).'</a>';
        } else {
            $value = convert_value($value);
        }
        break;
    default:
        $value = convert_value($value);
        break;
    }
    return array($key, $value);
}


/**
 * Convert a value to be shown.
 *
 * @param mixed $original
 * @return string
 */
function convert_value($original) {
    static $tr = array('(C)' => '&copy;',
                       '(c)' => '&copy;',
                       '(R)' => '&reg;',
                       '(r)' => '&reg;',
                       '(TM)' => '&trade;',
                       '(tm)' => '&trade;');
    if (is_string($original)) {
        return nl2br(strtr(utf8_encode($original), $tr));
    } elseif (is_int($original)) {
        return number_format($original, 0, '.', '');
    } elseif (is_double($original)) {
        return strval($original);
    } elseif (is_bool($original)) {
        return $original ? 'Yes' : 'No';
    } elseif (is_null($original)) {
        return '[empty]';
    } else {
        return '[value not printable]';
    }
}


/**
 * Convert a PHP value to a human readeable format.
 *
 * @param mixed $original
 * @return string
 */
function convert_php_value($original) {
    if (is_string($original)) {
        return "'".$original."'";
    } elseif (is_int($original)) {
        return number_format($original, 0, '.', '');
    } elseif (is_double($original)) {
        return strval($original);
    } elseif (is_bool($original)) {
        return $original ? 'true' : 'false';
    } elseif (is_null($original)) {
        return 'null';
    } elseif (is_array($original)) {
        return '[array of size '.count($original).']';
    } elseif (is_object($original)) {
        return '[object of type '.get_class($original).']';
    } elseif (is_resource($original)) {
        return '[resource of type '.get_resource_type($original).']';
    } else {
        return '[undefined value]';
    }
}



/**
 * Parse a documentation comment and fill an associative array.
 *
 * @param string $comment
 * @return array[string => string]
 */
function parse_comment($comment) {
    $lines = explode("\n", $comment);
    array_shift($lines);
    $data = array('description' => '');
    foreach ($lines as $line) {
        $line = substr(trim($line), 2);
        if ($line[0] == '@') {
            $pos_space = strpos($line, ' ');
            $attribute = substr($line, 0, $pos_space);
            $value = substr($line, $pos_space + 1);
            if (isset($data[$attribute])) {
                if (is_array($data[$attribute])) {
                    $data[$attribute][] = $value;
                } else {
                    $old_value = $data[$attribute];
                    $data[$attribute] = array($old_value, $value);
                }
            } else {
                $data[$attribute] = $value;
            }
            unset($pos_space, $attribute, $value);
        } else {
            $data['description'] .= $line."\n";
        }
    }
    $data['description'] = trim($data['description']);
    return $data;
}


/**
 * Get category comments and its line.
 *
 * @param string $filename
 * @return array[int => string]
 */
function get_category_comments($filename) {
    $lines = file($filename);
    $comments = array();
    foreach ($lines as $i => $line) {
        if (strpos($line, '///') === 0) {
            $comments[$i] = trim(substr($line, 3));
        }
    }
    return $comments;
}


/**
 * Get category from an element of specific line.
 *
 * @param array $comments category comments
 * @param int $line line of element
 * @return string
 */
function get_category(&$comments, $line) {
    $last = '';
    foreach ($comments as $l => $c) {
        if ($l > $line) {
            return $last;
        }
        $last = $c;
    }
    return $last;
}


/**
 * Get all classes/interfaces in current directory.
 *
 * @param bool $complete true returns "type classname" / false returns "classname"
 * @return array[string]
 */
function get_classes($complete = true) {
    $dirname = dirname(__FILE__).'/';
    $dir = scandir($dirname);
    $classes = array();
    $reg = '/^([a-z_]*)\.(class|interface)(\.php)$/';
    foreach ($dir as $file) {
        if (is_file($dirname.$file) && preg_match($reg, $file, $match)) {
            if ($complete) {
                $classes[$match[1]] = $match[2].' '.$match[1];
            } else {
                $classes[$match[1]] = $match[1];
            }
        }
    }
    asort($classes);
    return $classes;
}
Return current item: Hierarchy