k4st
-
PHP,
pasted
on Nov 29:
|
<?php
/* $Id: help.php 172 2008-07-23 15:48:01Z peter.goodman $ */
/**
* help(mixed) -> void
*
* Given a function name, class name, or object, output a nicely formatted
* documentation string.
*
* @author Peter Goodman
*/
function help($thing, $method = NULL) {
$str = '';
if($method !== NULL) {
$str = Doc::formatFunction(
Doc::get($thing, $method),
$method
);
} else if(is_string($thing)) {
// existing function
if(function_exists($thing)) {
$str = Doc::formatFunction(
Doc::get($thing),
$thing
);
// existing class/interface
} else if(class_exists($thing, FALSE) || interface_exists($thing, FALSE)) {
$str = Doc::formatClass($thing);
// could be a file rooted somewhere
} else {
if(strpos($thing, '.') !== FALSE) {
// todo, make it so that a file can also be introspected
}
}
// object
} else if(is_object($thing)) {
$str = Doc::formatClass(get_class($thing));
// unknown
} else {
throw new InvalidArgumentException(
"Function [help] expects either string or object as first ".
"parameter to be string or object. Neither given."
);
}
if(!empty($str)) {
echo "<pre>$str</pre>";
flush();
}
}
/**
* Class that handles building up class / function document strings. It is
* abstract because all methods are static and this is a non-instantiable
* class.
*
* @author Peter Goodman
* @internal
*/
abstract class Doc {
/**
* Doc::get(mixed $method_or_class[, string $method])__ -> string
*
* Gets a nicely formatted doc-block for a function/method/class/object. To
* get the documentation for a class/method, do:
* Doc::get('class name', 'methodname')
* Doc::get($obj, 'method name')
*
* @author Peter Goodman
*/
static public function get() {
$reflector = NULL;
$callback = func_get_args();
if(!isset($callback[0]))
return '';
try {
// class/object method
if(count($callback) === 2)
$reflector = new ReflectionMethod($callback[0], $callback[1]);
// obect
else if(is_object($callback[0]))
$reflector = new ReflectionObject($callback[0]);
// function/class
else {
if(function_exists($callback[0]))
$reflector = new ReflectionFunction($callback[0]);
else if(class_exists($callback[0], FALSE) ||
interface_exists($callback[0], FALSE))
$reflector = new ReflectionClass($callback[0]);
else
return '';
}
} catch(Exception $e) {
return '';
}
// get and return the formatted doc block
return self::formatBlock($reflector->getDocComment());
}
/**
* Doc::formatBlock(string) -> string
*
* Format a string as a doc block.
*/
static public function formatBlock($doc_block) {
$doc_block = preg_replace('~(\r?\n)+~', "\n", $doc_block);
// get rid of leading and trailing multi-line comments delimiters
$doc_block = preg_replace('~(/[*]{1,2}|[*]/)~', '', $doc_block);
// get rid of leading comment markers (*'s)
$doc_block = preg_replace('~(\n|\A)?(?<!\w)\s+[*][ ]?~', "\n", $doc_block);
// replace php-doc identifiers with something readable
$doc_block = preg_replace('~@internal~s', '', $doc_block);
$doc_block = preg_replace('~@(\w+)~e', 'ucfirst("$1").":"', $doc_block);
return htmlentities(trim($doc_block), ENT_QUOTES);
}
/**
* Doc::formatFunction(string $doc_block, string $function_name) -> string
*
* Given a paragraph of documentation and the name of a function, return a
* nicely formatted string.
*
* @internal
*/
static public function formatFunction($doc_block, $function_name) {
$doc_block = preg_replace('~\n~', "\n ", " ".trim($doc_block));
return "<i>{$function_name}(...)</i>\n{$doc_block}";
}
/**
* Doc::formatSection(string $doc_block, string $prefix) -> string
*
* Prefix each line of a documentation block.
*
* @internal
*/
static protected function formatSection($doc_block, $prefix) {
$doc_block = trim($doc_block);
if(!empty($doc_block))
return preg_replace('~\n~', "\n{$prefix}", "{$prefix}{$doc_block}");
return '';
}
/**
* Doc::formatConstructor(ReflectionClass) -> string
*
* Format the constructor of a class.
*
* @internal
*/
static protected function formatConstructor(ReflectionClass $reflector) {
try {
$constructor = $reflector->getConstructor();
if($constructor instanceof ReflectionMethod)
return self::formatBlock($constructor->getDocComment());
// ignore it
} catch(Exception $e) {}
return '';
}
/**
* Doc::getClassDescendants(string $class_name, [array &$parent[, array &$classes]])
* -> array
*
* Return a tree of the classes/interfaces the extend/implement this class or
* interface. If the class or interface name passed in does not exist then
* an InvalidArgumentException will be thrown.
*
* @note This function is anything but efficient so use it sparingly.
* @author Peter Goodman
* @internal
*/
static protected function getClassDescendants($class_name,
array &$parent = array(),
array &$classes = array()) {
if(!class_exists($class_name, FALSE) &&
!interface_exists($class_name, FALSE)) {
throw new InvalidArgumentException(
"The class/interface [{$class_name}] does not exist."
);
}
// merge the list of declared classes and iterators. we only find these
// once and them subtract from them
if(empty($classes)) {
$classes = array_merge(
get_declared_interfaces(),
get_declared_classes()
);
}
// go through the defined interfaces and classes and recursively build a
// tree of the extending classes/interfaces
foreach($classes as $i => $class) {
if($class === NULL)
continue;
// look at its parent class
if(get_parent_class($class) == $class_name)
$parent[$class] = array();
// look at its parent interfaces, this won't actually get a proper
// tree of things given that multiple interfaces can be extended/
// implemented at once, but it will give a good idea of things
else {
$interfaces = class_implements($class, FALSE);
$interfaces = array_intersect(
$interfaces === FALSE ? array() : $interfaces,
$classes
);
if(in_array($class_name, $interfaces))
$parent[$class] = array();
}
// get the descendants recursively
if(isset($parent[$class])) {
$classes[$i] = NULL;
self::getClassDescendants($class, $parent[$class], $classes);
}
// sort this level's classes
ksort($parent);
}
return $parent;
}
/**
* Doc::formatClassDescendants(array $descendants[, int $level]) -> string
*
* Return a formatted tree of class descendants from an array (tree) of
* class descendants. This function builds the formatted string recursively.
*
* @internal
*/
static protected function formatClassDescendants(array $descendants,
$level = 0) {
$str = '';
$level = abs($level);
// go over the descendants and build up the string
foreach($descendants as $class => $sub_classes) {
$str .= "\n";
if($level > 0)
$str .= str_repeat(' ', $level);
$str .= $class;
// recursively build up the sub-classes
$str .= self::formatClassDescendants($sub_classes, $level+1);
}
return $str;
}
/**
* Doc::formatClass(string $class_name) -> string
*
* Given a class or interface name, return a nicely formatted documentation
* string describing the public, private, and protected methods of that class.
*
* @internal
*/
static public function formatClass($class_name) {
// class doesn't exist
if(!class_exists($class_name, FALSE) &&
!interface_exists($class_name, FALSE)) {
throw new InvalidArgumentException(
"Class/Interface [{$class_name}] does not exist and therefore ".
"cannot be introspected."
);
}
// get the reflector
try {
$reflector = new ReflectionClass($class_name);
} catch(Exception $e) {
return '';
}
$is_interface = $reflector->isInterface();
// prefixes
$line_prefix = ' | ';
$section_prefix = "\n{$line_prefix}\n";
$method_prefix = "{$line_prefix} ";
// the doc block for the class
$doc_block = self::formatBlock($reflector->getDocComment());
// can this class be extended?
$final = $reflector->isFinal() && !$is_interface ? 'final ' : '';
// the type of the class
$type = $is_interface ? 'interface' : (
$reflector->isAbstract() ? 'abstract class' : 'class'
);
$str = "<b>{$final}{$type} {$class_name}</b>";
if(!empty($doc_block))
$str .= "\n". self::formatSection($doc_block, $line_prefix);
// show the class constants
$constants = $reflector->getConstants();
if(!empty($constants)) {
$str .= "{$section_prefix}{$line_prefix}<u>Constants:</u>";
foreach($constants as $name => $value)
$str .= "\n{$method_prefix} {$name} -> {$value}";
}
// format the class constructor (if it has one)
$doc_block = self::formatConstructor($reflector);
if(!empty($doc_block)) {
$str .= "{$section_prefix}{$line_prefix}<u>Constructor:</u>";
$str .= $section_prefix. self::formatSection(
$doc_block,
$method_prefix
);
}
// get and sort out the methods of this class
$methods = $reflector->getMethods();
$types = array(array(), array(), array());
$headers = array(
'Public Methods:',
'Protected Methods:',
'Private Methods:',
);
// sort the methods into {0:public, 1:protected, 2:private}
foreach($methods as $method) {
$type = $method->isPublic() ? 0 : ($method->isProtected() ? 1 : 2);
$types[$type][$method->getName()] = $method;
}
// output the method infos
foreach($headers as $i => $header) {
if(empty($types[$i]))
continue;
$str .= "{$section_prefix}{$line_prefix}<u>{$header}</u>";
ksort($types[$i]);
foreach($types[$i] as $method) {
// ignore the constructor and destructor
if($method->isConstructor() || $method->isDestructor())
continue;
// method modifiers (static, abstract)
$modifiers = array();
$method->isStatic() && ($modifiers[] = 'static');
$method->isAbstract() && ($modifiers[] = 'abstract');
if(!empty($modifiers))
$modifiers = '<'. implode(', ', $modifiers) .'>';
else
$modifiers = '';
// add in the formatted section
$str .= $section_prefix . self::formatSection(
self::formatFunction(
self::formatBlock($method->getDocComment()),
$method->getName() . $modifiers
),
$method_prefix
);
}
}
// get the extending classes
$classes = array();
self::getClassDescendants($class_name, $classes);
// there are parent classes so format them
if(!empty($classes)) {
$str .= "{$section_prefix}{$line_prefix}<u>Class Descendants:</u>\n";
$str .= "{$line_prefix}\n";
$str .= self::formatSection(
self::formatClassDescendants($classes),
$method_prefix
);
}
return $str;
}
}
help('Doc');
|
Output:
|
<pre><b>abstract class Doc</b>
| Class that handles building up class / function document strings. It is
| abstract because all methods are static and this is a non-instantiable
| class.
|
| Author: Peter Goodman
|
| <u>Public Methods:</u>
|
| <i>formatBlock<static>(...)</i>
| Doc::formatBlock(string) -> string
|
| Format a string as a doc block.
|
| <i>formatClass<static>(...)</i>
| Doc::formatClass(string $class_name) -> string
|
| Given a class or interface name, return a nicely formatted documentation
| string describing the public, private, and protected methods of that class.
|
| <i>formatFunction<static>(...)</i>
| Doc::formatFunction(string $doc_block, string $function_name) -> string
|
| Given a paragraph of documentation and the name of a function, return a
| nicely formatted string.
|
| <i>get<static>(...)</i>
| Doc::get(mixed $method_or_class[, string $method])__ -> string
|
| Gets a nicely formatted doc-block for a function/method/class/object. To
| get the documentation for a class/method, do:
| Doc::get('class name', 'methodname')
| Doc::get($obj, 'method name')
|
| Author: Peter Goodman
|
| <u>Protected Methods:</u>
|
| <i>formatClassDescendants<static>(...)</i>
| Doc::formatClassDescendants(array $descendants[, int $level]) -> string
|
| Return a formatted tree of class descendants from an array (tree) of
| class descendants. This function builds the formatted string recursively.
|
| <i>formatConstructor<static>(...)</i>
| Doc::formatConstructor(ReflectionClass) -> string
|
| Format the constructor of a class.
|
| <i>formatSection<static>(...)</i>
| Doc::formatSection(string $doc_block, string $prefix) -> string
|
| Prefix each line of a documentation block.
|
| <i>getClassDescendants<static>(...)</i>
| Doc::getClassDescendants(string $class_name, [array &$parent[, array &$classes]])
| -> array
|
| Return a tree of the classes/interfaces the extend/implement this class or
| interface. If the class or interface name passed in does not exist then
| an InvalidArgumentException will be thrown.
|
| Note: This function is anything but efficient so use it sparingly.
| Author: Peter Goodman</pre>
|
|