Skip to content

Commit 6c9ecc4

Browse files
committed
[+]: "PHPTrait" -> add support for "Trait"-files
1 parent 9d7e5e6 commit 6c9ecc4

File tree

7 files changed

+456
-0
lines changed

7 files changed

+456
-0
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changelog
22

3+
### 0.13.0 (2020-07-16)
4+
5+
- "PHPTrait" -> add support for "Trait"-files
6+
37
### 0.12.0 (2020-07-05)
48

59
- "PhpCodeParser" -> fix cache key
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
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+
}

src/voku/SimplePhpParser/Parsers/Helper/ParserContainer.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use voku\SimplePhpParser\Model\PHPConst;
99
use voku\SimplePhpParser\Model\PHPFunction;
1010
use voku\SimplePhpParser\Model\PHPInterface;
11+
use voku\SimplePhpParser\Model\PHPTrait;
1112

1213
class ParserContainer
1314
{
@@ -32,6 +33,13 @@ class ParserContainer
3233
*/
3334
private $classes = [];
3435

36+
/**
37+
* @var \voku\SimplePhpParser\Model\PHPTrait[]
38+
*
39+
* @psalm-var array<string, PHPTrait>
40+
*/
41+
private $traits = [];
42+
3543
/**
3644
* @var \voku\SimplePhpParser\Model\PHPInterface[]
3745
*
@@ -226,6 +234,18 @@ public function setClasses($classes): void
226234
}
227235
}
228236

237+
/**
238+
* @param array<string, \voku\SimplePhpParser\Model\PHPTrait> $traits
239+
*
240+
* @return void
241+
*/
242+
public function setTraits($traits): void
243+
{
244+
foreach ($traits as $traitName => $trait) {
245+
$this->traits[$traitName] = $trait;
246+
}
247+
}
248+
229249
public function setParseError(ParserErrorHandler $error): void
230250
{
231251
foreach ($error->getErrors() as $errorInner) {
@@ -243,6 +263,34 @@ public function addClass(PHPClass $class): void
243263
$this->classes[$class->name ?: \md5(\serialize($class))] = $class;
244264
}
245265

266+
/**
267+
* @param string $name
268+
*
269+
* @return \voku\SimplePhpParser\Model\PHPTrait|null
270+
*/
271+
public function getTrait(string $name): ?PHPTrait
272+
{
273+
return $this->traits[$name] ?? null;
274+
}
275+
276+
/**
277+
* @return \voku\SimplePhpParser\Model\PHPTrait[]
278+
*/
279+
public function getTraits(): array
280+
{
281+
return $this->traits;
282+
}
283+
284+
/**
285+
* @param \voku\SimplePhpParser\Model\PHPTrait $trait
286+
*
287+
* @return void
288+
*/
289+
public function addTrait(PHPTrait $trait): void
290+
{
291+
$this->traits[$trait->name ?: \md5(\serialize($trait))] = $trait;
292+
}
293+
246294
/**
247295
* @param string $name
248296
*

src/voku/SimplePhpParser/Parsers/PhpCodeParser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ public static function getPhpFiles(
9999

100100
foreach ($phpFilePromiseResponses as $response) {
101101
if ($response instanceof ParserContainer) {
102+
$parserContainer->setTraits($response->getTraits());
102103
$parserContainer->setClasses($response->getClasses());
103104
$parserContainer->setInterfaces($response->getInterfaces());
104105
$parserContainer->setConstants($response->getConstants());

0 commit comments

Comments
 (0)