/** * {@inheritdoc} */ public function process($elongate = false) { $return = parent::process(true); if (isset($return['type'])) { $return['entity'] = $return['type']; unset($return['type']); } // Property if (preg_match('/(\\w+::)?\\$\\w+/', $return['entity'])) { $return['entity_hint'] = 'property'; } elseif (preg_match('/(\\w+::)?[\\w_]+(\\(\\))/', $return['entity'])) { $return['entity_hint'] = 'method'; } elseif (preg_match('/[\\w_]+/', $return['entity'])) { $return['entity_hint'] = 'class'; } elseif (preg_match('/https?:/', $return['entity'])) { // Used @see when @link was more appropriate $formatter = ConsoleUtil::formatters(); Inconsistency::add('Used @' . $this->tag . ' when @link was more appropriate. => ' . $formatter->gold->apply(SystemStore::get('_.current'))); $return['entity_hint'] = 'uri'; } // Do we need to resolve? if (strpos($return['entity'], '::') !== false) { list($class, $entity) = explode('::', $return['entity']); $class = $this->ancestry->resolveNamespace($class); $return['entity'] = implode('::', array($class, $entity)); } elseif ($return['entity_hint'] === 'method' || $return['entity_hint'] === 'property') { $class = $this->ancestry->getClass(); $return['entity'] = implode('::', array($class, $return['entity'])); } return $return; }
/** * {@inheritdoc} */ public function process($elongate = false) { // Used @var, which is deprecated if (strtolower($this->tag) === 'var') { $formatter = ConsoleUtil::formatters(); Inconsistency::add('The @var keyword is deprecated. Use @type instead. => ' . $formatter->gold->apply(SystemStore::get('_.current'))); } return parent::process(true); }
/** * Executes all of the event handlers. * * @return void */ public static function events() { $self = get_called_class(); // vanity.command.complete event Dispatcher::get()->addListener('vanity.command.complete', function (Event $event) { $formatter = ConsoleUtil::formatters(); $stop_time = Timer::stop(); echo PHP_EOL; echo $formatter->pending->apply(' Completed in ' . ConsoleUtil::timeHMS(round($stop_time)) . ' (' . $stop_time . ') | Peak memory usage: ' . ConsoleUtil::formatSize(memory_get_peak_usage()) . ' (' . number_format(memory_get_peak_usage()) . ' bytes) ') . PHP_EOL; }); // vanity.command.log_path event Dispatcher::get()->addListener('vanity.command.log_path', function (EventStore $event) { $finder = new Finder(); $formatter = ConsoleUtil::formatters(); $log_path = $event->get('log_path'); $time = $event->get('time'); echo PHP_EOL; echo $formatter->yellow->apply('LOG FILES FOR THIS RUN') . PHP_EOL; $files = $finder->files()->name("vanity-run-{$time}-*.log")->depth(0)->in($log_path); $count = 0; foreach ($files as $file) { $count++; echo TAB . $formatter->green->apply('-> ') . $file->getRealpath() . PHP_EOL; } // Count the classes echo PHP_EOL; echo 'Found ' . $formatter->info->apply(" {$count} ") . ' log ' . ConsoleUtil::pluralize($count, 'file', 'files') . '.' . PHP_EOL; }); // vanity.command.parse.report.dependencies event Dispatcher::get()->addListener('vanity.command.parse.report.dependencies', function (Event $event) { // jsonify! $json = ConsoleUtil::json_encode(self::getDependencies()); // Make sure the directory is created $filesystem = new Filesystem(); $filesystem->mkdir(ConfigStore::get('vanity.reports')); file_put_contents(ConfigStore::get('vanity.reports') . '/dependencies.json', $json); }); // vanity.command.parse.warn.dependencies event Dispatcher::get()->addListener('vanity.command.parse.warn.dependencies', function (Event $event) use(&$self) { $formatter = ConsoleUtil::formatters(); $dependencies = $self::getDependencies(); echo PHP_EOL; echo $formatter->yellow->apply('REPORT: DEPENDENCIES ON EXTENSIONS') . PHP_EOL; foreach ($dependencies as $dependency) { echo TAB . $formatter->green->apply('-> ') . $dependency . PHP_EOL; } // Count the classes echo PHP_EOL; $count = count($dependencies); echo 'Found ' . $formatter->info->apply(" {$count} ") . ' ' . ConsoleUtil::pluralize($count, 'dependency', 'dependencies') . '.' . PHP_EOL; }); // vanity.command.parse.warn.inconsistencies event Dispatcher::get()->addListener('vanity.command.parse.warn.inconsistencies', function (Event $event) { $formatter = ConsoleUtil::formatters(); $inconsistencies = DocumentationInconsistencyCollector::read(); echo PHP_EOL; echo $formatter->yellow->apply('REPORT: DOCBLOCK INCONSISTENCIES') . PHP_EOL; // We really need \Array->apply(), don't we? echo 'Tags where type is inferred: ' . implode(', ', array_map(function ($w) use($formatter) { return $formatter->green->apply($w); }, explode(', ', '@param, @return, @returns, @see, @throw, @throws, @uses, @used-by, @type, @var'))) . '.' . PHP_EOL; foreach ($inconsistencies as $inconsistency) { echo TAB . $formatter->green->apply('-> ') . $inconsistency['message'] . PHP_EOL; } // Count the classes echo PHP_EOL; $count = count($inconsistencies); echo 'Found ' . $formatter->info->apply(" {$count} ") . ' ' . ConsoleUtil::pluralize($count, 'inconsistency', 'inconsistencies') . '.' . PHP_EOL; }); // Handle default HTML template DesktopHTMLTemplate::register('default-html'); }
public static function resolve($reflected) { $is_method = false; $is_property = false; // Are we working with a property or a method? if ($reflected instanceof ReflectionMethod) { $is_method = true; } elseif ($reflected instanceof ReflectionProperty) { $is_property = true; } else { throw new Exception('Only methods and properties can be reflected with ' . get_called_class()); } // Parse the docblock $docblock = new DocBlock($reflected->getDocComment()); $found_description = false; $return = $reflected; // Save these for messaging $__class = $reflected->getDeclaringClass()->getName(); $__kind = $reflected->getName(); // Can we just do a straight-up inherit? // @todo: Do a better job of handling {@inheritdoc} according to the spec. try { while (!$found_description && strpos($docblock->getShortDescription(), '{@inheritdoc}') !== false) { // Start over... $found_description = false; // Log that we're starting... Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Starting resolution:', array(sprintf("{$__class}%s{$__kind}%s", $is_method ? '::' : '::$', $is_method ? '()' : ''))); // Grab a reference to the class containing the entity with the {@inheritdoc} tag $klass = $reflected->getDeclaringClass(); // Is this an Interface? if ($klass->isInterface()) { throw new InheritdocInInterfaceException('The {@inheritdoc} tag is not resolvable from within Interfaces. Methods and properties should ' . 'be fully-documented.'); } elseif (SystemStore::get('_.php54') && $klass->isTrait()) { throw new InheritdocInTraitException('The {@inheritdoc} tag is not resolvable from within Traits. Methods and properties should ' . 'be fully-documented.'); } // Are we using Interfaces? if (!$found_description && ($interface_count = count($klass->getInterfaces())) > 0) { $count = 1; foreach ($klass->getInterfaces() as $rinterface) { Logger::get()->{ConfigStore::get('log.info')}("{@inheritdoc} Checking Interface {$count}/{$interface_count}:", array($rinterface->getName())); try { $return = $rinterface->getMethod($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($rinterface->getName(), $reflected->getName(), 'Method')); $found_description = true; break 2; } catch (Exception $e) { try { $return = $rinterface->getProperty($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($rinterface->getName(), $reflected->getName(), 'Property')); $found_description = true; break 2; } catch (Exception $e) { Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} No match. Will keep looking...', array($rinterface->getName(), $reflected->getName())); } } $count++; } } // Are we using Traits? if (!$found_description && SystemStore::get('_.php54') && ($trait_count = count($klass->getTraits())) > 0) { $count = 1; foreach ($klass->getTraits() as $rtrait) { Logger::get()->{ConfigStore::get('log.info')}("{@inheritdoc} Checking Trait {$count}/{$trait_count}:", array($rtrait->getName())); try { $return = $rtrait->getMethod($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($rtrait->getName(), $reflected->getName(), 'Method')); $found_description = true; break 2; } catch (Exception $e) { try { $return = $rtrait->getProperty($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($rtrait->getName(), $reflected->getName(), 'Property')); $found_description = true; break 2; } catch (Exception $e) { Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} No match. Will keep looking...', array($rtrait->getName(), $reflected->getName())); } } $count++; } } // Are we extending a class? if ($klass->getParentClass()) { // Continue climbing up the ancestry as necessary while (!$found_description && $klass->getParentClass()) { // Rewrite the reference to $klass $klass = $klass->getParentClass(); Logger::get()->{ConfigStore::get('log.info')}("{@inheritdoc} Checking the parent class:", array($klass->getName())); try { $return = $klass->getMethod($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($klass->getName(), $reflected->getName(), 'Method')); $found_description = true; break 2; } catch (Exception $e) { try { $return = $klass->getProperty($reflected->getName()); Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} Match!', array($klass->getName(), $reflected->getName(), 'Property')); $found_description = true; break 2; } catch (Exception $e) { Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} No match. Will keep looking...', array($klass->getName(), $reflected->getName())); } } } } // We couldn't find anything throw new CouldNotResolveInheritdocException('Leaving as-is. The tag will be viewable in the ' . 'resulting documentation.'); } } catch (InheritdocInInterfaceException $e) { $message = sprintf("{$__class}%s{$__kind}%s", $is_method ? '::' : '::$', $is_method ? '()' : ''); // Log that we're starting... Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} ' . $e->getMessage(), array($message)); $formatter = ConsoleUtil::formatters(); Inconsistency::add($message . $formatter->gold->apply(' => Could not resolve {@inheritdoc}. ' . $e->getMessage())); } catch (InheritdocInTraitException $e) { $message = sprintf("{$__class}%s{$__kind}%s", $is_method ? '::' : '::$', $is_method ? '()' : ''); // Log that we're starting... Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} ' . $e->getMessage(), array($message)); $formatter = ConsoleUtil::formatters(); Inconsistency::add($message . $formatter->gold->apply(' => Could not resolve {@inheritdoc}. ' . $e->getMessage())); } catch (CouldNotResolveInheritdocException $e) { $message = sprintf("{$__class}%s{$__kind}%s", $is_method ? '::' : '::$', $is_method ? '()' : ''); // Log that we're starting... Logger::get()->{ConfigStore::get('log.info')}('{@inheritdoc} ' . $e->getMessage(), array($message)); $formatter = ConsoleUtil::formatters(); Inconsistency::add($message . $formatter->gold->apply(' => Could not resolve {@inheritdoc}. ' . $e->getMessage())); } catch (VanityException $e) { } catch (Exception $e) { } return $return; }
/** * Resolves a namespace alias into a fully-qualified namespace. * * @param string $short A shortened namespace alias. * @return string The fully-qualified namespace, if available. */ public function resolveNamespace($short) { if (isset($this->aliases[$short])) { Logger::get()->{ConfigStore::get('log.aliases')}('Aliases: Matched in the list of known aliases.', array($short, $this->aliases[$short])); return $this->aliases[$short]; } else { // Handle implicit aliases in the same namespace. try { $namespace = $this->class->getNamespaceName() . '\\' . $short; new ReflectionClass($namespace); Logger::get()->{ConfigStore::get('log.aliases')}('Aliases: Matched in the current namespace.', array($short, $namespace)); // If we didn't throw an exception, we're good. return $namespace; } catch (ReflectionException $e) { // Handle implicit namespaces in an extended/implemented namespace. try { foreach ($this->namespaces as $ns) { try { $namespace = $ns . '\\' . $short; new ReflectionClass($namespace); Logger::get()->{ConfigStore::get('log.aliases')}('Aliases: Matched in an extended/implemented namespace.', array($short, $namespace)); // If we didn't throw an exception, we're good. return $namespace; } catch (ReflectionException $e) { } } throw new ReflectionException(); } catch (ReflectionException $e) { // Try removing the beginning '\' to see if we find a match. try { $class = preg_replace('/^\\\\/', '', $short); new ReflectionClass($class); Logger::get()->{ConfigStore::get('log.aliases')}('Aliases: Matched by stripping the \\ prefix.', array($short, $class)); // If we didn't throw an exception, we're good. return $class; } catch (ReflectionException $e) { $formatter = ConsoleUtil::formatters(); Inconsistency::add($class . $formatter->gold->apply(' => No match found for ' . $short . ' (' . SystemStore::get('_.current') . ')')); Logger::get()->{ConfigStore::get('log.aliases')}('Aliases: No match found.', array($short)); // No match. Return it as-is (without any starting backslash). return $class; } } } } }
/** * Retrieve the properties for the class. * * @return array A list of properties. */ public function getMethods() { $rclass_methods = $this->class->getMethods(); // Add methods and parameters $rclass_methods = array_values(array_filter($rclass_methods, function ($rmethod) { if ($exclusions = ConfigStore::get('source.exclude.methods')) { return !preg_match($exclusions, $rmethod->getName()); } return true; })); foreach ($rclass_methods as $rmethod) { $documentThis = true; if (!isset($this->methods['count'])) { $this->methods['count'] = count($rclass_methods); } if (!isset($this->methods['method'])) { $this->methods['method'] = array(); } $rmethod = InheritdocHandler::resolve($rmethod); $_tags = new TagHandler($rmethod->getDocComment(), $this->ancestry); $method_docblock = new DocBlock($rmethod->getDocComment()); $entry = array(); $entry['name'] = $rmethod->getName(); $entry['visibility'] = $this->methodAccess($rmethod); if ($extension = $rmethod->getExtensionName()) { $entry['extension'] = $extension; DependencyCollector::add($extension); } if ($rmethod->getFileName()) { $entry['path'] = str_replace(array(VANITY_SYSTEM . '/', VANITY_PROJECT_WORKING_DIR . '/'), '', $rmethod->getFileName()); $entry['lines'] = array('start' => $rmethod->getStartLine(), 'end' => $rmethod->getEndLine()); if ($viewsource = ConfigStore::get('source.viewsource')) { $entry['viewsource'] = str_replace(array('%PATH%', '%LINE%'), array($entry['path'], $entry['lines']['start']), $viewsource); } } if ($description = $_tags->getDescription()) { $entry['description'] = $description; } // Method inheritance if (($declaring_class = $rmethod->getDeclaringClass()->getName()) !== $this->class->getName()) { if (!isset($entry['inheritance'])) { $entry['inheritance'] = array(); } if (!isset($entry['inheritance']['class'])) { $entry['inheritance']['class'] = array(); } $declaring_class = new ReflectionClass($declaring_class); $subentry = array(); $subentry['name'] = $declaring_class->getName(); if ($declaring_class->getFileName()) { $subentry['path'] = str_replace(VANITY_PROJECT_WORKING_DIR . '/', '', $declaring_class->getFileName()); } $entry['inheritance']['class'][] = $subentry; } // Method tags if (count($method_docblock->getTags())) { if (!isset($entry['metadata'])) { $entry['metadata'] = array(); } if (!isset($entry['metadata']['tag'])) { $entry['metadata']['tag'] = array(); } foreach ($method_docblock->getTags() as $rtag) { $dtag = new Tag($rtag, $this->ancestry); $tagData = $dtag->determine()->process(ConfigStore::get('source.resolve_aliases')); if ($tagData['name'] === 'alias') { SystemStore::add('alias.' . $tagData['entity'], $this->class->getName() . '::' . $rmethod->getName()); $documentThis = false; } $entry['metadata']['tag'][] = $tagData; } } // Method parameters if ($count = count($rmethod->getParameters())) { if (!isset($entry['parameters'])) { $entry['parameters'] = array(); } if (!isset($entry['parameters']['count'])) { $entry['parameters']['count'] = $count; } if (!isset($entry['parameters']['parameter'])) { $entry['parameters']['parameter'] = array(); } foreach ($rmethod->getParameters() as $rparameter) { $tag_finder = new TagFinder($entry); $param = array(); $param['name'] = $rparameter->getName(); $param['required'] = !$rparameter->isOptional(); $param['passed_by_reference'] = $rparameter->isPassedByReference(); if ($rparameter->isDefaultValueAvailable()) { $param['default'] = $rparameter->getDefaultValue(); } // Pull-in from @tags if ($_description = $tag_finder->find('description', $param['name'])) { $param['description'] = $_description; } if ($_type = $tag_finder->find('type', $param['name'])) { $param['type'] = $this->ancestry->resolveNamespace($_type); } if ($_types = $tag_finder->find('types', $param['name'])) { $param['types'] = $_types; } // Clean-up parameter metadata tags if (isset($entry['metadata']) && isset($entry['metadata']['tag'])) { foreach ($entry['metadata']['tag'] as $index => $tag) { if ($tag['name'] === 'param' && $tag['variable'] === $param['name']) { unset($entry['metadata']['tag'][$index]); } } } // Type hinting trumps docblock if ($rparameter->getClass()) { if (isset($param['type']) && $param['type'] !== $rparameter->getClass()->getName()) { // @todo: Resolve namespace of declaring class. Inconsistency::add($this->class->getName() . '::' . $rmethod->getName() . '($' . $rparameter->getName() . ') [' . $param['type'] . ' => ' . $rparameter->getClass()->getName() . ']'); } $param['type'] = $rparameter->getClass()->getName(); if (isset($param['types'])) { unset($param['types']); } } $entry['parameters']['parameter'][] = $param; } } // Return value $entry['return'] = array('type' => 'void'); if (isset($entry['metadata']) && isset($entry['metadata']['tag'])) { foreach ($entry['metadata']['tag'] as $index => $tag) { if ($tag['name'] === 'return') { $entry['return'] = $tag; unset($entry['return']['name']); // Clean-up return metadata tags unset($entry['metadata']['tag'][$index]); } } } if ($documentThis) { $this->methods['method'][] = $entry; } } return $this->methods; }