Skip to content

Commit 3afb35d

Browse files
authored
Revamp Symfony generator (Close #97) (#99)
1 parent 4620b86 commit 3afb35d

30 files changed

+309
-259
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ composer require riverwaysoft/php-converter --dev
2222
If the installation causes dependency conflicts use the [standalone Phar version](#phar-installation) of the package.
2323

2424
2) Mark a few classes with `#[Dto]` annotation to convert them into TypeScript or Dart
25+
2526
```php
26-
use Riverwaysoft\PhpConverter\ClassFilter\Dto;
27+
use Riverwaysoft\PhpConverter\Filter\Attributes\Dto;
2728

2829
#[Dto]
2930
class UserOutput

bin/default-config.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
declare(strict_types=1);
44

55
use Riverwaysoft\PhpConverter\Ast\DtoVisitor;
6-
use Riverwaysoft\PhpConverter\ClassFilter\PhpAttributeFilter;
76
use Riverwaysoft\PhpConverter\CodeProvider\FileSystemCodeProvider;
87
use Riverwaysoft\PhpConverter\Config\PhpConverterConfig;
8+
use Riverwaysoft\PhpConverter\Filter\PhpAttributeFilter;
99
use Riverwaysoft\PhpConverter\OutputGenerator\TypeScript\TypeScriptOutputGenerator;
1010
use Riverwaysoft\PhpConverter\OutputGenerator\UnknownTypeResolver\ClassNameTypeResolver;
1111
use Riverwaysoft\PhpConverter\OutputGenerator\UnknownTypeResolver\DateTimeTypeResolver;

src/Ast/ConverterVisitor.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88

99
abstract class ConverterVisitor extends NodeVisitorAbstract
1010
{
11+
/** @phpstan-impure */
1112
abstract public function popResult(): ConverterResult;
1213
}

src/Ast/DtoVisitor.php

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

55
namespace Riverwaysoft\PhpConverter\Ast;
66

7+
use Exception;
78
use PhpParser\Node;
89
use PhpParser\Node\Stmt\Class_;
910
use PhpParser\Node\Stmt\Enum_;
1011
use PhpParser\NodeTraverser;
11-
use Riverwaysoft\PhpConverter\ClassFilter\ClassFilterInterface;
1212
use Riverwaysoft\PhpConverter\Dto\DtoClassProperty;
1313
use Riverwaysoft\PhpConverter\Dto\DtoEnumProperty;
1414
use Riverwaysoft\PhpConverter\Dto\DtoType;
1515
use Riverwaysoft\PhpConverter\Dto\ExpressionType;
1616
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpTypeFactory;
1717
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpTypeInterface;
1818
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpUnionType;
19-
use Exception;
2019
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpUnknownType;
21-
use function sprintf;
22-
use function get_class;
20+
use Riverwaysoft\PhpConverter\Filter\FilterInterface;
2321
use function array_map;
22+
use function get_class;
23+
use function sprintf;
2424

2525
class DtoVisitor extends ConverterVisitor
2626
{
@@ -29,7 +29,7 @@ class DtoVisitor extends ConverterVisitor
2929
private ConverterResult $converterResult;
3030

3131
public function __construct(
32-
private ?ClassFilterInterface $classFilter = null
32+
private ?FilterInterface $filter = null
3333
) {
3434
$this->phpDocTypeParser = new PhpDocTypeParser();
3535
$this->converterResult = new ConverterResult();
@@ -40,7 +40,7 @@ public function enterNode(Node $node)
4040
if (!$node instanceof Class_ && !$node instanceof Enum_) {
4141
return null;
4242
}
43-
if ($this->classFilter && !$this->classFilter->isMatch($node)) {
43+
if ($this->filter && !$this->filter->isMatch($node)) {
4444
return null;
4545
}
4646

src/Ast/PhpDocTypeParser.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ private function convertToDto(TypeNode $node): PhpTypeInterface|null
145145
return new PhpUnionType(array_map(fn (TypeNode $child) => $this->convertToDto($child), $node->types));
146146
}
147147
if ($node instanceof GenericTypeNode) {
148-
return PhpTypeFactory::create($node->type->name, [], array_map(
148+
return PhpTypeFactory::create($node->type->name, array_map(
149149
fn (TypeNode $child) => $this->convertToDto($child),
150150
$node->genericTypes,
151151
));

src/Ast/PhpDocTypeParserTest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -173,11 +173,11 @@ public static function getDataVarAndReturn(): array
173173
'/**
174174
* @return JsonResponse<PaginatedResponse<User>>
175175
*/',
176-
new PhpUnknownType('JsonResponse', [], [
177-
new PhpUnknownType('PaginatedResponse', [], [
176+
new PhpUnknownType('JsonResponse', [
177+
new PhpUnknownType('PaginatedResponse', [
178178
new PhpUnknownType('User'),
179-
]),
180-
]),
179+
], []),
180+
], []),
181181
],
182182
];
183183
}

src/Bridge/ApiPlatform/ApiPlatformDtoResourceVisitor.php

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Riverwaysoft\PhpConverter\Bridge\ApiPlatform;
66

7+
use Exception;
78
use Jawira\CaseConverter\Convert;
89
use PhpParser\Node;
910
use PhpParser\Node\Attribute;
@@ -14,22 +15,22 @@
1415
use Riverwaysoft\PhpConverter\Ast\ConverterResult;
1516
use Riverwaysoft\PhpConverter\Ast\ConverterVisitor;
1617
use Riverwaysoft\PhpConverter\Bridge\Symfony\SymfonyRoutingParser;
17-
use Riverwaysoft\PhpConverter\ClassFilter\ClassFilterInterface;
1818
use Riverwaysoft\PhpConverter\Dto\ApiClient\ApiEndpoint;
1919
use Riverwaysoft\PhpConverter\Dto\ApiClient\ApiEndpointMethod;
2020
use Riverwaysoft\PhpConverter\Dto\ApiClient\ApiEndpointParam;
2121
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpBaseType;
2222
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpOptionalType;
2323
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpTypeFactory;
24-
use Exception;
25-
use function sprintf;
24+
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpUnknownType;
25+
use Riverwaysoft\PhpConverter\Filter\FilterInterface;
2626
use function array_key_last;
27-
use function str_replace;
28-
use function count;
2927
use function array_map;
30-
use function rtrim;
31-
use function ltrim;
28+
use function count;
3229
use function in_array;
30+
use function ltrim;
31+
use function rtrim;
32+
use function sprintf;
33+
use function str_replace;
3334

3435
class ApiPlatformDtoResourceVisitor extends ConverterVisitor
3536
{
@@ -41,11 +42,10 @@ class ApiPlatformDtoResourceVisitor extends ConverterVisitor
4142

4243
public const API_PLATFORM_ATTRIBUTE = 'ApiResource';
4344

44-
// Is used to wrap output types in CollectionResponse<T>
45-
public const COLLECTION_RESPONSE_CONTEXT_KEY = 'isCollectionResponse';
45+
public const RESPONSE_TYPE_NAME = 'CollectionResponse';
4646

4747
public function __construct(
48-
private ?ClassFilterInterface $classFilter = null
48+
private ?FilterInterface $filter = null
4949
) {
5050
$this->converterResult = new ConverterResult();
5151
$this->iriGenerator = new ApiPlatformIriGenerator();
@@ -58,7 +58,7 @@ public function enterNode(Node $node)
5858
return null;
5959
}
6060

61-
if ($this->classFilter && !$this->classFilter->isMatch($node)) {
61+
if ($this->filter && !$this->filter->isMatch($node)) {
6262
return null;
6363
}
6464

@@ -189,10 +189,15 @@ private function createApiEndpoint(
189189
if (!$output) {
190190
throw new Exception(sprintf("The output is required for ApiResource %s. Context: %s", $node->name->name, $this->prettyPrinter->prettyPrint([$apiResourceAttribute])));
191191
}
192-
$outputTypeContext = $isCollection ? [
193-
self::COLLECTION_RESPONSE_CONTEXT_KEY => true,
194-
] : [];
195-
$outputType = PhpTypeFactory::create($output, $outputTypeContext);
192+
193+
$outputType = PhpTypeFactory::create($output);
194+
if ($isCollection) {
195+
$outputType = new PhpUnknownType(self::RESPONSE_TYPE_NAME, [
196+
$outputType,
197+
], [
198+
PhpUnknownType::GENERIC_IGNORE_NO_RESOLVER => true,
199+
]);
200+
}
196201

197202
$queryParams = [];
198203
$routeParams = [];
@@ -296,10 +301,15 @@ private function createApiEndpointFromLegacyCode(
296301
if (!$output) {
297302
throw new Exception(sprintf("The output is required for ApiResource %s. Context: %s", $node->name->name, $this->prettyPrinter->prettyPrint([$apiResourceAttribute])));
298303
}
299-
$outputTypeContext = $isCollection && $method->equals(ApiEndpointMethod::get()) ? [
300-
self::COLLECTION_RESPONSE_CONTEXT_KEY => true,
301-
] : [];
302-
$outputType = PhpTypeFactory::create($output, $outputTypeContext);
304+
305+
$outputType = PhpTypeFactory::create($output);
306+
if ($isCollection && $method->equals(ApiEndpointMethod::get())) {
307+
$outputType = new PhpUnknownType(self::RESPONSE_TYPE_NAME, [
308+
$outputType,
309+
], [
310+
PhpUnknownType::GENERIC_IGNORE_NO_RESOLVER => true,
311+
]);
312+
}
303313

304314
$input = null;
305315
if ($arrayItemValue) {

src/Bridge/ApiPlatform/CollectionResponseTypeResolver.php

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/Bridge/Symfony/SymfonyControllerVisitor.php

Lines changed: 18 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Riverwaysoft\PhpConverter\Bridge\Symfony;
66

7+
use Exception;
78
use PhpParser\Node;
89
use PhpParser\Node\Attribute;
910
use PhpParser\Node\Stmt\ClassMethod;
@@ -15,16 +16,15 @@
1516
use Riverwaysoft\PhpConverter\Dto\ApiClient\ApiEndpointMethod;
1617
use Riverwaysoft\PhpConverter\Dto\ApiClient\ApiEndpointParam;
1718
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpBaseType;
18-
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpListType;
1919
use Riverwaysoft\PhpConverter\Dto\PhpType\PhpTypeFactory;
20-
use Exception;
21-
use function in_array;
22-
use function count;
20+
use Riverwaysoft\PhpConverter\Filter\FilterInterface;
2321
use function array_flip;
24-
use function sprintf;
2522
use function array_key_first;
26-
use function implode;
2723
use function array_map;
24+
use function count;
25+
use function implode;
26+
use function in_array;
27+
use function sprintf;
2828

2929
class SymfonyControllerVisitor extends ConverterVisitor
3030
{
@@ -33,16 +33,19 @@ class SymfonyControllerVisitor extends ConverterVisitor
3333
private PhpDocTypeParser $phpDocTypeParser;
3434

3535
public function __construct(
36-
// TODO: consider using class filter interface?
37-
private string $attribute,
36+
private ?FilterInterface $filter,
3837
) {
3938
$this->converterResult = new ConverterResult();
4039
$this->phpDocTypeParser = new PhpDocTypeParser();
4140
}
4241

4342
public function enterNode(Node $node)
4443
{
45-
if (!$node instanceof ClassMethod || !$this->findAttribute($node, $this->attribute)) {
44+
if (!$node instanceof ClassMethod) {
45+
return null;
46+
}
47+
48+
if ($this->filter && !$this->filter->isMatch($node)) {
4649
return null;
4750
}
4851

@@ -53,7 +56,7 @@ public function enterNode(Node $node)
5356

5457
private function findAttribute(ClassMethod|Node\Param $node, string $name): Attribute|null
5558
{
56-
$attrGroups = $node instanceof Node\Param ? $node->attrGroups : $node->getAttrGroups();
59+
$attrGroups = $node->attrGroups;
5760

5861
foreach ($attrGroups as $attrGroup) {
5962
foreach ($attrGroup->attrs as $attr) {
@@ -82,7 +85,7 @@ private function createApiEndpoint(ClassMethod $node): void
8285
$routeAttribute = $this->findAttribute($node, 'Route');
8386

8487
if (!$routeAttribute) {
85-
throw new Exception('#[DtoEndpoint] is used on a method, that does not have #[Route] attribute');
88+
throw new Exception('The method was marked as generated but it does not have #[Route] attribute');
8689
}
8790

8891
$route = null;
@@ -130,28 +133,13 @@ private function createApiEndpoint(ClassMethod $node): void
130133
throw new Exception('#[Route()] argument "methods" is required');
131134
}
132135

133-
$dtoReturnAttribute = $this->findAttribute($node, 'DtoEndpoint');
134-
if (!$dtoReturnAttribute) {
135-
throw new Exception('Should not be reached, checked earlier');
136-
}
137-
138136
$outputType = PhpBaseType::null();
139137
$methodComment = $node->getDocComment()?->getText();
140138

141-
if ($methodComment && $returnType = $this->phpDocTypeParser->parseVarOrReturn($methodComment)) {
142-
$outputType = $returnType;
143-
} else {
144-
if ($arg = $this->getAttributeArgumentByName($dtoReturnAttribute, 'returnOne')) {
145-
if (!($arg->value instanceof Node\Expr\ClassConstFetch)) {
146-
throw new Exception('Argument of returnOne should be a class string');
147-
}
148-
$outputType = PhpTypeFactory::create($arg->value->class->getParts()[0]);
149-
}
150-
if ($arg = $this->getAttributeArgumentByName($dtoReturnAttribute, 'returnMany')) {
151-
if (!($arg->value instanceof Node\Expr\ClassConstFetch)) {
152-
throw new Exception('Argument of returnMany should be a class string');
153-
}
154-
$outputType = new PhpListType(PhpTypeFactory::create($arg->value->class->getParts()[0]));
139+
if ($methodComment) {
140+
$returnType = $this->phpDocTypeParser->parseVarOrReturn($methodComment);
141+
if ($returnType) {
142+
$outputType = $returnType;
155143
}
156144
}
157145

src/ClassFilter/AndFilter.php

Lines changed: 0 additions & 27 deletions
This file was deleted.

0 commit comments

Comments
 (0)