1212use Exception ;
1313use 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