riverwaysoft/php-converter

PHP DTO converter to TypeScript / Dart

0.6.3 2022-03-28 06:51 UTC

This package is auto-updated.

Last update: 2022-05-13 10:22:04 UTC


README

Generates TypeScript & Dart out of your PHP DTO classes.

Why?

Statically typed languages like TypeScript or Dart are great because they allow catching bugs without even running your code. But unless you have well-defined contracts between API and consumer apps, you have to always fix outdated typings when the API changes. This library generates types for you so you can move faster and encounter fewer bugs.

Quick start

  1. Installation
composer require riverwaysoft/php-converter --dev
  1. Mark a few classes with #[Dto] annotation to convert them into TypeScript or Dart
use Riverwaysoft\DtoConverter\ClassFilter\Dto;

#[Dto]
class User
{
    public string $id;
    public int $age;
    public ?User $bestFriend;
    /** @var User[] */
    public array $friends;
}
  1. Run CLI command to generate TypeScript
vendor/bin/dto-converter generate --from=/path/to/project/src --to=.

or

vendor/bin/dto-converter generate --from=git@remote/project.git --branch=branch_name --to=.

You'll get file generated.ts with the following contents:

type User = {
  id: string;
  age: number;
  bestFriend: User | null;
  friends: User[];
}

Features

  • Union types / Nullable types / Enums / Array types with PHP DocBlock support e.g. User[]
  • Nested DTO / Recursive DTO
  • Custom type resolvers (e.g. DateTimeImmutable)
  • Generate a single output file or multiple files (entity per class)
  • Custom class filters
  • Generate files from local git repository or remote

Error list

Here is a list of errors dto-converter can throw and description what to do if you encounter these errors:

1. Property z of class X has no type. Please add PHP type

It means that you've forgotten to add type for property a of class Y. Example:

#[Dto]
class X {
  public $z;
} 

At the moment there is no strict / loose mode in dto-converter. It is always strict. If you don't know the PHP type just use mixed type to explicitly convert it to any/Object. It could silently convert such types to TypeScript any or Dart Object if we needed it. But we prefer an explicit approach. Feel free to raise an issue if having loose mode makes sense for you.

2. PHP Type X is not supported

It means dto-converter doesn't know how to convert the type X into TypeScript or Dart. If you are using #[Dto] attribute you probably forgot to add it to class X. Example:

#[Dto]
class A {
  public X $x;
}

class X {
  public int $foo;
}

Customize

If you'd like to customize dto-convert you need to copy the generator script to your project folder:

cp vendor/bin/dto-converter bin/dto-convert

Now you can start customizing the dto-converter by editing the executable file.

How to customize generated output?

By default dto-converter writes all the types into one file. You can configure it to put each type / class in a separate file with all the required imports. Here is an example how to achieve it:

+ $fileNameGenerator = new KebabCaseFileNameGenerator('.ts');

$application->add(
    new ConvertCommand(
        new Converter(Normalizer::factory(
            new PhpAttributeFilter('Dto'),
        )),
        new TypeScriptGenerator(
-            new SingleFileOutputWriter('generated.ts'),
+            new EntityPerClassOutputWriter(
+                $fileNameGenerator,
+                new TypeScriptImportGenerator(
+                    $fileNameGenerator,
+                    new DtoTypeDependencyCalculator()
+                )
+            ),
            [
                new DateTimeTypeResolver(),
                new ClassNameTypeResolver(),
            ],
        ),
        new Filesystem(),
        new OutputDiffCalculator(),
        new FileSystemCodeProvider('/\.php$/'),
        new RemoteRepoCodeProvider(
            new FileSystemCodeProvider('/\.php$/'),
            new Filesystem(),
        ),
    )
);

Feel free to create your own OutputWriter.

How to customize class filtering?

Suppose you don't want to mark each DTO individually with #[Dto] but want to convert all the files ending with "Dto" automatically:

$application->add(
    new ConvertCommand(
-       new Converter(Normalizer::factory(
-           new PhpAttributeFilter('Dto'),
-       )),
+       new Converter(Normalizer::factory()),
        new TypeScriptGenerator(
            new SingleFileOutputWriter('generated.ts'),
            [
                new DateTimeTypeResolver(),
                new ClassNameTypeResolver(),
            ],
        ),
        new Filesystem(),
        new OutputDiffCalculator(),
-       new FileSystemCodeProvider('/\.php$/'),
+       new FileSystemCodeProvider('/Dto\.php$/'),
        new RemoteRepoCodeProvider(
            new FileSystemCodeProvider('/\.php$/'),
            new Filesystem(),
        ),
    )
);

You can even go further and use NegationFilter to exclude specific files as shown in unit tests.

How to write custom type resolvers?

dto-converter takes care of converting basic PHP types like number, string and so on. But what if you have a type that isn't a DTO? For example \DateTimeImmutable. You can write a class that implements UnknownTypeResolverInterface. There is also a shortcut to achieve it - use InlineTypeResolver:

$application->add(
    new ConvertCommand(
       new Converter(Normalizer::factory(
           new PhpAttributeFilter('Dto'),
       )),
        new TypeScriptGenerator(
            new SingleFileOutputWriter('generated.ts'),
            [
                new DateTimeTypeResolver(),
                new ClassNameTypeResolver(),
+               new InlineTypeResolver([
+                 // Convert libphonenumber object to a string
+                 'PhoneNumber' => 'string', 
+                 // Convert PHP Money object to a custom TypeScript type
+                 'Money' => '{ amount: number; currency: string }',
+                 // Convert Doctrine Embeddable to an existing Dto marked as #[Dto]
+                 'SomeDoctrineEmbeddable' => 'SomeDoctrineEmbeddableDto',
+               ])
            ],
        ),
        new Filesystem(),
        new OutputDiffCalculator(),
        new FileSystemCodeProvider('/\.php$/'),
        new RemoteRepoCodeProvider(
            new FileSystemCodeProvider('/\.php$/'),
            new Filesystem(),
        ),
    )
);

How to add support for other languages?

To write a custom converter you can implement LanguageGeneratorInterface. Here is an example how to do it for Go language: GoGeneratorSimple. Check how to use it here. It covers only basic scenarios to get you an idea, so feel free to modify it to your needs.

Testing

composer test

How it is different from alternatives?

  • Unlike spatie/typescript-transformer dto-converter supports not only TypeScript but also Dart. Support for other languages can be easily added by implementing LanguageInterface. dto-converter can also output generated types / classes into different files.
  • Unlike grpc dto-converter doesn't require to modify your app or install some extensions.

Contributing

Please see CONTRIBUTING for details.