protected function processClass($class) { $doc = new ClassDoc(); $doc->name = $class->getName(); $doc->loadSource($class); $this->_currentClass = $doc->name; for ($parent = $class; $parent = $parent->getParentClass();) { $doc->parentClasses[] = $parent->getName(); } foreach ($class->getInterfaces() as $interface) { $doc->interfaces[] = $interface->getName(); } $doc->isInterface = $class->isInterface(); $doc->isAbstract = $class->isAbstract(); $doc->isFinal = $class->isFinal(); $doc->methods = $this->processMethods($class); $doc->properties = $this->processProperties($class); $doc->signature = ($doc->isInterface ? 'interface ' : 'class ') . $doc->name; if ($doc->isFinal) { $doc->signature = 'final ' . $doc->signature; } if ($doc->isAbstract && !$doc->isInterface) { $doc->signature = 'abstract ' . $doc->signature; } if (in_array('CComponent', $doc->parentClasses)) { $doc->properties = array_merge($doc->properties, $this->processComponentProperties($class)); $doc->events = $this->processComponentEvents($class); } ksort($doc->properties); foreach ($doc->properties as $property) { if ($property->isProtected) { $doc->protectedPropertyCount++; } else { $doc->publicPropertyCount++; } if (!$property->isInherited) { $doc->nativePropertyCount++; } } foreach ($doc->methods as $method) { if ($method->isProtected) { $doc->protectedMethodCount++; } else { $doc->publicMethodCount++; } if (!$method->isInherited) { $doc->nativeMethodCount++; } } foreach ($doc->events as $event) { if (!$event->isInherited) { $doc->nativeEventCount++; } } $this->processComment($doc, $class->getDocComment()); return $doc; }
/** * Returns a method for a class by a given name * * @param text.doclet.ClassDoc self * @param string name * @return text.doclet.MethodDoc * @throws lang.ElementNotFoundException */ public static function methodNamed(ClassDoc $self, $name) { foreach ($self->methods as $method) { if ($name === $method->name()) { return $method; } } raise('lang.ElementNotFoundException', 'No such method ' . $name . ' in ' . $self->name()); }
/** Add a class to this package. * * @param ClassDoc class */ function addClass(ClassDoc $class) { if (isset($this->_classes[$class->name()])) { $phpdoctor = $this->_root->phpdoctor(); echo "\n"; $phpdoctor->warning('Found class ' . $class->name() . ' again, overwriting previous version'); } $this->_classes[$class->name()] = $class; }
/** * Parses a class file and returns a classdoc element * * @param string classname fully qualified class name * @return text.doclet.ClassDoc * @throws lang.IllegalArgumentException if class could not be found or parsed */ public function classNamed($classname) { static $cache = array(); static $map = array('uses' => self::T_USES, 'define' => self::T_DEFINE); // Check cache if (isset($cache[$classname])) { return $cache[$classname]; } // Check for php namespace - in this case, we have a builtin class. These // classes will not be documented for the moment. if ('php.' == substr($classname, 0, 4)) { return NULL; } // Find class if (!($loader = $this->findClass($classname))) { throw new IllegalArgumentException(sprintf('Could not find %s in %s', xp::stringOf($classname), xp::stringOf($this->sourcepath))); } // Tokenize contents $tokens = @token_get_all($loader->loadClassBytes($classname)); if (!$tokens || T_OPEN_TAG !== $tokens[0][0]) { throw new IllegalArgumentException(sprintf('Could not parse "%s" from %s, first token: %s', $classname, xp::stringOf($loader), xp::stringOf($tokens[0]))); } with($doc = new ClassDoc(), $doc->setLoader($loader), $doc->setRoot($this)); $annotations = $comment = $package = NULL; $modifiers = array(); $state = self::ST_INITIAL; for ($i = 0, $s = sizeof($tokens); $i < $s; $i++) { $t = $tokens[$i]; if (is_array($t) && isset($map[$t[1]])) { $t[0] = $map[$t[1]]; } switch ($state . $t[0]) { case self::ST_INITIAL . T_DOC_COMMENT: case self::ST_CLASS_BODY . T_DOC_COMMENT: $comment = $t[1]; break; case self::ST_INITIAL . T_COMMENT: case self::ST_CLASS_BODY . T_COMMENT: if (strncmp('#[@', $t[1], 3) == 0) { $annotations = substr($t[1], 2); } else { if (strncmp('#', $t[1], 1) == 0) { $annotations .= substr($t[1], 1); } } if (']' == substr(rtrim($t[1]), -1)) { $annotations = '[' . trim($annotations); } break; case self::ST_INITIAL . T_VARIABLE: if ('$package' === $t[1]) { // RFC #0037: $package= 'lang.reflect'; while (T_CONSTANT_ENCAPSED_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $package = trim($tokens[$i][1], '\'"'); } break; case self::ST_INITIAL . self::T_USES: $state = self::ST_USES; break; case self::ST_USES . T_CONSTANT_ENCAPSED_STRING: $cn = trim($t[1], '"\''); if (!$this->findClass($cn)) { throw new IllegalStateException('Could not find used class "' . $cn . '" for class ' . $classname . ' in ' . xp::stringOf($this->sourcepath)); } $doc->usedClasses->classes[] = $cn; break; case self::ST_USES . ')': $state = self::ST_INITIAL; break; case self::ST_INITIAL . self::T_DEFINE: $state = self::ST_DEFINE; break; case self::ST_INITIAL . T_NAMESPACE: $package = ''; $i += 2; // Eat "namespace" while (';' !== $tokens[$i][0] && $i < $s) { $package .= trim($tokens[$i++][1]); } $package = strtr($package, '\\', '.'); break; case self::ST_DEFINE . T_CONSTANT_ENCAPSED_STRING: $state = self::ST_DEFINE_VALUE; $define = trim($t[1], '"\''); break; case self::ST_DEFINE_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_DEFINE_VALUE . T_LNUMBER: case self::ST_DEFINE_VALUE . T_DNUMBER: case self::ST_DEFINE_VALUE . T_STRING: $doc->constants[$define] = $t[1]; break; case self::ST_DEFINE_VALUE . ')': $state = self::ST_INITIAL; break; case self::ST_INITIAL . T_INTERFACE: $doc->type = INTERFACE_CLASS; // Fall-through intended // Fall-through intended case self::ST_INITIAL . T_CLASS: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $name = $tokens[$i][1]; if ($package && substr(strtr($name, '·', '.'), 0, strlen($package)) == $package) { $name = substr($name, strlen($package) + 1); } $doc->name = $name; $doc->qualifiedName = $classname; $doc->rawComment = $comment; $doc->annotations = $annotations; $doc->modifiers = $modifiers; $comment = $annotations = NULL; $modifiers = array(); $state = self::ST_CLASS; break; case self::ST_CLASS . T_EXTENDS: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } $doc->superclass = $this->classNamed($this->qualifyName($doc, $tokens[$i][1])); break; case self::ST_CLASS . T_IMPLEMENTS: $state = self::ST_IMPLEMENTS; break; case self::ST_IMPLEMENTS . T_STRING: $doc->interfaces->classes[] = $this->qualifyName($doc, $t[1]); break; case self::ST_CLASS . '{': case self::ST_IMPLEMENTS . '{': $state = self::ST_CLASS_BODY; break; case self::ST_CLASS_BODY . T_VARIABLE: $state = self::ST_CLASS_VAR; // Fall-through intended // Fall-through intended case self::ST_CLASS_VAR . T_VARIABLE: unset($field); $field = new FieldDoc(); $field->declaring = $doc; $field->name = $t[1]; $field->modifiers = $modifiers; break; case self::ST_CLASS_VAR . '=': $state = self::ST_VARIABLE_VALUE; break; case self::ST_CLASS_VAR . ',': $doc->fields[] = $field; break; case self::ST_CLASS_VAR . ';': $doc->fields[] = $field; $state = self::ST_CLASS_BODY; $modifiers = array(); break; case self::ST_VARIABLE_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_VARIABLE_VALUE . T_LNUMBER: case self::ST_VARIABLE_VALUE . T_DNUMBER: case self::ST_VARIABLE_VALUE . T_STRING: $field->constantValue = $t[1]; $state = self::ST_CLASS_VAR; break; case self::ST_VARIABLE_VALUE . T_ARRAY: $brackets = 0; $src = ''; do { $t = $tokens[$i]; $src .= is_array($t) ? $t[1] : $t; if ('(' == $t[0]) { $brackets++; } else { if (')' == $t[0] and --$brackets <= 0) { break; } } } while (++$i < $s); $field->constantValue = $src; $state = self::ST_CLASS_VAR; break; // Before member declaration (e.g. public static $..., protected function ...) // Before member declaration (e.g. public static $..., protected function ...) case self::ST_CLASS_BODY . T_PUBLIC: case self::ST_CLASS_BODY . T_PRIVATE: case self::ST_CLASS_BODY . T_PROTECTED: case self::ST_CLASS_BODY . T_STATIC: case self::ST_CLASS_BODY . T_FINAL: case self::ST_CLASS_BODY . T_ABSTRACT: // Before class declaration (e.g. abstract class ...) // Before class declaration (e.g. abstract class ...) case self::ST_INITIAL . T_FINAL: case self::ST_INITIAL . T_ABSTRACT: $modifiers[$t[1]] = TRUE; break; case self::ST_CLASS_BODY . T_FUNCTION: while (T_STRING !== $tokens[$i][0] && $i < $s) { $i++; } with($method = new MethodDoc(), $method->setRoot($this)); $method->name = $tokens[$i][1]; $method->rawComment = $comment; $method->annotations = $annotations; $method->modifiers = $modifiers; $method->declaring = $doc; // Omit static initializer, it's not a real function if ('__static' != $method->name) { $doc->methods[] = $method; } $comment = $annotations = NULL; $modifiers = array(); $state = self::ST_FUNCTION; break; case self::ST_FUNCTION . '(': $state = self::ST_FUNCTION_ARGUMENTS; $argument = NULL; break; case self::ST_FUNCTION_ARGUMENTS . T_VARIABLE: $argument = $t[1]; break; case self::ST_FUNCTION_ARGUMENTS . ',': $method->arguments[$argument] = NULL; break; case self::ST_FUNCTION_ARGUMENTS . '=': $state = self::ST_ARGUMENT_VALUE; break; case self::ST_ARGUMENT_VALUE . T_CONSTANT_ENCAPSED_STRING: case self::ST_ARGUMENT_VALUE . T_LNUMBER: case self::ST_ARGUMENT_VALUE . T_DNUMBER: case self::ST_ARGUMENT_VALUE . T_STRING: $method->arguments[$argument] = $t[1]; break; case self::ST_ARGUMENT_VALUE . T_ARRAY: $brackets = 0; $src = ''; do { $t = $tokens[$i]; $src .= is_array($t) ? $t[1] : $t; if ('(' == $t[0]) { $brackets++; } else { if (')' == $t[0] and --$brackets <= 0) { break; } } } while (++$i < $s); $method->arguments[$argument] = $src; break; case self::ST_ARGUMENT_VALUE . ',': $state = self::ST_FUNCTION_ARGUMENTS; break; case self::ST_ARGUMENT_VALUE . ')': $state = self::ST_FUNCTION; break; case self::ST_FUNCTION_ARGUMENTS . ')': $argument && ($method->arguments[$argument] = NULL); $state = self::ST_FUNCTION; break; case self::ST_FUNCTION . ';': // Interface and abstract methods have no body $state = self::ST_CLASS_BODY; break; case self::ST_FUNCTION . '{': $brackets = 0; do { $c = $tokens[$i][0]; if ('{' == $c) { $brackets++; } else { if ('}' == $c and --$brackets <= 0) { break; } } } while (++$i < $s); $state = self::ST_CLASS_BODY; break; case self::ST_CLASS_BODY . '}': $state = self::ST_INITIAL; break; } } return $cache[$classname] = $doc; }
/** Build the class hierarchy tree which is placed at the top of the page. * * @param RootDoc rootDoc The root doc * @param ClassDoc class Class to generate tree for * @param int depth Depth of recursion * @return mixed[] */ function _buildTree(RootDoc $rootDoc, ClassDoc $class, $depth = NULL) { if ($depth === NULL) { $start = TRUE; $depth = 0; } else { $start = FALSE; } $output = ''; $undefinedClass = FALSE; if ($class->superclass()) { echo "Class:" . $class->_name . " - Superclass: " . $class->superClass() . PHP_EOL; $superclass = $rootDoc->classNamed($class->superclass()); if ($superclass) { $result = $this->_buildTree($rootDoc, $superclass, $depth); $output .= $result[0]; $depth = ++$result[1]; } else { $output .= $class->superclass() . '<br>'; //$output .= str_repeat(' ', $depth).' └─'; $output .= str_repeat(' ', $depth) . '⌊ '; $depth++; $undefinedClass = TRUE; } } if ($depth > 0 && !$undefinedClass) { //$output .= str_repeat(' ', $depth).' └─'; $output .= str_repeat(' ', $depth) . '⌊ '; } if ($start) { $output .= '<strong>' . $class->name() . '</strong><br />'; } else { $output .= '<a href="' . str_repeat('../', $this->_depth) . $class->asPath() . '">' . $class->name() . '</a><br>'; } return array($output, $depth); }
/** * Generate class documentation for a given class and write it to the * given output stream * * @param text.doclet.ClassDoc doc * @param io.streams.OutputStream * @return io.streams.OutputStream */ protected function writeDoc(ClassDoc $doc, OutputStream $stream) { $this->writeHeader($doc->name(), $stream); $base = str_repeat('../', substr_count($doc->qualifiedName(), '.')); // Summary $stream->write('<h4><a href="' . $base . 'index.html">Overview</a>'); $stream->write(' » <a href="summary.html">' . $doc->containingPackage()->name() . '</a></h4>'); $stream->write('<h1>' . ucfirst($doc->classType()) . ' ' . $doc->name() . '</h1>'); $stream->write('<dl>'); if ($doc->isInterface()) { $implementations = $this->classesImplementing($doc); if (!empty($implementations)) { $stream->write('<dt>All known implementing classes:</dt><dd>'); for ($i = 0, $s = sizeof($implementations); $i < $s; $i++) { $stream->write($this->typeLink($implementations[$i]->qualifiedName(), $base)); $i < $s - 1 && $stream->write(', '); } $stream->write('</dd>'); } } else { $implemented = $this->allInterfacesImplementedBy($doc); if (!empty($implemented)) { $stream->write('<dt>All implemented interfaces:</dt><dd>'); for ($i = 0, $s = sizeof($implemented); $i < $s; $i++) { $stream->write($this->typeLink($implemented[$i]->qualifiedName(), $base)); $i < $s - 1 && $stream->write(', '); } $stream->write('</dd>'); } $subclasses = $this->directSubclassesOf($doc); if (!empty($subclasses)) { $stream->write('<dt>Direct known subclasses:</dt><dd>'); for ($i = 0, $s = sizeof($subclasses); $i < $s; $i++) { $stream->write($this->typeLink($subclasses[$i]->qualifiedName(), $base)); $i < $s - 1 && $stream->write(', '); } $stream->write('</dd>'); } } $stream->write('</dl>'); $stream->write('<hr/>'); // Signature $stream->write('<code>'); $stream->write(implode(' ', array_keys($doc->getModifiers())) . ' ' . $doc->classType() . ' ' . $doc->name()); if (!$doc->isInterface()) { $doc->superclass && $stream->write(' extends ' . $this->typeLink($doc->superclass->qualifiedName(), $base)); $implemented = $this->interfacesImplementedBy($doc); if (!empty($implemented)) { $stream->write(' implements '); for ($i = 0, $s = sizeof($implemented); $i < $s; $i++) { $stream->write($this->typeLink($implemented[$i]->qualifiedName(), $base)); $i < $s - 1 && $stream->write(', '); } } } $stream->write('</code>'); $this->writeMarkup($doc->commentText(), $stream); // Class tags $stream->write('<dl>'); $this->writeTags('See also', $doc->tags('see'), $base, $stream); $this->writeTags('Verified by', $doc->tags('test'), $base, $stream); $stream->write('</dl>'); $stream->write('<hr/>'); // Field summary $stream->write('<fieldset><legend>Fields Summary</legend><ul>'); foreach ($doc->fields as $field) { $v = $field->constantValue(); $stream->write('<li><code>'); $stream->write($this->isStatic($field) ? 'static ' : ''); $stream->write($field->name() . ($v ? ' = ' . $v : '')); $stream->write('</code><p>' . $this->firstSentence($field->commentText()) . '</p></li>'); } $stream->write('</ul></fieldset>'); // Method summary $stream->write('<fieldset><legend>Method Summary</legend><ul>'); foreach ($doc->methods as $method) { $stream->write('<li><code>'); $stream->write($this->isStatic($method) ? 'static ' : ''); $this->writeSignature($method, $base, $stream); $stream->write('</code><p>' . $this->firstSentence($method->commentText()) . '</p></li>'); } $stream->write('</ul></fieldset>'); // Method details foreach ($doc->methods as $method) { $stream->write('<a name="' . $method->name() . '"><h3>' . $method->name() . '</h3></a>'); $stream->write('<code>' . implode(' ', array_keys($method->getModifiers())) . ' '); $this->writeSignature($method, $base, $stream); $stream->write('</code><div class="dfn">'); $this->writeMarkup($method->commentText(), $stream); $stream->write('<dl>'); $this->writeTags('Parameters', $method->tags('param'), $base, $stream); $this->writeTags('Returns', $method->tags('return'), $base, $stream); $this->writeTags('Throws', $method->tags('throws'), $base, $stream); $this->writeTags('See also', $method->tags('see'), $base, $stream); $stream->write('</dl>'); $stream->write('</div>'); $stream->write('<hr/>'); } $this->writeFooter($stream); return $stream; }
/** * Build the class tree branch for the given element * * @param ClassDoc[] tree * @param ClassDoc element */ function _buildTree(array &$tree, ClassDoc $element) { $tree[$element->name()] = $element; if ($element->superclass()) { $rootDoc = $this->_doclet->rootDoc(); $superclass = $rootDoc->classNamed($element->superclass()); if ($superclass) { $this->_buildTree($tree, $superclass); } } }