Skip to content

Commit 32de397

Browse files
committed
Convert TypescriptifyModel class to non-static, implement visibility for hidden properties
1 parent f435ea8 commit 32de397

File tree

2 files changed

+79
-81
lines changed

2 files changed

+79
-81
lines changed

src/Commands/TypeScriptifyModelCommand.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ class TypeScriptifyModelCommand extends Command {
1212
*
1313
* @var string
1414
*/
15-
protected $signature = 'typescriptify:model {model : The fully qualified class name for the model - e.g. App\Models\User}';
15+
protected $signature = 'typescriptify:model
16+
{model : The fully qualified class name for the model - e.g. App\Models\User}
17+
{--includeHidden : Include the protected $hidden properties}';
1618

1719
/**
1820
* The console command description.
@@ -27,7 +29,9 @@ class TypeScriptifyModelCommand extends Command {
2729
* @return int
2830
*/
2931
public function handle() {
30-
echo TypeScriptifyModel::generate($this->argument('model'));
32+
echo (new TypeScriptifyModel($this->argument('model')))
33+
->includeHidden($this->option('includeHidden'))
34+
->generate();
3135

3236
return Command::SUCCESS;
3337
}

src/TypeScriptifyModel.php

Lines changed: 73 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,45 @@
1212
use Exception;
1313
use stdClass;
1414

15-
class TypeScriptifyModel {
15+
final class TypeScriptifyModel {
1616
/**
17-
* The fully qualified model name.
17+
* The supported database connections.
1818
*
19-
* @var string|null
19+
* @var array
2020
*/
21-
private static string|null $fullyQualifiedModelName = null;
21+
private const SUPPORTED_DATABASE_CONNECTIONS = [
22+
'mysql',
23+
];
2224

2325
/**
2426
* The instantiated model.
2527
*
26-
* @var \Illuminate\Database\Eloquent\Model|null
28+
* @var \Illuminate\Database\Eloquent\Model
2729
*/
28-
private static Model|null $model = null;
30+
private readonly Model $model;
2931

3032
/**
31-
* The supported database connections.
33+
* Whether to include the model's $hidden properties.
3234
*
33-
* @var array
35+
* @var bool $includeHidden
3436
*/
35-
private const SUPPORTED_DATABASE_CONNECTIONS = [
36-
'mysql',
37-
];
37+
private bool $includeHidden = false;
38+
39+
/**
40+
* @param string $fullyQualifiedModelName The fully qualified model class name.
41+
*/
42+
public function __construct(
43+
private readonly string $fullyQualifiedModelName,
44+
) {
45+
$this->model = new $fullyQualifiedModelName;
46+
}
3847

3948
/**
4049
* Check if the current database connection type is supported.
4150
*
4251
* @return bool
4352
*/
44-
private static function hasSupportedDatabaseConnection(): bool {
53+
private function hasSupportedDatabaseConnection(): bool {
4554
return collect(self::SUPPORTED_DATABASE_CONNECTIONS)->contains(DB::getDefaultConnection());
4655
}
4756

@@ -50,72 +59,70 @@ private static function hasSupportedDatabaseConnection(): bool {
5059
*
5160
* @return bool
5261
*/
53-
private static function hasValidModel(): bool {
54-
$className = self::$fullyQualifiedModelName;
55-
56-
if (is_null($className)) return false;
57-
if (!class_exists($className)) return false;
58-
if (!is_subclass_of($className, Model::class)) return false;
62+
private function hasValidModel(): bool {
63+
if (is_null($this->fullyQualifiedModelName)) return false;
64+
if (!class_exists($this->fullyQualifiedModelName)) return false;
65+
if (!is_subclass_of($this->fullyQualifiedModelName, Model::class)) return false;
5966

6067
return true;
6168
}
6269

6370
/**
64-
* Get the table name for the supplied model.
71+
* Check if the `$attribute` attribute exists in the protected $dates array.
6572
*
66-
* @return string
67-
*/
68-
private static function getTableName(): string {
69-
return (self::$model)->getTable();
70-
}
71-
72-
/**
73-
* Check if the `$columnField` attribute exists in the protected $dates array.
74-
*
75-
* @param string $columnField
73+
* @param string $attribute
7674
*
7775
* @return bool
7876
*/
79-
private static function isAttributeCastedInDates(string $columnField): bool {
80-
return in_array($columnField, (self::$model)->getDates(), false);
77+
private function isAttributeCastedInDates(string $attribute): bool {
78+
return in_array($attribute, $this->model->getDates(), false);
8179
}
8280

8381
/**
84-
* Check if the `$columnField` attribute has a native type cast.
82+
* Check if the `$attribute` attribute has a native type cast.
8583
*
86-
* @param string $columnField
84+
* @param string $attribute
8785
*
8886
* @return bool
8987
*/
90-
private static function isAttributeNativelyCasted(string $columnField): bool {
91-
$model = self::$model;
92-
88+
private function isAttributeNativelyCasted(string $attribute): bool {
9389
// If $columnField exists in the $model->casts array.
94-
if ($model->hasCast($columnField)) return true;
90+
if ($this->model->hasCast($attribute)) return true;
9591

9692
// If $columnField exists in the $model->dates array.
97-
if (self::isAttributeCastedInDates($columnField)) return true;
93+
if ($this->isAttributeCastedInDates($attribute)) return true;
9894

9995
return false;
10096
}
10197

98+
/**
99+
* Is `$attribute` a hidden attribute?
100+
*
101+
* @param string $attribute
102+
*
103+
* @return bool
104+
*/
105+
private function isAttributeHidden(string $attribute): bool {
106+
return in_array($attribute, $this->model->getHidden());
107+
}
108+
102109
/**
103110
* Map a native casted attribute (casted via $casts/$dates) to a TypeScript type.
104111
*
105112
* @param string $columnField
106113
*
107114
* @return string
108115
*/
109-
private static function mapNativeCastToTypeScriptType(string $columnField): string {
116+
private function mapNativeCastToTypeScriptType(string $columnField): string {
110117
// If the attribute is casted to a date via $model->dates, it won't exist in the underlying $model->casts array.
111118
// That means if we called `getCastType` with it, it would throw an error because the key wouldn't exist.
112119
// We know dates get serialized to strings, so we can avoid that by short circuiting here.
113-
if (self::isAttributeCastedInDates($columnField)) return 'string';
120+
if ($this->isAttributeCastedInDates($columnField)) return 'string';
114121

115122
// The `getCastType` method is protected, therefore we need to use reflection to call it.
116-
$getCastType = new ReflectionMethod(self::$model, 'getCastType');
123+
$getCastType = new ReflectionMethod($this->model, 'getCastType');
117124

118-
$castType = Str::of($getCastType->invoke(self::$model, $columnField));
125+
$castType = Str::of($getCastType->invoke($this->model, $columnField));
119126

120127
return match (true) {
121128
$castType->is('int') => 'number',
@@ -149,7 +156,7 @@ private static function mapNativeCastToTypeScriptType(string $columnField): stri
149156
*
150157
* @return string
151158
*/
152-
private static function mapDatabaseTypeToTypeScriptType(Stringable $columnType): string {
159+
private function mapDatabaseTypeToTypeScriptType(Stringable $columnType): string {
153160
return match (true) {
154161
$columnType->startsWith('bit') => 'number',
155162
$columnType->startsWith('int') => 'number',
@@ -195,13 +202,13 @@ private static function mapDatabaseTypeToTypeScriptType(Stringable $columnType):
195202
*
196203
* @return string
197204
*/
198-
private static function getTypeScriptType(stdClass $columnSchema): string {
205+
private function getTypeScriptType(stdClass $columnSchema): string {
199206
$columnType = Str::of($columnSchema->Type);
200207

201-
if (self::isAttributeNativelyCasted($columnSchema->Field)) {
202-
$mappedType = self::mapNativeCastToTypeScriptType($columnSchema->Field);
208+
if ($this->isAttributeNativelyCasted($columnSchema->Field)) {
209+
$mappedType = $this->mapNativeCastToTypeScriptType($columnSchema->Field);
203210
} else {
204-
$mappedType = self::mapDatabaseTypeToTypeScriptType($columnType);
211+
$mappedType = $this->mapDatabaseTypeToTypeScriptType($columnType);
205212
}
206213

207214
// We can't do much with an unknown type.
@@ -217,13 +224,15 @@ private static function getTypeScriptType(stdClass $columnSchema): string {
217224
*
218225
* @return string
219226
*/
220-
private static function generateInterface(): string {
221-
$tableColumns = collect(DB::select(DB::raw('SHOW COLUMNS FROM ' . self::getTableName())));
227+
private function generateInterface(): string {
228+
$tableColumns = collect(DB::select(DB::raw('SHOW COLUMNS FROM ' . $this->model->getTable())));
222229

223-
$str = 'interface ' . (Str::of(self::$fullyQualifiedModelName)->afterLast('\\')) . " {\n";
230+
$str = 'interface ' . (Str::of($this->fullyQualifiedModelName)->afterLast('\\')) . " {\n";
224231

225232
$tableColumns->each(function ($column) use (&$str) {
226-
$str .= (' ' . $column->Field . ': ' . self::getTypeScriptType($column) . ";\n");
233+
if (!$this->includeHidden && $this->isAttributeHidden($column->Field)) return;
234+
235+
$str .= (' ' . $column->Field . ': ' . $this->getTypeScriptType($column) . ";\n");
227236
});
228237

229238
$str .= "}\n";
@@ -232,49 +241,34 @@ private static function generateInterface(): string {
232241
}
233242

234243
/**
235-
* Initialize this class.
244+
* Set whether we should include the model's protected $hidden attributes.
236245
*
237-
* @param string $fullyQualifiedModelName
246+
* @param bool $includeHidden
238247
*
239-
* @return void
248+
* @return self
240249
*/
241-
private static function initialize(string $fullyQualifiedModelName): void {
242-
self::$fullyQualifiedModelName = $fullyQualifiedModelName;
243-
self::$model = new (self::$fullyQualifiedModelName);
244-
}
250+
public function includeHidden(bool $includeHidden): self {
251+
$this->includeHidden = $includeHidden;
245252

246-
/**
247-
* Reset this class.
248-
*
249-
* @return void
250-
*/
251-
private static function reset(): void {
252-
self::$fullyQualifiedModelName = null;
253-
self::$model = null;
253+
return $this;
254254
}
255255

256256
/**
257257
* Generate the TypeScript interface.
258258
*
259-
* @param string $fullyQualifiedModelName
260-
*
261259
* @return string
260+
*
261+
* @throws \Exception
262262
*/
263-
public static function generate(string $fullyQualifiedModelName): string {
264-
self::initialize($fullyQualifiedModelName);
265-
266-
if (!self::hasValidModel()) {
263+
public function generate(): string {
264+
if (!$this->hasValidModel()) {
267265
throw new Exception('That\'s not a valid model!');
268266
}
269267

270-
if (!self::hasSupportedDatabaseConnection()) {
271-
throw new Exception('Your database connection is currently unsupported! The following database connections are supported: ' . collect(self::SUPPORTED_DATABASE_CONNECTIONS)->join(', '));
268+
if (!$this->hasSupportedDatabaseConnection()) {
269+
throw new Exception('Your database connection is currently unsupported! The following database connections are supported: ' . implode(', ', self::SUPPORTED_DATABASE_CONNECTIONS));
272270
}
273271

274-
$interface = self::generateInterface();
275-
276-
self::reset();
277-
278-
return $interface;
272+
return $this->generateInterface();
279273
}
280274
}

0 commit comments

Comments
 (0)