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

RonasIT / laravel-entity-generator / 21060145457

16 Jan 2026 08:16AM UTC coverage: 99.806% (-0.2%) from 100.0%
21060145457

Pull #235

github

web-flow
Merge 0055c0d95 into 962b971b7
Pull Request #235: feat: ability to set nova resource for nova tests generator

70 of 72 new or added lines in 3 files covered. (97.22%)

1027 of 1029 relevant lines covered (99.81%)

10.8 hits per line

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

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

3
namespace RonasIT\Support\Commands;
4

5
use Exception;
6
use Illuminate\Console\Command;
7
use Illuminate\Support\Arr;
8
use Illuminate\Support\Facades\Config;
9
use Illuminate\Support\Facades\Event;
10
use Illuminate\Support\Str;
11
use InvalidArgumentException;
12
use RonasIT\Support\DTO\RelationsDTO;
13
use RonasIT\Support\Events\SuccessCreateMessage;
14
use RonasIT\Support\Events\WarningEvent;
15
use RonasIT\Support\Exceptions\ClassNotExistsException;
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\SeederGenerator;
27
use RonasIT\Support\Generators\ServiceGenerator;
28
use RonasIT\Support\Generators\TestsGenerator;
29
use RonasIT\Support\Generators\TranslationsGenerator;
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
    public function handle(): void
95
    {
96
        $this->validateInput();
13✔
97
        $this->checkConfigs();
10✔
98
        $this->listenEvents();
10✔
99
        $this->parseRelations();
10✔
100
        $this->entityName = $this->convertToPascalCase($this->entityName);
10✔
101

102
        try {
103
            $this->generate();
10✔
104
        } catch (Exception $e) {
3✔
105
            $this->error($e->getMessage());
3✔
106
        }
107
    }
108

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

114
        $projectConfigs = config('entity-generator');
10✔
115

116
        $newConfig = $this->outputNewConfig($packageConfigs, $projectConfigs);
10✔
117

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

125
    protected function outputNewConfig(array $packageConfigs, array $projectConfigs): array
126
    {
127
        $flattenedPackageConfigs = Arr::dot($packageConfigs);
10✔
128
        $flattenedProjectConfigs = Arr::dot($projectConfigs);
10✔
129

130
        $newConfig = array_merge($flattenedPackageConfigs, $flattenedProjectConfigs);
10✔
131

132
        $differences = array_diff_key($newConfig, $flattenedProjectConfigs);
10✔
133

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

138
        return array_undot($newConfig);
10✔
139
    }
140

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

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

157
        return preg_replace(array_keys($patterns), array_values($patterns), $defaultExpression);
1✔
158
    }
159

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

164
        $entitiesPath = $paths[$path];
1✔
165

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

168
        return file_exists($classPath);
1✔
169
    }
170

171
    protected function validateInput(): void
172
    {
173
        $this->validateEntityName();
13✔
174
        $this->extractEntityNameAndPath();
12✔
175
        $this->validateOnlyApiOption();
12✔
176
        $this->validateCrudOptions();
11✔
177
    }
178

179
    protected function generate(): void
180
    {
181
        $providedOnlyOptions = $this->getProvidedOnlyOptions();
10✔
182

183
        $generators = !empty($providedOnlyOptions)
10✔
184
            ? $this->getOnlyGenerators($providedOnlyOptions)
5✔
185
            : $this->getGeneratorsMap();
5✔
186

187
        array_map(fn ($generator) => $this->runGeneration($generator), $generators);
10✔
188
    }
189

190
    protected function getProvidedOnlyOptions(): array
191
    {
192
        $providedOptions = array_filter(
10✔
193
            array: $this->options(),
10✔
194
            callback: fn ($value, $name) => Str::startsWith($name, 'only-') && $value === true,
10✔
195
            mode: ARRAY_FILTER_USE_BOTH,
10✔
196
        );
10✔
197

198
        return array_keys($providedOptions);
10✔
199
    }
200

201
    protected function getOnlyGenerators(array $providedOptions): array
202
    {
203
        $generators = [];
5✔
204

205
        if (in_array('only-api', $providedOptions)) {
5✔
NEW
206
            array_push($generators, 'resource', 'controller', 'requests', 'factory', 'tests');
×
207
        }
208

209
        if (in_array('only-entity', $providedOptions)) {
5✔
210
            array_push($generators, 'migration', 'model', 'repository', 'service', 'factory', 'seeder');
1✔
211
        }
212

213
        if (in_array('only-tests', $providedOptions)) {
5✔
NEW
214
            array_push($generators, 'factory', 'tests');
×
215
        }
216

217
        $onlyGenerators = Arr::map($providedOptions, fn ($option) => Str::replace('only-', '', $option));
5✔
218

219
        array_push($generators, ...$onlyGenerators);
5✔
220

221
        return array_filter(
5✔
222
            array: $this->getGeneratorsMap(),
5✔
223
            callback: fn ($generator) => in_array($generator, $generators),
5✔
224
            mode: ARRAY_FILTER_USE_KEY,
5✔
225
        );
5✔
226
    }
227

228
    protected function getGeneratorsMap(): array
229
    {
230
        return [
10✔
231
            'model' => app(ModelGenerator::class),
10✔
232
            'repository' => app(RepositoryGenerator::class),
10✔
233
            'service' => app(ServiceGenerator::class),
10✔
234
            'resource' => app(ResourceGenerator::class),
10✔
235
            'controller' => app(ControllerGenerator::class),
10✔
236
            'requests' => app(RequestsGenerator::class),
10✔
237
            'migration' => app(MigrationGenerator::class),
10✔
238
            'factory' => app(FactoryGenerator::class),
10✔
239
            'tests' => app(TestsGenerator::class),
10✔
240
            'seeder' => app(SeederGenerator::class),
10✔
241
            'translations' => app(TranslationsGenerator::class),
10✔
242
            'nova-resource' => app(NovaResourceGenerator::class),
10✔
243
            'nova-tests' => app(NovaTestGenerator::class)->setNovaResource($this->option('nova-resource-name')),
10✔
244
        ];
10✔
245
    }
246

247
    protected function runGeneration(EntityGenerator $generator): void
248
    {
249
        $generator
10✔
250
            ->setModel($this->entityName)
10✔
251
            ->setModelSubFolder($this->entityNamespace)
10✔
252
            ->setFields($this->getFields())
10✔
253
            ->setRelations($this->relations)
10✔
254
            ->setCrudOptions($this->getCrudOptions())
10✔
255
            ->generate();
10✔
256
    }
257

258
    protected function getCrudOptions(): array
259
    {
260
        return str_split($this->option('methods'));
11✔
261
    }
262

263
    protected function parseRelations(): void
264
    {
265
        $this->relations = new RelationsDTO(
10✔
266
            hasOne: $this->prepareRelations($this->option('has-one')),
10✔
267
            hasMany: $this->prepareRelations($this->option('has-many')),
10✔
268
            belongsTo: $this->prepareRelations($this->option('belongs-to')),
10✔
269
            belongsToMany: $this->prepareRelations($this->option('belongs-to-many')),
10✔
270
        );
10✔
271
    }
272

273
    protected function prepareRelations(array $relations): array
274
    {
275
        return array_map(function ($relation) {
10✔
276
            $relation = Str::trim($relation, '/');
4✔
277

278
            return $this->convertToPascalCase($relation);
4✔
279
        }, $relations);
10✔
280
    }
281

282
    protected function getFields(): array
283
    {
284
        return Arr::only($this->options(), EntityGenerator::AVAILABLE_FIELDS);
10✔
285
    }
286

287
    protected function validateEntityName(): void
288
    {
289
        if (!preg_match('/^[A-Za-z0-9\/]+$/', $this->argument('name'))) {
13✔
290
            throw new InvalidArgumentException("Invalid entity name {$this->argument('name')}");
1✔
291
        }
292
    }
293

294
    protected function extractEntityNameAndPath(): void
295
    {
296
        list($this->entityName, $entityPath) = extract_last_part($this->argument('name'), '/');
12✔
297

298
        $this->entityNamespace = Str::trim($entityPath, '/');
12✔
299
    }
300

301
    protected function validateCrudOptions(): void
302
    {
303
        $crudOptions = $this->getCrudOptions();
11✔
304

305
        foreach ($crudOptions as $crudOption) {
11✔
306
            if (!in_array($crudOption, MakeEntityCommand::CRUD_OPTIONS)) {
11✔
307
                throw new UnexpectedValueException("Invalid method {$crudOption}.");
1✔
308
            }
309
        }
310
    }
311

312
    protected function validateOnlyApiOption(): void
313
    {
314
        if ($this->option('only-api')) {
12✔
315
            $modelName = Str::studly($this->argument('name'));
1✔
316
            if (!$this->classExists('services', "{$modelName}Service")) {
1✔
317
                throw new ClassNotExistsException('Cannot create API without entity.');
1✔
318
            }
319
        }
320
    }
321

322
    protected function listenEvents(): void
323
    {
324
        Event::listen(
10✔
325
            events: SuccessCreateMessage::class,
10✔
326
            listener: fn (SuccessCreateMessage $event) => $this->info($event->message),
10✔
327
        );
10✔
328

329
        Event::listen(
10✔
330
            events: WarningEvent::class,
10✔
331
            listener: fn (WarningEvent $event) => $this->warn($event->message),
10✔
332
        );
10✔
333
    }
334

335
    protected function convertToPascalCase(string $entityName): string
336
    {
337
        $pascalEntityName = Str::studly($entityName);
10✔
338

339
        if ($entityName !== $pascalEntityName) {
10✔
340
            $this->warn("{$entityName} was converted to {$pascalEntityName}");
1✔
341
        }
342

343
        return $pascalEntityName;
10✔
344
    }
345
}
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