Convert PHP model classes to TypeScript interfaces
⚠ This project is still in a very early state ⚠
Everything is subject to change. Use at your own risk!
☄️ Bug reports / feature requests »
- 👋 About The Project
- 🚀 Installation
- ⚙ Configuration
- 👀 Usage
- 💻 API
- 🙈 Known Limitations
- 🔨 TODOs / Roadmap
- ❤️ Contributing
- ⭐ License
- 🌐 Acknowledgments
This bundle aims to provide a simple way of working with strongly typed JSON response data.
Given a PHP class like this:
<?php
namespace App\Model\TypeScriptables;
use Brainshaker95\PhpToTsBundle\Attribute\AsTypeScriptable;
/**
* This is a class description
*
* @deprecated use MyOtherClass instead
*/
#[AsTypeScriptable]
final class MyClass extends MyParentClass
{
/**
* @param non-empty-list<array{
* foo: int,
* bar: array{
* baz: Baz,
* },
* }>[] $foo1 This is a promoted constructor property description
* with a new line
* and another one
*/
public function __construct(
public array $foo1,
public readonly Foo&Bar $bar1,
public ?bool $baz1,
) {}
/**
* @var non-empty-string|array<int,string>
*/
public $foo2;
/**
* This is a property description
* with a new line
*
* @deprecated
*/
public readonly iterable $bar2;
/**
* @template T of 'foo'|'bar'|'baz' = 'bar'
*
* @var T|float|null
*/
public string|float|null $baz2;
}
A TypeScript interface like this will be generated:
/**
* Auto-generated by PhpToTsBundle
* Do not modify directly!
*/
import type { Bar } from './bar';
import type { Baz } from './baz';
import type { Foo } from './foo';
import type { MyParentClass } from './my-parent-class';
/**
* This is a class description
*
* @deprecated use MyOtherClass instead
*/
export interface MyClass<
T extends ('foo' | 'bar' | 'baz') = 'bar',
> extends MyParentClass {
readonly bar1: (Foo & Bar);
/**
* This is a property description
* with a new line
*
* @deprecated
*/
readonly bar2: unknown[];
baz1: (boolean | null);
/**
* This is a promoted constructor property description
* with a new line
* and another one
*/
foo1: Array<{
foo: number;
bar: {
baz: Baz;
};
}>[];
baz2: (T | number | null);
foo2: (string | Record<number, string>);
}
Currently this bundle is not available as a composer package. It can still be installed like this:
composer.json
{
// ...
"require": {
// ...
"brainshaker95/php-to-ts-bundle": "dev-master"
},
"repositories": [
// ...
{
"type": "vcs",
"url": "git@github.com:Brainshaker95/php-to-ts-bundle.git"
}
]
}
composer update brainshaker95/php-to-ts-bundle
config/bundles.php
<?php
return [
// ...
Brainshaker95\PhpToTsBundle\PhpToTsBundle::class => ['dev' => true],
];
packages\php_to_ts.yaml
# Default configuration
php_to_ts:
# Directory in which to look for models to include
input_dir: src/Model
# Directory in which to dump TypeScript interfaces
output_dir: assets/ts/types/php-to-ts
# File type to use for TypeScript interfaces
file_type: !php/const Brainshaker95\PhpToTsBundle\Model\Config\FileType::TYPE_MODULE
# Type definition type to use for TypeScript interfaces
type_definition_type: !php/const Brainshaker95\PhpToTsBundle\Model\Config\TypeDefinitionType::TYPE_INTERFACE
# Indentation used for TypeScript interfaces
indent:
# Indent style used for TypeScript interfaces
style: !php/const Brainshaker95\PhpToTsBundle\Model\Config\Indent::STYLE_SPACE
# Number of indent style characters per indent
count: 2
# Quote style used for strings in TypeScript interfaces
quotes: !php/const Brainshaker95\PhpToTsBundle\Model\Config\Quotes::STYLE_SINGLE
# Class names of sort strategies used for TypeScript properties
sort_strategies:
- Brainshaker95\PhpToTsBundle\Model\Config\SortStrategy\AlphabeticalAsc
- Brainshaker95\PhpToTsBundle\Model\Config\SortStrategy\ConstructorFirst
- Brainshaker95\PhpToTsBundle\Model\Config\SortStrategy\ReadonlyFirst
# Class name of file name strategies used for TypeScript files
file_name_strategy: Brainshaker95\PhpToTsBundle\Model\Config\FileNameStrategy\KebabCase
This bundle exposes 3 different commands.
All of them use the default configuration when no options are passed.
Run bin/console <command> -h
for a full list of available options.
Dumps all TypeScriptables in the given directory (input-dir: string
):
bin/console phptots:dump:dir [<input-dir>] [options]
Dumps all TypeScriptables in the given files and directories (input-files: string[]
):
bin/console phptots:dump:files <input-files> [options]
Dumps all TypeScriptables in the given file (input-file: string
):
bin/console phptots:dump:file <input-file> [options]
Each time a TsInterface, TsEnum or TsProperty instance is generated during the dumping process an event is dispatched.
You can subscribe to these events if it is necessary to modify the output right before dumping.
Example implementation:
<?php
declare(strict_types=1);
namespace App\EventSubscriber;
use Brainshaker95\PhpToTsBundle\Event\TsInterfaceGeneratedEvent;
use Brainshaker95\PhpToTsBundle\Event\TsEnumGeneratedEvent;
use Brainshaker95\PhpToTsBundle\Event\TsPropertyGeneratedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
final class TsInterfaceGeneratedSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
TsInterfaceGeneratedEvent::class => 'onGeneratedTsInterface',
TsEnumGeneratedEvent::class => 'onGeneratedTsEnum',
TsPropertyGeneratedEvent::class => 'onGeneratedTsProperty',
];
}
public function onGeneratedTsInterface(TsInterfaceGeneratedEvent $event): void
{
$tsInterface = $event->tsInterface;
$classNode = $event->classNode;
// ...
}
public function onGeneratedTsEnum(TsEnumGeneratedEvent $event): void
{
$tsEnum = $event->tsEnum;
$enumNode = $event->enumNode;
// ...
}
public function onGeneratedTsProperty(TsPropertyGeneratedEvent $event): void
{
$tsProperty = $event->tsProperty;
$propertyNode = $event->propertyNode;
// ...
}
}
If you do want to implement your own way of initiating the dump process you can inject the dumper service into your own services.
This of course can be done via all the various different ways of dependency injection and not only the one shown here:
<?php
declare(strict_types=1);
namespace App\Service;
use Brainshaker95\PhpToTsBundle\Model\Config\FileType;
use Brainshaker95\PhpToTsBundle\Model\Config\PartialConfig;
use Brainshaker95\PhpToTsBundle\Service\Dumper;
final class MyService
{
public function __construct(
private readonly Dumper $dumper,
) {}
public function doTheThingsAndStuff(): void
{
// See method descriptions for more detail
$this->dumper->dumpDir();
$this->dumper->dumpFiles(['path/to/file1', 'path/to/file2']);
$this->dumper->dumpFile('path/to/file', new PartialConfig(fileType: FileType::TYPE_DECLARATION));
$this->dumper->getTsInterfacesFromFile('path/to/file');
}
}
- All class identifiers used need to point to classes tagged with the
AsTypeScriptable
attribute, otherwise invalid TypeScript interfaces will be generated. - Types are only recognized as class identifiers if they start with an uppercase letter.
- Multiline
@deprecated
and@template
descriptions cannot contain empty lines between paragraphs. Only a single new line can be used as a separator. All other lines will be considered as part of the property description. - No support for nested readonly types for array shapes. Only the array property itself will be marked as readonly, which would technically allow nested properties to be modified.
- No support for array shapes where some items have keys and some do not.
- No support for
value-of
on backed enums. - No support for automatically removing generated TypeScript files when corresponding TypeScriptable is deleted. (See: #27)
- Document example TypeScriptable class
- Document example TypeScriptable enum
- Document usage of Hidden attribute
- Document usage of TsController
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
- Fork the Project
- Create your Feature Branch =>
git checkout -b feature/my-new-feature
- Commit your Changes =>
git commit -m 'feat(my-new-feature): add some awesome new feature'
- Push to the Branch =>
git push origin feature/my-new-feature
- Open a Pull Request
Distributed under the MIT License. See LICENSE for more information.