Completed
Pull Request — master (#66)
by
unknown
02:30 queued 46s
created

ResourcesCommand::getDependencies()   B

Complexity

Conditions 8
Paths 4

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 40
rs 8.0355
c 0
b 0
f 0
cc 8
nc 4
nop 3
1
<?php namespace Wn\Generators\Commands;
2
3
use InvalidArgumentException;
4
use Symfony\Component\Yaml\Yaml;
5
6
7
class ResourcesCommand extends BaseCommand {
8
9
    protected $signature = 'wn:resources
10
        {files* : Paths to the files containing resources declarations}
11
        {--path= : where to store the model files.}
12
        {--routes= : where to store the routes.}
13
        {--no-routes : do not add routes.}
14
        {--controllers= : where to store the controllers.}
15
        {--no-controllers : do not generate controllers.}
16
        {--no-migration : do not migrate.}
17
        {--check-only : only check supplied files for valide relationships.}
18
        {--skip-check : skip validity check before processing.}
19
        {--force= : override the existing files}
20
        {--force-redefine : Force model redefinition.}
21
        {--laravel= : Use Laravel style route definitions}
22
    ';
23
24
    protected $description = 'Generates multiple resources from a couple of files';
25
26
    protected $pivotTables = [];
27
    protected $morphTables = [];
28
29
    private $checkedErrors = 0;
30
    private $checkErrors = [];
31
    private $checkInfo = [];
32
33
    public function handle()
34
    {
35
        $files = $this->argument('files');
36
        $nodes = [];
37
38
        if (empty($files)) {
39
            $this->error("No resource file(s) supplied!");
40
            return;
41
        }
42
        if (is_string($files)) {
43
            $files = [ $files ];
44
        }
45
46
        foreach ($files as $file) {
47
            $nodes = $this->mergeNodes($nodes, $this->readFile($file), $this->option('force-redefine'));
48
        }
49
50
        $this->line('');
51
        $this->info('Bringing models to order...');
52
53
        $nodes = $this->sortDependencies($nodes);
54
        $pivotTables = $this->uniqueArray($this->getTables($nodes, 'pivotTables'));
55
        $morphTables = $this->uniqueArray($this->getTables($nodes, 'morphTables'));
56
57
        if (! $this->option('skip-check')) {
58
        	$this->info('Checking Relationships...');
59
        	$keys = array_keys($nodes);
60
        	foreach ($nodes as $model => $i) {
61
        		$this->checkRelations($i['belongsTo'], 'belongsTo', $i['filename'], $i['uniquename'], $keys);
62
        		// $this->checkRelations($i['hasManyThrough'], 'hasManyThrough', $file, $model);
63
        	}
64
        	$this->checkPivotRelations($nodes, $pivotTables, 'pivot');
65
        	$this->checkPivotRelations($nodes, $morphTables, 'morph');
66
        }
67
68 View Code Duplication
        if ($this->checkedErrors > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
        	$this->line('');
70
        	if ($this->option('check-only')) {
71
        		$this->info('Checking only, we have found ' . $this->checkedErrors . ' errors.');
72
        	}
73
        	$this->printErrors();
74
        }
75
76
        $proceed = (! $this->option('check-only') && $this->checkedErrors == 0) || $this->option('skip-check');
77 View Code Duplication
        if (! $this->option('check-only') && $this->checkedErrors > 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
78
        	$this->line('');
79
        	$proceed = $this->confirm("We have found " . $this->checkedErrors . " errors. Are you sure you want to continue?");
80
        }
81
        if ($proceed) {
82
        	$this->buildResources($nodes);
83
84
        	// if (!$this->option('no-migration')) {
85
        	// 	$this->call('migrate'); // actually needed for pivot seeders !
86
        	// }
87
88
        	$this->line('');
89
            $this->buildTables('Pivot-Table', 'wn:pivot-table', 'model1', 'model2', $pivotTables);
90
91
        	$this->line('');
92
            $this->buildTables('Morph-Table', 'wn:morph-table', 'model', 'morphable', $morphTables);
93
94
        	if (!$this->option('no-migration')) {
95
        		$this->call('migrate');
96
        	}
97
        }
98
    }
99
100
    protected function uniqueArray($array)
101
    {
102
        return array_map(
103
            'unserialize',
104
            array_unique(array_map('serialize', $array))
105
        );
106
    }
107
108
    protected function readFile($file)
109
    {
110
        $this->info("Reading file ".$file);
111
112
        $content = $this->fs->get($file);
113
        $content = Yaml::parse($content);
114
115
        $nodes = [];
116
117
        foreach ($content as $model => $i){
118
            /*
119
                $i['modelname'] = as originally in YAML defined
120
                $i['name']      = as originally defined in snake_case
121
                $i['uniquename']= for key in singular studly_case
122
            */
123
            $i['filename'] = $file;
124
            $i['modelname'] = $model;
125
            $model = studly_case(str_singular($model));
126
            $i['uniquename'] = $model;
127
128
            $nodes[] = $this->getResourceParams($i);
129
        }
130
131
        return $nodes;
132
    }
133
134
    protected function mergeNodes($nodes, $toMerge, $forceRedefinition = false) {
135
        foreach($toMerge as $node) {
136
            $nodes = $this->mergeNode($nodes, $node, $forceRedefinition);
137
        }
138
139
        return $nodes;
140
    }
141
142
    protected function mergeNode($nodes, $toMerge, $forceRedefinition = false) {
143
        if (empty($nodes[$toMerge['uniquename']]) || $forceRedefinition) {
144
            if (!empty($nodes[$toMerge['uniquename']])) {
145
                $this->checkError($toMerge['uniquename'] . ": forced to redefine (in file " . $nodes[$toMerge['uniquename']]['filename'] . ", redefined from file ".$toMerge['filename'].")");
146
            }
147
            $nodes[$toMerge['uniquename']] = $toMerge;
148
        } else {
149
            $this->checkError($toMerge['uniquename'] . ": already defined (in file " . $nodes[$toMerge['uniquename']]['filename'] . ", trying to redefine from file ".$toMerge['filename']."; Use --force-redefine to force redefinition)");
150
        }
151
152
        return $nodes;
153
    }
154
155
    protected function getTables($nodes, $key) {
156
        $tables = [];
157
        foreach($nodes as $node) {
158
            if (!empty($node[$key])) {
159
                $tables = array_merge($tables, $node[$key]);
160
            }
161
        }
162
163
        return $tables;
164
    }
165
166
    protected function buildResources($nodes)
167
    {
168
        $modelIndex = 0;
169
        $migrationIdLength = strlen((string)count($nodes));
170
        foreach ($nodes as $i) {
171
            $migrationName = 'Create' .  ucwords(str_plural($i['name']));
172
            $migrationFile = date('Y_m_d_His') . '-' . str_pad($modelIndex , $migrationIdLength, 0, STR_PAD_LEFT) . '_' . snake_case($migrationName) . '_table';
173
174
            $this->line('');
175
            $this->info('Building Model ' . $i['uniquename']);
176
177
            $options = [
178
                'name' => $i['name'],
179
                'fields' => $i['fields'],
180
                '--add' => $i['add'],
181
                '--has-many' => $i['hasMany'],
182
                '--has-one' => $i['hasOne'],
183
                '--belongs-to' => $i['belongsTo'],
184
                '--belongs-to-many' => $i['belongsToMany'],
185
                '--has-many-through' => $i['hasManyThrough'],
186
                '--morph-to' => $i['morphTo'],
187
                '--morph-many' => $i['morphMany'],
188
                '--morph-to-many' => $i['morphToMany'],
189
                '--morphed-by-many' => $i['morphedByMany'],
190
                '--no-routes' => $this->option('no-routes'),
191
                '--no-controller' => $this->option('no-controllers'),
192
                '--force' => $this->option('force'),
193
                '--migration-file' => $migrationFile,
194
            ];
195
            if ($this->option('laravel')) {
196
                $options['--laravel'] = true;
197
            }
198
            if ($this->option('routes')) {
199
                $options['--routes'] = $this->option('routes');
200
            }
201
            if ($this->option('controllers')) {
202
                $options['--controller'] = $this->option('controllers');
203
            }
204
            if ($this->option('path')) {
205
                $options['--path'] = $this->option('path');
206
            }
207
208
            $this->call('wn:resource', $options);
209
            $modelIndex++;
210
        }
211
    }
212
213
    protected function buildTables($type, $command, $model1, $model2, $tableAssignment)
214
    {
215
        foreach ($tableAssignment as $tables) {
216
            $this->info('Building '.$type.' ' . $tables[0] . ' - ' . $tables[1]);
217
            $this->call($command, [
218
                $model1 => $tables[0],
219
                $model2 => $tables[1],
220
                '--force' => $this->option('force')
221
            ]);
222
223
            // $this->call('wn:pivot-seeder', [
224
            //     'model1' => $tables[0],
225
            //     'model2' => $tables[1],
226
            //     '--force' => $this->option('force')
227
            // ]);
228
        }
229
    }
230
231
    protected function getResourceParams($i)
232
    {
233
        $modelName = $i['modelname'];
234
235
        $i['filename'] = $i['filename'];
236
        $i['name'] = snake_case($modelName);
237
        $i['modelname'] = $i['modelname'];
238
        $i['uniquename'] = $i['uniquename'];
239
240
        foreach(['hasMany', 'hasOne', 'add', 'belongsTo', 'belongsToMany', 'hasManyThrough', 'morphTo', 'morphMany', 'morphToMany', 'morphedByMany'] as $relation){
241
            if(isset($i[$relation])){
242
                $i[$relation] = $this->convertArray($i[$relation], ' ', ',');
243
            } else {
244
                $i[$relation] = false;
245
            }
246
        }
247
248
        if($i['belongsToMany']){
249
            $i['pivotTables'] = $this->belongsTo($i['name'], $modelName, $i['belongsToMany']);
250
        }
251
252
        if($i['morphToMany']){
253
            $i['morphTables'] = $this->morphToMany($modelName, $i['morphToMany']);
254
        }
255
256
        if($i['morphedByMany']){
257
            $i['morphTables'] = array_merge($i['morphTables'], $this->morphedByMany($i['name'], $modelName, $i['morphedByMany']));
258
        }
259
260
        $fields = [];
261
        foreach($i['fields'] as $name => $value) {
262
            $value['name'] = $name;
263
            $fields[] = $this->serializeField($value);
264
        }
265
        $i['fields'] = implode(' ', $fields);
266
267
        return $i;
268
    }
269
270
    protected function parseRelations($parser, $relations, $callback)
271
    {
272
        $parsedRelations = [];
273
        $relations = $this->getArgumentParser($parser)->parse($relations);
274
        foreach ($relations as $relation){
275
            $parsedRelations[] = $callback($relation);
276
        }
277
278
        return $parsedRelations;
279
    }
280
281
    protected function belongsTo($name, $modelName, $belongsTo)
282
    {
283
        return $this->parseRelations('relations', $belongsTo, function($relation) use ($name, $modelName) {
284 View Code Duplication
            if(! $relation['model']){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
285
                $table = snake_case($relation['name']);
286
            } else {
287
                $table = snake_case($this->extractClassName($relation['model']));
288
            }
289
290
            $tables = [ str_singular($table), $name ];
291
            sort($tables);
292
            $tables[] = $modelName;
293
            return $tables;
294
        });
295
    }
296
297
    protected function morphToMany($modelName, $morphToMany)
298
    {
299
        return $this->parseRelations('relations-morphMany', $morphToMany, function($relation) use ($modelName) {
300 View Code Duplication
            if(! $relation['through']){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
301
                $name = snake_case($relation['name']);
302
            } else {
303
                $name = snake_case($this->extractClassName($relation['model']));
304
            }
305
306
            return $this->getMorphableRelation($relation, $name, $modelName);
307
        });
308
    }
309
310
    protected function morphedByMany($name, $modelName, $morphedByMany)
311
    {
312
        return $this->parseRelations('relations-morphMany', $morphedByMany, function($relation) use ($name, $modelName) {
313
            return $this->getMorphableRelation($relation, $name, $modelName);
314
        });
315
    }
316
317
    protected function getMorphableRelation($relation, $relationName, $modelName) {
318 View Code Duplication
        if(! $relation['through']){
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
319
            $morphable = snake_case($this->extractClassName($relation['model']));
320
        } else {
321
            $morphable = snake_case($this->extractClassName($relation['through']));
322
        }
323
324
        return [ str_singular($relationName), str_singular($morphable), $modelName ];
325
    }
326
327
    protected function serializeField($field)
328
    {
329
        $name = $field['name'];
330
        $schema = $this->convertArray(str_replace(':', '.', $field['schema']), ' ', ':');
331
        $rules = (isset($field['rules'])) ? $this->convertArray(trim($field['rules']), ' ', '|') : '';
332
        $tags = !empty($field['tags']) ? $this->convertArray($field['tags'], ' ', ',') : '';
333
334
        $string = "{$name};{$schema};{$rules};{$tags}";
335
336
        if(isset($field['factory']) && !empty($field['factory'])){
337
            $string .= ';' . $field['factory'];
338
        }
339
340
        return $string;
341
    }
342
343
    protected function convertArray($list, $old, $new)
344
    {
345
        return implode($new, array_filter(explode($old, $list), function($item){
346
            return !empty($item);
347
        }));
348
    }
349
350
    private function sortDependencies($nodes) {
351
        $load_order = array();
352
        $seen       = array();
353
354
        foreach($nodes as $key => $item) {
355
            $tmp = $this->getDependencies($nodes, $key, $seen);
356
357
            // if($tmp[2] === false) {
358
            $load_order = array_merge($load_order, $tmp[0]);
359
            $seen       = $tmp[1];
360
            // }
361
        }
362
363
        return $load_order;
364
    }
365
366
    private function getDependencies($nodes, $key, $seen = array()) {
367
        if(array_key_exists($key, $seen) === true) {
368
            return array(array(), $seen);
369
        }
370
371
372
        if(!empty($nodes[$key])) {
373
            $order = array();
374
            // $failed         = array();
375
376
            if($nodes[$key]['belongsTo']) {
377
                $deps = $this->getArgumentParser('relations')->parse($nodes[$key]['belongsTo']);
378
                foreach($deps as $dependency) {
379
                    if(! $dependency['model']){
380
	                    $dependency['model'] = $dependency['name'];
381
	                } else if(strpos($dependency['model'], '\\') !== false ){
382
	                    $dependency['model'] = substr($dependency['model'], strpos($dependency['model'], '\\')+1); // Cut offs first namespace part
383
	                }
384
                    $dependency['model'] = studly_case(str_singular($dependency['model']));
385
                    if ($dependency['model'] != $key) {
386
                        $tmp = $this->getDependencies($nodes, $dependency['model'], $seen);
387
388
                        $order  = array_merge($order, $tmp[0]);
389
                        $seen   = $tmp[1];
390
                    }
391
392
                    // if($tmp[2] !== false) {
393
                    //     $failed = array_merge($tmp[2], $failed);
394
                    // }
395
                }
396
            }
397
            $seen[$key]  = true;
398
            $order[$key] = $nodes[$key];
399
            // $failed     = (count($failed) > 0) ? $failed : false;
400
401
            return array($order, $seen);//, $failed
402
        }
403
404
        return array(array(), $seen);//, array($item)
405
    }
406
407
    protected function checkError($message, $model = "", $file = "") {
408
        $this->checkErrors[] = array("message" => $message, "model" => $model, "file" => $file);
409
        $this->checkedErrors++;
410
    }
411
412
    protected function checkInfo($message, $model = "", $file = "") {
413
        $this->checkInfo[] = array("message" => $message, "model" => $model, "file" => $file);
414
    }
415
416
    protected function printErrors() {
417
        foreach ($this->checkErrors as $error) {
418
            $this->error($error['message']);
419
        }
420
        foreach ($this->checkInfo as $info) {
421
            $this->info($info['message']);
422
        }
423
    }
424
425
    protected function checkRelations($relations, $type, $file, $model, $keys) {
426
        if ($relations) {
427
            $position = array_search($model, $keys);
428
            $relations = $this->getArgumentParser('relations')->parse($relations);
429
            foreach($relations as $relation) {
430
                $rModel = $relation['model'] ? $relation['model'] : $relation['name'];
431
                $search = array_search(studly_case(str_singular($rModel)), $keys);
432
                if (($search === false || $search > $position) && !class_exists($this->prependNamespace($rModel)) && !class_exists($this->prependNamespace($rModel, 'App'))) {
433
                    $this->checkError(studly_case(str_singular($rModel)) . ": undefined (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")");
434
                } else if (class_exists($this->prependNamespace($rModel))) {
435
                    $this->checkInfo(studly_case(str_singular($rModel)) . ": already defined in Namespace " . $this->getNamespace() . " (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")");
436
                } else if (class_exists($this->prependNamespace($rModel, 'App'))) {
437
                    $this->checkInfo(studly_case(str_singular($rModel)) . ": already defined in Namespace App\\ (used in " . $type . "-relationship of model " . $model . " in file " . $file . ")");
438
                }
439
            }
440
        }
441
    }
442
443
    protected function checkPivotRelations($nodes, $relations, $relationType) {
444
        if ($relations) {
445
            foreach($relations as $relation) {
446
                $relation['0'] = studly_case(str_singular($relation['0']));
447
                $relation['1'] = studly_case(str_singular($relation['1']));
448
                $relation['2'] = studly_case(str_singular($relation['2']));
449
450
                $this->checkRelation($nodes, $relationType, $relation['0'], $relation['2']);
451
                if ($relationType == "pivot") {
452
                    $this->checkRelation($nodes, $relationType, $relation['1'], $relation['2']);
453
                }
454
            }
455
        }
456
    }
457
458
    protected function checkRelation($nodes, $relationType, $relation, $model) {
459
        if (empty($nodes[$relation]) && !class_exists($this->getNamespace() . '\\' . $relation) && !class_exists('App\\' . $relation)) {
460
            $this->checkError($relation . ": undefined (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")");
461
        } else if (class_exists($this->getNamespace() . '\\' . ucwords(camel_case($relation)))) {
462
            $this->checkInfo(studly_case(str_singular($relation)) . ": already defined in Namespace " . $this->getNamespace() . " (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")");
463
        } else if (class_exists('App\\' . ucwords(camel_case($relation)))) {
464
            $this->checkInfo(studly_case(str_singular($relation)) . ": already defined in Namespace App\\ (used in " . $relationType . "-based relationship of model " . $model . " in file " . $nodes[$model]['filename'] . ")");
465
        }
466
    }
467
468
}
469