Skip to content

Commit 16abe01

Browse files
authored
Merge pull request #46 from smeghead/support-constructer-fields
Support constructer fields
2 parents 51d90d3 + b7d7a3e commit 16abe01

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+161
-66
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions

dogfood-model.png

4.42 KB

dogfood-package.png

8.86 KB

dogfood.png

13.5 KB
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Smeghead\PhpClassDiagram\Php\Finders;
6+
7+
use PhpParser\Node;
8+
use PhpParser\Node\Param;
9+
use PhpParser\Node\Stmt\Class_;
10+
use PhpParser\Node\Stmt\ClassLike;
11+
use PhpParser\Node\Stmt\ClassMethod;
12+
use PhpParser\NodeFinder;
13+
14+
class FindConstructerProperties
15+
{
16+
private array $properties = [];
17+
private ?ClassMethod $constructer;
18+
19+
public function __construct(ClassLike $class)
20+
{
21+
$finder = new NodeFinder();
22+
/** @var ClassMethod $constructer */
23+
$constructer = $finder->findFirst($class, function (Node $node) {
24+
return $node instanceof ClassMethod && $node->name->toString() === '__construct';
25+
});
26+
if ($constructer === null) {
27+
return;
28+
}
29+
$this->constructer = $constructer;
30+
foreach ($constructer->getParams() as $p) {
31+
if ($p->flags & Class_::MODIFIER_PUBLIC) {
32+
$this->properties[] = $p;
33+
} else if ($p->flags & Class_::MODIFIER_PRIVATE) {
34+
$this->properties[] = $p;
35+
} else if ($p->flags & Class_::MODIFIER_PROTECTED) {
36+
$this->properties[] = $p;
37+
}
38+
}
39+
}
40+
41+
/**
42+
* @return Param[]
43+
*/
44+
public function getProperties(): array
45+
{
46+
return $this->properties;
47+
}
48+
49+
public function getConstructer(): ?ClassMethod
50+
{
51+
return $this->constructer;
52+
}
53+
}

src/Php/PhpAccessModifier.php

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace Smeghead\PhpClassDiagram\Php;
66

7+
use PhpParser\Node\Param;
78
use PhpParser\Node\Stmt\{
9+
Class_,
810
ClassConst,
911
ClassMethod,
1012
Property,
@@ -19,14 +21,20 @@ class PhpAccessModifier
1921
private bool $final = false;
2022
private bool $static = false;
2123

22-
public function __construct(ClassConst|Property|ClassMethod $stmt)
24+
public function __construct(ClassConst|Property|ClassMethod|Param $stmt)
2325
{
24-
$this->public = $stmt->isPublic();
25-
$this->protected = $stmt->isProtected();
26-
$this->private = $stmt->isPrivate();
27-
$this->static = $stmt->isStatic();
28-
if ($stmt instanceof ClassMethod) {
29-
$this->abstract = $stmt->isAbstract();
26+
if ($stmt instanceof Param) {
27+
$this->public = boolval($stmt->flags & Class_::MODIFIER_PUBLIC);
28+
$this->protected = boolval($stmt->flags & Class_::MODIFIER_PROTECTED);
29+
$this->private = boolval($stmt->flags & Class_::MODIFIER_PRIVATE);
30+
} else {
31+
$this->public = $stmt->isPublic();
32+
$this->protected = $stmt->isProtected();
33+
$this->private = $stmt->isPrivate();
34+
$this->static = $stmt->isStatic();
35+
if ($stmt instanceof ClassMethod) {
36+
$this->abstract = $stmt->isAbstract();
37+
}
3038
}
3139
}
3240

src/Php/PhpClass.php

Lines changed: 23 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@
44

55
namespace Smeghead\PhpClassDiagram\Php;
66

7-
use PhpParser\Node\{
8-
NullableType,
9-
Identifier,
10-
Name,
11-
UnionType,
12-
};
137
use PhpParser\Node\Name\FullyQualified;
148
use PhpParser\Node\Stmt;
159
use PhpParser\Node\Stmt\{
@@ -23,6 +17,7 @@
2317
Use_,
2418
};
2519
use Smeghead\PhpClassDiagram\Php\Doc\PhpDocComment;
20+
use Smeghead\PhpClassDiagram\Php\Finders\FindConstructerProperties;
2621

2722
class PhpClass
2823
{
@@ -98,7 +93,12 @@ public function getProperties(): array
9893
$properties = $this->getPropertiesFromSyntax();
9994
$props = [];
10095
foreach ($properties as $p) {
101-
$props[] = new PhpProperty($p, $this);
96+
$props[] = PhpProperty::buildByProperty($p, $this);
97+
}
98+
99+
$finder = new FindConstructerProperties($this->syntax);
100+
foreach ($finder->getProperties() as $param) {
101+
$props[] = PhpProperty::buildByParam($param, $finder->getConstructer(), $this);
102102
}
103103
return $props;
104104
}
@@ -148,27 +148,13 @@ private function getUsesRec($stmts, $uses = [])
148148
* * useしているクラスに目的のクラスがあるかを探す
149149
* * 自身のクラス名が目的のクラスかどうか ... (不要かもしれない。暗黙の参照と統合可能
150150
* * 暗黙の参照として、自身のnamespaceを返却する
151+
*
152+
* Since name resolution is done with NameResolver, unnecessary processing has been deleted.
151153
*/
152154
private function findNamespaceByTypeParts(array $type_parts): array
153155
{
154-
$type = str_replace('[]', '', array_pop($type_parts));
155-
$primitives = ['string', 'bool', 'boolean', 'int', 'integer', 'float', 'double', 'array', 'object', 'resource'];
156-
if (in_array($type, $primitives)) {
157-
return [];
158-
}
159-
foreach ($this->getUses() as $u) {
160-
if ($u->getName() === $type) {
161-
return $u->getNamespace();
162-
}
163-
}
164-
// 探したいクラスが、自身の型だった場合
165-
$t = $this->getClassType();
166-
if ($t->getName() === $type) {
167-
return $t->getNamespace();
168-
}
169-
170-
// 暗黙的な参照と見做す
171-
return $this->getNamespace();
156+
array_pop($type_parts);
157+
return $type_parts;
172158
}
173159

174160
/** @return PhpMethod[] メソッド一覧 */
@@ -205,25 +191,19 @@ public function getExtends(): array
205191
'',
206192
end($Name->parts)
207193
);
208-
} else {
209-
$parts = $Name->parts;
210-
$namespace = [];
211-
if (count($parts) > 0) {
212-
$namespace = $this->findNamespaceByTypeParts($parts);
213-
$typeName = array_pop($parts);
214-
}
215-
$extends[] = new PhpType(array_merge($namespace, $parts), 'Stmt_Class', $typeName);
216194
}
217195
}
196+
218197
if (!empty($this->syntax->implements)) {
219-
foreach ($this->syntax->implements as $i) {
220-
$parts = $i->parts;
221-
$namespace = [];
222-
if (count($parts) > 0) {
223-
$namespace = $this->findNamespaceByTypeParts($parts);
224-
$typeName = array_pop($parts);
198+
foreach ($this->syntax->implements as $Name) {
199+
// $parts = $i->parts;
200+
if ($Name instanceof FullyQualified) {
201+
$extends[] = new PhpType(
202+
array_slice($Name->parts, 0, count($Name->parts) - 1),
203+
'',
204+
end($Name->parts)
205+
);
225206
}
226-
$extends[] = new PhpType(array_merge($namespace, $parts), 'Stmt_Interface', $typeName);
227207
}
228208
}
229209
return $extends;
@@ -234,7 +214,7 @@ public function getExtends(): array
234214
*/
235215
public function getEnumCases(): array
236216
{
237-
if ( ! $this->syntax instanceof Enum_) {
217+
if (!$this->syntax instanceof Enum_) {
238218
return [];
239219
}
240220
$cases = [];
@@ -246,7 +226,8 @@ public function getEnumCases(): array
246226
return $cases;
247227
}
248228

249-
public function getDescription(): string {
229+
public function getDescription(): string
230+
{
250231
$doc = new PhpDocComment($this->syntax);
251232
return $doc->getDescription();
252233
}

src/Php/PhpProperty.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
namespace Smeghead\PhpClassDiagram\Php;
66

7+
use PhpParser\Node\Param;
78
use PhpParser\Node\Stmt\{
9+
ClassMethod,
810
Property,
911
};
1012

@@ -14,11 +16,26 @@ class PhpProperty
1416
private PhpTypeExpression $type;
1517
private PhpAccessModifier $accessModifier;
1618

17-
public function __construct(Property $p, PhpClass $class)
19+
private function __construct()
1820
{
19-
$this->name = $p->props[0]->name->toString();
20-
$this->type = PhpTypeExpression::buildByVar($p, $class->getNamespace(), $class->getUses());
21-
$this->accessModifier = new PhpAccessModifier($p);
21+
}
22+
23+
public static function buildByProperty(Property $p, PhpClass $class): self
24+
{
25+
$instance = new self();
26+
$instance->name = $p->props[0]->name->toString();
27+
$instance->type = PhpTypeExpression::buildByVar($p, $class->getNamespace(), $class->getUses());
28+
$instance->accessModifier = new PhpAccessModifier($p);
29+
return $instance;
30+
}
31+
32+
public static function buildByParam(Param $param, ClassMethod $method, PhpClass $class): self
33+
{
34+
$instance = new self();
35+
$instance->name = $param->var->name;
36+
$instance->type = PhpTypeExpression::buildByMethodParam($param, $class->getNamespace(), $method, $param->var->name, $class->getUses());
37+
$instance->accessModifier = new PhpAccessModifier($param);
38+
return $instance;
2239
}
2340

2441
public function getName(): string

src/Php/PhpReader.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
Namespace_,
1212
ClassLike,
1313
};
14+
use PhpParser\NodeTraverser;
15+
use PhpParser\NodeVisitor\NameResolver;
1416
use Smeghead\PhpClassDiagram\Config\Options;
1517

1618
class PhpReader
@@ -50,6 +52,11 @@ public static function parseFile(string $directory, string $filename, Options $o
5052
$parser = (new ParserFactory)->create($targetVesion);
5153
try {
5254
$ast = $parser->parse($code);
55+
$nameResolver = new NameResolver();
56+
$nodeTraverser = new NodeTraverser();
57+
$nodeTraverser->addVisitor($nameResolver);
58+
// Resolve names
59+
$ast = $nodeTraverser->traverse($ast);
5360
} catch (Error $error) {
5461
throw new \Exception("Parse error: {$error->getMessage()} file: {$filename}\n");
5562
}

test/PhpReflectionTest.php

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,36 @@ public function testDump_Price(): void
6161
$this->assertSame([], $data->getProperties()[0]->getType()->getTypes()[0]->getNamespace(), 'namespace.');
6262
$this->assertSame(true, $data->getProperties()[0]->getAccessModifier()->isPrivate(), 'property price Modifiers.');
6363
}
64-
// public function testDump_php8_Price(): void {
65-
// $options = new Options([]);
66-
// $directory = sprintf('%s/php8', $this->fixtureDir);
67-
// $filename = sprintf('%s/php8/product/Price.php', $this->fixtureDir);
68-
// $classes = PhpReader::parseFile($directory, $filename, $options);
64+
public function testDump_php8_Price(): void
65+
{
66+
$options = new Options([]);
67+
$directory = sprintf('%s/php8', $this->fixtureDir);
68+
$filename = sprintf('%s/php8/product/Price.php', $this->fixtureDir);
69+
$classes = PhpReader::parseFile($directory, $filename, $options);
70+
71+
$data = $classes[0]->getInfo();
72+
$this->assertSame('Price', $data->getClassType()->getName(), 'class type name.');
73+
$this->assertSame(['hoge', 'fuga', 'product'], $data->getClassType()->getNamespace(), 'namespace name.');
74+
$this->assertSame('price8', $data->getProperties()[0]->getName(), 'property price.');
75+
$this->assertSame('int|float', $data->getProperties()[0]->getType()->getName(), 'property price type. php8 union type.');
76+
$this->assertSame([], $data->getProperties()[0]->getType()->getTypes()[0]->getNamespace(), 'namespace.');
77+
$this->assertSame(true, $data->getProperties()[0]->getAccessModifier()->isPrivate(), 'property price Modifiers.');
78+
}
79+
public function testDump_php8_constructer_properties(): void
80+
{
81+
$options = new Options([]);
82+
$directory = sprintf('%s/php8', $this->fixtureDir);
83+
$filename = sprintf('%s/php8/product/Price.php', $this->fixtureDir);
84+
$classes = PhpReader::parseFile($directory, $filename, $options);
6985

70-
// $data = $classes[0]->getInfo();
71-
// $this->assertSame('Price', $data->getClassType()->name, 'class type name.');
72-
// $this->assertSame(['hoge', 'fuga', 'product'], $data->getClassType()->getNamespace(), 'namespace name.');
73-
// $this->assertSame('price', $data->getProperties()[0]->name, 'property price.');
74-
// $this->assertSame('int|float', $data->getProperties()[0]->type->getName(), 'property price type. php8 union type.');
75-
// $this->assertSame(['hoge', 'fuga', 'product'], $data->getProperties()[0]->type->getNamespace(), 'namespace.');
76-
// $this->assertSame(true, $data->getProperties()[0]->accessModifier->private, 'property price Modifiers.');
77-
// }
86+
$data = $classes[0]->getInfo();
87+
$this->assertSame('Price', $data->getClassType()->getName(), 'class type name.');
88+
$this->assertSame(['hoge', 'fuga', 'product'], $data->getClassType()->getNamespace(), 'namespace name.');
89+
$this->assertSame('field1', $data->getProperties()[1]->getName(), 'property field1.');
90+
$this->assertSame('int', $data->getProperties()[1]->getType()->getName(), 'property field1 type. php8 union type.');
91+
$this->assertSame([], $data->getProperties()[1]->getType()->getTypes()[0]->getNamespace(), 'namespace.');
92+
$this->assertSame(true, $data->getProperties()[1]->getAccessModifier()->isPrivate(), 'property field1 Modifiers.');
93+
}
7894

7995
public function testDump_with_namespace(): void
8096
{

0 commit comments

Comments
 (0)