Skip to content

Commit 3b2357b

Browse files
authored
Support API Platform's #[ApiResource] attribute (#55)
* Introduce ConverterVisitor to improve modularity * Adapt tests * Support collectionOperations * phpstan * Support itemOperations
1 parent 21bbbb4 commit 3b2357b

23 files changed

+589
-102
lines changed

README.md

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ By default `dto-converter` writes all the types into one file. You can configure
7676

7777
$application->add(
7878
new ConvertCommand(
79-
new Converter(new PhpAttributeFilter('Dto')),
79+
new Converter([
80+
new DtoVisitor(new PhpAttributeFilter('Dto')),
81+
]),
8082
new TypeScriptGenerator(
8183
- new SingleFileOutputWriter('generated.ts'),
8284
+ new EntityPerClassOutputWriter(
@@ -106,8 +108,10 @@ Suppose you don't want to mark each DTO individually with `#[Dto]` but want to c
106108
```diff
107109
$application->add(
108110
new ConvertCommand(
109-
- new Converter(new PhpAttributeFilter('Dto')),
110-
+ new Converter(),
111+
- new Converter([
112+
- new DtoVisitor(new PhpAttributeFilter('Dto')),
113+
- ]),
114+
+ new Converter([new DtoVisitor()]),
111115
new TypeScriptGenerator(
112116
new SingleFileOutputWriter('generated.ts'),
113117
[
@@ -133,7 +137,9 @@ You can even go further and use `NegationFilter` to exclude specific files as sh
133137

134138
$application->add(
135139
new ConvertCommand(
136-
new Converter(new PhpAttributeFilter('Dto')),
140+
new Converter([
141+
new DtoVisitor(new PhpAttributeFilter('Dto')),
142+
]),
137143
new TypeScriptGenerator(
138144
new SingleFileOutputWriter('generated.ts'),
139145
[
@@ -160,12 +166,14 @@ $application->add(
160166

161167
### How to customize generated output file?
162168

163-
You may want to apply some transformations on the resulted file with types. For example you may want to format it with tool of your choice or prepend code with a warning like "// The file was autogenerated, don't edit it manually". To add such a warning you can already use the built-in extension:
169+
You may want to apply some transformations on the resulted file with types. For example, you may want to format it with tool of your choice or prepend code with a warning like "// The file was autogenerated, don't edit it manually". To add such a warning you can already use the built-in extension:
164170

165171
```diff
166172
$application->add(
167173
new ConvertCommand(
168-
new Converter(new PhpAttributeFilter('Dto')),
174+
new Converter([
175+
new DtoVisitor(new PhpAttributeFilter('Dto')),
176+
]),
169177
new TypeScriptGenerator(
170178
new SingleFileOutputWriter('generated.ts'),
171179
[
@@ -220,7 +228,9 @@ Then add it to the list:
220228
```diff
221229
$application->add(
222230
new ConvertCommand(
223-
new Converter(new PhpAttributeFilter('Dto')),
231+
new Converter([
232+
new DtoVisitor(new PhpAttributeFilter('Dto')),
233+
]),
224234
new TypeScriptGenerator(
225235
new SingleFileOutputWriter('generated.ts'),
226236
[

bin/dto-converter-ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ declare(strict_types=1);
66
require_once __DIR__ . '/../vendor/autoload.php';
77

88
use Riverwaysoft\DtoConverter\Ast\Converter;
9+
use Riverwaysoft\DtoConverter\Ast\DtoVisitor;
10+
use Riverwaysoft\DtoConverter\Bridge\Symfony\SymfonyControllerVisitor;
911
use Riverwaysoft\DtoConverter\ClassFilter\PhpAttributeFilter;
1012
use Riverwaysoft\DtoConverter\Cli\ConvertCommand;
1113
use Riverwaysoft\DtoConverter\CodeProvider\FileSystemCodeProvider;
@@ -21,9 +23,10 @@ $application = new Application();
2123

2224
$application->add(
2325
new ConvertCommand(
24-
new Converter(
25-
new PhpAttributeFilter('Dto'),
26-
),
26+
new Converter([
27+
new DtoVisitor(new PhpAttributeFilter('Dto')),
28+
new SymfonyControllerVisitor('DtoEndpoint'),
29+
]),
2730
new TypeScriptGenerator(
2831
new SingleFileOutputWriter('generated.ts'),
2932
[

src/Ast/Converter.php

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,34 @@
1919
class Converter
2020
{
2121
private Parser $parser;
22-
private PhpDocTypeParser $phpDocTypeParser;
23-
private PhpTypeFactory $phpTypeFactory;
2422

2523
public function __construct(
26-
private ?ClassFilterInterface $dtoClassFilter = null,
24+
/** @var ConverterVisitor[] */
25+
private array $visitors,
2726
) {
28-
$this->phpTypeFactory = new PhpTypeFactory();
2927
$this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
30-
$this->phpDocTypeParser = new PhpDocTypeParser($this->phpTypeFactory);
3128
}
3229

3330
/** @param string[]|iterable $listings */
3431
public function convert(iterable $listings): ConverterResult
3532
{
36-
$dtoList = new DtoList();
37-
$apiEndpointList = new ApiEndpointList();
33+
$converterResult = new ConverterResult();
3834

3935
foreach ($listings as $listing) {
40-
$this->normalize($listing, $dtoList, $apiEndpointList);
41-
}
36+
$ast = $this->parser->parse($listing);
37+
$traverser = new NodeTraverser();
4238

43-
return new ConverterResult(
44-
dtoList: $dtoList,
45-
apiEndpointList: $apiEndpointList,
46-
);
47-
}
39+
foreach ($this->visitors as $visitor) {
40+
$traverser->addVisitor($visitor);
41+
}
4842

49-
private function normalize(string $code, DtoList $dtoList, ApiEndpointList $apiEndpointList): void
50-
{
51-
$ast = $this->parser->parse($code);
52-
$dtoVisitor = new DtoVisitor($dtoList, $this->phpDocTypeParser, $this->phpTypeFactory, $this->dtoClassFilter);
53-
$symfonyControllerVisitor = new SymfonyControllerVisitor('DtoEndpoint', $apiEndpointList, $this->phpTypeFactory);
54-
$traverser = new NodeTraverser();
55-
$traverser->addVisitor($dtoVisitor);
56-
$traverser->addVisitor($symfonyControllerVisitor);
57-
$traverser->traverse($ast);
43+
$traverser->traverse($ast);
44+
45+
foreach ($this->visitors as $visitor) {
46+
$converterResult->merge($visitor->popResult());
47+
}
48+
}
49+
50+
return $converterResult;
5851
}
5952
}

src/Ast/ConverterResult.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,22 @@
99

1010
class ConverterResult
1111
{
12-
public function __construct(
13-
public DtoList $dtoList,
14-
public ApiEndpointList|null $apiEndpointList = null
15-
) {
12+
public DtoList $dtoList;
13+
public ApiEndpointList $apiEndpointList;
14+
15+
public function __construct()
16+
{
17+
$this->dtoList = new DtoList();
18+
$this->apiEndpointList = new ApiEndpointList();
19+
}
20+
21+
public function merge(self $result): void
22+
{
23+
foreach ($result->dtoList->getList() as $anotherDto) {
24+
$this->dtoList->add($anotherDto);
25+
}
26+
foreach ($result->apiEndpointList->getList() as $anotherEndpoint) {
27+
$this->apiEndpointList->add($anotherEndpoint);
28+
}
1629
}
1730
}

src/Ast/ConverterVisitor.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Riverwaysoft\DtoConverter\Ast;
6+
7+
use PhpParser\NodeVisitorAbstract;
8+
9+
abstract class ConverterVisitor extends NodeVisitorAbstract
10+
{
11+
abstract public function popResult(): ConverterResult;
12+
}

src/Ast/DtoVisitor.php

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@
77
use PhpParser\Node;
88
use PhpParser\Node\Stmt\Class_;
99
use PhpParser\Node\Stmt\Enum_;
10-
use PhpParser\NodeVisitorAbstract;
1110
use Riverwaysoft\DtoConverter\ClassFilter\ClassFilterInterface;
1211
use Riverwaysoft\DtoConverter\Dto\DtoClassProperty;
1312
use Riverwaysoft\DtoConverter\Dto\DtoEnumProperty;
14-
use Riverwaysoft\DtoConverter\Dto\DtoList;
1513
use Riverwaysoft\DtoConverter\Dto\DtoType;
1614
use Riverwaysoft\DtoConverter\Dto\ExpressionType;
1715
use Riverwaysoft\DtoConverter\Dto\PhpType\PhpTypeFactory;
1816
use Riverwaysoft\DtoConverter\Dto\PhpType\PhpTypeInterface;
1917
use Riverwaysoft\DtoConverter\Dto\PhpType\PhpUnionType;
2018

21-
class DtoVisitor extends NodeVisitorAbstract
19+
class DtoVisitor extends ConverterVisitor
2220
{
21+
private PhpDocTypeParser $phpDocTypeParser;
22+
private ConverterResult $converterResult;
23+
2324
public function __construct(
24-
private DtoList $dtoList,
25-
private PhpDocTypeParser $phpDocTypeParser,
26-
private PhpTypeFactory $phpTypeFactory,
2725
private ?ClassFilterInterface $classFilter = null
2826
) {
27+
$this->phpDocTypeParser = new PhpDocTypeParser();
28+
$this->converterResult = new ConverterResult();
2929
}
3030

3131
public function leaveNode(Node $node)
@@ -63,7 +63,7 @@ private function createSingleType(
6363
? $param->parts[0]
6464
: $param->name;
6565

66-
return $this->phpTypeFactory->create($typeName);
66+
return PhpTypeFactory::create($typeName);
6767
}
6868

6969
private function createDtoType(Class_|Enum_ $node): void
@@ -137,7 +137,7 @@ private function createDtoType(Class_|Enum_ $node): void
137137
}
138138
}
139139

140-
$this->dtoList->add(new DtoType(
140+
$this->converterResult->dtoList->add(new DtoType(
141141
name: $node->name->name,
142142
expressionType: $expressionType,
143143
properties: $properties,
@@ -158,4 +158,11 @@ public function resolveExpressionType(Class_|Enum_ $node): ExpressionType
158158
? ExpressionType::enumNonStandard()
159159
: ExpressionType::class();
160160
}
161+
162+
public function popResult(): ConverterResult
163+
{
164+
$result = $this->converterResult;
165+
$this->converterResult = new ConverterResult();
166+
return $result;
167+
}
161168
}

src/Ast/PhpDocTypeParser.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class PhpDocTypeParser
2525
private Lexer $lexer;
2626
private PhpDocParser $phpDocParser;
2727

28-
public function __construct(private PhpTypeFactory $phpTypeFactory)
28+
public function __construct()
2929
{
3030
$this->lexer = new Lexer();
3131
$constExprParser = new ConstExprParser();
@@ -62,7 +62,7 @@ public function parse(string $input): PhpTypeInterface|null
6262
private function convertToDto(TypeNode $node): PhpTypeInterface|null
6363
{
6464
if ($node instanceof IdentifierTypeNode) {
65-
return $this->phpTypeFactory->create($node->name);
65+
return PhpTypeFactory::create($node->name);
6666
}
6767
if ($node instanceof ArrayTypeNode) {
6868
return new PhpListType($this->convertToDto($node->type));

src/Ast/PhpDocTypeParserTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class PhpDocTypeParserTest extends TestCase
1717
/** @dataProvider getData */
1818
public function testBasicScenario(string $explanation, string $input, PhpTypeInterface|null $expected): void
1919
{
20-
$parser = new PhpDocTypeParser(new PhpTypeFactory());
20+
$parser = new PhpDocTypeParser();
2121
$result = $parser->parse($input);
2222
$this->assertEquals($result, $expected, sprintf("Assert failed. Data row: '%s'", $explanation));
2323
}

0 commit comments

Comments
 (0)