General overview | typescript-transformer | Spatie

 SPATIE

  TypeScript Transformer
=========================

spatie.be/open-source

  [Docs](https://spatie.be/docs)  [Typescript-transformer](https://spatie.be/docs/typescript-transformer/v1)  Usage  General overview

 Version   v3   v2   v1

 Other versions for crawler [v3](https://spatie.be/docs/typescript-transformer/v3) [v2](https://spatie.be/docs/typescript-transformer/v2) [v1](https://spatie.be/docs/typescript-transformer/v1)

- [ Introduction ](https://spatie.be/docs/typescript-transformer/v1/introduction)
- [ Postcardware ](https://spatie.be/docs/typescript-transformer/v1/postcardware)
- [ Installation ](https://spatie.be/docs/typescript-transformer/v1/installation)
- [ Under the hood ](https://spatie.be/docs/typescript-transformer/v1/under-the-hood)
- [ Questions &amp; issues ](https://spatie.be/docs/typescript-transformer/v1/questions-and-issues)
- [ Changelog ](https://spatie.be/docs/typescript-transformer/v1/changelog)
- [ About us ](https://spatie.be/docs/typescript-transformer/v1/about-us)

Usage
-----

- [ General overview ](https://spatie.be/docs/typescript-transformer/v1/usage/general-overview)
- [ Getting started ](https://spatie.be/docs/typescript-transformer/v1/usage/getting-started)
- [ Using transformers ](https://spatie.be/docs/typescript-transformer/v1/usage/using-transformers)
- [ Customizing the output using annotations ](https://spatie.be/docs/typescript-transformer/v1/usage/annotations)
- [ Selecting classes using collectors ](https://spatie.be/docs/typescript-transformer/v1/usage/selecting-classes-using-collectors)

Dto's
-----

- [ Transforming DTOs ](https://spatie.be/docs/typescript-transformer/v1/dtos/transforming-dtos)
- [ Typing properties ](https://spatie.be/docs/typescript-transformer/v1/dtos/typing-properties)
- [ Changing types using class property processors ](https://spatie.be/docs/typescript-transformer/v1/dtos/changing-types-with-class-property-processors)

Laravel
-------

- [ Installation and setup ](https://spatie.be/docs/typescript-transformer/v1/laravel/installation-and-setup)
- [ Executing the transform command ](https://spatie.be/docs/typescript-transformer/v1/laravel/executing-the-transform-command)

      You are viewing the documentation for **an older version** of this package. You can check the version you are using with the following command:

 `                                    composer show spatie/typescript-transformer                                                                                                                                                                                                                                    `

General overview
================

###  On this page

1. [ Using resources in your project ](#content-using-resources-in-your-project)
2. [ Transforming DTOs to TypeScript ](#content-transforming-dtos-to-typescript)
3. [ Using collectors to find resources ](#content-using-collectors-to-find-resources)
4. [ Further reading ](#content-further-reading)

Let's take a look at a real world use case of how the package can transform PHP types to TypeScript. We're not going to use the default Laravel resources because they cannot be typed. Instead, we're going to use the [spatie.data-transfer-object](https://github.com/spatie/data-transfer-object) package.

Let's first create a `UserResource`:

```
class UserResource extends DataTransferObject implements Arrayable
{
    public ?int $age = null;

    public ?string $name = null;

    public ?string $email = null;

    public ?AddressResource $address = null;
}
```

Here is the code of `AddressResource`:

```
class AddressResource extends DataTransferObject implements Arrayable
{
    public ?string $street = null;

    public ?string $number = null;

    public ?string $city = null;

    public ?string $postal = null;

    public ?string $country = null;
}
```

Each property is nullable, so it's easy to send an empty instance to the front end where necessary.

To easily convert a user to a `UserResource`, we're going to add a static `make` function to it. We'll also implement `Illuminate\Contracts\Support\Arrayable` so the resource can be converted to an array when sending it to the front end. This interface requires the object to have a `toArray` method. The implementation of the `toArray` method lives in the `DataTransferObject` base class which will use the public properties of the object.

When applying the changes described, the `UserResource` will now look like this:

```
use Illuminate\Contracts\Support\Arrayable;

class UserResource extends DataTransferObject implements Arrayable
{
    public ?int $age = null;

    public ?string $name = null;

    public ?string $email = null;

    public ?AddressResource $address = null;

    public static function make(User $user): self
    {
        return new static([
            'age' => $user->age,
            'name' => "{$user->first_name} {$user->last_name}",
            'email' => $user->email,
            'address' => AddressResource::make($user->address ?? new Address()),
        ]);
    }
}
```

Let's also apply the same changes to the `AddressResource`.

```
class AddressResource extends DataTransferObject implements Arrayable
{
    public ?string $street = null;

    public ?string $number = null;

    public ?string $city = null;

    public ?string $postal = null;

    public ?string $country = null;

    public static function make(Address $address): self
    {
        return new self([
            'street' => $address->street,
            'number' => $address->number,
            'city' => $address->city,
            'postal' => $address->postal,
            'country' => $address->country,
        ]);
    }
}
```

When using DTO's, it's impossible to assign a `string` to an `int` type. Another benefit is IDE completion. You can now construct your resource with all the information hinted by your IDE.

Using resources in your project
-----------------------------------------------------------------------------------------------------------------------------------------------------

Let's use the `UserResource` in a controller.

```
class UserController
{
    public function create()
    {
        return UserResource::make(new User());
    }

    public function update(User $user)
    {
        return UserResource::make($user);
    }
}
```

Transforming DTOs to TypeScript
-----------------------------------------------------------------------------------------------------------------------------------------------------

```
/** @typescript */
class UserResource extends DataTransferObject implements Arrayable
{
    // ...
}

/** @typescript */
class AddressResource extends DataTransferObject implements Arrayable
{
    // ...
}
```

With that annotation in place, the typescript equivalents can be generated by executing this command:

```
php artisan typescript:transform
```

Then we get the following output:

```
+------------------------------------+------------------------------------+
| PHP class                          | TypeScript entity                  |
+------------------------------------+------------------------------------+
| App\Http\Resources\UserResource    | App.Http.Resources.UserResource    |
| App\Http\Resources\AddressResource | App.Http.Resources.AddressResource |
+------------------------------------+------------------------------------+
Transformed 2 PHP types to TypeScript

```

A new file was created in the `resources/js` directory of our application. `generated.ts` contains two types:

```
namespace App.Http.Resources {
    export type AddressResource = {
        street: string | null;
        number: string | null;
        city: string | null;
        postal: string | null;
        country: string | null;
    }

    export type UserResource = {
        age: number | null;
        name: string | null;
        email: string | null;
        address: App.Http.Resources.AddressResource | null;
    }
}
```

These types can now be used in TypeScript code. Referencing a `UserResource` can now be done using `App.Http.Resource.UserResource`.

Using collectors to find resources
--------------------------------------------------------------------------------------------------------------------------------------------------------------

Instead of manually adding `@typescript` to each class, we can use a [collector](https://spatie.be/docs/typescript-transformer/v1/usage/collectors).

Let's first create an abstract class Resource:

```
abstract class Resource extends DataTransferObject implements Arrayable
{
}
```

Next, the `UserResource` and `AddressResource` should extend `Resource`:

```
class UserResource extends Resource
{
   // ...
}

class AddressResource extends Resource
{
   // ...
}
```

With that in place, we can create a collector that will process all classes that extend `Resource`

```
class ResourceCollector extends Collector
{
    public function shouldCollect(ReflectionClass $class): bool
    {
        return $class->isSubclassOf(Resource::class);
    }

    public function getCollectedOccurrence(ReflectionClass $class): CollectedOccurrence
    {
        return CollectedOccurrence::create(
            new DtoTransformer($this->config),
            Str::before($class->getShortName(), 'Resource') // do not use the "Resource" suffix in the converted typescript
        );
    }
}
```

Finally, `ResourceCollector` should be added to the list of collectors in the configuration file `typescript-transformer.php`:

```
	...

    /*
    |--------------------------------------------------------------------------
    | Collectors
    |--------------------------------------------------------------------------
    |
    | In these classes you define which classes will be collected and fed to
    | transformers. By default, we include an AnnotationCollector which will
    | search for @typescript annotated classes to transform.
    |
    */

    'collectors' => [
        Spatie\TypeScriptTransformer\Collectors\AnnotationCollector::class,
        App\Support\TypeScriptTransformer\ ResourceCollector::class,
    ],

    ...
```

Now you can run `php artisan typescript:transform` to create the TypeScript definitions.

### Using class property replacements

You can specify to which TypeScript type a PHP type should be converted.

Let's add a `$birthday` property to the `UserResource`, which is of type `Carbon`.

```
class UserResource extends DataTransferObject implements Arrayable
{
    public ?int $age = null;

    public ?string $name = null;

    public ?string $email = null;

    public ?AddressResource $address = null;

    public ?Carbon $birthday = null;

    public static function make(User $user): self
    {
        return new self([
            'age' => $user->age,
            'name' => "{$user->first_name} {$user->last_name}",
            'email' => $user->email,
            'address' => AddressResource::make($user->address ?? new Address()),
            'birthday' => $user->birthday,
        ]);
    }
}
```

Since `Carbon` isn't a primitive type like a `string`, `int`, `bool`, `array` we actually cannot send it directly to the front. Using `Resource` class we can convert the `Carbon` instance to a string:

```
abstract class Resource extends DataTransferObject implements Arrayable
{
    public function toArray(): array
    {
        return collect(parent::toArray())->map(function ($value) {
            if ($value instanceof Carbon) {
                return $value->toAtomString();
            }

            return $value;
        })->toArray();
    }
}
```

This class will transform it will transform it to the `any` TypeScript type, but you can make it more specific. In the `typescript-transformer` config file, you can specify to which TypeScript type and PHP type should be converted to.

```
	...

    /*
    |--------------------------------------------------------------------------
    | Class property replacements
    |--------------------------------------------------------------------------
    |
    | In your DTO's you sometimes have properties that should always be replaced
    | by TypeScript representations. For example, you can replace a Datetime
    | always with a string. These replacements can be defined here.
    |
    */

    'class_property_replacements' => [
        // ...
        Carbon::class => 'string',
    ],

    ...
```

### Using transformer to manually convert a type

In the previous section a `Carbon` type was converted to a string. If you want to have fine-grained control over how a PHP type should be converted to a type script type, you can use a `Transformer`. Let's convert `Carbon` to a type that has a day, month, and year.

First we need to create a PHP class that has the desired structure.

```
@typescript
class CustomDate
{
    private int $year;

    private int $month;

    private int $day;

    public function __construct(int $year, int $month, int $day)
    {
        $this->year = $year;
        $this->month = $month;
        $this->day = $day;
    }

    public function get(): array
    {
        return [
            'year' => $this->year,
            'month' => $this->month,
            'day' => $this->day,
            'is_today' => date('Y/m/d') === "{$this->year}/{$this->month}/{$this->day}"
        ];
    }
}
```

Next, the `UserResource` needs to use the `CustomDate` type:

```
class UserResource extends Resource
{
    public ?int $age = null;

    public ?string $name = null;

    public ?string $email = null;

    public ?AddressResource $address = null;

    public ?CustomDate $birthday = null;

    public static function make(User $user): self
    {
        return new self([
            'age' => $user->age,
            'name' => "{$user->first_name} {$user->last_name}",
            'email' => $user->email,
            'address' => AddressResource::make($user->address ?? new Address()),
            'birthday' => new CustomDate(
                $user->birthday->year,
                $user->birthday->month,
                $user->birthday->day
            ),
        ]);
    }
}
```

And the base `Resource` needs to be aware of the `CustomDate` as well.

```
abstract class Resource extends DataTransferObject implements Arrayable
{
    public function toArray(): array
    {
        return collect(parent::toArray())
           ->map(function ($value) {
               if ($value instanceof CustomDate) {
                   return $value->get();
               }

               return $value;
           })
           ->toArray();
    }
}
```

If we would run `php artisan typescript:transform` now, this would be the result

```
export type User = {
    age: number | null;
    name: string | null;
    email: string | null;
    address: App.Http.Resources.Address | null;
    birthday: any | null;
}
```

That `any` does not describe our home-made `CustomDate` type. Let's fix that by uisng a `Transformer`:

```
class CustomDateTransformer implements Transformer
{
    public function canTransform(ReflectionClass $class): bool
    {
        return $class->getName() === CustomDate::class;
    }

    public function transform(ReflectionClass $class, string $name): TransformedType
    {
        $type =  [
        // ...
        CustomDate::class => TypeScriptType::create(
