├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── changelog.md
├── composer.json
├── config
└── laravel-typescript.php
├── contributing.md
├── docs
└── index.md
├── license.md
├── phpunit.xml
├── readme.md
└── src
├── Console
└── Commands
│ └── LaravelTypescriptCommand.php
├── Facades
└── LaravelTypescript.php
├── Helpers
├── Attributes.php
├── Column.php
├── Generator.php
├── ModelInspectionResult.php
├── ModelInspector.php
├── Reflection.php
└── Types.php
├── LaravelTypescript.php
├── LaravelTypescriptServiceProvider.php
├── Traits
└── HasTypeGenerators.php
├── Types
├── ColumnType.php
├── Definition.php
├── EnumType.php
├── LaravelCastType.php
├── PhpType.php
├── RelationType.php
└── Type.php
└── Util
├── AccessorResolver.php
└── Counter.php
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/changelog.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `Typescript Transformer` will be documented in this file.
4 |
5 | ## Version 1.0
6 |
7 | ### Added
8 | - Everything
9 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wovosoft/laravel-typescript",
3 | "description": "Transforms Laravel Models to Typescript Interfaces/Types",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Narayan Adhikary",
8 | "email": "wovosoft@gmail.com",
9 | "homepage": "https://wovosoft.com"
10 | }
11 | ],
12 | "homepage": "https://github.com/wovosoft/laravel-typescript",
13 | "keywords": [
14 | "Laravel",
15 | "Typescript Transformer",
16 | "Typescript Interfaces",
17 | "Typescript Types",
18 | "php to typescript"
19 | ],
20 | "require": {
21 | "composer/class-map-generator": "*"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "Wovosoft\\LaravelTypescript\\": "src/"
26 | }
27 | },
28 | "autoload-dev": {
29 | "psr-4": {
30 | "Wovosoft\\LaravelTypescript\\Tests\\": "tests"
31 | }
32 | },
33 | "extra": {
34 | "laravel": {
35 | "providers": [
36 | "Wovosoft\\LaravelTypescript\\LaravelTypescriptServiceProvider"
37 | ],
38 | "aliases": {
39 | "LaravelTypescript": "Wovosoft\\LaravelTypescript\\Facades\\LaravelTypescript"
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/config/laravel-typescript.php:
--------------------------------------------------------------------------------
1 | resource_path('js/types/models.d.ts'),
5 | 'source_dir' => app_path('Models'),
6 | /**
7 | * Custom attributes should have return types defined.
8 | * But if it is not, then the return type should be this type.
9 | * And this value should be php supported return types.
10 | * like primitive types or any other classes.
11 | */
12 | 'custom_attributes' => [
13 | 'fallback_return_type' => 'string',
14 | /*
15 | * Return type resolver for the new style attribute's accessor method.
16 | * eg. prlDate():Attribute => Attribute::get(fn():return_type=>return_value)
17 | */
18 | 'accessor_resolvers' => \Wovosoft\LaravelTypescript\Util\AccessorResolver::class,
19 | ],
20 | /**
21 | * Custom relations should have return types defined.
22 | * But if it is not, then the return type should be this type.
23 | * And this value should be php supported return types.
24 | * like primitive types or any other classes.
25 | * @see https://github.com/staudenmeir/eloquent-has-many-deep
26 | */
27 | "counter" => \Wovosoft\LaravelTypescript\Util\Counter::class
28 | ];
29 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are welcome and will be fully credited.
4 |
5 | Contributions are accepted via Pull Requests on [Github](https://github.com/wovosoft/typescript-transformer).
6 |
7 | # Things you could do
8 | If you want to contribute but do not know where to start, this list provides some starting points.
9 | - Add license text
10 | - Remove rewriteRules.php
11 | - Set up TravisCI, StyleCI, ScrutinizerCI
12 | - Write a comprehensive ReadMe
13 |
14 | ## Pull Requests
15 |
16 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
17 |
18 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date.
19 |
20 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option.
21 |
22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
23 |
24 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
25 |
26 |
27 | **Happy coding**!
28 |
--------------------------------------------------------------------------------
/docs/index.md:
--------------------------------------------------------------------------------
1 | ## Story
2 |
3 | ```php
4 | ### First we need to get the model classes in a directory
5 |
6 | use Wovosoft\LaravelTypescript\Helpers;
7 |
8 | $modelClasses = Helpers\ModelInspector::getModelsIn(app_path('Models'));
9 |
10 |
11 |
12 | ### Then, these models are passed to the generator
13 |
14 | $contents = Helpers\Transformer::generate($modelClasses)
15 |
16 | ```
17 |
18 | ### What happens in `Helpers\Transfer::generate` method
19 |
20 | Generates a collection of provided models including attributes, columns and relations
21 | in a parsable format.
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | # The license
2 |
3 | Free to use
4 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests/
15 |
16 |
17 |
18 |
19 | src/
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Laravel Typescript
2 |
3 | [![Latest Version on Packagist][ico-version]][link-packagist]
4 | [![Total Downloads][ico-downloads]][link-downloads]
5 | [![Build Status][ico-travis]][link-travis]
6 | [![StyleCI][ico-styleci]][link-styleci]
7 |
8 | Transforms Laravel Models to Typescript Interfaces/Types
9 |
10 | ## Precautions
11 |
12 | This packages instantiates Models to retrieve it's casts,
13 | attributes, relations in some cases. So, if your models have
14 | some sensitive actions inside __constructor method, please
15 | be aware of using this package.
16 |
17 | ## Installation
18 |
19 | Via Composer
20 |
21 | ``` bash
22 | composer require --dev wovosoft/laravel-typescript
23 | ```
24 |
25 | ## Publish Configuration
26 |
27 | Run the command given below. This will publish `laravel-typescript.php` config file.
28 |
29 | ```bash
30 | php artisan vendor:publish --provider="Wovosoft\LaravelTypescript\LaravelTypescriptServiceProvider"
31 | ```
32 |
33 | Configure the configurations
34 |
35 | ```php
36 | return [
37 | 'output_path' => resource_path('js/types/models.d.ts'),
38 | 'source_dir' => app_path('Models'),
39 | /**
40 | * Custom attributes should have return types defined.
41 | * But if it is not, then the return type should be this type.
42 | * And this value should be php supported return types.
43 | * like primitive types or any other classes
44 | */
45 | "custom_attributes" => [
46 | "fallback_return_type" => "string"
47 | ]
48 | ];
49 | ```
50 |
51 | ## Usage
52 |
53 | Run the command given below to generate typescript types.
54 |
55 | ```bash
56 | php artisan laravel-typescript:transform
57 | ```
58 |
59 | Generated contents will be written in configured location.
60 |
61 | ## Advanced Usage
62 |
63 | Sometimes Models can be stored in different locations, like in some packages, some directories etc.,
64 | in that case, please check the source of
65 | [./src/LaravelTypescript.php](https://github.com/wovosoft/laravel-typescript/blob/master/src/LaravelTypescript.php)
66 |
67 | You can just instantiate this class, and generate types for models in some other directories.
68 |
69 | ```php
70 | use Wovosoft\LaravelTypescript\Facades\LaravelTypescript;
71 |
72 |
73 | $dirs = [
74 | "models" => app_path("Models"),
75 | "hrmPerson" => base_path("packages/wovosoft/hrms-person/src/Models"),
76 | ];
77 |
78 | foreach ($dirs as $name => $dir) {
79 | LaravelTypescript::generate(
80 | sourceDir : $dir,
81 | outputPath: resource_path("js/types/$name.d.ts")
82 | );
83 |
84 | echo "Generated $name.d.ts";
85 | }
86 | ```
87 |
88 | ## Note on New Model Attributes
89 |
90 | For new Model Attributes, return type of the Closure function should be defined,
91 | otherwise, it will generate value of
92 | `config('laravel-typescript.custom_attributes.fallback_return_type')` type for the related
93 | property.
94 |
95 | ```php
96 | use \Illuminate\Database\Eloquent\Model;
97 | use \Illuminate\Database\Eloquent\Casts\Attribute;
98 |
99 | class User extends Model{
100 | public function isActive() : Attribute
101 | {
102 | return Attribute::get(fn(): bool =>$this->status==='active');
103 | }
104 |
105 | public function getIsInactiveAttribute():bool
106 | {
107 | return $this->status==="inactive";
108 | }
109 | }
110 | ```
111 |
112 | ## Change log
113 |
114 | Please see the [changelog](changelog.md) for more information on what has changed recently.
115 |
116 | ## Contributing
117 |
118 | Please see [contributing.md](contributing.md) for details and a todolist.
119 |
120 | ## Security
121 |
122 | If you discover any security related issues, please create issues
123 | in [Issues Tracker](https://github.com/wovosoft/laravel-typescript/issues)
124 |
125 | ## Credits
126 |
127 | - [Narayan Adhikary][link-author]
128 | - [All Contributors][link-contributors]
129 |
130 | ## License
131 |
132 | MIT. Please see the [license file](license.md) for more information.
133 |
134 | [ico-version]: https://img.shields.io/packagist/v/wovosoft/laravel-typescript.svg?style=flat-square
135 |
136 | [ico-downloads]: https://img.shields.io/packagist/dt/wovosoft/laravel-typescript.svg?style=flat-square
137 |
138 | [ico-travis]: https://img.shields.io/travis/wovosoft/laravel-typescript/master.svg?style=flat-square
139 |
140 | [ico-styleci]: https://github.styleci.io/repos/661637738/shield?branch=master
141 |
142 | [link-packagist]: https://packagist.org/packages/wovosoft/laravel-typescript
143 |
144 | [link-downloads]: https://packagist.org/packages/wovosoft/laravel-typescript
145 |
146 | [link-travis]: https://travis-ci.org/wovosoft/laravel-typescript
147 |
148 | [link-styleci]: https://github.styleci.io/repos/661637738
149 |
150 | [link-author]: https://github.com/wovosoft
151 |
152 | [link-contributors]: ../../contributors
153 |
--------------------------------------------------------------------------------
/src/Console/Commands/LaravelTypescriptCommand.php:
--------------------------------------------------------------------------------
1 | info('Successfully Generated Typescript Model Interfaces');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Facades/LaravelTypescript.php:
--------------------------------------------------------------------------------
1 | getName());
34 | /**
35 | * In old style, get{Prop}Attribute() method's returns type can be
36 | * Enum Type, we do not need to care about it.
37 | */
38 | if (
39 | $methodName->startsWith('get')
40 | && $methodName->endsWith('Attribute')
41 | && $methodName->value() !== 'getAttribute'
42 | ) {
43 | return true;
44 | }
45 |
46 | /**
47 | * in new style, custom attribute defining method always returns Attribute::class,
48 | * so, when it returns other than Attribute::class, it is not a custom attribute
49 | */
50 | if ($reflectionMethod->getReturnType() instanceof ReflectionUnionType) {
51 | return false;
52 | }
53 |
54 | /**
55 | * If it returns Attribute::class it is a custom attribute
56 | */
57 | return $reflectionMethod->getReturnType()?->getName() === Attribute::class;
58 | }
59 |
60 | /**
61 | * Determines if a method is used to define model relation or not
62 | */
63 | public static function isRelation(ReflectionMethod $method): bool
64 | {
65 | return $method->hasReturnType()
66 | && $method->getReturnType() instanceof ReflectionNamedType
67 | && is_subclass_of($method->getReturnType()->getName(), Relation::class);
68 | }
69 |
70 | /**
71 | * @description Determines if the attribute is of new style or old style
72 | *
73 | * @param ReflectionMethod $method
74 | *
75 | * @return bool
76 | */
77 | public static function isNewStyled(ReflectionMethod $method): bool
78 | {
79 | if (is_null($method->getReturnType())) {
80 | return false;
81 | }
82 |
83 | $name = str($method->getName());
84 |
85 | /**
86 | * First we check if it is of old style attribute
87 | * old style attributes are defined like, 'get{PropName}Attribute()'
88 | */
89 | if (
90 | $name->startsWith('get')
91 | && $name->endsWith('Attribute')
92 | && $name->value() !== 'getAttribute'
93 | ) {
94 | return false;
95 | }
96 |
97 | /**
98 | * New style attributes don't return multiple values,
99 | * it returns only one, and it is of type.
100 | *
101 | * @link \Illuminate\Database\Eloquent\Casts\Attribute::class
102 | */
103 | if ($method->getReturnType() instanceof ReflectionUnionType) {
104 | return false;
105 | }
106 |
107 | /**
108 | * If return type is exactly of Illuminate\Database\Eloquent\Casts\Attribute::class,
109 | * then it is of new style attribute
110 | */
111 | return $method->getReturnType()->getName() === Attribute::class;
112 | }
113 |
114 | /**
115 | * @description Get Reflection of new style attributes 'get' method
116 | * This method should be called for new style attributes only.
117 | *
118 | * @note In most cases, it is safe to retrieve types in this way,
119 | * because calling new-styled attribute doesn't make any database/php operations,
120 | * it just returns an instance of Attribute::class
121 | * @note It is safe to check if the method defines new style attribute before calling this function
122 | *
123 | * @param ReflectionMethod $method
124 | *
125 | * @throws ReflectionException
126 | * @throws Exception
127 | *
128 | * @return ReflectionFunction
129 | */
130 | public static function getReflectionOfNewStyleAttribute(ReflectionMethod $method): ReflectionFunction
131 | {
132 | $model = ModelInspector::parseModel($method->getDeclaringClass()->getName());
133 |
134 | return new ReflectionFunction($model->{$method->getName()}()->get);
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/Helpers/Column.php:
--------------------------------------------------------------------------------
1 | name;
20 | }
21 |
22 | public function getType(): string {
23 | return $this->type;
24 | }
25 |
26 | public function getTypeName(): string {
27 | return $this->type_name;
28 | }
29 |
30 | public function getNotNull(): bool {
31 | return !$this->nullable;
32 | }
33 |
34 | public static function fromArray(array $data): static {
35 | $item = new static();
36 | $item->name = $data['name'];
37 | $item->type_name = $data['type_name'];
38 | $item->type = $data['type'];
39 | $item->collation = $data['collation'];
40 | $item->nullable = $data['nullable'];
41 | $item->default = $data['default'];
42 | $item->comment = $data['comment'];
43 | $item->auto_increment = $data['auto_increment'];
44 | $item->generation = $data['generation'];
45 | return $item;
46 | }
47 |
48 | public function toArray(): array {
49 | return [
50 | 'name' => $this->name,
51 | 'type_name' => $this->type_name,
52 | 'type' => $this->type,
53 | 'collation' => $this->collation,
54 | 'nullable' => $this->nullable,
55 | 'default' => $this->default,
56 | 'comment' => $this->comment,
57 | 'auto_increment' => $this->auto_increment,
58 | 'generation' => $this->generation,
59 | ];
60 | }
61 |
62 | public function toJson(): false|string {
63 | return json_encode($this->toArray());
64 | }
65 |
66 | public function toCollection(): Collection {
67 | return collect($this->toArray());
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Helpers/Generator.php:
--------------------------------------------------------------------------------
1 |
34 | * @throws ReflectionException
35 | *
36 | */
37 | public function getDefinitions(): Collection
38 | {
39 | return $this
40 | ->getColumnDefinitions()
41 | ->merge($this->getCustomAttributeDefinitions())
42 | ->merge($this->getRelationDefinitions())
43 | ->keyBy('name');
44 | }
45 |
46 | /**
47 | * @description Generates interface
48 | *
49 | * @return string
50 | * @throws ReflectionException
51 | *
52 | */
53 | public function __toString(): string
54 | {
55 | return $this->toTypescript();
56 | }
57 |
58 | /**
59 | * @description Generates interface
60 | *
61 | * @throws ReflectionException
62 | */
63 | public function toTypescript(): string
64 | {
65 | $typings = $this
66 | ->getDefinitions()
67 | ->implode(function (Definition $def, string $key) {
68 | return "\t\t$key" . ($def->isUndefinable ? '?' : '') . ": $def;";
69 | }, PHP_EOL);
70 |
71 | $reflection = Reflection::model($this->result->getModel());
72 |
73 | return str($typings)
74 | ->prepend("\texport interface " . $reflection->getShortName() . ' {' . PHP_EOL)
75 | ->append(PHP_EOL . "\t}");
76 | }
77 |
78 | /**
79 | * @description Returns database column definitions
80 | *
81 | * @return Collection
82 | * @throws ReflectionException
83 | *
84 | * @throws Exception
85 | */
86 | private function getColumnDefinitions(): Collection
87 | {
88 | $modelReflection = Reflection::model($this->result->getModel());
89 | $model = ModelInspector::parseModel($this->result->getModel());
90 |
91 | return $this->result
92 | ->getColumns()
93 | ->map(function (Column $column, string $key) use ($model, $modelReflection) {
94 | /*
95 | * Database columns can be cast in different format
96 | */
97 | if ($model->hasCast($key)) {
98 | /*
99 | * When, the cast is of type Enum it should be rendered as union type
100 | * @todo : Rendered union type can be stored in separate scope rather then being
101 | * rendered as values directly.
102 | */
103 | if (is_string($model->getCasts()[$key]) && enum_exists($model->getCasts()[$key])) {
104 | $type = EnumType::toType($model->getCasts()[$key]);
105 | } elseif (LaravelCastType::isBuiltIn($model->getCasts()[$key])) {
106 | $type = LaravelCastType::getType($model->getCasts()[$key]);
107 | } else {
108 | $type = PhpType::toType($model->getCasts()[$key]);
109 | }
110 | } else {
111 | $type = ColumnType::toType($column->getTypeName());
112 | }
113 |
114 | return new Definition(
115 | namespace : $modelReflection->getNamespaceName(),
116 | name : $column->getName(),
117 | model : $modelReflection->getName(),
118 | modelShortName: $modelReflection->getShortName(),
119 | types : [$type],
120 | isRequired : $column->getNotnull(),
121 | isUndefinable : false
122 | );
123 | });
124 | }
125 |
126 | /**
127 | * @return Collection
128 | */
129 | private function getCustomAttributeDefinitions(): Collection
130 | {
131 | return $this->result
132 | ->getCustomAttributes()
133 | ->mapWithKeys(function (ReflectionMethod $method) {
134 | $decClass = $method->getDeclaringClass();
135 | $types = $this->getAttributeReturnTypes($method);
136 |
137 | /*
138 | * When there is return type is not defined
139 | */
140 | if ($types->isEmpty()) {
141 | $types->add(
142 | new Type(
143 | name : config('laravel-typescript.custom_attributes.fallback_return_type'),
144 | isMultiple: false
145 | )
146 | );
147 | }
148 |
149 | return [
150 | $this->qualifyAttributeName($method) => new Definition(
151 | namespace : $decClass->getNamespaceName(),
152 | name : $this->qualifyAttributeName($method),
153 | model : $decClass->getName(),
154 | modelShortName: $decClass->getShortName(),
155 | types : $types,
156 | isRequired : $this->isRequiredReturnType($method),
157 | isUndefinable : true
158 | ),
159 | ];
160 | });
161 | }
162 |
163 | /**
164 | * @description Returns definitions of relations
165 | *
166 | * @return Collection
167 | * @throws ReflectionException
168 | *
169 | */
170 | private function getRelationDefinitions(): Collection
171 | {
172 | if ($this->result->getModel() instanceof Model) {
173 | $model = $this->result->getModel();
174 | } else {
175 | $modelClass = $this->result->getModel();
176 | $model = new $modelClass();
177 | }
178 |
179 | $modelReflection = Reflection::model($model);
180 |
181 | return $this->result
182 | ->getRelations()
183 | ->mapWithKeys(function (ReflectionMethod $method) use ($model, $modelReflection) {
184 | try {
185 | $relation = $method->invoke($model);
186 | $relatedModel = get_class($relation->getRelated());
187 |
188 | } catch (Throwable) {
189 | return [];
190 | }
191 |
192 | $relatedModelReflection = Reflection::model($relatedModel);
193 |
194 | /**
195 | * When Model and Related Morels are from the same namespace,
196 | * only short name is enough
197 | * When Model and Related Model are from different namespaces,
198 | * full namespace name should be used.
199 | */
200 |
201 | $typeName = Reflection::isSameNamespace($relatedModelReflection, $modelReflection)
202 | ? $relatedModelReflection->getShortName()
203 | : $relatedModelReflection->getName();
204 |
205 | return [
206 | $this->qualifyAttributeName($method) => new Definition(
207 | namespace : $modelReflection->getNamespaceName(),
208 | name : $this->qualifyAttributeName($method),
209 | model : get_class($model),
210 | modelShortName: $modelReflection->getShortName(),
211 | types : [
212 | new Type(
213 | name : $typeName,
214 | isMultiple: RelationType::getReturnCountType($method->getReturnType()->getName())
215 | ),
216 | ],
217 | //model relations are not set by their method nemo,
218 | //so in typescript it should be nullable (not required) and undefinable
219 | isRequired : false,
220 | isUndefinable : true
221 | )
222 | ];
223 | });
224 | }
225 |
226 | /**
227 | * @description Returns Collection of Return Types (Type)
228 | *
229 | * @param ReflectionMethod $method
230 | *
231 | * @return Collection
232 | * @throws ReflectionException
233 | *
234 | */
235 | private function getAttributeReturnTypes(ReflectionMethod $method): Collection
236 | {
237 | if (Attributes::isNewStyled($method)) {
238 | $type = Attributes::getReflectionOfNewStyleAttribute($method)->getReturnType();
239 | } else {
240 | $type = $method->getReturnType();
241 | }
242 |
243 | if ($type instanceof ReflectionNamedType) {
244 | $types = [$type];
245 | } else {
246 | $types = $type?->getTypes();
247 | }
248 |
249 | return collect($types)
250 | ->map(function (ReflectionNamedType $type) {
251 | if ($type->isBuiltin()) {
252 | /**
253 | * @todo In php it is not possible to define array's member type
254 | * but in docblock it is possible. So, do it later.
255 | */
256 | //if ($type->getName() === "array") {
257 | // $doc = new DocBlock($method->getDocComment());
258 | // if ($doc->hasTag('return')) {
259 | // /** @var DocBlock\Tag\ReturnTag $returnTag */
260 | // $returnTag = $doc->getTagsByName('return')[0];
261 | // dump($returnTag->getTypes());
262 | // }
263 | //}
264 |
265 | return PhpType::toType($type->getName() ?: config('laravel-typescript.custom_attributes.fallback_return_type'));
266 | } elseif (enum_exists($type->getName())) {
267 | return EnumType::toType($type->getName());
268 | } /**
269 | * When the return type is of type Model, it's qualified namespace should be used.
270 | * The returning model should be rendered in separate interface.
271 | */
272 | elseif (ModelInspector::isOfModelType($type->getName())) {
273 | return Type::model(name: $type->getName());
274 | }
275 |
276 | $resolver = config('laravel-typescript.custom_attributes.accessor_resolvers');
277 | if (is_callable($resolver)) {
278 | return $resolver($type->getName());
279 | }
280 |
281 | return Type::any();
282 | });
283 | }
284 |
285 | /**
286 | * @description Returns Prop name to be generated
287 | *
288 | * @param ReflectionMethod $method
289 | *
290 | * @return string
291 | */
292 | private function qualifyAttributeName(ReflectionMethod $method): string
293 | {
294 | $name = str($method->getName());
295 |
296 | if (Attributes::isRelation($method) || Attributes::isNewStyled($method)) {
297 | return $name->snake()->value();
298 | }
299 |
300 | return $name->after('get')->before('Attribute')->snake()->value();
301 | }
302 |
303 | /**
304 | * @description Determines if a props value is required or nullable
305 | *
306 | * @throws ReflectionException
307 | */
308 | private function isRequiredReturnType(ReflectionMethod $method): bool
309 | {
310 | /*
311 | * The New style attribute returns an instance of Attribute,
312 | * which has two methods get and set. We only need to care about the
313 | * return types of get method.
314 | * Because it is set dynamically as a Closure and the returning type is
315 | * added in the Closure, the attribute method should be called to
316 | * achieve the instance and then get the returning type from that
317 | * instance of get method.
318 | *
319 | * And according to the implementation logic, the method (prop():Attribute) should only return
320 | * the instance of Attribute, it shouldn't perform any other action.
321 | * So, it is safe to call it to have the instance of Attribute.
322 | */
323 | if (Attributes::isNewStyled($method)) {
324 | /*
325 | * NOTE: When $model->newStyleAttribute or $model->new_style_attribute
326 | * is called, it is being resolved directly by the model itself.
327 | * So, we have to call it as a function to get the return type of the
328 | * callback named get.
329 | */
330 |
331 | return !Attributes::getReflectionOfNewStyleAttribute($method)->getReturnType()?->allowsNull();
332 | }
333 |
334 | return !$method->getReturnType()?->allowsNull();
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/src/Helpers/ModelInspectionResult.php:
--------------------------------------------------------------------------------
1 | |Model $model
13 | * @param Collection $columns
14 | * @param Collection $custom_attributes
15 | * @param Collection $relations
16 | */
17 | public function __construct(
18 | private string|Model $model,
19 | private Collection $columns,
20 | private Collection $custom_attributes,
21 | private Collection $relations
22 | ) {
23 | }
24 |
25 | /**
26 | * @description Generates the model interface
27 | *
28 | * @return string
29 | */
30 | public function __toString(): string {
31 | return $this->getGenerator();
32 | }
33 |
34 | /**
35 | * @return Model|string
36 | */
37 | public function getModel(): Model|string {
38 | return $this->model;
39 | }
40 |
41 | /**
42 | * @return Collection
43 | */
44 | public function getColumns(): Collection {
45 | return $this->columns;
46 | }
47 |
48 | /**
49 | * @return Collection
50 | */
51 | public function getCustomAttributes(): Collection {
52 | return $this->custom_attributes;
53 | }
54 |
55 | /**
56 | * @return Collection
57 | */
58 | public function getRelations(): Collection {
59 | return $this->relations;
60 | }
61 |
62 | public function getGenerator(): Generator {
63 | return new Generator($this);
64 | }
65 |
66 | public function toTypescript(): string {
67 | return (string) $this->getGenerator();
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Helpers/ModelInspector.php:
--------------------------------------------------------------------------------
1 | |Model|null $model
32 | */
33 | public function __construct(
34 | private string|Model|null $model = null
35 | ) {
36 | }
37 |
38 | private static array $defaultRelations = [
39 | BelongsTo::class,
40 | BelongsToMany::class,
41 | HasMany::class,
42 | HasManyThrough::class,
43 | HasOne::class,
44 | HasOneOrMany::class,
45 | HasOneThrough::class,
46 | MorphMany::class,
47 | MorphOne::class,
48 | MorphOneOrMany::class,
49 | MorphPivot::class,
50 | MorphTo::class,
51 | MorphToMany::class,
52 | Pivot::class,
53 | Relation::class
54 | ];
55 |
56 | /**
57 | * @description Returns the list of model-classes in a directory
58 | *
59 | * @param string|array $directories
60 | *
61 | * @return Collection>
62 | *
63 | * @link https://github.com/composer/class-map-generator
64 | */
65 | public static function getModelsIn(string|array $directories): Collection
66 | {
67 | if (is_string($directories)) {
68 | $directories = [$directories];
69 | }
70 |
71 | return collect($directories)
72 | ->map(fn(string $dir) => array_keys(ClassMapGenerator::createMap($dir)))
73 | ->collapse()
74 | ->filter(fn($class) => static::isOfModelType($class));
75 | }
76 |
77 | /**
78 | * @description Checks if the provided class/object is of type Model
79 | *
80 | * @note the parameter $model's type is set to be string|Model, because
81 | * we need to check any kind of object/class to be checked if it
82 | * is of type Model or not. If string|Model is used, strings of any
83 | * type will be passed, but objects other than Model won't be passed
84 | * for testing whether it is of type Model or not.
85 | *
86 | * @param mixed $model
87 | *
88 | * @return bool
89 | */
90 | public static function isOfModelType(mixed $model): bool
91 | {
92 | if (is_string($model) && class_exists($model)) {
93 | return is_subclass_of($model, Model::class);
94 | }
95 |
96 | return $model instanceof Model;
97 | }
98 |
99 | /**
100 | * @description Returns new instance of the Model Inspector class.
101 | *
102 | * @note If value for $model is provided @method inspectionFor() doesn't need to be used
103 | *
104 | * @param class-string|Model|null $model
105 | *
106 | * @return static
107 | */
108 | public static function new(string|Model|null $model = null): static
109 | {
110 | return new static($model);
111 | }
112 |
113 | /**
114 | * @description Used to set Model class for inspection
115 | *
116 | * @param class-string|Model $model
117 | *
118 | * @return $this
119 | */
120 | public function inspectionFor(string|Model $model): static
121 | {
122 | $this->model = $model;
123 |
124 | return $this;
125 | }
126 |
127 | /**
128 | * @description Return model inspection result which contains
129 | * list of database columns, custom attributes, and relations.
130 | *
131 | * @return ModelInspectionResult
132 | * @throws \Exception
133 | *
134 | * @throws ReflectionException
135 | */
136 | public function getInspectionResult(): ModelInspectionResult
137 | {
138 | $this->isModelSet();
139 |
140 | return new ModelInspectionResult(
141 | model : $this->model,
142 | columns : $this->getColumns(),
143 | custom_attributes: $this->getCustomAttributes(),
144 | relations : $this->getRelations()
145 | );
146 | }
147 |
148 | /**
149 | * @description Returns Collection of Database columns
150 | *
151 | * @return Collection
152 | *
153 | * @throws \Exception
154 | */
155 | private function getColumns(): Collection
156 | {
157 | $this->isModelSet();
158 |
159 | $model = static::parseModel($this->model);
160 |
161 | $columns = Schema::getColumns($model->getTable());
162 |
163 | /**
164 | * Model fields name should be exact like column name.
165 | * Fields which are marked as hidden, should not be generated.
166 | * So, those fields are being forgotten (omitted) from the collection.
167 | */
168 | return collect($columns)
169 | ->when(!empty($model->getHidden()), fn(Collection $cols) => $cols->forget($model->getHidden()))
170 | ->map(fn($col) => Column::fromArray($col));
171 | }
172 |
173 | /**
174 | * @description Returns methods which are used to define Custom Attributes
175 | *
176 | * @return Collection
177 | * @throws ReflectionException
178 | *
179 | */
180 | private function getCustomAttributes(): Collection
181 | {
182 | return $this->getMethods()->filter(fn(ReflectionMethod $rf) => Attributes::isAttribute($rf));
183 | }
184 |
185 | /**
186 | * @description Returns methods of a given model, which are used to define relations
187 | *
188 | * @return Collection
189 | * @throws ReflectionException
190 | *
191 | * @throws \Exception
192 | */
193 | private function getRelations(): Collection
194 | {
195 | $this->isModelSet();
196 |
197 | return $this->getMethods()
198 | ->filter(fn(ReflectionMethod $rf) => Attributes::isRelation($rf));
199 | }
200 |
201 |
202 | /**
203 | * @throws ReflectionException
204 | */
205 | private function getMethods(): Collection
206 | {
207 | return collect((new ReflectionClass($this->model))->getMethods(ReflectionMethod::IS_PUBLIC));
208 | }
209 |
210 | /**
211 | * @param class-string|Model $model
212 | *
213 | * @return Model
214 | * @throws \Exception
215 | *
216 | */
217 | public static function parseModel(string|Model $model): Model
218 | {
219 | if (is_string($model)) {
220 | if (!is_subclass_of($model, Model::class)) {
221 | throw new \Exception("$model is not a valid Model Class");
222 | }
223 |
224 | return new $model();
225 | }
226 |
227 | return $model;
228 | }
229 |
230 | /**
231 | * @throws \Exception
232 | */
233 | private function isModelSet(): bool
234 | {
235 | if (!isset($this->model)) {
236 | throw new \Exception('Model Not Set');
237 | }
238 |
239 | return true;
240 | }
241 |
242 | public static function getQualifiedNamespace(string $name): string
243 | {
244 | return str($name)->replace('\\', '.')->value();
245 | }
246 |
247 | public static function getDefaultRelations(): array
248 | {
249 | return static::$defaultRelations;
250 | }
251 |
252 | public static function isDefaultRelation(string $relation): bool
253 | {
254 | return in_array($relation, static::$defaultRelations);
255 | }
256 | }
257 |
--------------------------------------------------------------------------------
/src/Helpers/Reflection.php:
--------------------------------------------------------------------------------
1 | getNamespaceName() === $ro2->getNamespaceName();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Helpers/Types.php:
--------------------------------------------------------------------------------
1 | GenericType::string(comment: 'ASCII String'),
11 | 'bigint' => GenericType::number(),
12 | 'int8' => GenericType::number(),
13 | 'bool' => GenericType::boolean(),
14 | 'binary' => GenericType::unknown(comment: 'Binary Data'),
15 | 'blob' => GenericType::unknown(comment: 'Binary Large Object'),
16 | 'boolean' => GenericType::boolean(),
17 | 'timestamp' => GenericType::string(comment: 'Timestamp'),
18 | 'character' => GenericType::string(comment: 'Character'),
19 | 'varchar' => GenericType::string(comment: 'Variable Character'),
20 | 'char' => GenericType::string(comment: 'Character'),
21 | 'date' => GenericType::string(comment: 'Date'),
22 | 'date_mutable' => GenericType::mutableDate(),
23 | 'date_immutable' => GenericType::immutableDate(),
24 | 'dateinterval' => GenericType::string(comment: 'date interval'),
25 | 'datetime' => GenericType::string(comment: 'Datetime'),
26 | 'datetime_immutable' => GenericType::immutableDatetime(),
27 | 'datetimetz_mutable' => GenericType::mutableDatetimeZ(),
28 | 'datetimetz_immutable' => GenericType::immutableDatetimeZ(),
29 | 'decimal' => GenericType::number(),
30 | 'float' => GenericType::float(),
31 | 'guid' => GenericType::string(comment: 'GUID'),
32 | 'integer' => GenericType::number(),
33 | 'json' => GenericType::json(),
34 | 'simple_array' => GenericType::any(isMultiple: true, comment: 'Simple Array'),
35 | 'smallint' => GenericType::number(comment: "Small Integer"),
36 | 'string' => GenericType::string(),
37 | 'text' => GenericType::string(comment: "Text"),
38 | 'time_mutable' => GenericType::mutableTime(),
39 | 'time_immutable' => GenericType::immutableTime(),
40 | 'serial' => GenericType::number(comment: "Serial"),
41 | 'serial8' => GenericType::number(comment: "Serial 8"),
42 | 'money' => GenericType::number(comment: "Money"),
43 | 'xml' => GenericType::string(comment: "XML"),
44 | 'uuid' => GenericType::string(comment: "UUID"),
45 | 'citext' => GenericType::string(comment: "Case Insensitive Text"),
46 | 'macaddr' => GenericType::string(comment: "MAC Address"),
47 | 'inet' => GenericType::string(comment: "Internet Address"),
48 | 'cidr' => GenericType::string(comment: "CIDR"),
49 | 'tsvector' => GenericType::string(comment: "Text Search Vector"),
50 | 'tsquery' => GenericType::string(comment: "Text Search Query"),
51 | 'tinyint' => GenericType::number(comment: "Tiny Integer"),
52 | 'mediumint' => GenericType::number(comment: "Medium Integer"),
53 | 'numeric' => GenericType::number(comment: "Numeric"),
54 | 'dec' => GenericType::number(comment: "Decimal"),
55 | 'double' => GenericType::number(comment: "Double"),
56 | 'double_precision' => GenericType::number(comment: "Double Precision"),
57 | 'real' => GenericType::number(comment: "Real"),
58 | 'bit' => GenericType::number(comment: "Bit"),
59 | 'enum' => GenericType::string(comment: "Enum"),
60 | 'set' => GenericType::string(comment: "Set"),
61 | 'smalldatetime' => GenericType::string(comment: "Small Datetime"),
62 | 'datetime2' => GenericType::string(comment: "Datetime2"),
63 | 'datetimeoffset' => GenericType::string(comment: "Datetime Offset"),
64 | 'time' => GenericType::string(comment: "Time"),
65 | 'smallmoney' => GenericType::number(comment: "Small Money"),
66 | 'image' => GenericType::unknown(comment: "Image"),
67 | 'ntext' => GenericType::string(comment: "NText"),
68 | 'uniqueidentifier' => GenericType::string(comment: "Unique Identifier"),
69 | 'rowversion' => GenericType::string(comment: "Row Version"),
70 | 'geography' => GenericType::string(comment: "Geography"),
71 | 'geometry' => GenericType::string(comment: "Geometry"),
72 | 'null' => GenericType::unknown(comment: "Null"),
73 | ];
74 |
75 | return $transformers[$key] ?? GenericType::any();
76 | }
77 |
78 | private function __construct() {
79 | }
80 | }
81 |
82 |
--------------------------------------------------------------------------------
/src/LaravelTypescript.php:
--------------------------------------------------------------------------------
1 | {path,contents}
21 | * @throws ReflectionException
22 | */
23 | public function generate(
24 | string|array $sourceDir,
25 | string $outputPath,
26 | ): array {
27 | File::ensureDirectoryExists(dirname($outputPath));
28 |
29 | $contents = $this->toTypescript($sourceDir);
30 |
31 | File::put(path: $outputPath, contents: $contents);
32 |
33 | return [
34 | $outputPath,
35 | $contents,
36 | ];
37 | }
38 |
39 | /**
40 | * @param string|array $sourceDir
41 | *
42 | * @return string
43 | * @throws ReflectionException
44 | */
45 | public function toTypescript(string|array $sourceDir): string {
46 |
47 | return ModelInspector::getModelsIn($sourceDir)
48 | ->map(fn(string $modelClass) => [
49 | 'namespace' => (new ReflectionClass($modelClass))->getNamespaceName(),
50 | 'model' => $modelClass,
51 | ])
52 | ->groupBy('namespace')
53 | ->mapWithKeys(fn(Collection $models, string $namespace) => [
54 | $namespace => $models->pluck('model'),
55 | ])
56 | ->map(function (Collection $modelClasses, string $namespace) {
57 | $namespace = ModelInspector::getQualifiedNamespace($namespace);
58 |
59 | return "declare namespace $namespace {"
60 | .PHP_EOL
61 | .$modelClasses
62 | ->map(
63 | function (string $modelClass) {
64 | return ModelInspector::new($modelClass)
65 | ->getInspectionResult()
66 | ->getGenerator()
67 | ->toTypescript();
68 | }
69 | )
70 | ->implode(fn(string $content) => $content, PHP_EOL.PHP_EOL)
71 | .PHP_EOL
72 | .'}'
73 | .PHP_EOL;
74 | })
75 | ->implode(fn(string $content) => $content, PHP_EOL.PHP_EOL);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/LaravelTypescriptServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->runningInConsole()) {
19 | $this->bootForConsole();
20 | }
21 | }
22 |
23 | /**
24 | * Register any package services.
25 | *
26 | * @return void
27 | */
28 | public function register(): void
29 | {
30 | $this->mergeConfigFrom(__DIR__ . '/../config/laravel-typescript.php', 'laravel-typescript');
31 |
32 | // Register the service the package provides.
33 | $this->app->singleton('laravel-typescript', function ($app) {
34 | return LaravelTypescript::new();
35 | });
36 | }
37 |
38 | /**
39 | * Get the services provided by the provider.
40 | *
41 | * @return array
42 | */
43 | public function provides(): array
44 | {
45 | return ['laravel-typescript'];
46 | }
47 |
48 | /**
49 | * Console-specific booting.
50 | *
51 | * @return void
52 | */
53 | protected function bootForConsole(): void
54 | {
55 | // Publishing the configuration file.
56 | $this->publishes([
57 | __DIR__ . '/../config/laravel-typescript.php' => config_path('laravel-typescript.php'),
58 | ], 'laravel-typescript.config');
59 |
60 | // Registering package commands.
61 | $this->commands([
62 | LaravelTypescriptCommand::class,
63 | ]);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Traits/HasTypeGenerators.php:
--------------------------------------------------------------------------------
1 | getName();
14 | }
15 |
16 | return Types::getGenericType($type);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Types/Definition.php:
--------------------------------------------------------------------------------
1 | value pairs
11 | */
12 | class Definition
13 | {
14 | /**
15 | * @param string $namespace
16 | * @param string $name
17 | * @param string $model
18 | * @param string $modelShortName
19 | * @param array|Collection $types
20 | * @param bool $isRequired
21 | * @param bool $isUndefinable
22 | */
23 | public function __construct(
24 | public string $namespace,
25 | public string $name,
26 | public string $model,
27 | public string $modelShortName,
28 | public array|Collection $types,
29 | public bool $isRequired,
30 | public bool $isUndefinable
31 | )
32 | {
33 | if (is_array($types)) {
34 | $this->types = collect($types);
35 | }
36 | }
37 |
38 | public function getTypes(): Collection
39 | {
40 | return $this->types;
41 | }
42 |
43 | public function __toString(): string
44 | {
45 | return $this->types->implode(fn(Type $type) => (string)$type, ' | ')
46 | . (!$this->isRequired ? ' | null' : '');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Types/EnumType.php:
--------------------------------------------------------------------------------
1 | $enum
12 | *
13 | * @return Type
14 | */
15 | public static function toType(string $enum): Type
16 | {
17 | if (enum_exists($enum)) {
18 | return Type::new(
19 | name: collect($enum::cases())->implode(fn ($option) => "\"$option->value\"", ' | ')
20 | );
21 | }
22 |
23 | return Type::any();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Types/LaravelCastType.php:
--------------------------------------------------------------------------------
1 | before(':')->value(), [
10 | 'int', 'integer', 'real', 'float', 'double', 'decimal', 'string',
11 | 'bool', 'boolean', 'object', 'array', 'json', 'collection', 'date',
12 | 'datetime', 'custom_datetime', 'immutable_date', 'immutable_custom_datetime',
13 | 'immutable_datetime', 'timestamp',
14 | ]);
15 | }
16 |
17 | /**
18 | * @param string $castType
19 | *
20 | * @return Type
21 | *
22 | * @link vendor/laravel/framework/src/Illuminate/Database/Eloquent/Concerns/HasAttributes.php
23 | */
24 | public static function getType(string $castType): Type
25 | {
26 | return match (str($castType)->before(':')->value()) {
27 | 'int',
28 | 'integer',
29 | 'real',
30 | 'float',
31 | 'double',
32 | 'decimal' => Type::number(),
33 | 'string' => Type::string(),
34 | 'bool',
35 | 'boolean' => Type::boolean(),
36 | 'object' => Type::object(),
37 | 'array' => Type::array(),
38 | 'json',
39 | 'collection' => Type::json(),
40 | 'date' => Type::string(comment: 'date string'),
41 | 'datetime' => Type::string(comment: 'datetime string'),
42 | 'custom_datetime' => Type::string(comment: 'custom_datetime string'),
43 | 'immutable_date' => Type::string(comment: 'immutable_date string'),
44 | 'immutable_custom_datetime' => Type::string(comment: 'immutable_custom_datetime string'),
45 | 'immutable_datetime' => Type::string(comment: 'immutable_datetime string'),
46 | 'timestamp' => Type::string(comment: 'timestamp string'),
47 | default => Type::string(comment: 'no specific type')
48 | };
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Types/PhpType.php:
--------------------------------------------------------------------------------
1 | Type::number(),
27 |
28 | /*
29 | * @todo : docblock should be checked to have exact array of type
30 | */
31 | self::ARRAY => Type::any(isMultiple: true),
32 | /*
33 | * @todo : Rather than just generating a generic object interface,
34 | * more detailed interface can be generated in future versions.
35 | */
36 | self::OBJECT => Type::object(),
37 | self::STRING => Type::string(),
38 | self::BOOLEAN, self::BOOL => Type::boolean(),
39 | //self::NULL => Type::any(),
40 | default => Type::any()
41 | };
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Types/RelationType.php:
--------------------------------------------------------------------------------
1 | run($relationClass);
31 | }
32 |
33 | return match ($relationClass) {
34 | //HasOne::class,
35 | //HasOneThrough::class,
36 | //BelongsTo::class,
37 | //MorphOne::class,
38 | //MorphTo::class,
39 | //MorphPivot::class => RelationType::One,
40 |
41 | HasManyThrough::class,
42 | HasMany::class,
43 | BelongsToMany::class,
44 | MorphMany::class,
45 | MorphToMany::class => RelationType::Many,
46 |
47 | HasOneOrMany::class,
48 | MorphOneOrMany::class => RelationType::OneOrMany,
49 |
50 | default => RelationType::One
51 | };
52 | }
53 |
54 | /**
55 | * @param string $relationClass
56 | * @return RelationType
57 | * @todo : Implement Custom relations
58 | */
59 | public static function getCustomReturnCountType(string $relationClass): RelationType
60 | {
61 | return match ($relationClass) {
62 | default => RelationType::One
63 | };
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Types/Type.php:
--------------------------------------------------------------------------------
1 | name)->replace('\\', '.')->value();
25 | }
26 |
27 | public function getName(): string
28 | {
29 | return $this->name;
30 | }
31 |
32 | public function getIsMultiple(): bool|RelationType
33 | {
34 | return $this->isMultiple;
35 | }
36 |
37 | public function __toString(): string
38 | {
39 | $name = $this->getQualifiedName();
40 |
41 | /**
42 | * When the type is of Relation, it can be of one, multiple or one_many in number.
43 | */
44 | if ($this->isMultiple instanceof RelationType) {
45 | return match ($this->getIsMultiple()) {
46 | RelationType::Many => $name . '[]',
47 | RelationType::OneOrMany => implode(' | ', [$name, $name . '[]']),
48 | //RelationType::One => $name,
49 | default => $name
50 | } . ($this->comment ? " /** $this->comment **/" : '');
51 | }
52 |
53 | return $name . ($this->getIsMultiple() ? '[]' : '') . ($this->comment ? " /** $this->comment **/" : '');
54 | }
55 |
56 | public static function new(string $name, bool $isMultiple = false, ?string $comment = null): static
57 | {
58 | return new static(
59 | name : $name,
60 | isMultiple: $isMultiple,
61 | comment : $comment
62 | );
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Util/AccessorResolver.php:
--------------------------------------------------------------------------------
1 | Type::immutableDatetime(),
17 | Carbon::class,
18 | CarbonMutable::class => Type::mutableDatetime(),
19 | default => Type::any()
20 | };
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Util/Counter.php:
--------------------------------------------------------------------------------
1 | RelationType::One,
21 | "Staudenmeir\EloquentHasManyDeep\HasManyDeep" => RelationType::Many,
22 | default => RelationType::One
23 | };
24 | }
25 | }
26 |
--------------------------------------------------------------------------------