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

RonasIT / laravel-entity-generator / 21353881375

26 Jan 2026 10:10AM UTC coverage: 99.902% (-0.1%) from 100.0%
21353881375

Pull #235

github

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

63 of 64 new or added lines in 3 files covered. (98.44%)

1020 of 1021 relevant lines covered (99.9%)

10.85 hits per line

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

99.16
/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_walk($generators, fn ($generator) => $this->runGeneration($generator));
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 = Arr::map($providedOptions, fn ($option) => Str::replace('only-', '', $option));
5✔
204

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

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

213
        return array_intersect_key($this->getGeneratorsMap(), array_flip($generators));
5✔
214
    }
215

216
    protected function getGeneratorsMap(): array
217
    {
218
        return [
10✔
219
            'model' => app(ModelGenerator::class),
10✔
220
            'repository' => app(RepositoryGenerator::class),
10✔
221
            'service' => app(ServiceGenerator::class),
10✔
222
            'resource' => app(ResourceGenerator::class),
10✔
223
            'controller' => app(ControllerGenerator::class),
10✔
224
            'requests' => app(RequestsGenerator::class),
10✔
225
            'migration' => app(MigrationGenerator::class),
10✔
226
            'factory' => app(FactoryGenerator::class),
10✔
227
            'tests' => app(TestsGenerator::class),
10✔
228
            'seeder' => app(SeederGenerator::class),
10✔
229
            'translations' => app(TranslationsGenerator::class),
10✔
230
            'nova-resource' => app(NovaResourceGenerator::class),
10✔
231
            'nova-tests' => app(NovaTestGenerator::class)->setNovaResource($this->option('nova-resource-name')),
10✔
232
        ];
10✔
233
    }
234

235
    protected function runGeneration(EntityGenerator $generator): void
236
    {
237
        $generator
10✔
238
            ->setModel($this->entityName)
10✔
239
            ->setModelSubFolder($this->entityNamespace)
10✔
240
            ->setFields($this->getFields())
10✔
241
            ->setRelations($this->relations)
10✔
242
            ->setCrudOptions($this->getCrudOptions())
10✔
243
            ->generate();
10✔
244
    }
245

246
    protected function getCrudOptions(): array
247
    {
248
        return str_split($this->option('methods'));
11✔
249
    }
250

251
    protected function parseRelations(): void
252
    {
253
        $this->relations = new RelationsDTO(
10✔
254
            hasOne: $this->prepareRelations($this->option('has-one')),
10✔
255
            hasMany: $this->prepareRelations($this->option('has-many')),
10✔
256
            belongsTo: $this->prepareRelations($this->option('belongs-to')),
10✔
257
            belongsToMany: $this->prepareRelations($this->option('belongs-to-many')),
10✔
258
        );
10✔
259
    }
260

261
    protected function prepareRelations(array $relations): array
262
    {
263
        return array_map(function ($relation) {
10✔
264
            $relation = Str::trim($relation, '/');
4✔
265

266
            return $this->convertToPascalCase($relation);
4✔
267
        }, $relations);
10✔
268
    }
269

270
    protected function getFields(): array
271
    {
272
        return Arr::only($this->options(), EntityGenerator::AVAILABLE_FIELDS);
10✔
273
    }
274

275
    protected function validateEntityName(): void
276
    {
277
        if (!preg_match('/^[A-Za-z0-9\/]+$/', $this->argument('name'))) {
13✔
278
            throw new InvalidArgumentException("Invalid entity name {$this->argument('name')}");
1✔
279
        }
280
    }
281

282
    protected function extractEntityNameAndPath(): void
283
    {
284
        list($this->entityName, $entityPath) = extract_last_part($this->argument('name'), '/');
12✔
285

286
        $this->entityNamespace = Str::trim($entityPath, '/');
12✔
287
    }
288

289
    protected function validateCrudOptions(): void
290
    {
291
        $crudOptions = $this->getCrudOptions();
11✔
292

293
        foreach ($crudOptions as $crudOption) {
11✔
294
            if (!in_array($crudOption, MakeEntityCommand::CRUD_OPTIONS)) {
11✔
295
                throw new UnexpectedValueException("Invalid method {$crudOption}.");
1✔
296
            }
297
        }
298
    }
299

300
    protected function validateOnlyApiOption(): void
301
    {
302
        if ($this->option('only-api')) {
12✔
303
            $modelName = Str::studly($this->argument('name'));
1✔
304
            if (!$this->classExists('services', "{$modelName}Service")) {
1✔
305
                throw new ClassNotExistsException('Cannot create API without entity.');
1✔
306
            }
307
        }
308
    }
309

310
    protected function listenEvents(): void
311
    {
312
        Event::listen(
10✔
313
            events: SuccessCreateMessage::class,
10✔
314
            listener: fn (SuccessCreateMessage $event) => $this->info($event->message),
10✔
315
        );
10✔
316

317
        Event::listen(
10✔
318
            events: WarningEvent::class,
10✔
319
            listener: fn (WarningEvent $event) => $this->warn($event->message),
10✔
320
        );
10✔
321
    }
322

323
    protected function convertToPascalCase(string $entityName): string
324
    {
325
        $pascalEntityName = Str::studly($entityName);
10✔
326

327
        if ($entityName !== $pascalEntityName) {
10✔
328
            $this->warn("{$entityName} was converted to {$pascalEntityName}");
1✔
329
        }
330

331
        return $pascalEntityName;
10✔
332
    }
333
}
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