Skip to content

Commit b5dd38c

Browse files
authored
Merge pull request #176 from RonasIT/add-subfolders-model
feat: add ability to generate models in sub-folders
2 parents cf7c240 + 8dbba1d commit b5dd38c

34 files changed

+931
-82
lines changed

src/Commands/MakeEntityCommand.php

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Support\Facades\Config;
88
use Illuminate\Support\Facades\Event;
99
use Illuminate\Support\Str;
10+
use InvalidArgumentException;
1011
use RonasIT\Support\DTO\RelationsDTO;
1112
use RonasIT\Support\Events\SuccessCreateMessage;
1213
use RonasIT\Support\Events\WarningEvent;
@@ -30,6 +31,10 @@
3031

3132
class MakeEntityCommand extends Command
3233
{
34+
private string $entityName;
35+
private string $entityNamespace;
36+
private RelationsDTO $relations;
37+
3338
const CRUD_OPTIONS = [
3439
'C', 'R', 'U', 'D'
3540
];
@@ -118,6 +123,7 @@ public function handle(): void
118123
$this->validateInput();
119124
$this->checkConfigs();
120125
$this->listenEvents();
126+
$this->parseRelations();
121127

122128
try {
123129
$this->generate();
@@ -126,7 +132,7 @@ public function handle(): void
126132
}
127133
}
128134

129-
protected function checkConfigs()
135+
protected function checkConfigs(): void
130136
{
131137
$packageConfigPath = __DIR__ . '/../../config/entity-generator.php';
132138
$packageConfigs = require $packageConfigPath;
@@ -142,7 +148,7 @@ protected function checkConfigs()
142148
}
143149
}
144150

145-
protected function outputNewConfig($packageConfigs, $projectConfigs)
151+
protected function outputNewConfig(array $packageConfigs, array $projectConfigs): array
146152
{
147153
$flattenedPackageConfigs = Arr::dot($packageConfigs);
148154
$flattenedProjectConfigs = Arr::dot($projectConfigs);
@@ -158,7 +164,7 @@ protected function outputNewConfig($packageConfigs, $projectConfigs)
158164
return array_undot($newConfig);
159165
}
160166

161-
protected function customVarExport($expression)
167+
protected function customVarExport(array $expression): string
162168
{
163169
$defaultExpression = var_export($expression, true);
164170

@@ -177,7 +183,7 @@ protected function customVarExport($expression)
177183
return preg_replace(array_keys($patterns), array_values($patterns), $defaultExpression);
178184
}
179185

180-
protected function classExists($path, $name)
186+
protected function classExists(string $path, string $name): bool
181187
{
182188
$paths = config('entity-generator.paths');
183189

@@ -188,13 +194,15 @@ protected function classExists($path, $name)
188194
return file_exists($classPath);
189195
}
190196

191-
protected function validateInput()
197+
protected function validateInput(): void
192198
{
199+
$this->validateEntityName();
200+
$this->extractEntityNameAndPath();
193201
$this->validateOnlyApiOption();
194202
$this->validateCrudOptions();
195203
}
196204

197-
protected function generate()
205+
protected function generate(): void
198206
{
199207
foreach ($this->rules['only'] as $option => $generators) {
200208
if ($this->option($option)) {
@@ -211,37 +219,60 @@ protected function generate()
211219
}
212220
}
213221

214-
protected function runGeneration($generator)
222+
protected function runGeneration(string $generator): void
215223
{
216224
app($generator)
217-
->setModel($this->argument('name'))
225+
->setModel($this->entityName)
226+
->setModelSubFolder($this->entityNamespace)
218227
->setFields($this->getFields())
219-
->setRelations($this->getRelations())
228+
->setRelations($this->relations)
220229
->setCrudOptions($this->getCrudOptions())
221230
->generate();
222231
}
223232

224-
protected function getCrudOptions()
233+
protected function getCrudOptions(): array
225234
{
226235
return str_split($this->option('methods'));
227236
}
228237

229-
protected function getRelations()
238+
protected function parseRelations(): void
230239
{
231-
return new RelationsDTO(
232-
hasOne: $this->option('has-one'),
233-
hasMany: $this->option('has-many'),
234-
belongsTo: $this->option('belongs-to'),
235-
belongsToMany: $this->option('belongs-to-many'),
240+
$this->relations = new RelationsDTO(
241+
hasOne: $this->trimRelations($this->option('has-one')),
242+
hasMany: $this->trimRelations($this->option('has-many')),
243+
belongsTo: $this->trimRelations($this->option('belongs-to')),
244+
belongsToMany: $this->trimRelations($this->option('belongs-to-many')),
236245
);
237246
}
238247

239-
protected function getFields()
248+
protected function trimRelations(array $relations): array
249+
{
250+
return array_map(
251+
callback: fn ($relation) => Str::trim($relation, '/'),
252+
array: $relations,
253+
);
254+
}
255+
256+
protected function getFields(): array
240257
{
241258
return Arr::only($this->options(), EntityGenerator::AVAILABLE_FIELDS);
242259
}
243260

244-
protected function validateCrudOptions()
261+
protected function validateEntityName(): void
262+
{
263+
if (!preg_match('/^[A-Za-z0-9\/]+$/', $this->argument('name'))) {
264+
throw new InvalidArgumentException("Invalid entity name {$this->argument('name')}");
265+
}
266+
}
267+
268+
protected function extractEntityNameAndPath(): void
269+
{
270+
list($this->entityName, $entityPath) = extract_last_part($this->argument('name'), '/');
271+
272+
$this->entityNamespace = Str::trim($entityPath, '/');
273+
}
274+
275+
protected function validateCrudOptions(): void
245276
{
246277
$crudOptions = $this->getCrudOptions();
247278

@@ -252,7 +283,7 @@ protected function validateCrudOptions()
252283
}
253284
}
254285

255-
protected function validateOnlyApiOption()
286+
protected function validateOnlyApiOption(): void
256287
{
257288
if ($this->option('only-api')) {
258289
$modelName = Str::studly($this->argument('name'));

src/Generators/AbstractTestsGenerator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ protected function buildEntityObject($model): array
158158
return $result;
159159
}
160160

161-
protected function getModelFields($model): array
161+
protected function getModelFields(string $model): array
162162
{
163163
$modelClass = $this->getModelClass($model);
164164

src/Generators/EntityGenerator.php

Lines changed: 45 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -36,53 +36,48 @@ abstract class EntityGenerator
3636

3737
protected $paths = [];
3838
protected $model;
39+
protected $modelSubFolder = null;
3940
protected $fields;
4041
protected $relations = [];
4142
protected $crudOptions;
4243

43-
/**
44-
* @param array $crudOptions
45-
* @return $this
46-
*/
47-
public function setCrudOptions($crudOptions)
44+
45+
public function setCrudOptions(array $crudOptions): self
4846
{
4947
$this->crudOptions = $crudOptions;
5048

5149
return $this;
5250
}
5351

54-
/**
55-
* @param string $model
56-
* @return $this
57-
*/
58-
public function setModel($model)
52+
public function setModel(string $model): self
5953
{
6054
$this->model = Str::studly($model);
6155

6256
return $this;
6357
}
6458

65-
/**
66-
* @param array $fields
67-
* @return $this
68-
*/
69-
public function setFields($fields)
59+
public function setModelSubFolder(string $folder): self
60+
{
61+
$this->modelSubFolder = $folder;
62+
63+
return $this;
64+
}
65+
66+
public function setFields(array $fields): self
7067
{
7168
$this->fields = $fields;
7269

7370
return $this;
7471
}
7572

76-
/**
77-
* @param array $relations
78-
* @return $this
79-
*/
80-
public function setRelations(RelationsDTO $relations)
73+
public function setRelations(RelationsDTO $relations): self
8174
{
8275
$this->relations = $relations;
8376

8477
foreach ($relations->belongsTo as $field) {
85-
$name = Str::snake($field) . '_id';
78+
$relatedModel = Str::afterLast($field, '/');
79+
80+
$name = Str::snake($relatedModel) . '_id';
8681

8782
$this->fields['integer-required'][] = $name;
8883
}
@@ -95,9 +90,10 @@ public function __construct()
9590
$this->paths = config('entity-generator.paths');
9691
}
9792

98-
protected function getOrCreateNamespace(string $configPath): string
93+
protected function getOrCreateNamespace(string $configPath, ?string $subFolder = null): string
9994
{
100-
$path = $this->paths[$configPath];
95+
$path = when($subFolder, fn () => Str::finish($this->paths[$configPath], '/') . $subFolder, $this->paths[$configPath]);
96+
10197
$pathParts = explode('/', $path);
10298

10399
if (Str::endsWith(Arr::last($pathParts), '.php')) {
@@ -134,27 +130,29 @@ protected function isFolderHasCorrectCase(string $folder, string $configPath): b
134130

135131
abstract public function generate(): void;
136132

137-
protected function classExists($path, $name): bool
133+
protected function classExists(string $path, string $name, ?string $subFolder = null): bool
138134
{
139135
$entitiesPath = $this->paths[$path];
140136

137+
if (!empty($subFolder)) {
138+
$entitiesPath = "{$entitiesPath}/{$subFolder}";
139+
}
140+
141141
$classPath = base_path("{$entitiesPath}/{$name}.php");
142142

143143
return file_exists($classPath);
144144
}
145145

146-
protected function saveClass($path, $name, $content, $additionalEntityFolder = false): string
146+
protected function saveClass($path, $name, $content, ?string $entityFolder = null): string
147147
{
148148
$entitiesPath = base_path($this->paths[$path]);
149149

150150
if (Str::endsWith($entitiesPath, '.php')) {
151-
$pathParts = explode('/', $entitiesPath);
152-
array_pop($pathParts);
153-
$entitiesPath = implode('/', $pathParts);
151+
list(, $entitiesPath) = extract_last_part($entitiesPath, '/');
154152
}
155153

156-
if ($additionalEntityFolder) {
157-
$entitiesPath = $entitiesPath . "/{$additionalEntityFolder}";
154+
if (!empty($entityFolder)) {
155+
$entitiesPath = "{$entitiesPath}/{$entityFolder}";
158156
}
159157

160158
$classPath = "{$entitiesPath}/{$name}.php";
@@ -230,17 +228,32 @@ protected function getRelatedModels(string $model, string $creatableClass): arra
230228
continue;
231229
}
232230

233-
$relatedModels[] = class_basename(get_class($result->getRelated()));
231+
$relatedModels[] = $this->generateRelativePath(get_class($result->getRelated()), $this->paths['models']);
234232
}
235233

236234
DB::rollBack();
237235

238236
return $relatedModels;
239237
}
240238

239+
protected function generateRelativePath(string $namespace, string $basePath): string
240+
{
241+
return Str::after(
242+
subject: $this->namespaceToPath($namespace),
243+
search: $this->namespaceToPath($basePath) . '/',
244+
);
245+
}
246+
247+
protected function namespaceToPath(string $namespace): string
248+
{
249+
return str_replace('\\', '/', $namespace);
250+
}
251+
241252
protected function getModelClass(string $model): string
242253
{
243-
$modelNamespace = $this->getOrCreateNamespace('models');
254+
$subfolder = when($model === $this->model, $this->modelSubFolder);
255+
256+
$modelNamespace = $this->getOrCreateNamespace('models', $subfolder);
244257

245258
return "{$modelNamespace}\\{$model}";
246259
}

src/Generators/FactoryGenerator.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ class FactoryGenerator extends EntityGenerator
2727

2828
public function generate(): void
2929
{
30-
if (!$this->classExists('models', $this->model)) {
30+
if (!$this->classExists('models', $this->model, $this->modelSubFolder)) {
31+
// TODO: pass $this->modelSubfolder to Exception after refactoring in https://github.com/RonasIT/laravel-entity-generator/issues/179
3132
$this->throwFailureException(
3233
exceptionClass: ClassNotExistsException::class,
3334
failureMessage: "Cannot create {$this->model}Factory cause {$this->model} Model does not exists.",
@@ -51,7 +52,7 @@ public function generate(): void
5152
'namespace' => $this->getOrCreateNamespace('factories'),
5253
'entity' => $this->model,
5354
'fields' => $this->prepareFields(),
54-
'modelNamespace' => $this->getOrCreateNamespace('models'),
55+
'modelNamespace' => $this->getOrCreateNamespace('models', $this->modelSubFolder),
5556
]);
5657

5758
$this->saveClass('factories', "{$this->model}Factory", $factoryContent);

src/Generators/MigrationGenerator.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public function generate(): void
2121
'class' => $this->getPluralName($this->model),
2222
'entity' => $this->model,
2323
'entities' => $entities,
24-
'relations' => $this->relations->toArray(),
24+
'relations' => $this->prepareRelations(),
2525
'fields' => $this->fields,
2626
'table' => $this->generateTable($this->fields)
2727
]);
@@ -33,6 +33,17 @@ public function generate(): void
3333
event(new SuccessCreateMessage("Created a new Migration: {$entities}_create_table"));
3434
}
3535

36+
protected function prepareRelations(): array
37+
{
38+
$result = [];
39+
40+
foreach ($this->relations->toArray() as $relationType => $relations) {
41+
$result[$relationType] = array_map(fn ($relation) => Str::afterLast($relation, '/'), $relations);
42+
}
43+
44+
return $result;
45+
}
46+
3647
protected function isJson(string $typeName): bool
3748
{
3849
return $typeName === 'json';

0 commit comments

Comments
 (0)