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

RonasIT / laravel-entity-generator / 21617314987

03 Feb 2026 04:42AM UTC coverage: 99.906% (+0.004%) from 99.902%
21617314987

Pull #241

github

web-flow
Merge 27cda4fc6 into d1c1c7c42
Pull Request #241: [239]: append field modifiers instead of uppercase options

137 of 137 new or added lines in 15 files covered. (100.0%)

1 existing line in 1 file now uncovered.

1060 of 1061 relevant lines covered (99.91%)

11.08 hits per line

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

99.19
/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\FieldsDTO;
13
use RonasIT\Support\DTO\RelationsDTO;
14
use RonasIT\Support\Enums\FieldTypeEnum;
15
use RonasIT\Support\Events\SuccessCreateMessage;
16
use RonasIT\Support\Events\WarningEvent;
17
use RonasIT\Support\Exceptions\ClassNotExistsException;
18
use RonasIT\Support\Generators\ControllerGenerator;
19
use RonasIT\Support\Generators\EntityGenerator;
20
use RonasIT\Support\Generators\FactoryGenerator;
21
use RonasIT\Support\Generators\MigrationGenerator;
22
use RonasIT\Support\Generators\ModelGenerator;
23
use RonasIT\Support\Generators\NovaResourceGenerator;
24
use RonasIT\Support\Generators\NovaTestGenerator;
25
use RonasIT\Support\Generators\RepositoryGenerator;
26
use RonasIT\Support\Generators\RequestsGenerator;
27
use RonasIT\Support\Generators\ResourceGenerator;
28
use RonasIT\Support\Generators\SeederGenerator;
29
use RonasIT\Support\Generators\ServiceGenerator;
30
use RonasIT\Support\Generators\TestsGenerator;
31
use RonasIT\Support\Generators\TranslationsGenerator;
32
use RonasIT\Support\Support\Fields\FieldsParser;
33
use UnexpectedValueException;
34

35
class MakeEntityCommand extends Command
36
{
37
    protected FieldsParser $fieldsParser;
38
    private string $entityName;
39
    private string $entityNamespace;
40
    private RelationsDTO $relations;
41
    private FieldsDTO $fields;
42

43
    const CRUD_OPTIONS = [
44
        'C', 'R', 'U', 'D',
45
    ];
46

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

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

72
        {--i|integer=* : Add integer field to entity.}
73
        {--f|float=* : Add float field to entity.}
74
        {--s|string=* : Add string field to entity. Default type is VARCHAR(255) but you can change it manually in migration.}
75
        {--b|boolean=* : Add boolean field to entity.}
76
        {--t|timestamp=* : Add timestamp field to entity.}
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
    protected $rules = [
92
        'only' => [
93
            'only-api' => [ResourceGenerator::class, ControllerGenerator::class, RequestsGenerator::class, TestsGenerator::class],
94
            'only-entity' => [MigrationGenerator::class, ModelGenerator::class, RepositoryGenerator::class, ServiceGenerator::class, FactoryGenerator::class, SeederGenerator::class],
95
            'only-model' => [ModelGenerator::class],
96
            'only-repository' => [RepositoryGenerator::class],
97
            'only-service' => [ServiceGenerator::class],
98
            'only-resource' => [ResourceGenerator::class],
99
            'only-controller' => [ControllerGenerator::class],
100
            'only-requests' => [RequestsGenerator::class],
101
            'only-migration' => [MigrationGenerator::class],
102
            'only-factory' => [FactoryGenerator::class],
103
            'only-tests' => [FactoryGenerator::class, TestsGenerator::class],
104
            'only-seeder' => [SeederGenerator::class],
105
            'only-nova-resource' => [NovaResourceGenerator::class],
106
            'only-nova-tests' => [NovaTestGenerator::class],
107
        ],
108
    ];
109

110
    public $generators = [
111
        ModelGenerator::class, RepositoryGenerator::class, ServiceGenerator::class, RequestsGenerator::class,
112
        ResourceGenerator::class, ControllerGenerator::class, MigrationGenerator::class, FactoryGenerator::class,
113
        TestsGenerator::class, TranslationsGenerator::class, SeederGenerator::class, NovaResourceGenerator::class,
114
        NovaTestGenerator::class,
115
    ];
116

117
    public function __construct()
118
    {
119
        parent::__construct();
16✔
120

121
        $this->fieldsParser = app(FieldsParser::class);
16✔
122
    }
123

124
    /**
125
     * Execute the console command.
126
     */
127
    public function handle(): void
128
    {
129
        $this->validateInput();
14✔
130
        $this->checkConfigs();
11✔
131
        $this->listenEvents();
11✔
132
        $this->parseFields();
11✔
133
        $this->parseRelations();
10✔
134
        $this->entityName = $this->convertToPascalCase($this->entityName);
10✔
135

136
        try {
137
            $this->generate();
10✔
138
        } catch (Exception $e) {
3✔
139
            $this->error($e->getMessage());
3✔
140
        }
141
    }
142

143
    protected function checkConfigs(): void
144
    {
145
        $packageConfigPath = __DIR__ . '/../../config/entity-generator.php';
11✔
146
        $packageConfigs = require $packageConfigPath;
11✔
147

148
        $projectConfigs = config('entity-generator');
11✔
149

150
        $newConfig = $this->outputNewConfig($packageConfigs, $projectConfigs);
11✔
151

152
        if ($newConfig !== $projectConfigs) {
11✔
153
            $this->comment('Config has been updated');
1✔
154
            Config::set('entity-generator', $newConfig);
1✔
155
            file_put_contents(config_path('entity-generator.php'), "<?php\n\nreturn" . $this->customVarExport($newConfig) . ';');
1✔
156
        }
157
    }
158

159
    protected function outputNewConfig(array $packageConfigs, array $projectConfigs): array
160
    {
161
        $flattenedPackageConfigs = Arr::dot($packageConfigs);
11✔
162
        $flattenedProjectConfigs = Arr::dot($projectConfigs);
11✔
163

164
        $newConfig = array_merge($flattenedPackageConfigs, $flattenedProjectConfigs);
11✔
165

166
        $differences = array_diff_key($newConfig, $flattenedProjectConfigs);
11✔
167

168
        foreach ($differences as $differenceKey => $differenceValue) {
11✔
169
            $this->comment("Key '{$differenceKey}' was missing in your config, we added it with the value '{$differenceValue}'");
1✔
170
        }
171

172
        return array_undot($newConfig);
11✔
173
    }
174

175
    protected function customVarExport(array $expression): string
176
    {
177
        $defaultExpression = var_export($expression, true);
1✔
178

179
        $patterns = [
1✔
180
            '/array/' => '',
1✔
181
            '/\(/' => '[',
1✔
182
            '/\)/' => ']',
1✔
183
            '/=> \\n/' => '=>',
1✔
184
            '/=>.+\[/' => '=> [',
1✔
185
            '/^ {8}/m' => str_repeat(' ', 10),
1✔
186
            '/^ {6}/m' => str_repeat(' ', 8),
1✔
187
            '/^ {4}/m' => str_repeat(' ', 6),
1✔
188
            '/^ {2}/m' => str_repeat(' ', 4),
1✔
189
        ];
1✔
190

191
        return preg_replace(array_keys($patterns), array_values($patterns), $defaultExpression);
1✔
192
    }
193

194
    protected function classExists(string $path, string $name): bool
195
    {
196
        $paths = config('entity-generator.paths');
1✔
197

198
        $entitiesPath = $paths[$path];
1✔
199

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

202
        return file_exists($classPath);
1✔
203
    }
204

205
    protected function validateInput(): void
206
    {
207
        $this->validateEntityName();
14✔
208
        $this->extractEntityNameAndPath();
13✔
209
        $this->validateOnlyApiOption();
13✔
210
        $this->validateCrudOptions();
12✔
211
    }
212

213
    protected function generate(): void
214
    {
215
        $providedOnlyOptions = $this->getProvidedOnlyOptions();
10✔
216

217
        $generators = (!empty($providedOnlyOptions))
10✔
218
            ? $this->getOnlyGenerators($providedOnlyOptions)
5✔
219
            : $this->getGeneratorsMap();
5✔
220

221
        array_walk($generators, fn ($generator) => $this->runGeneration($generator));
10✔
222
    }
223

224
    protected function getProvidedOnlyOptions(): array
225
    {
226
        $providedOptions = array_filter(
10✔
227
            array: $this->options(),
10✔
228
            callback: fn ($value, $name) => Str::startsWith($name, 'only-') && $value === true,
10✔
229
            mode: ARRAY_FILTER_USE_BOTH,
10✔
230
        );
10✔
231

232
        return array_keys($providedOptions);
10✔
233
    }
234

235
    protected function getOnlyGenerators(array $providedOptions): array
236
    {
237
        $generators = Arr::map($providedOptions, fn ($option) => Str::replace('only-', '', $option));
5✔
238

239
        if (in_array('api', $generators)) {
5✔
UNCOV
240
            array_push($generators, 'resource', 'controller', 'requests', 'factory', 'tests');
×
241
        }
242

243
        if (in_array('entity', $generators)) {
5✔
244
            array_push($generators, 'migration', 'model', 'repository', 'service', 'factory', 'seeder');
1✔
245
        }
246

247
        return array_intersect_key($this->getGeneratorsMap(), array_flip($generators));
5✔
248
    }
249

250
    protected function getGeneratorsMap(): array
251
    {
252
        return [
10✔
253
            'model' => app(ModelGenerator::class),
10✔
254
            'repository' => app(RepositoryGenerator::class),
10✔
255
            'service' => app(ServiceGenerator::class),
10✔
256
            'resource' => app(ResourceGenerator::class),
10✔
257
            'controller' => app(ControllerGenerator::class),
10✔
258
            'requests' => app(RequestsGenerator::class),
10✔
259
            'migration' => app(MigrationGenerator::class),
10✔
260
            'factory' => app(FactoryGenerator::class),
10✔
261
            'tests' => app(TestsGenerator::class),
10✔
262
            'seeder' => app(SeederGenerator::class),
10✔
263
            'translations' => app(TranslationsGenerator::class),
10✔
264
            'nova-resource' => app(NovaResourceGenerator::class),
10✔
265
            'nova-tests' => app(NovaTestGenerator::class)->setNovaResource($this->option('nova-resource-name')),
10✔
266
        ];
10✔
267
    }
268

269
    protected function runGeneration(EntityGenerator $generator): void
270
    {
271
        $generator
10✔
272
            ->setModel($this->entityName)
10✔
273
            ->setModelSubFolder($this->entityNamespace)
10✔
274
            ->setFields($this->fields)
10✔
275
            ->setRelations($this->relations)
10✔
276
            ->setCrudOptions($this->getCrudOptions())
10✔
277
            ->generate();
10✔
278
    }
279

280
    protected function getCrudOptions(): array
281
    {
282
        return str_split($this->option('methods'));
12✔
283
    }
284

285
    protected function parseRelations(): void
286
    {
287
        $this->relations = new RelationsDTO(
10✔
288
            hasOne: $this->prepareRelations($this->option('has-one')),
10✔
289
            hasMany: $this->prepareRelations($this->option('has-many')),
10✔
290
            belongsTo: $this->prepareRelations($this->option('belongs-to')),
10✔
291
            belongsToMany: $this->prepareRelations($this->option('belongs-to-many')),
10✔
292
        );
10✔
293
    }
294

295
    protected function prepareRelations(array $relations): array
296
    {
297
        return array_map(function ($relation) {
10✔
298
            $relation = Str::trim($relation, '/');
4✔
299

300
            return $this->convertToPascalCase($relation);
4✔
301
        }, $relations);
10✔
302
    }
303

304
    protected function parseFields(): void
305
    {
306
        $rawFields = Arr::only($this->options(), FieldTypeEnum::values());
11✔
307

308
        $this->fields = $this->fieldsParser->parse($rawFields);
11✔
309
    }
310

311
    protected function validateEntityName(): void
312
    {
313
        if (!preg_match('/^[A-Za-z0-9\/]+$/', $this->argument('name'))) {
14✔
314
            throw new InvalidArgumentException("Invalid entity name {$this->argument('name')}");
1✔
315
        }
316
    }
317

318
    protected function extractEntityNameAndPath(): void
319
    {
320
        list($this->entityName, $entityPath) = extract_last_part($this->argument('name'), '/');
13✔
321

322
        $this->entityNamespace = Str::trim($entityPath, '/');
13✔
323
    }
324

325
    protected function validateCrudOptions(): void
326
    {
327
        $crudOptions = $this->getCrudOptions();
12✔
328

329
        foreach ($crudOptions as $crudOption) {
12✔
330
            if (!in_array($crudOption, MakeEntityCommand::CRUD_OPTIONS)) {
12✔
331
                throw new UnexpectedValueException("Invalid method {$crudOption}.");
1✔
332
            }
333
        }
334
    }
335

336
    protected function validateOnlyApiOption(): void
337
    {
338
        if ($this->option('only-api')) {
13✔
339
            $modelName = Str::studly($this->argument('name'));
1✔
340
            if (!$this->classExists('services', "{$modelName}Service")) {
1✔
341
                throw new ClassNotExistsException('Cannot create API without entity.');
1✔
342
            }
343
        }
344
    }
345

346
    protected function listenEvents(): void
347
    {
348
        Event::listen(
11✔
349
            events: SuccessCreateMessage::class,
11✔
350
            listener: fn (SuccessCreateMessage $event) => $this->info($event->message),
11✔
351
        );
11✔
352

353
        Event::listen(
11✔
354
            events: WarningEvent::class,
11✔
355
            listener: fn (WarningEvent $event) => $this->warn($event->message),
11✔
356
        );
11✔
357
    }
358

359
    protected function convertToPascalCase(string $entityName): string
360
    {
361
        $pascalEntityName = Str::studly($entityName);
10✔
362

363
        if ($entityName !== $pascalEntityName) {
10✔
364
            $this->warn("{$entityName} was converted to {$pascalEntityName}");
1✔
365
        }
366

367
        return $pascalEntityName;
10✔
368
    }
369
}
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