|
| 1 | +<?php |
| 2 | + |
| 3 | +declare(strict_types=1); |
| 4 | + |
| 5 | +namespace voku\SimplePhpParser\Model; |
| 6 | + |
| 7 | +use PhpParser\Node\Stmt\Trait_; |
| 8 | +use Roave\BetterReflection\Reflection\ReflectionClass; |
| 9 | +use voku\SimplePhpParser\Parsers\Helper\Utils; |
| 10 | + |
| 11 | +final class PHPTrait extends BasePHPClass |
| 12 | +{ |
| 13 | + /** |
| 14 | + * @var string|null |
| 15 | + * |
| 16 | + * @psalm-var null|class-string |
| 17 | + */ |
| 18 | + public $name; |
| 19 | + |
| 20 | + /** |
| 21 | + * @param Trait_ $node |
| 22 | + * @param null $dummy |
| 23 | + * |
| 24 | + * @return $this |
| 25 | + */ |
| 26 | + public function readObjectFromPhpNode($node, $dummy = null): self |
| 27 | + { |
| 28 | + $this->prepareNode($node); |
| 29 | + |
| 30 | + $this->name = static::getFQN($node); |
| 31 | + |
| 32 | + /** @noinspection NotOptimalIfConditionsInspection */ |
| 33 | + /** @noinspection ArgumentEqualsDefaultValueInspection */ |
| 34 | + if (\trait_exists($this->name, true)) { |
| 35 | + $reflectionClass = ReflectionClass::createFromName($this->name); |
| 36 | + $this->readObjectFromBetterReflection($reflectionClass); |
| 37 | + } |
| 38 | + |
| 39 | + $this->collectTags($node); |
| 40 | + |
| 41 | + $docComment = $node->getDocComment(); |
| 42 | + if ($docComment) { |
| 43 | + $this->readPhpDocProperties($docComment->getText()); |
| 44 | + } |
| 45 | + |
| 46 | + foreach ($node->getProperties() as $property) { |
| 47 | + $propertyNameTmp = $this->getConstantFQN($property, $property->props[0]->name->name); |
| 48 | + |
| 49 | + if (isset($this->properties[$propertyNameTmp])) { |
| 50 | + $this->properties[$propertyNameTmp] = $this->properties[$propertyNameTmp]->readObjectFromPhpNode($property, $this->name); |
| 51 | + } else { |
| 52 | + $this->properties[$propertyNameTmp] = (new PHPProperty($this->parserContainer))->readObjectFromPhpNode($property, $this->name); |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + foreach ($node->getMethods() as $method) { |
| 57 | + $methodNameTmp = $method->name->name; |
| 58 | + |
| 59 | + if (isset($this->methods[$methodNameTmp])) { |
| 60 | + $this->methods[$methodNameTmp] = $this->methods[$methodNameTmp]->readObjectFromPhpNode($method, $this->name); |
| 61 | + } else { |
| 62 | + $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromPhpNode($method, $this->name); |
| 63 | + } |
| 64 | + |
| 65 | + if (!$this->methods[$methodNameTmp]->file) { |
| 66 | + $this->methods[$methodNameTmp]->file = $this->file; |
| 67 | + } |
| 68 | + } |
| 69 | + |
| 70 | + return $this; |
| 71 | + } |
| 72 | + |
| 73 | + /** |
| 74 | + * @param ReflectionClass $clazz |
| 75 | + * |
| 76 | + * @return $this |
| 77 | + */ |
| 78 | + public function readObjectFromBetterReflection($clazz): self |
| 79 | + { |
| 80 | + if (!$clazz->isTrait()) { |
| 81 | + return $this; |
| 82 | + } |
| 83 | + |
| 84 | + $this->name = $clazz->getName(); |
| 85 | + |
| 86 | + if (!$this->line) { |
| 87 | + $this->line = $clazz->getStartLine(); |
| 88 | + } |
| 89 | + |
| 90 | + $file = $clazz->getFileName(); |
| 91 | + if ($file) { |
| 92 | + $this->file = $file; |
| 93 | + } |
| 94 | + |
| 95 | + foreach ($clazz->getProperties() as $property) { |
| 96 | + $propertyPhp = (new PHPProperty($this->parserContainer))->readObjectFromBetterReflection($property); |
| 97 | + $this->properties[$propertyPhp->name] = $propertyPhp; |
| 98 | + } |
| 99 | + |
| 100 | + foreach ($clazz->getMethods() as $method) { |
| 101 | + $methodNameTmp = $method->getName(); |
| 102 | + |
| 103 | + $this->methods[$methodNameTmp] = (new PHPMethod($this->parserContainer))->readObjectFromBetterReflection($method); |
| 104 | + |
| 105 | + if (!$this->methods[$methodNameTmp]->file) { |
| 106 | + $this->methods[$methodNameTmp]->file = $this->file; |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + foreach ($clazz->getReflectionConstants() as $constant) { |
| 111 | + $constantNameTmp = $constant->getName(); |
| 112 | + |
| 113 | + $this->constants[$constantNameTmp] = (new PHPConst($this->parserContainer))->readObjectFromBetterReflection($constant); |
| 114 | + |
| 115 | + if (!$this->constants[$constantNameTmp]->file) { |
| 116 | + $this->constants[$constantNameTmp]->file = $this->file; |
| 117 | + } |
| 118 | + } |
| 119 | + |
| 120 | + return $this; |
| 121 | + } |
| 122 | + |
| 123 | + /** |
| 124 | + * @param string[] $access |
| 125 | + * @param bool $skipMethodsWithLeadingUnderscore |
| 126 | + * |
| 127 | + * @return array |
| 128 | + * |
| 129 | + * @psalm-return array<string, array{type: null|string, typeFromPhpDocMaybeWithComment: null|string, typeFromPhpDoc: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocPslam: null|string, typeFromDefaultValue: null|string}> |
| 130 | + */ |
| 131 | + public function getPropertiesInfo( |
| 132 | + array $access = ['public', 'protected', 'private'], |
| 133 | + bool $skipMethodsWithLeadingUnderscore = false |
| 134 | + ): array { |
| 135 | + // init |
| 136 | + $allInfo = []; |
| 137 | + |
| 138 | + foreach ($this->properties as $property) { |
| 139 | + if (!\in_array($property->access, $access, true)) { |
| 140 | + continue; |
| 141 | + } |
| 142 | + |
| 143 | + if ($skipMethodsWithLeadingUnderscore && \strpos($property->name, '_') === 0) { |
| 144 | + continue; |
| 145 | + } |
| 146 | + |
| 147 | + $types = []; |
| 148 | + $types['type'] = $property->type; |
| 149 | + $types['typeFromPhpDocMaybeWithComment'] = $property->typeFromPhpDocMaybeWithComment; |
| 150 | + $types['typeFromPhpDoc'] = $property->typeFromPhpDoc; |
| 151 | + $types['typeFromPhpDocSimple'] = $property->typeFromPhpDocSimple; |
| 152 | + $types['typeFromPhpDocPslam'] = $property->typeFromPhpDocPslam; |
| 153 | + $types['typeFromDefaultValue'] = $property->typeFromDefaultValue; |
| 154 | + |
| 155 | + $allInfo[$property->name] = $types; |
| 156 | + } |
| 157 | + |
| 158 | + return $allInfo; |
| 159 | + } |
| 160 | + |
| 161 | + /** |
| 162 | + * @param string[] $access |
| 163 | + * @param bool $skipDeprecatedMethods |
| 164 | + * @param bool $skipMethodsWithLeadingUnderscore |
| 165 | + * |
| 166 | + * @return array<mixed> |
| 167 | + * |
| 168 | + * @psalm-return array<string, array{fullDescription: string, line: null|int, file: null|string, error: string, is_deprecated: bool, is_static: null|bool, is_meta: bool, is_internal: bool, is_removed: bool, paramsTypes: array<string, array{type: null|string, typeFromPhpDoc: null|string, typeFromPhpDocPslam: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocMaybeWithComment: null|string, typeFromDefaultValue: null|string}>, returnTypes: array{type: null|string, typeFromPhpDoc: null|string, typeFromPhpDocPslam: null|string, typeFromPhpDocSimple: null|string, typeFromPhpDocMaybeWithComment: null|string}}> |
| 169 | + */ |
| 170 | + public function getMethodsInfo( |
| 171 | + array $access = ['public', 'protected', 'private'], |
| 172 | + bool $skipDeprecatedMethods = false, |
| 173 | + bool $skipMethodsWithLeadingUnderscore = false |
| 174 | + ): array { |
| 175 | + // init |
| 176 | + $allInfo = []; |
| 177 | + |
| 178 | + foreach ($this->methods as $method) { |
| 179 | + if (!\in_array($method->access, $access, true)) { |
| 180 | + continue; |
| 181 | + } |
| 182 | + |
| 183 | + if ($skipDeprecatedMethods && $method->hasDeprecatedTag) { |
| 184 | + continue; |
| 185 | + } |
| 186 | + |
| 187 | + if ($skipMethodsWithLeadingUnderscore && \strpos($method->name, '_') === 0) { |
| 188 | + continue; |
| 189 | + } |
| 190 | + |
| 191 | + $paramsTypes = []; |
| 192 | + foreach ($method->parameters as $tagParam) { |
| 193 | + $paramsTypes[$tagParam->name]['type'] = $tagParam->type; |
| 194 | + $paramsTypes[$tagParam->name]['typeFromPhpDocMaybeWithComment'] = $tagParam->typeFromPhpDocMaybeWithComment; |
| 195 | + $paramsTypes[$tagParam->name]['typeFromPhpDoc'] = $tagParam->typeFromPhpDoc; |
| 196 | + $paramsTypes[$tagParam->name]['typeFromPhpDocSimple'] = $tagParam->typeFromPhpDocSimple; |
| 197 | + $paramsTypes[$tagParam->name]['typeFromPhpDocPslam'] = $tagParam->typeFromPhpDocPslam; |
| 198 | + $paramsTypes[$tagParam->name]['typeFromDefaultValue'] = $tagParam->typeFromDefaultValue; |
| 199 | + } |
| 200 | + |
| 201 | + $returnTypes = []; |
| 202 | + $returnTypes['type'] = $method->returnType; |
| 203 | + $returnTypes['typeFromPhpDocMaybeWithComment'] = $method->returnTypeFromPhpDocMaybeWithComment; |
| 204 | + $returnTypes['typeFromPhpDoc'] = $method->returnTypeFromPhpDoc; |
| 205 | + $returnTypes['typeFromPhpDocSimple'] = $method->returnTypeFromPhpDocSimple; |
| 206 | + $returnTypes['typeFromPhpDocPslam'] = $method->returnTypeFromPhpDocPslam; |
| 207 | + |
| 208 | + $infoTmp = []; |
| 209 | + $infoTmp['fullDescription'] = \trim($method->summary . "\n\n" . $method->description); |
| 210 | + $infoTmp['paramsTypes'] = $paramsTypes; |
| 211 | + $infoTmp['returnTypes'] = $returnTypes; |
| 212 | + $infoTmp['line'] = $method->line; |
| 213 | + $infoTmp['file'] = $method->file; |
| 214 | + $infoTmp['error'] = \implode("\n", $method->parseError); |
| 215 | + foreach ($method->parameters as $parameter) { |
| 216 | + $infoTmp['error'] .= ($infoTmp['error'] ? "\n" : '') . \implode("\n", $parameter->parseError); |
| 217 | + } |
| 218 | + $infoTmp['is_deprecated'] = $method->hasDeprecatedTag; |
| 219 | + $infoTmp['is_static'] = $method->is_static; |
| 220 | + $infoTmp['is_meta'] = $method->hasMetaTag; |
| 221 | + $infoTmp['is_internal'] = $method->hasInternalTag; |
| 222 | + $infoTmp['is_removed'] = $method->hasRemovedTag; |
| 223 | + |
| 224 | + $allInfo[$method->name] = $infoTmp; |
| 225 | + } |
| 226 | + |
| 227 | + \asort($allInfo); |
| 228 | + |
| 229 | + return $allInfo; |
| 230 | + } |
| 231 | + |
| 232 | + /** |
| 233 | + * @param string $docComment |
| 234 | + * |
| 235 | + * @return void |
| 236 | + */ |
| 237 | + private function readPhpDocProperties(string $docComment): void |
| 238 | + { |
| 239 | + if ($docComment === '') { |
| 240 | + return; |
| 241 | + } |
| 242 | + |
| 243 | + try { |
| 244 | + $phpDoc = Utils::createDocBlockInstance()->create($docComment); |
| 245 | + |
| 246 | + /** @noinspection AdditionOperationOnArraysInspection */ |
| 247 | + $parsedPropertyTags = $phpDoc->getTagsByName('property') |
| 248 | + + $phpDoc->getTagsByName('property-read') |
| 249 | + + $phpDoc->getTagsByName('property-write'); |
| 250 | + |
| 251 | + if (!empty($parsedPropertyTags)) { |
| 252 | + foreach ($parsedPropertyTags as $parsedPropertyTag) { |
| 253 | + if ( |
| 254 | + $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyRead |
| 255 | + || |
| 256 | + $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\PropertyWrite |
| 257 | + || |
| 258 | + $parsedPropertyTag instanceof \phpDocumentor\Reflection\DocBlock\Tags\Property |
| 259 | + ) { |
| 260 | + $propertyPhp = new PHPProperty($this->parserContainer); |
| 261 | + |
| 262 | + $nameTmp = $parsedPropertyTag->getVariableName(); |
| 263 | + if (!$nameTmp) { |
| 264 | + continue; |
| 265 | + } |
| 266 | + |
| 267 | + $propertyPhp->name = $nameTmp; |
| 268 | + |
| 269 | + $propertyPhp->access = 'public'; |
| 270 | + |
| 271 | + $type = $parsedPropertyTag->getType(); |
| 272 | + |
| 273 | + $propertyPhp->typeFromPhpDoc = Utils::normalizePhpType($type . ''); |
| 274 | + |
| 275 | + $typeFromPhpDocMaybeWithCommentTmp = \trim((string) $parsedPropertyTag); |
| 276 | + if ( |
| 277 | + $typeFromPhpDocMaybeWithCommentTmp |
| 278 | + && |
| 279 | + \strpos($typeFromPhpDocMaybeWithCommentTmp, '$') !== 0 |
| 280 | + ) { |
| 281 | + $propertyPhp->typeFromPhpDocMaybeWithComment = $typeFromPhpDocMaybeWithCommentTmp; |
| 282 | + } |
| 283 | + |
| 284 | + $typeTmp = Utils::parseDocTypeObject($type); |
| 285 | + if ($typeTmp !== '') { |
| 286 | + $propertyPhp->typeFromPhpDocSimple = $typeTmp; |
| 287 | + } |
| 288 | + |
| 289 | + if ($propertyPhp->typeFromPhpDoc) { |
| 290 | + /** @noinspection PhpUsageOfSilenceOperatorInspection */ |
| 291 | + $propertyPhp->typeFromPhpDocPslam = (string) @\Psalm\Type::parseString($propertyPhp->typeFromPhpDoc); |
| 292 | + } |
| 293 | + |
| 294 | + $this->properties[$propertyPhp->name] = $propertyPhp; |
| 295 | + } |
| 296 | + } |
| 297 | + } |
| 298 | + } catch (\Exception $e) { |
| 299 | + $tmpErrorMessage = ($this->name ?? '?') . ':' . ($this->line ?? '?') . ' | ' . \print_r($e->getMessage(), true); |
| 300 | + $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage; |
| 301 | + } |
| 302 | + } |
| 303 | +} |
0 commit comments