烟尘应用/模型类型

从Laravel模型生成TypeScript接口


README

模型类型器

从Laravel模型生成TypeScript接口

Packagist License Latest Stable Version Total Downloads

安装

起始支持Laravel v10+和PHP v8.1+

使用以下命令通过Composer安装此包

composer require --dev fumeapp/modeltyper

可选,您可以使用以下命令发布配置文件

php artisan vendor:publish --provider="FumeApp\ModelTyper\ModelTyperServiceProvider" --tag=config

注意 此包可能需要您安装Doctrine DBAL。如果是这样,您可以运行

composer require --dev doctrine/dbal

用法

php artisan model:typer

将输出

export interface User {
    // columns
    id: number;
    email: string;
    name: string;
    created_at?: Date;
    updated_at?: Date;
    // mutators
    first_name: string;
    initials: string;
    // relations
    teams: Teams;
}
export type Users = Array<User>;

export interface Team {
    // columns
    id: number;
    name: string;
    logo: string;
    created_at?: Date;
    updated_at?: Date;
    // mutators
    initials: string;
    slug: string;
    url: string;
    // relations
    users: Users;
}
export type Teams = Array<Team>;

它是如何工作的?

此命令将遍历您的所有模型,并根据列、访问器和关系生成TypeScript接口。然后,您可以将输出管道传输到您的首选???.d.ts

要求

  1. 您必须为您的模型关系指定返回类型
public function providers(): HasMany // <- this
{
    return $this->hasMany(Provider::class);
}
  1. 您必须为您的模型突变指定返回类型
public function getFirstNameAttribute(): string // <- this
{
    return explode(' ', $this->name)[0];
}

附加选项

--model= : Generate typescript interfaces for a specific model
--global : Generate typescript interfaces in a global namespace named models
--json : Output the result as json
--use-enums : Use typescript enums instead of object literals
--plurals : Output model plurals
--no-relations : Do not include relations
--optional-relations : Make relations optional fields on the model type
--no-hidden : Do not include hidden model attributes
--timestamps-date : Output timestamps as a Date object type
--optional-nullables : Output nullable attributes as optional fields
--api-resources : Output api.MetApi interfaces
--resolve-abstract : Attempt to resolve abstract models)
--fillables : Output model fillables
--fillable-suffix=fillable
--all : Enable all output options (equivalent to --plurals --api-resources)'

自定义接口

如果您为模型使用了自定义接口,可以在保留的interfaces数组中指定它们

例如,对于在Location模型中使用的自定义Point接口,您可以在模型中放入以下内容

public array $interfaces = [
    'coordinate' => [
        'import' => "@/types/api",
        'type' => 'Point',
    ],
];

并且它应该生成

import { Point } from "@/types/api";

export interface Location {
    // columns
    coordinate: Point;
}

这将覆盖所有列、访问器和关系

您还可以指定一个接口是可空的

public array $interfaces = [
    'choices' => [
        'import' => '@/types/api',
        'type' => 'ChoicesWithPivot',
        'nullable' => true,
    ],
];

您还可以选择省略导入,仅使用类型

public array $interfaces = [
    'choices' => [
        'type' => "'good' | 'bad'",
    ],
];

并且它应该生成

export interface Location {
    // columns
    choices: "good" | "bad";
}

使用自定义接口也是一个添加您想添加到接口中的任何附加属性的好地方。

例如,如果您的接口在Vuex之类的存储中保持一些额外的状态,您可以将其添加到接口中

    public array $interfaces = [
        'state' => [
            'type' => "found' | 'not_found' | 'searching' | 'reset'",
        ],
    ];

这将生成

export interface Location {
    // ...
    // overrides
    state: "found" | "not_found" | "searching" | "reset";
    // ...
}

覆盖默认映射/添加新映射

您可以通过发布配置覆盖Model Typer提供的默认映射或添加新映射

然后在custom_mappings内部添加Laravel类型作为键,并分配TypeScript类型作为其值

您还可以添加对您的自定义类型转换的映射

'custom_mappings' => [
    'App\Casts\YourCustomCast' => 'string|null',
    'binary' => 'Blob',
    'bool' => 'boolean',
    'point' => 'CustomPointInterface',
    'year' => 'string',
],

声明全局

在名为model的全局命名空间中生成您的接口

artisan model:typer --global
export {}
declare global {
  export namespace models {

    export interface Provider {
      // columns
      id: number
      user_id: number
      avatar?: string
...

为集合输出复数接口

artisan model:typer --plurals

例如,当存在User模型时

export type Users = User[]

输出Api.MetApi*资源

artisan model:typer --api-resources

输出

export interface UserResults extends api.MetApiResults { data: Users }
export interface UserResult extends api.MetApiResults { data: User }
export interface UserMetApiData extends api.MetApiData { data: User }
export interface UserResponse extends api.MetApiResponse { data: UserMetApiData }

启用所有输出选项

artisan model:typer --all

输出复数和api-resources。即,它等同于

artisan model:typer --plurals --api-resources

Laravel V9属性支持

Laravel现在有非常不同的方式来指定访问器和突变。为了告诉ModelTyper您的属性类型,请确保添加属性返回的类型

    /**
     * Determine if the user is a captain of a team
     *
     * @return Attribute
     */
    public function isCaptain(): Attribute
    {
        return Attribute::make(
            get: fn (): bool => $this->teams[0]->pivot->captain ?? false,
        );
    }

这将生成类似的内容

export interface User {
    // columns
    id: number;
    email: string;
    name?: string;
    created_at?: Date;
    updated_at?: Date;
    // mutators
    is_captain: boolean;
    // relations
    teams: TeamUsers;
}

对于单个模型

为单个模型生成您的接口

artisan model:typer --model=User

输出为JSON

将您的接口生成为JSON

artisan model:typer --json

枚举Eloquent属性类型转换

Laravel现在允许您在模型中类型转换枚举。这将检测并引入您的枚举类以及您的注释

app/Enums/UserRoleEnum.php

<?php

namespace App\Enums;

/**
 * @property ADMIN - Can do anything
 * @property USER - Standard read-only
 */
enum UserRoleEnum: string
{
    case ADMIN = 'admin';
    case USER = 'user';
}

然后在我们的用户模型内部

app/Models/User.php

protected $casts = [
    'role' => App\Enums\UserRoleEnum::class,
];

现在我们的 ModelTyper 输出将如下所示

const UserRoleEnum = {
  /** Can do anything */
  ADMIN: 'admin',
  /** Standard read-only */
  USER: 'user',
}
export type UseRoleEnum = typeof UseRoleEnum[keyof typeof UserRoleEnum]
export interface User {
  ...
  role: UserRoleEnum
  ...
}

ModelTyper 默认使用对象字面量而不是 TypeScript 枚举,这是出于某些观点的考虑。[了解更多](https://maxheiber.medium.com/alternatives-to-typescript-enums-50e4c16600b1 "外部链接")。但您可以使用 --use-enums 选项来使用 TypeScript 枚举而不是对象字面量。

注意注释是如何被找到和解析的——它们必须遵循指定的格式