• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

RonasIT / laravel-entity-generator / 20984197786

14 Jan 2026 06:03AM UTC coverage: 95.794% (-4.2%) from 100.0%
20984197786

Pull #235

github

web-flow
Merge 7cfaaa0d2 into 7ea745467
Pull Request #235: feat: ability to set nova resource for nova tests generator

66 of 111 new or added lines in 3 files covered. (59.46%)

29 existing lines in 1 file now uncovered.

1025 of 1070 relevant lines covered (95.79%)

10.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.21
/src/Commands/MakeEntityCommand.php
1
<?php
2

3
namespace RonasIT\Support\Commands;
4

5
use Illuminate\Console\Command;
6
use Illuminate\Support\Arr;
7
use Illuminate\Support\Facades\Config;
8
use Illuminate\Support\Facades\Event;
9
use Illuminate\Support\Str;
10
use InvalidArgumentException;
11
use RonasIT\Support\DTO\RelationsDTO;
12
use RonasIT\Support\Events\SuccessCreateMessage;
13
use RonasIT\Support\Events\WarningEvent;
14
use RonasIT\Support\Exceptions\ClassNotExistsException;
15
use Exception;
16
use RonasIT\Support\Generators\ControllerGenerator;
17
use RonasIT\Support\Generators\EntityGenerator;
18
use RonasIT\Support\Generators\FactoryGenerator;
19
use RonasIT\Support\Generators\MigrationGenerator;
20
use RonasIT\Support\Generators\ModelGenerator;
21
use RonasIT\Support\Generators\NovaResourceGenerator;
22
use RonasIT\Support\Generators\NovaTestGenerator;
23
use RonasIT\Support\Generators\RepositoryGenerator;
24
use RonasIT\Support\Generators\RequestsGenerator;
25
use RonasIT\Support\Generators\ResourceGenerator;
26
use RonasIT\Support\Generators\ServiceGenerator;
27
use RonasIT\Support\Generators\TestsGenerator;
28
use RonasIT\Support\Generators\TranslationsGenerator;
29
use RonasIT\Support\Generators\SeederGenerator;
30
use UnexpectedValueException;
31

32
class MakeEntityCommand extends Command
33
{
34
    private string $entityName;
35
    private string $entityNamespace;
36
    private RelationsDTO $relations;
37

38
    const CRUD_OPTIONS = [
39
        'C', 'R', 'U', 'D'
40
    ];
41

42
    /**
43
     * The name and signature of the console command.
44
     *
45
     * @var string
46
     */
47
    protected $signature = 'make:entity {name : The name of the entity. This name will use as name of models class.}
48
        
49
        {--only-api : Set this flag if you want to create resource, controller, route, requests, tests.}
50
        {--only-entity : Set this flag if you want to create migration, model, repository, service, factory, seeder.}
51
        {--only-model : Set this flag if you want to create only model. This flag is a higher priority than --only-migration, --only-tests and --only-repository.} 
52
        {--only-repository : Set this flag if you want to create only repository. This flag is a higher priority than --only-tests and --only-migration.}
53
        {--only-service : Set this flag if you want to create only service.}
54
        {--only-resource : Set this flag if you want to create only resource.}
55
        {--only-controller : Set this flag if you want to create only controller.}
56
        {--only-requests : Set this flag if you want to create only requests.}
57
        {--only-migration : Set this flag if you want to create only repository. This flag is a higher priority than --only-tests.}
58
        {--only-factory : Set this flag if you want to create only factory.}
59
        {--only-tests : Set this flag if you want to create only tests.}
60
        {--only-seeder : Set this flag if you want to create only seeder.}
61
        {--only-nova-resource : Set this flag if you want to create only nova resource.}
62
        {--only-nova-tests : Set this flag if you want to create only nova resource tests.}
63

64
        {--nova-resource-name= : Override the default Nova resource name to generate a Nova test resource.}           
65
        {--methods=CRUD : Set types of methods to create. Affect on routes, requests classes, controller\'s methods and tests methods.} 
66

67
        {--i|integer=* : Add integer field to entity.}
68
        {--I|integer-required=* : Add required integer field to entity. If you want to specify default value you have to do it manually.}
69
        {--f|float=* : Add float field to entity.}
70
        {--F|float-required=* : Add required float field to entity. If you want to specify default value you have to do it manually.}
71
        {--s|string=* : Add string field to entity. Default type is VARCHAR(255) but you can change it manually in migration.}
72
        {--S|string-required=* : Add required string field to entity. If you want to specify default value ir size you have to do it manually.}
73
        {--b|boolean=* : Add boolean field to entity.}
74
        {--B|boolean-required=* : Add boolean field to entity. If you want to specify default value you have to do it manually.}
75
        {--t|timestamp=* : Add timestamp field to entity.}
76
        {--T|timestamp-required=* : Add timestamp field to entity. If you want to specify default value you have to do it manually.}
77
        {--j|json=* : Add json field to entity.}
78
        
79
        {--a|has-one=* : Set hasOne relations between you entity and existed entity.}
80
        {--A|has-many=* : Set hasMany relations between you entity and existed entity.}
81
        {--e|belongs-to=* : Set belongsTo relations between you entity and existed entity.}
82
        {--E|belongs-to-many=* : Set belongsToMany relations between you entity and existed entity.}';
83

84
    /**
85
     * The console command description.
86
     *
87
     * @var string
88
     */
89
    protected $description = 'Make entity with Model, Repository, Service, Migration, Controller, Resource and Nova Resource.';
90

91
    /**
92
     * Execute the console command.
93
     *
94
     * @return void
95
     */
96
    public function handle(): void
97
    {
98
        $this->validateInput();
12✔
99
        $this->checkConfigs();
9✔
100
        $this->listenEvents();
9✔
101
        $this->parseRelations();
9✔
102
        $this->entityName = $this->convertToPascalCase($this->entityName);
9✔
103

104
        try {
105
            $this->generate();
9✔
106
        } catch (Exception $e) {
3✔
107
            $this->error($e->getMessage());
3✔
108
        }
109
    }
110

111
    protected function checkConfigs(): void
112
    {
113
        $packageConfigPath = __DIR__ . '/../../config/entity-generator.php';
9✔
114
        $packageConfigs = require $packageConfigPath;
9✔
115

116
        $projectConfigs = config('entity-generator');
9✔
117

118
        $newConfig = $this->outputNewConfig($packageConfigs, $projectConfigs);
9✔
119

120
        if ($newConfig !== $projectConfigs) {
9✔
121
            $this->comment('Config has been updated');
1✔
122
            Config::set('entity-generator', $newConfig);
1✔
123
            file_put_contents(config_path('entity-generator.php'), "<?php\n\nreturn" . $this->customVarExport($newConfig) . ';');
1✔
124
        }
125
    }
126

127
    protected function outputNewConfig(array $packageConfigs, array $projectConfigs): array
128
    {
129
        $flattenedPackageConfigs = Arr::dot($packageConfigs);
9✔
130
        $flattenedProjectConfigs = Arr::dot($projectConfigs);
9✔
131

132
        $newConfig = array_merge($flattenedPackageConfigs, $flattenedProjectConfigs);
9✔
133

134
        $differences = array_diff_key($newConfig, $flattenedProjectConfigs);
9✔
135

136
        foreach ($differences as $differenceKey => $differenceValue) {
9✔
137
            $this->comment("Key '{$differenceKey}' was missing in your config, we added it with the value '{$differenceValue}'");
1✔
138
        }
139

140
        return array_undot($newConfig);
9✔
141
    }
142

143
    protected function customVarExport(array $expression): string
144
    {
145
        $defaultExpression = var_export($expression, true);
1✔
146

147
        $patterns = [
1✔
148
            '/array/' => '',
1✔
149
            '/\(/' => '[',
1✔
150
            '/\)/' => ']',
1✔
151
            '/=> \\n/' => '=>',
1✔
152
            '/=>.+\[/' => '=> [',
1✔
153
            '/^ {8}/m' => str_repeat(' ', 10),
1✔
154
            '/^ {6}/m' => str_repeat(' ', 8),
1✔
155
            '/^ {4}/m' => str_repeat(' ', 6),
1✔
156
            '/^ {2}/m' => str_repeat(' ', 4),
1✔
157
        ];
1✔
158

159
        return preg_replace(array_keys($patterns), array_values($patterns), $defaultExpression);
1✔
160
    }
161

162
    protected function classExists(string $path, string $name): bool
163
    {
164
        $paths = config('entity-generator.paths');
1✔
165

166
        $entitiesPath = $paths[$path];
1✔
167

168
        $classPath = base_path("{$entitiesPath}/{$name}.php");
1✔
169

170
        return file_exists($classPath);
1✔
171
    }
172

173
    protected function validateInput(): void
174
    {
175
        $this->validateEntityName();
12✔
176
        $this->extractEntityNameAndPath();
11✔
177
        $this->validateOnlyApiOption();
11✔
178
        $this->validateCrudOptions();
10✔
179
    }
180
    protected function generate(): void
181
    {
182
        $providedOptions = $this->getProvidedOnlyOptions();
9✔
183

184
        if (!empty($providedOptions)) {
9✔
185
            foreach ($providedOptions as $option) {
4✔
186
                $generators = $this->getGenerators($option);
4✔
187

188
                foreach ($generators as $generator) {
4✔
189
                    $this->runGeneration($generator);
4✔
190
                }
191
            }
192
        } else {
193
            array_map(fn ($generator) => $this->runGeneration($generator), [
5✔
194
                app(ModelGenerator::class),
5✔
195
                app(RepositoryGenerator::class),
5✔
196
                app(ServiceGenerator::class),
5✔
197
                app(RequestsGenerator::class),
5✔
198
                app(ResourceGenerator::class),
5✔
199
                app(ControllerGenerator::class),
5✔
200
                app(MigrationGenerator::class),
5✔
201
                app(FactoryGenerator::class),
5✔
202
                app(TestsGenerator::class),
5✔
203
                app(TranslationsGenerator::class),
5✔
204
                app(SeederGenerator::class),
5✔
205
                app(NovaResourceGenerator::class),
5✔
206
                app(NovaTestGenerator::class),
5✔
207
            ]);
5✔
208
        }
209
    }
210

211
    protected function getProvidedOnlyOptions(): array
212
    {
213
        $providedOptions = array_filter(
9✔
214
            array: $this->options(),
9✔
215
            callback: fn ($value, $name) => Str::startsWith($name, 'only-') && $value === true,
9✔
216
            mode: ARRAY_FILTER_USE_BOTH,
9✔
217
        );
9✔
218

219
        return array_keys($providedOptions);
9✔
220
    }
221

222
    protected function getGenerators(string $option): array
223
    {
224
        return match ($option) {
4✔
NEW
225
            'only-api' => [
×
NEW
226
                app(ResourceGenerator::class),
×
NEW
227
                app(ControllerGenerator::class),
×
NEW
228
                app(RequestsGenerator::class),
×
NEW
229
                app(TestsGenerator::class),
×
NEW
230
            ],
×
NEW
231
            'only-entity' => [
×
NEW
232
                app(MigrationGenerator::class),
×
NEW
233
                app(ModelGenerator::class),
×
NEW
234
                app(RepositoryGenerator::class),
×
NEW
235
                app(ServiceGenerator::class),
×
NEW
236
                app(FactoryGenerator::class),
×
NEW
237
                app(SeederGenerator::class),
×
NEW
238
            ],
×
239
            'only-model' => [
3✔
240
                app(ModelGenerator::class),
3✔
241
            ],
3✔
242
            'only-repository' => [
1✔
243
                app(RepositoryGenerator::class),
1✔
244
            ],
1✔
NEW
UNCOV
245
            'only-service' => [
×
NEW
UNCOV
246
                app(ServiceGenerator::class),
×
NEW
247
            ],
×
NEW
UNCOV
248
            'only-resource' => [
×
NEW
249
                app(ResourceGenerator::class),
×
NEW
UNCOV
250
            ],
×
NEW
UNCOV
251
            'only-controller' => [
×
NEW
UNCOV
252
                app(ControllerGenerator::class),
×
NEW
UNCOV
253
            ],
×
NEW
UNCOV
254
            'only-requests' => [
×
NEW
UNCOV
255
                app(RequestsGenerator::class),
×
NEW
UNCOV
256
            ],
×
NEW
UNCOV
257
            'only-migration' => [
×
NEW
UNCOV
258
                app(MigrationGenerator::class),
×
NEW
UNCOV
259
            ],
×
NEW
UNCOV
260
            'only-factory' => [
×
NEW
UNCOV
261
                app(FactoryGenerator::class),
×
NEW
UNCOV
262
            ],
×
NEW
UNCOV
263
            'only-tests' => [
×
NEW
UNCOV
264
                app(FactoryGenerator::class),
×
NEW
UNCOV
265
                app(TestsGenerator::class),
×
NEW
UNCOV
266
            ],
×
NEW
UNCOV
267
            'only-seeder' => [
×
NEW
UNCOV
268
                app(SeederGenerator::class),
×
NEW
UNCOV
269
            ],
×
NEW
UNCOV
270
            'only-nova-resource' => [
×
NEW
UNCOV
271
                app(NovaResourceGenerator::class),
×
NEW
UNCOV
272
            ],
×
NEW
UNCOV
273
            'only-nova-tests' => [
×
NEW
UNCOV
274
                app(NovaTestGenerator::class)->setNovaResource($this->option('nova-resource-name')),
×
NEW
UNCOV
275
            ],
×
276
            default => [],
4✔
277
        };
4✔
278
    }
279

280
    protected function runGeneration(EntityGenerator $generator): void
281
    {
282
        $generator
9✔
283
            ->setModel($this->entityName)
9✔
284
            ->setModelSubFolder($this->entityNamespace)
9✔
285
            ->setFields($this->getFields())
9✔
286
            ->setRelations($this->relations)
9✔
287
            ->setCrudOptions($this->getCrudOptions())
9✔
288
            ->generate();
9✔
289
    }
290

291
    protected function getCrudOptions(): array
292
    {
293
        return str_split($this->option('methods'));
10✔
294
    }
295

296
    protected function parseRelations(): void
297
    {
298
        $this->relations = new RelationsDTO(
9✔
299
            hasOne: $this->prepareRelations($this->option('has-one')),
9✔
300
            hasMany: $this->prepareRelations($this->option('has-many')),
9✔
301
            belongsTo: $this->prepareRelations($this->option('belongs-to')),
9✔
302
            belongsToMany: $this->prepareRelations($this->option('belongs-to-many')),
9✔
303
        );
9✔
304
    }
305

306
    protected function prepareRelations(array $relations): array
307
    {
308
        return array_map(function ($relation) {
9✔
309
            $relation = Str::trim($relation, '/');
4✔
310

311
            return $this->convertToPascalCase($relation);
4✔
312
        }, $relations);
9✔
313
    }
314

315
    protected function getFields(): array
316
    {
317
        return Arr::only($this->options(), EntityGenerator::AVAILABLE_FIELDS);
9✔
318
    }
319

320
    protected function validateEntityName(): void
321
    {
322
        if (!preg_match('/^[A-Za-z0-9\/]+$/', $this->argument('name'))) {
12✔
323
            throw new InvalidArgumentException("Invalid entity name {$this->argument('name')}");
1✔
324
        }
325
    }
326

327
    protected function extractEntityNameAndPath(): void
328
    {
329
        list($this->entityName, $entityPath) = extract_last_part($this->argument('name'), '/');
11✔
330

331
        $this->entityNamespace = Str::trim($entityPath, '/');
11✔
332
    }
333

334
    protected function validateCrudOptions(): void
335
    {
336
        $crudOptions = $this->getCrudOptions();
10✔
337

338
        foreach ($crudOptions as $crudOption) {
10✔
339
            if (!in_array($crudOption, MakeEntityCommand::CRUD_OPTIONS)) {
10✔
340
                throw new UnexpectedValueException("Invalid method {$crudOption}.");
1✔
341
            }
342
        }
343
    }
344

345
    protected function validateOnlyApiOption(): void
346
    {
347
        if ($this->option('only-api')) {
11✔
348
            $modelName = Str::studly($this->argument('name'));
1✔
349
            if (!$this->classExists('services', "{$modelName}Service")) {
1✔
350
                throw new ClassNotExistsException('Cannot create API without entity.');
1✔
351
            }
352
        }
353
    }
354

355
    protected function listenEvents(): void
356
    {
357
        Event::listen(
9✔
358
            events: SuccessCreateMessage::class,
9✔
359
            listener: fn (SuccessCreateMessage $event) => $this->info($event->message),
9✔
360
        );
9✔
361

362
        Event::listen(
9✔
363
            events: WarningEvent::class,
9✔
364
            listener: fn (WarningEvent $event) => $this->warn($event->message),
9✔
365
        );
9✔
366
    }
367

368
    protected function convertToPascalCase(string $entityName): string
369
    {
370
        $pascalEntityName = Str::studly($entityName);
9✔
371

372
        if ($entityName !== $pascalEntityName) {
9✔
373
            $this->warn("{$entityName} was converted to {$pascalEntityName}");
1✔
374
        }
375

376
        return $pascalEntityName;
9✔
377
    }
378
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc