Passed
Push — master ( b5b0f8...85ff3e )
by Arthur
05:23
created

GeneratorTrait::solveFormRequest()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 9
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 0
1
<?php
2
3
namespace SoliDry\Controllers;
4
5
use SoliDry\Blocks\Controllers;
6
use SoliDry\Blocks\Entities;
7
use SoliDry\Blocks\FileManager;
8
use SoliDry\Blocks\FormRequest;
9
use SoliDry\Blocks\Migrations;
10
use SoliDry\Blocks\Routes;
11
use SoliDry\Blocks\Tests;
12
use SoliDry\Exceptions\DirectoryException;
13
use SoliDry\Helpers\Console;
14
use SoliDry\Types\ConsoleInterface;
15
use SoliDry\Types\CustomsInterface;
16
use SoliDry\Types\DefaultInterface;
17
use SoliDry\Types\DirsInterface;
18
use SoliDry\Types\ErrorsInterface;
19
use SoliDry\Types\PhpInterface;
20
use SoliDry\Types\ApiInterface;
21
use Symfony\Component\Yaml\Yaml;
22
23
/**
24
 * Trait GeneratorTrait
25
 *
26
 * @package SoliDry\Controllers
27
 */
28
trait GeneratorTrait
29
{
30
    // all generated entities/resources
31
    private $forms;
32
    private $mappers;
33
    private $routes;
34
    private $migrations;
35
    private $controllers;
36
    private $tests;
37
38
    // gen dir found in history
39
    private $genDir;
40
41
    /**
42
     * Standard generation
43
     */
44
    private function generateResources(): void
45
    {
46
        $this->outputEntity();
47
        $this->createControllers();
48
49
        // create controller
50
        $this->createControllers();
51
52
        // create FormRequest
53
        $this->solveFormRequest();
54
55
        // create entities/models
56
        $this->solveEntities();
57
58
        // create routes
59
        $this->routes = new Routes($this);
60
        $this->routes->create();
61
62
        // create tests
63
        if (empty($this->options[ConsoleInterface::OPTION_TESTS]) === false) {
64
            try {
65
                FileManager::createPath($this->formatFuncTestsPath());
0 ignored issues
show
Bug introduced by
It seems like formatFuncTestsPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

65
                FileManager::createPath($this->/** @scrutinizer ignore-call */ formatFuncTestsPath());
Loading history...
66
            } catch (DirectoryException $e) {
67
                $this->error($e->getTraceAsString());
0 ignored issues
show
Bug introduced by
It seems like error() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

67
                $this->/** @scrutinizer ignore-call */ 
68
                       error($e->getTraceAsString());
Loading history...
68
            }
69
70
            $this->tests = new Tests($this);
71
            $this->tests->createEntity($this->formatFuncTestsPath(), DefaultInterface::FUNCTIONAL_POSTFIX);
72
        }
73
74
        $this->createMigrations();
75
    }
76
77
    /**
78
     *  Generation with merge option
79
     */
80
    private function mergeResources(): void
81
    {
82
        $this->outputEntity();
83
        $this->createControllers();
84
85
        $this->solveFormRequest();
86
87
        $this->solveEntities();
88
89
        // create routes
90
        $this->routes = new Routes($this);
91
        $this->routes->create();
92
93
        $this->createMigrations();
94
    }
95
96
    private function solveFormRequest(): void
97
    {
98
        $this->forms = new FormRequest($this);
99
        $formRequestPath = $this->formatRequestsPath();
0 ignored issues
show
Bug introduced by
It seems like formatRequestsPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

99
        /** @scrutinizer ignore-call */ 
100
        $formRequestPath = $this->formatRequestsPath();
Loading history...
100
        if (empty($this->options[ConsoleInterface::OPTION_MERGE]) === false
101
            && file_exists($this->forms->getEntityFile($formRequestPath, DefaultInterface::FORM_REQUEST_POSTFIX)) === true) {
102
            $this->forms->recreateEntity($formRequestPath, DefaultInterface::FORM_REQUEST_POSTFIX);
103
        } else {
104
            $this->forms->createEntity($formRequestPath, DefaultInterface::FORM_REQUEST_POSTFIX);
105
        }
106
    }
107
108
    /**
109
     *  Decide whether to generate new Entities or mutate existing
110
     */
111
    private function solveEntities(): void
112
    {
113
        // create entities/models
114
        $this->mappers = new Entities($this);
115
        $this->mappers->createPivot();
116
        $entitiesPath = $this->formatEntitiesPath();
0 ignored issues
show
Bug introduced by
It seems like formatEntitiesPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

116
        /** @scrutinizer ignore-call */ 
117
        $entitiesPath = $this->formatEntitiesPath();
Loading history...
117
        if (empty($this->options[ConsoleInterface::OPTION_MERGE]) === false
118
            && file_exists($this->forms->getEntityFile($entitiesPath)) === true) {
119
            $this->mappers->recreateEntity($entitiesPath);
120
        } else {
121
            $this->mappers->createEntity($entitiesPath);
122
        }
123
    }
124
125
    private function outputEntity(): void
126
    {
127
        Console::out(
128
            '===============' . PhpInterface::SPACE . $this->objectName
129
            . PhpInterface::SPACE . DirsInterface::ENTITIES_DIR
130
        );
131
    }
132
133
    /**
134
     *  Creates Controllers and leaves those generated in case of merge
135
     */
136
    private function createControllers(): void
137
    {
138
        $this->controllers = new Controllers($this);
139
        $this->controllers->createDefault();
140
        $this->controllers->createEntity($this->formatControllersPath(), DefaultInterface::CONTROLLER_POSTFIX);
0 ignored issues
show
Bug introduced by
It seems like formatControllersPath() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

140
        $this->controllers->createEntity($this->/** @scrutinizer ignore-call */ formatControllersPath(), DefaultInterface::CONTROLLER_POSTFIX);
Loading history...
141
    }
142
143
    /**
144
     *  Creates migrations for every entity if there is merge option - adds additional
145
     */
146
    private function createMigrations(): void
147
    {
148
        if (empty($this->options[ConsoleInterface::OPTION_MIGRATIONS]) === false) {
149
            $this->migrations = new Migrations($this);
150
            $this->migrations->create();
151
            $this->migrations->createPivot();
152
        }
153
    }
154
155
    /**
156
     *  Collects all attrs, Types and diffs for further code-generation
157
     *
158
     * @throws \Symfony\Component\Yaml\Exception\ParseException
159
     */
160
    private function setMergedTypes(): void
161
    {
162
        $opMerge = $this->options[ConsoleInterface::OPTION_MERGE];
163
        $timeCheck = strtotime($opMerge); // only for validation - with respect to diff timezones
164
165
        if (false !== $timeCheck) {
166
            try {
167
                $this->mergeTime($opMerge);
168
            } catch (DirectoryException $e) {
169
                $this->error($e->getTraceAsString());
170
            }
171
        } else if (is_numeric($opMerge) !== false) {
172
            $this->mergeStep($opMerge);
173
        } else if ($opMerge === ConsoleInterface::MERGE_DEFAULT_VALUE) {
174
            $this->mergeLast();
175
        }
176
    }
177
178
    /**
179
     * Merges history OAS files with current by time in the past
180
     *
181
     * @param string $opMerge
182
     * @throws \Symfony\Component\Yaml\Exception\ParseException
183
     * @throws DirectoryException
184
     */
185
    private function mergeTime(string $opMerge): void
186
    {
187
        $dateTime = explode(PhpInterface::SPACE, $opMerge);
188
        $this->composeTypes($this->composeTimeFiles($dateTime), $this->files);
189
    }
190
191
    /**
192
     * @param array $dateTime
193
     * @return array
194
     * @throws DirectoryException
195
     */
196
    private function composeTimeFiles(array $dateTime): array
197
    {
198
        $time = str_replace(':', '', $dateTime[1]);
199
200
        $this->genDir = $dateTime[0];
201
        $path = DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR . $this->genDir . DIRECTORY_SEPARATOR;
202
203
        if (is_dir($path) === false) {
204
            throw new DirectoryException('The directory: ' . $path . ' was not found.', ErrorsInterface::CODE_DIR_NOT_FOUND);
205
        }
206
207
        $files = glob($path . $time . '*');
208
        foreach ($files as &$fullPath) {
209
            $fullPath = str_replace($path, '', $fullPath);
210
        }
211
212
        $files = array_diff($files, DirsInterface::EXCLUDED_DIRS);
213
214
        return $this->adjustFiles($files);
215
    }
216
217
    /**
218
     * Merges history OAS files with current by backward steps
219
     *
220
     * @param int $step
221
     * @throws \Symfony\Component\Yaml\Exception\ParseException
222
     */
223
    private function mergeStep(int $step): void
224
    {
225
        $dirs = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR, SCANDIR_SORT_DESCENDING);
226
        if ($dirs !== false) {
227
            $dirs = array_diff($dirs, DirsInterface::EXCLUDED_DIRS);
228
            $this->composeTypes($this->composeStepFiles($dirs, $step), $this->files);
229
        }
230
    }
231
232
    /**
233
     * Composes files for step back in history via .gen dir
234
     *
235
     * @param array $dirs
236
     * @param int $step
237
     * @return array
238
     */
239
    private function composeStepFiles(array $dirs, int $step): array
240
    {
241
        $filesToPass = [];
242
        foreach ($dirs as $dir) {
243
            $files = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR . $dir, SCANDIR_SORT_DESCENDING);
244
            $files = array_diff($files, DirsInterface::EXCLUDED_DIRS);
245
246
            $prefixFlag = '';
247
            foreach ($files as $kFile => $file) {
248
                $prefix = substr($file, 0, 6); // Hms
249
                $template = '/^' . $prefix . '.*$/i';
250
251
                if ($prefix !== $prefixFlag) {
252
                    --$step;
253
                    $prefixFlag = $prefix;
254
                    if ($step > 0) {
255
                        $skip = preg_grep($template, $files);
256
                        $files = array_diff($files, $skip);
257
                    }
258
                }
259
260
                if ($step <= 0) {
261
                    $files = preg_grep($template, $files);
262
                    $this->genDir = $dir;
263
                    $filesToPass = $files;
264
                    break 2;
265
                }
266
            }
267
        }
268
269
        return $this->adjustFiles($filesToPass);
270
    }
271
272
    /**
273
     *  Merges current state with the last one
274
     *
275
     * @throws \Symfony\Component\Yaml\Exception\ParseException
276
     */
277
    private function mergeLast(): void
278
    {
279
        $lastFiles = $this->getLastFiles();
280
        if (empty($lastFiles) === false) {
281
            $this->composeTypes($lastFiles, $this->files);
282
        }
283
    }
284
285
    /**
286
     * Gets last files according to main file named "openapi" by spec of OAS
287
     * and it's included files defined in "uses" property
288
     *
289
     * @return array
290
     * @throws \Symfony\Component\Yaml\Exception\ParseException
291
     */
292
    private function getLastFiles(): array
293
    {
294
        $dirs = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR, SCANDIR_SORT_DESCENDING);
295
        if ($dirs !== false) {
296
            $dirs = array_diff($dirs, DirsInterface::EXCLUDED_DIRS);
297
            $this->genDir = $dirs[0]; // desc last date YYYY-mm-dd
298
299
            $files = scandir(DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR . $this->genDir, SCANDIR_SORT_DESCENDING);
300
            $files = array_diff($files, DirsInterface::EXCLUDED_DIRS);
301
302
            $lastFiles = [];
303
            foreach ($files as $file) {
304
                if (($pos = strpos($file, ApiInterface::OPEN_API_KEY)) !== false) {
305
                    $lastFiles[] = $file;
306
                    $content = Yaml::parse(file_get_contents($this->formatGenPathByDir() .$file));
0 ignored issues
show
Bug introduced by
It seems like formatGenPathByDir() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

306
                    $content = Yaml::parse(file_get_contents($this->/** @scrutinizer ignore-call */ formatGenPathByDir() .$file));
Loading history...
307
                    if (empty($content[ApiInterface::RAML_KEY_USES]) === false) {
308
                        foreach ($content[ApiInterface::RAML_KEY_USES] as $subFile) {
309
                            $lastFiles[] = substr($file, 0, $pos) . basename($subFile);
310
                        }
311
                    }
312
                    break;
313
                }
314
            }
315
316
            return $this->adjustFiles($lastFiles);
317
        }
318
319
        return [];
320
    }
321
322
    /**
323
     * Gets history files and merges them with current OAS files
324
     *
325
     * @param array $files      files from .gen/ dir saved history
326
     * @param array $inputFiles file that were passed as an option + files from uses RAML property
327
     * @throws \Symfony\Component\Yaml\Exception\ParseException
328
     */
329
    private function composeTypes(array $files, array $inputFiles): void
330
    {
331
        $attrsCurrent = [];
332
        $attrsHistory = [];
333
334
        $path = DirsInterface::GEN_DIR . DIRECTORY_SEPARATOR . $this->genDir . DIRECTORY_SEPARATOR;
335
        foreach ($files as $file) {
336
            foreach ($inputFiles as $inFile) {
337
338
                if (mb_strpos($file, basename($inFile), null, PhpInterface::ENCODING_UTF8) !== false) {
339
                    $dataCurrent = Yaml::parse(file_get_contents($inFile));
340
                    $dataHistory = Yaml::parse(file_get_contents($path . $file));
341
342
                    $this->currentTypes = $dataCurrent[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
0 ignored issues
show
Bug Best Practice introduced by
The property currentTypes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
343
                    $this->historyTypes = $dataHistory[ApiInterface::API_COMPONENTS][ApiInterface::API_SCHEMAS];
0 ignored issues
show
Bug Best Practice introduced by
The property historyTypes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
344
                    $this->types += array_merge_recursive($this->historyTypes, $this->currentTypes);
345
346
                    $attrsCurrent += array_filter($this->currentTypes, function ($k) {
347
                        return strpos($k, CustomsInterface::CUSTOM_TYPES_ATTRIBUTES) !== false;
348
                    }, ARRAY_FILTER_USE_KEY);
349
                    $attrsHistory += array_filter($this->historyTypes, function ($k) {
350
                        return strpos($k, CustomsInterface::CUSTOM_TYPES_ATTRIBUTES) !== false;
351
                    }, ARRAY_FILTER_USE_KEY);
352
                }
353
            }
354
        }
355
356
        $this->composeDiffs($attrsCurrent, $attrsHistory);
357
    }
358
359
    /**
360
     * Compares attributes for current and previous history and sets the diffTypes prop
361
     * to process additional migrations creation
362
     * @param array $attrsCurrent Current attributes
363
     * @param array $attrsHistory History attributes
364
     */
365
    private function composeDiffs(array $attrsCurrent, array $attrsHistory): void
366
    {
367
        // make diffs on current array to add columns/indices to migrations
368
        foreach ($attrsCurrent as $k => $v) {
369
            if (empty($attrsHistory[$k][ApiInterface::RAML_PROPS]) === false
370
                && (empty($v[ApiInterface::RAML_PROPS]) === false)) {
371
372
                foreach ($v[ApiInterface::RAML_PROPS] as $attr => $attrValue) {
373
                    if (empty($attrsHistory[$k][ApiInterface::RAML_PROPS][$attr])) { // if there is no such element in history data - collect
374
                        $this->diffTypes[$k][$attr] = $attrValue;
0 ignored issues
show
Bug Best Practice introduced by
The property diffTypes does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
375
                    }
376
                }
377
            }
378
        }
379
380
        // reflect array from history to append lost props
381
        foreach ($attrsHistory as $k => $v) {
382
            if (empty($attrsCurrent[$k][ApiInterface::RAML_PROPS]) === false
383
                && (empty($v[ApiInterface::RAML_PROPS]) === false)) {
384
385
                foreach ($v[ApiInterface::RAML_PROPS] as $attr => $attrValue) {
386
                    if (empty($attrsCurrent[$k][ApiInterface::RAML_PROPS][$attr])) { // if there is no such element in current data - collect
387
                        $this->diffTypes[$k][$attr] = $attrValue;
388
                    }
389
                }
390
            }
391
        }
392
    }
393
394
    /**
395
     * Gets an unordered array of files and returns an ordered one
396
     * stating from *openapi.yaml
397
     *
398
     * @param array $files
399
     * @return array
400
     */
401
    private function adjustFiles(array $files): array
402
    {
403
        $tmpFile = '';
404
        foreach ($files as $k => $file) {
405
            if (strpos($file, ApiInterface::OPEN_API_KEY) !== false) {
406
                $tmpFile = $file;
407
                unset($files[$k]);
408
                break;
409
            }
410
        }
411
        array_unshift($files, $tmpFile);
412
413
        return $files;
414
    }
415
}