Skip to content

Commit 0d824d1

Browse files
committed
Introduce ClassContFactory class and fromNodes() method
1 parent 5b08972 commit 0d824d1

File tree

4 files changed

+298
-4
lines changed

4 files changed

+298
-4
lines changed

README.md

+33
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,36 @@ final class TestClass extends BaseClass implements \Iterator, Bar
8888
```
8989

9090
Now, change the body of the `toInt()` method to something else. You will see that your changes will *NOT* be overwritten.
91+
92+
### Reverse usage
93+
94+
It is also possible to create a factory class from parsed PHP AST. You can create an instance of `OpenCodeModeling\CodeAst\Factory\ClassFactory` by
95+
calling `OpenCodeModeling\CodeAst\Factory\ClassFactory::fromNodes()`.
96+
97+
```php
98+
<?php
99+
$expected = <<<'EOF'
100+
<?php
101+
102+
declare (strict_types=1);
103+
namespace My\Awesome\Service;
104+
105+
use Foo\Bar;
106+
final class TestClass extends BaseClass implements \Iterator, Bar
107+
{
108+
private const PRIV = 'private';
109+
}
110+
EOF;
111+
112+
113+
$ast = $parser->parse($expected);
114+
115+
$classFactory = OpenCodeModeling\CodeAst\Factory\ClassFactory::fromNodes(...$ast);
116+
117+
$classFactory->getName(); // TestClass
118+
$classFactory->getExtends(); // BaseClass
119+
$classFactory->isFinal(); // true
120+
$classFactory->isStrict(); // true
121+
$classFactory->isAbstract(); // false
122+
123+
```

src/Factory/ClassConstFactory.php

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
<?php
2+
3+
/**
4+
* @see https://github.com/open-code-modeling/php-code-ast for the canonical source repository
5+
* @copyright https://github.com/open-code-modeling/php-code-ast/blob/master/COPYRIGHT.md
6+
* @license https://github.com/open-code-modeling/php-code-ast/blob/master/LICENSE.md MIT License
7+
*/
8+
9+
declare(strict_types=1);
10+
11+
namespace OpenCodeModeling\CodeAst\Factory;
12+
13+
use OpenCodeModeling\CodeAst\Code\ClassConstGenerator;
14+
use OpenCodeModeling\CodeAst\NodeVisitor\ClassConstant;
15+
use PhpParser\Node;
16+
use PhpParser\NodeVisitor;
17+
18+
final class ClassConstFactory
19+
{
20+
/** @var string */
21+
private $name;
22+
23+
/** @var mixed */
24+
private $value;
25+
26+
/**
27+
* @var int
28+
*/
29+
private $visibility;
30+
31+
private function __construct()
32+
{
33+
}
34+
35+
public static function fromNode(Node\Stmt\ClassConst $node): self
36+
{
37+
$self = new self();
38+
39+
$self->name = $node->consts[0]->name->name;
40+
// @phpstan-ignore-next-line
41+
$self->value = $node->consts[0]->value->value;
42+
$self->visibility = $node->flags;
43+
44+
return $self;
45+
}
46+
47+
public static function fromScratch(string $name, $value): self
48+
{
49+
$self = new self();
50+
$self->name = $name;
51+
$self->value = $value;
52+
53+
return $self;
54+
}
55+
56+
public function setPrivate(): self
57+
{
58+
$this->visibility = ClassConstGenerator::FLAG_PRIVATE;
59+
60+
return $this;
61+
}
62+
63+
public function setProtected(): self
64+
{
65+
$this->visibility = ClassConstGenerator::FLAG_PROTECTED;
66+
67+
return $this;
68+
}
69+
70+
public function setPublic(): self
71+
{
72+
$this->visibility = ClassConstGenerator::FLAG_PUBLIC;
73+
74+
return $this;
75+
}
76+
77+
public function generate(): NodeVisitor
78+
{
79+
return ClassConstant::forClassConstant($this->name, $this->value, $this->visibility);
80+
}
81+
}

src/Factory/ClassFactory.php

+143-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use OpenCodeModeling\CodeAst\NodeVisitor\ClassNamespace;
1818
use OpenCodeModeling\CodeAst\NodeVisitor\NamespaceUse;
1919
use OpenCodeModeling\CodeAst\NodeVisitor\StrictType;
20+
use PhpParser\Node;
2021
use PhpParser\NodeTraverser;
2122
use PhpParser\NodeVisitor;
2223

@@ -29,18 +30,18 @@ final class ClassFactory
2930
private $name;
3031

3132
/** @var bool */
32-
private $strict;
33+
private $strict = false;
3334

3435
/** @var bool */
35-
private $typed;
36+
private $typed = false;
3637

3738
/** @var bool */
3839
private $final = false;
3940

4041
/** @var bool */
4142
private $abstract = false;
4243

43-
/** @var string */
44+
/** @var string|null */
4445
private $extends;
4546

4647
/** @var string[] */
@@ -49,10 +50,24 @@ final class ClassFactory
4950
/** @var string[] */
5051
private $namespaceUse = [];
5152

53+
/** @var ClassConstFactory[] */
54+
private $constants = [];
55+
5256
private function __construct()
5357
{
5458
}
5559

60+
public static function fromNodes(Node ...$nodes): self
61+
{
62+
$self = new self();
63+
64+
foreach ($nodes as $node) {
65+
$self->unpackNode($node);
66+
}
67+
68+
return $self;
69+
}
70+
5671
public static function fromScratch(
5772
string $className,
5873
string $namespace = null,
@@ -110,6 +125,72 @@ public function setNamespaceUse(string ...$namespaces): self
110125
return $this;
111126
}
112127

128+
public function setConstants(ClassConstFactory ...$constants): self
129+
{
130+
$this->constants = $constants;
131+
132+
return $this;
133+
}
134+
135+
public function getNamespace(): ?string
136+
{
137+
return $this->namespace;
138+
}
139+
140+
public function getName(): ?string
141+
{
142+
return $this->name;
143+
}
144+
145+
public function isStrict(): bool
146+
{
147+
return $this->strict;
148+
}
149+
150+
public function isTyped(): bool
151+
{
152+
return $this->typed;
153+
}
154+
155+
public function isFinal(): bool
156+
{
157+
return $this->final;
158+
}
159+
160+
public function isAbstract(): bool
161+
{
162+
return $this->abstract;
163+
}
164+
165+
public function getExtends(): ?string
166+
{
167+
return $this->extends;
168+
}
169+
170+
/**
171+
* @return string[]
172+
*/
173+
public function getImplements(): array
174+
{
175+
return $this->implements;
176+
}
177+
178+
/**
179+
* @return string[]
180+
*/
181+
public function getNamespaceUse(): array
182+
{
183+
return $this->namespaceUse;
184+
}
185+
186+
/**
187+
* @return ClassConstFactory[]
188+
*/
189+
public function getConstants(): array
190+
{
191+
return $this->constants;
192+
}
193+
113194
/**
114195
* @return NodeVisitor[]
115196
*/
@@ -137,10 +218,69 @@ public function generate(): array
137218
if ($this->implements) {
138219
$visitors[] = new ClassImplements(...$this->implements);
139220
}
221+
if (\count($this->constants) > 0) {
222+
\array_push(
223+
$visitors,
224+
...\array_map(
225+
static function (ClassConstFactory $const) {
226+
return $const->generate();
227+
},
228+
$this->constants
229+
)
230+
);
231+
}
140232

141233
return $visitors;
142234
}
143235

236+
private function unpackNode(Node $node): void
237+
{
238+
switch (true) {
239+
case $node instanceof Node\Stmt\Declare_:
240+
if ($node->declares[0]->key->name === 'strict_types') {
241+
$this->strict = true;
242+
}
243+
break;
244+
case $node instanceof Node\Stmt\Namespace_:
245+
$this->namespace = $node->name->toString();
246+
247+
foreach ($node->stmts as $stmt) {
248+
$this->unpackNode($stmt);
249+
}
250+
break;
251+
case $node instanceof Node\Stmt\Use_:
252+
foreach ($node->uses as $use) {
253+
$this->unpackNode($use);
254+
}
255+
break;
256+
case $node instanceof Node\Stmt\UseUse:
257+
$this->namespaceUse[] = $node->name->toString();
258+
break;
259+
case $node instanceof Node\Stmt\Class_:
260+
$this->name = $node->name->name;
261+
$this->final = $node->isFinal();
262+
$this->extends = $node->extends ? $node->extends->toString() : null;
263+
264+
foreach ($node->stmts as $stmt) {
265+
$this->unpackNode($stmt);
266+
}
267+
$this->implements = \array_map(
268+
static function (Node\Name $name) {
269+
return $name instanceof Node\Name\FullyQualified
270+
? '\\' . $name->toString()
271+
: $name->toString();
272+
},
273+
$node->implements
274+
);
275+
break;
276+
case $node instanceof Node\Stmt\ClassConst:
277+
$this->constants[] = ClassConstFactory::fromNode($node);
278+
break;
279+
default:
280+
break;
281+
}
282+
}
283+
144284
private function classGenerator(): ClassGenerator
145285
{
146286
$flags = 0;

tests/Factory/ClassFactoryTest.php

+41-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace OpenCodeModelingTest\CodeAst\Factory;
66

7+
use OpenCodeModeling\CodeAst\Factory\ClassConstFactory;
78
use OpenCodeModeling\CodeAst\Factory\ClassFactory;
89
use PhpParser\NodeTraverser;
910
use PhpParser\Parser;
@@ -41,7 +42,8 @@ public function it_generates_class_for_empty_file(): void
4142
->setFinal(true)
4243
->setExtends('BaseClass')
4344
->setNamespaceUse('Foo\\Bar')
44-
->setImplements('\\Iterator', 'Bar');
45+
->setImplements('\\Iterator', 'Bar')
46+
->setConstants(ClassConstFactory::fromScratch('PRIV', 'private')->setPrivate());
4547

4648
$nodeTraverser = new NodeTraverser();
4749
$classFactory->injectVisitors($nodeTraverser);
@@ -55,9 +57,47 @@ public function it_generates_class_for_empty_file(): void
5557
use Foo\Bar;
5658
final class TestClass extends BaseClass implements \Iterator, Bar
5759
{
60+
private const PRIV = 'private';
5861
}
5962
EOF;
6063

6164
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($ast)));
6265
}
66+
67+
/**
68+
* @test
69+
*/
70+
public function it_generates_class_for_empty_file_from_template(): void
71+
{
72+
$expected = <<<'EOF'
73+
<?php
74+
75+
declare (strict_types=1);
76+
namespace My\Awesome\Service;
77+
78+
use Foo\Bar;
79+
final class TestClass extends BaseClass implements \Iterator, Bar
80+
{
81+
const FIRST = 1;
82+
private const PRIV = 'private';
83+
protected const PROT = 'protected';
84+
public const PUB = 'public';
85+
}
86+
EOF;
87+
88+
$ast = $this->parser->parse($expected);
89+
90+
$classFactory = ClassFactory::fromNodes(...$ast);
91+
92+
$this->assertSame('TestClass', $classFactory->getName());
93+
$this->assertSame('BaseClass', $classFactory->getExtends());
94+
$this->assertTrue($classFactory->isFinal());
95+
$this->assertTrue($classFactory->isStrict());
96+
$this->assertFalse($classFactory->isAbstract());
97+
98+
$nodeTraverser = new NodeTraverser();
99+
$classFactory->injectVisitors($nodeTraverser);
100+
101+
$this->assertSame($expected, $this->printer->prettyPrintFile($nodeTraverser->traverse($this->parser->parse(''))));
102+
}
63103
}

0 commit comments

Comments
 (0)