<?php namespace PHPFUI\InstaDoc\Section; class CodeCommon extends \PHPFUI\InstaDoc\Section { protected \phpDocumentor\Reflection\DocBlockFactory $factory; protected \PHPFUI\InstaDoc\MarkDownParser $parsedown; protected $reflection; public function __construct(\PHPFUI\InstaDoc\Controller $controller, string $fullClassPath = '') { parent::__construct($controller); $this->factory = \phpDocumentor\Reflection\DocBlockFactory::createInstance(); $this->parsedown = new \PHPFUI\InstaDoc\MarkDownParser(); if ($fullClassPath) { try { $this->reflection = new \ReflectionClass($fullClassPath); try { $this->reflection->isInstantiable(); } catch (\Throwable) { $this->reflection = new \ReflectionEnum($fullClassPath); } } catch (\Throwable) { } } } public function getMethodParameters(\ReflectionFunction|\ReflectionMethod $method, array $parameterComments = []) : string { $info = $comma = ''; foreach ($method->getParameters() as $parameter) { $info .= $comma; $comma = ', '; if ($parameter->hasType()) { $type = $parameter->getType(); $info .= $this->getColor('type', $this->getValueString($type)); } $info .= ' '; $name = $parameter->getName(); $tip = '$' . $name; if (isset($parameterComments[$name])) { $tip = new \PHPFUI\ToolTip($tip, \htmlspecialchars($parameterComments[$name])); $tip->addAttribute('data-allow-html'); } $info .= $this->getColor('variable', $tip); if ($parameter->isDefaultValueAvailable()) { $value = $parameter->getDefaultValue(); $info .= ' = ' . $this->getValueString($value); if ($parameter->isDefaultValueConstant()) { if (\is_object($value)) { $value = $value::class; } $extra = $parameter->getDefaultValueConstantName(); $info .= \str_replace($value, '', $extra); } } } return $info; } public function getValueString(mixed $value) : string { switch (\gettype($value)) { case 'array': $index = 0; $text = $this->getColor('operator', '['); $comma = ''; foreach ($value as $key => $part) { $text .= $comma; if ($index !== $key) { $text .= $this->getValueString($key) . ' ' . $this->getColor('operator', '=>') . ' '; } ++$index; $text .= $this->getValueString($part); $comma = ', '; } $text .= $this->getColor('operator', ']'); $value = $text; break; case 'string': $value = \htmlspecialchars($value); $value = $this->getColor('string', "'{$value}'"); break; case 'object': if (\enum_exists($value::class)) { $value = \property_exists($value, 'value') ? $value->value : ''; if ('string' == \gettype($value)) { $value = $this->getColor('string', "'{$value}'"); } } else { $value = $this->getClassName($value); } break; case 'resource': $value = $this->getColor('keyword', 'resource'); break; case 'boolean': $value = $this->getColor('keyword', $value ? 'true' : 'false'); break; case 'NULL': $value = $this->getColor('keyword', 'NULL'); break; default: $value = $this->getColor('number', (string)$value); } return $value; } protected function formatAttribute(\ReflectionAttribute $attribute) : string { $parameters = ''; $arguments = $attribute->getArguments(); if ($arguments) { $parameters = ' ('; $comma = ''; foreach ($arguments as $name => $argument) { $name = \is_int($name) ? '' : $this->getAttributeName($name) . ': '; if (\is_string($argument)) { $link = $this->getAttributeName($argument, true); } else { $link = $this->getValueString($argument); } $parameters .= "{$comma} {$name}{$link}"; $comma = ', '; } $parameters .= ')'; } $targeting = ''; return $this->getClassName($attribute->getName()) . $parameters . $targeting; } protected function formatComments(?\phpDocumentor\Reflection\DocBlock $docBlock, \ReflectionMethod | \ReflectionClass | null $reflection = null) : string { if (! $docBlock) { return ''; } $container = new \PHPFUI\Container(); $container->add($this->parsedown->text($this->getInheritedText($docBlock, $reflection, 'getSummary'))); $desc = $this->getInheritedText($docBlock, $reflection); if ($desc) { $div = new \PHPFUI\HTML5Element('div'); $div->addClass('description'); $div->add($this->parsedown->text($desc)); $container->add($div); } $tags = $docBlock->getTags(); if ($reflection instanceof \ReflectionMethod) { $tags = $this->getInheritedDocBlock($tags, $reflection); } if ($tags) { $ul = new \PHPFUI\UnorderedList(); foreach ($tags as $tag) { $name = $tag->getName(); $description = \method_exists($tag, 'getDescription') ? \trim($tag->getDescription() ?? '') : ''; $body = ''; if (\in_array($name, ['method', 'inheritdoc'])) { continue; } if (\method_exists($tag, 'getType')) { $type = $tag->getType(); } else { $type = ''; } if ('var' == $name || 'param' == $name) { if (! $description && ! $type) { continue; } } if (\method_exists($tag, 'getAuthorName')) { $body .= \PHPFUI\Link::email($tag->getEmail(), $tag->getAuthorName()); } if (\method_exists($tag, 'getReference')) { $body .= $tag->getReference(); } if (\method_exists($tag, 'getVersion')) { $body .= $tag->getVersion(); } if (\method_exists($tag, 'getLink')) { $body .= new \PHPFUI\Link($tag->getLink(), '', false); } if ($type) { $body .= $this->getClassName($type) . ' '; } if (\method_exists($tag, 'getVariableName')) { $varname = $tag->getVariableName(); if ($varname) { $body .= $this->getColor('variable', '$' . $varname) . ' '; } } $parts = \explode('$', $description); if (\count($parts) > 1) { $parts[0] = \str_replace(['<', '>'], ['<', '>'], $parts[0]); $description = \implode('$', $parts); } $body .= $this->parsedown->html($description); $ul->addItem(new \PHPFUI\ListItem($this->getColor('name', $name) . ' ' . $this->getColor('description', $body))); } $attributes = $this->getAttributes($reflection); foreach ($attributes as $attribute) { $ul->addItem(new \PHPFUI\ListItem($this->getColor('name', 'attribute') . ' ' . $this->formatAttribute($attribute))); } $container->add($ul); } return $container; } protected function getAttributes(?object $reflection) : array { if ($reflection && \method_exists($reflection, 'getAttributes')) { return $reflection->getAttributes(); } return []; } protected function getClassName(string | object $class, bool $asLink = true) : string { $array = ''; if (! \is_string($class)) { $className = $class::class; if ($class instanceof \phpDocumentor\Reflection\Type) { return \htmlspecialchars($class); } elseif ('ReflectionNamedType' == $className) { return ($class->allowsNull() ? '?' : '') . $this->getClassName($class->getName()); } elseif ('ReflectionUnionType' == $className) { $types = $class->getTypes(); $value = $bar = ''; foreach ($types as $type) { $value .= $bar; $bar = '|'; $value .= $this->getClassName($type); } return $value; } elseif ('ReflectionIntersectionType' == $className) { $types = $class->getTypes(); $value = '('; $bar = ''; foreach ($types as $type) { $value .= $bar; $bar = '&'; $value .= $this->getClassName($type->getName()); } return $value . ')'; } return $this->getClassName($class::class); } if ($asLink && $class) { $parts = \explode('|', $class); if (\count($parts) > 1) { $returnValue = []; foreach ($parts as $part) { $returnValue[] = $this->getClassName($part, true); } return \implode('|', $returnValue); } if ('\\' == $class[0]) { $class = \substr($class, 1); } if (\str_contains($class, '[]')) { $array = '[]'; $class = \str_replace($array, '', $class); } if (\PHPFUI\InstaDoc\NamespaceTree::hasClass($class)) { return new \PHPFUI\Link($this->controller->getClassUrl($class), \str_replace('\\', '<wbr>\\', $class), false) . $array; } $namespacedClass = $this->reflection->getNamespaceName() . '\\' . $class; if (\PHPFUI\InstaDoc\NamespaceTree::hasClass($namespacedClass)) { return new \PHPFUI\Link($this->controller->getClassUrl($namespacedClass), \str_replace('\\', '<wbr>\\', $namespacedClass), false) . $array; } } return $this->getColor('type', \htmlspecialchars($class)) . $array; } protected function getColor(string $class, string $name) : string { $span = new \PHPFUI\HTML5Element('span'); $span->addClass($class); $span->add($name); return $span; } protected function getComments(?\phpDocumentor\Reflection\DocBlock $docBlock, ?\ReflectionMethod $reflection = null) : string { if (! $docBlock) { return ''; } $gridX = new \PHPFUI\GridX(); $cell1 = new \PHPFUI\Cell(1); $cell1->add(' '); $gridX->add($cell1); $cell11 = new \PHPFUI\Cell(11); $cell11->add($this->formatComments($docBlock, $reflection)); $gridX->add($cell11); return $gridX; } protected function getDocBlock(object $method) : ?\phpDocumentor\Reflection\DocBlock { $comments = $method->getDocComment(); if (! $comments) { return null; } $comments = \str_ireplace('inheritdocs', 'inheritdoc', $comments); $comments = \str_ireplace('{@inheritdoc}', '@inheritdoc', $comments); try { $docBlock = $this->factory->create($comments); } catch (\Exception) { $docBlock = null; } return $docBlock; } protected function getHtmlClass(string $class) : string { return \str_replace('\\', '-', $class); } protected function getInheritedDocBlock(array $tags, \ReflectionMethod | \ReflectionClass | null $reflection) : array { foreach ($tags as $index => $tag) { if (false !== \stripos($tag->getName(), 'inheritdoc')) { $reflectionClass = ($reflection instanceof \ReflectionMethod) ? $reflection->getDeclaringClass() : $reflection; $parent = $reflectionClass->getParentClass(); while ($parent) { try { $method = $parent->getMethod($reflection->name); } catch (\Throwable) { $method = null; } if ($method) { $docBlock = $this->getDocBlock($method); if ($docBlock) { \array_splice($tags, $index, 1, $docBlock->getTags()); return $this->getInheritedDocBlock($tags, $method); } } $parent = $parent->getParentClass(); } break; } } return $tags; } protected function getInheritedText(\phpDocumentor\Reflection\DocBlock $docBlock, \ReflectionMethod | \ReflectionClass | null $reflection = null, string $textType = 'getDescription') : string { $summary = $docBlock->{$textType}(); if (! $reflection || 'getSummary' == $textType) { return $summary; } $tags = $docBlock->getTags(); foreach ($tags as $index => $tag) { if (false !== \stripos($tag->getName(), 'inheritdoc')) { if ($reflection instanceof \ReflectionMethod) { $reflectionClass = $reflection->getDeclaringClass(); $parent = $reflectionClass->getParentClass(); while ($parent) { try { $method = $parent->getMethod($reflection->name); } catch (\Throwable) { $method = null; } if ($method) { $docBlock = $this->getDocBlock($method); if ($docBlock) { return $this->getInheritedText($docBlock, $method) . $summary; } } $parent = $parent->getParentClass(); } break; } $parent = $reflection->getParentClass(); while ($parent) { $comments = $parent->getDocComment(); if ($comments) { $comments = \str_replace('{@inheritdoc}', '@inheritdoc', $comments); $docblock = $this->factory->create($comments); $summary = $this->formatComments($docblock, $parent) . $summary; } $parent = $parent->getParentClass(); } break; } } return $summary; } protected function getMethodParametersBlock(\ReflectionFunction|\ReflectionMethod $method) : string { $docBlock = $this->getDocBlock($method); $parameterComments = $this->getParameterComments($docBlock); $info = '(' . $this->getMethodParameters($method, $parameterComments) . ')'; if ($method->hasReturnType()) { $info .= ' : ' . $this->getValueString($method->getReturnType()); } $info .= $this->getComments($docBlock, $method instanceof \ReflectionMethod ? $method : null); return $info; } protected function getParameterComments(?\phpDocumentor\Reflection\DocBlock $docBlock) : array { $comments = []; if (! $docBlock) { return $comments; } foreach ($docBlock->getTags() as $tag) { $name = $tag->getName(); $description = \method_exists($tag, 'getDescription') ? \trim($tag->getDescription() ?? '') : ''; if ('param' == $name && $description) { $var = $tag->getVariableName(); $comments[$var] = $this->parsedown->html($description); } } return $comments; } protected function section(string $name) : string { if (! $name) { return ''; } $section = new \PHPFUI\HTML5Element('span'); $section->add($name); $section->addClass('callout'); $section->addClass('small'); $section->addClass('primary'); return $section; } private function getAttributeName(string $name, bool $asValue = false) : string { $link = $this->getClassName($name); if (\strpos($link, 'href=')) { $name = $link; } elseif ($asValue) { $name = $this->getValueString($name); } return $name; } }