Skip to content

Commit

Permalink
Dto rework (#181)
Browse files Browse the repository at this point in the history
* Simplified code

* Removed marker interface

* Reworked response codes

* Fixed code style

* Change CS php version

* Fixed statical errors

* Removed forced cache clearing

* Refresh command removed, generate command now removes unused files

* Updated README.md

* Improved handling of empty responses

* Added skipHttpCodes option

* Renamed object type to object schema

* Simplified DTO structure and unified request handler interface generation

* Added components support

* Fixed codestyle

* Fixed namings

* Fixed  `Variable "_" is not in valid camel caps format` error

* fixed phpstan errors

* Fixed DocblockTypeContradiction error

* fixed UnnecessaryVarAnnotation

* fixed generation and functional tests

* generation test. Fixed error

* Functional test. Fixed error

* Fixed ObjectType and ResponseDto errors

* fixed RequestDtoDefinitionTest

* fixed unit tests

* fixed code styles errors

* fixed mutation tests

* codestyle fix

* fixed file paths on windows os

* returned var annotations

* refactoring

---------

Co-authored-by: Dimannn <dk@csgo.com>
Co-authored-by: Marat Salakhov <salakhov.marat@gmail.com>
  • Loading branch information
3 people authored May 4, 2023
1 parent 8df21f3 commit 1aec01f
Show file tree
Hide file tree
Showing 94 changed files with 1,770 additions and 1,679 deletions.
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,19 @@ You can configure the bundle by adding the following parameters to your `/config

```yaml
open_api_server:
root_name_space: App\Generated # Namespace for DTOs and Api Interfaces
#root_name_space: App\Generated # Namespace for DTOs and Api Interfaces
## The bundle will try to derive the paths for the generated files from the namespace. If you do not want them to be
## stored in \App namespace or if you \App namespace is not in %kernel.project_dir%/src/, then you
## can specify this path manually:
root_path: %kernel.project_dir%/src/Generated
language_level: 7.4.0 # minimum PHP version the generated code should be compatible with
generated_dir_permissions: 0755 # permissions for the generated directories
full_doc_blocks: false # whether to generate DocBlocks for typed variables and params
send_nulls: false # return null values in responses if property is nullable and not required
#root_path: %kernel.project_dir%/src/Generated
#language_level: 8.0.0 # minimum PHP version the generated code should be compatible with
#generated_dir_permissions: 0755 # permissions for the generated directories
#full_doc_blocks: false # whether to generate DocBlocks for typed variables and params
#send_nulls: false # return null values in responses if property is nullable and not required
#skip_http_codes: [] # List of response codes ignored while parsing specification.
## Can be any open api response code ( like 500, "5XX", "default"), or
## "5**", which will include both numeric (500) and XX ("5XX") codes.
## Might be useful if you want to generate error responses in event listener.
specs:
petstore:
path: '../spec/petstore.yaml' # path to OpenApi specification
Expand Down Expand Up @@ -87,17 +91,16 @@ Currently, there are also the following limitations:

## Generating the API Server code

There are three console commands that work with the generated API server code:
There are two console commands that work with the generated API server code:

- Generate the server code: `php bin/console open-api:generate`
- Refresh the server code: `php bin/console open-api:refresh`
- Delete the server code: `php bin/console open-api:delete`

Most of the time you should use the `refresh` command.
Most of the time you should use the `generate` command.
It will clear the bundle cache, delete the old generated server code if it exists and generate the new code.

Be careful with the refresh and delete commands, they will delete the entire contents of the directory you have specified
in `root_name_space` in the `/config/packages/open_api_server.yaml` file. That directory should contain no files except
Be careful with the generate and delete commands, they will delete the entire contents of the directory you have specified
in `root_path` in the `/config/packages/open_api_server.yaml` file. That directory should contain no files except
the code generated by this bundle, as it will be deleted every time you generate the API server code.

For each operation described in the specification, a API call handler interface will be generated that you should implement
Expand Down
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
"symfony/dependency-injection": "^6.0",
"symfony/event-dispatcher": "^6.0",
"symfony/http-kernel": "^6.0",
"symfony/process": "^6.0",
"symfony/psr-http-message-bridge": "^2.1",
"symfony/routing": "^6.0",
"symfony/yaml": "^6.0",
Expand Down Expand Up @@ -86,8 +85,8 @@
}
},
"scripts": {
"cs": "phpcs --config-set php_version 7040 && phpcs",
"csfix": "phpcs --config-set php_version 7040 && phpcbf",
"cs": "phpcs --config-set php_version 8000 && phpcs",
"csfix": "phpcs --config-set php_version 8000 && phpcbf",
"psalm": "psalm",
"stan": "phpstan analyse --memory-limit=-1 --xdebug",
"tests": "phpunit --fail-on-warning",
Expand Down
12 changes: 3 additions & 9 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ parameters:
level: 8
paths:
- src
- test
# - test
checkMissingIterableValueType: false
treatPhpDocTypesAsCertain: false
excludePaths:
Expand All @@ -17,18 +17,12 @@ parameters:
message: '#Variable static method call on class-string<OnMoon\\OpenApiServerBundle\\Interfaces\\Dto>.#'
paths:
- %currentWorkingDirectory%/src/Serializer/ArrayDtoSerializer.php
-
message: '#Call to function is_array\(\) with array<string> will always evaluate to true.#'
paths:
- %currentWorkingDirectory%/src/Specification/SpecificationParser.php

-
message: '#Variable static method call on OnMoon\\OpenApiServerBundle\\Types\\TypeSerializer.#'
paths:
- %currentWorkingDirectory%/src/Types/ScalarTypesResolver.php
-
message: '#Instanceof between cebe\\openapi\\spec\\MediaType and cebe\\openapi\\spec\\MediaType will always evaluate to true\.#'
paths:
- %currentWorkingDirectory%/src/Specification/SpecificationParser.php

-
message: '#OnMoon\\OpenApiServerBundle\\Router\\RouteLoader::__construct\(\) does not call parent constructor from Symfony\\Component\\Config\\Loader\\Loader\.#'
paths:
Expand Down
14 changes: 9 additions & 5 deletions src/CodeGenerator/ApiServerCodeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,31 @@
use OnMoon\OpenApiServerBundle\Event\CodeGenerator\FilesReadyEvent;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;

use const DIRECTORY_SEPARATOR;

class ApiServerCodeGenerator
{
private GraphGenerator $graphGenerator;
private NameGenerator $nameGenerator;
private InterfaceGenerator $interfaceGenerator;
private FileGenerator $filesGenerator;
private AttributeGenerator $attributeGenerator;
private FileWriter $writer;
private EventDispatcherInterface $eventDispatcher;

public function __construct(GraphGenerator $graphGenerator, NameGenerator $nameGenerator, InterfaceGenerator $interfaceGenerator, FileGenerator $filesGenerator, AttributeGenerator $attributeGenerator, FileWriter $writer, EventDispatcherInterface $eventDispatcher)
public function __construct(GraphGenerator $graphGenerator, NameGenerator $nameGenerator, FileGenerator $filesGenerator, AttributeGenerator $attributeGenerator, FileWriter $writer, EventDispatcherInterface $eventDispatcher)
{
$this->graphGenerator = $graphGenerator;
$this->nameGenerator = $nameGenerator;
$this->interfaceGenerator = $interfaceGenerator;
$this->filesGenerator = $filesGenerator;
$this->attributeGenerator = $attributeGenerator;
$this->writer = $writer;
$this->eventDispatcher = $eventDispatcher;
}

public function generate(): void
/** @return string[] */
public function generate(): array
{
$graph = $this->graphGenerator->generateClassGraph();
$this->interfaceGenerator->setAllInterfaces($graph);
$this->attributeGenerator->setAllAttributes($graph);
$this->nameGenerator->setAllNamesAndPaths($graph);

Expand All @@ -42,9 +42,13 @@ public function generate(): void
$files = $this->filesGenerator->generateAllFiles($graph);

$this->eventDispatcher->dispatch(new FilesReadyEvent($files));
$writtenFiles = [];

foreach ($files as $item) {
$this->writer->write($item->getClass()->getFilePath(), $item->getClass()->getFileName(), $item->getFileContents());
$writtenFiles[] = $item->getClass()->getFilePath() . DIRECTORY_SEPARATOR . $item->getClass()->getFileName();
}

return $writtenFiles;
}
}
66 changes: 40 additions & 26 deletions src/CodeGenerator/AttributeGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,63 +5,77 @@
namespace OnMoon\OpenApiServerBundle\CodeGenerator;

use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoDefinition;
use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\DtoReference;
use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\GraphDefinition;
use OnMoon\OpenApiServerBundle\CodeGenerator\Definitions\PropertyDefinition;
use OnMoon\OpenApiServerBundle\Specification\Definitions\Property;

class AttributeGenerator
{
public function setAllAttributes(GraphDefinition $graph): void
{
foreach ($graph->getSpecifications() as $specificationDefinition) {
foreach ($specificationDefinition->getComponents() as $component) {
$this->componentsPass($component->getDto());
}

foreach ($specificationDefinition->getOperations() as $operation) {
$request = $operation->getRequest();
if ($request !== null) {
$this->requestPass($request);
}
$this->requestPass($operation->getRequest());

foreach ($operation->getResponses() as $response) {
$this->responsePass($response);
$this->responsePass($response->getResponseBody());
}
}
}
}

public function requestPass(DtoDefinition $root): void
public function componentsPass(?DtoReference $root): void
{
foreach ($root->getProperties() as $property) {
$specProperty = $property->getSpecProperty();
$willExist = $specProperty->isRequired() || $specProperty->getDefaultValue() !== null;
$this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void {
$needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null;
$property
->setHasGetter(true)
->setHasSetter(! $needValue)
->setNullable(! $needValue || $specProperty->isNullable())
->setInConstructor($needValue);
});
}

public function requestPass(?DtoReference $root): void
{
$this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void {
$willExist = $specProperty->isRequired() || $specProperty->getDefaultValue() !== null;
$property
->setHasGetter(true)
->setHasSetter(false)
->setNullable(! $willExist || $specProperty->isNullable())
->setInConstructor(false);

$object = $property->getObjectTypeDefinition();
if ($object === null) {
continue;
}

$this->requestPass($object);
}
});
}

public function responsePass(DtoDefinition $root): void
public function responsePass(?DtoReference $root): void
{
foreach ($root->getProperties() as $property) {
$specProperty = $property->getSpecProperty();
$needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null;
$this->treeWalk($root, static function (Property $specProperty, PropertyDefinition $property): void {
$needValue = $specProperty->isRequired() && $specProperty->getDefaultValue() === null;
$property
->setHasGetter(true)
->setHasSetter(! $needValue)
->setNullable(! $needValue || $specProperty->isNullable())
->setInConstructor($needValue);
});
}

$object = $property->getObjectTypeDefinition();
if ($object === null) {
continue;
}
/** @param callable(Property, PropertyDefinition): void $action */
private function treeWalk(?DtoReference $root, callable $action): void
{
if (! $root instanceof DtoDefinition) {
return;
}

$this->responsePass($object);
foreach ($root->getProperties() as $property) {
$specProperty = $property->getSpecProperty();
$action($specProperty, $property);
$this->treeWalk($property->getObjectTypeDefinition(), $action);
}
}
}
2 changes: 1 addition & 1 deletion src/CodeGenerator/Definitions/ClassDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use function Safe\substr;
use function strrpos;

class ClassDefinition
class ClassDefinition implements ClassReference
{
private string $className;
private string $namespace;
Expand Down
14 changes: 14 additions & 0 deletions src/CodeGenerator/Definitions/ClassReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions;

interface ClassReference
{
public function getClassName(): string;

public function getNamespace(): string;

public function getFQCN(): string;
}
31 changes: 31 additions & 0 deletions src/CodeGenerator/Definitions/ComponentDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions;

class ComponentDefinition
{
private DtoDefinition $dto;

public function __construct(private string $name)
{
}

public function getName(): string
{
return $this->name;
}

public function getDto(): DtoDefinition
{
return $this->dto;
}

public function setDto(DtoDefinition $dto): self
{
$this->dto = $dto;

return $this;
}
}
32 changes: 32 additions & 0 deletions src/CodeGenerator/Definitions/ComponentReference.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

declare(strict_types=1);

namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions;

class ComponentReference implements DtoReference
{
public function __construct(private ComponentDefinition $referencedComponent)
{
}

public function getClassName(): string
{
return $this->referencedComponent->getDto()->getClassName();
}

public function getNamespace(): string
{
return $this->referencedComponent->getDto()->getNamespace();
}

public function getFQCN(): string
{
return $this->referencedComponent->getDto()->getFQCN();
}

public function isEmpty(): bool
{
return $this->referencedComponent->getDto()->isEmpty();
}
}
14 changes: 7 additions & 7 deletions src/CodeGenerator/Definitions/DtoDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,22 @@

namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions;

use OnMoon\OpenApiServerBundle\Interfaces\Dto;

use function count;

class DtoDefinition extends GeneratedClassDefinition
class DtoDefinition extends GeneratedClassDefinition implements DtoReference
{
/** @var PropertyDefinition[] $properties; */
private array $properties;
private ?ClassDefinition $implements = null;
private ?ClassReference $implements;

/**
* @param PropertyDefinition[] $properties
*/
public function __construct(array $properties)
{
$this->implements = ClassDefinition::fromFQCN(Dto::class);
$this->properties = $properties;
}

Expand All @@ -33,15 +36,12 @@ final public function getProperties(): array
return $this->properties;
}

final public function getImplements(): ?ClassDefinition
final public function getImplements(): ?ClassReference
{
return $this->implements;
}

/**
* @return DtoDefinition
*/
final public function setImplements(?ClassDefinition $implements): self
final public function setImplements(?ClassReference $implements): self
{
$this->implements = $implements;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace OnMoon\OpenApiServerBundle\CodeGenerator\Definitions;

final class RequestBodyDtoDefinition extends DtoDefinition
interface DtoReference extends ClassReference
{
public function isEmpty(): bool;
}
Loading

0 comments on commit 1aec01f

Please sign in to comment.