Completed
Pull Request — master (#66)
by
unknown
01:44
created

ResourcesCommand::serializeField()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 4
nop 1
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
        if ($this->checkedErrors > 0) {
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
        if (! $this->option('check-only') && $this->checkedErrors > 0) {
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 parseRelations($parser, $relations, $callback)
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_STRING, expecting T_FUNCTION or T_CONST
Loading history...
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
            if(! $relation['model']){
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
            if(! $relation['through']){
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
        if(! $relation['through']){
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