Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 399 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
namespace Illuminate\Console;
4
 
5
use Illuminate\Console\Concerns\CreatesMatchingTest;
399 lars 6
use Illuminate\Contracts\Console\PromptsForMissingInput;
148 lars 7
use Illuminate\Filesystem\Filesystem;
8
use Illuminate\Support\Str;
9
use Symfony\Component\Console\Input\InputArgument;
399 lars 10
use Symfony\Component\Finder\Finder;
148 lars 11
 
399 lars 12
abstract class GeneratorCommand extends Command implements PromptsForMissingInput
148 lars 13
{
14
    /**
15
     * The filesystem instance.
16
     *
17
     * @var \Illuminate\Filesystem\Filesystem
18
     */
19
    protected $files;
20
 
21
    /**
22
     * The type of class being generated.
23
     *
24
     * @var string
25
     */
26
    protected $type;
27
 
28
    /**
29
     * Reserved names that cannot be used for generation.
30
     *
31
     * @var string[]
32
     */
33
    protected $reservedNames = [
34
        '__halt_compiler',
35
        'abstract',
36
        'and',
37
        'array',
38
        'as',
39
        'break',
40
        'callable',
41
        'case',
42
        'catch',
43
        'class',
44
        'clone',
45
        'const',
46
        'continue',
47
        'declare',
48
        'default',
49
        'die',
50
        'do',
51
        'echo',
52
        'else',
53
        'elseif',
54
        'empty',
55
        'enddeclare',
56
        'endfor',
57
        'endforeach',
58
        'endif',
59
        'endswitch',
60
        'endwhile',
61
        'enum',
62
        'eval',
63
        'exit',
64
        'extends',
65
        'false',
66
        'final',
67
        'finally',
68
        'fn',
69
        'for',
70
        'foreach',
71
        'function',
72
        'global',
73
        'goto',
74
        'if',
75
        'implements',
76
        'include',
77
        'include_once',
78
        'instanceof',
79
        'insteadof',
80
        'interface',
81
        'isset',
82
        'list',
83
        'match',
84
        'namespace',
85
        'new',
86
        'or',
87
        'print',
88
        'private',
89
        'protected',
90
        'public',
91
        'readonly',
92
        'require',
93
        'require_once',
94
        'return',
621 lars 95
        'self',
148 lars 96
        'static',
97
        'switch',
98
        'throw',
99
        'trait',
100
        'true',
101
        'try',
102
        'unset',
103
        'use',
104
        'var',
105
        'while',
106
        'xor',
107
        'yield',
108
        '__CLASS__',
109
        '__DIR__',
110
        '__FILE__',
111
        '__FUNCTION__',
112
        '__LINE__',
113
        '__METHOD__',
114
        '__NAMESPACE__',
115
        '__TRAIT__',
116
    ];
117
 
118
    /**
119
     * Create a new controller creator command instance.
120
     *
121
     * @param  \Illuminate\Filesystem\Filesystem  $files
122
     * @return void
123
     */
124
    public function __construct(Filesystem $files)
125
    {
126
        parent::__construct();
127
 
128
        if (in_array(CreatesMatchingTest::class, class_uses_recursive($this))) {
129
            $this->addTestOptions();
130
        }
131
 
132
        $this->files = $files;
133
    }
134
 
135
    /**
136
     * Get the stub file for the generator.
137
     *
138
     * @return string
139
     */
140
    abstract protected function getStub();
141
 
142
    /**
143
     * Execute the console command.
144
     *
145
     * @return bool|null
146
     *
147
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
148
     */
149
    public function handle()
150
    {
151
        // First we need to ensure that the given name is not a reserved word within the PHP
152
        // language and that the class name will actually be valid. If it is not valid we
153
        // can error now and prevent from polluting the filesystem using invalid files.
154
        if ($this->isReservedName($this->getNameInput())) {
155
            $this->components->error('The name "'.$this->getNameInput().'" is reserved by PHP.');
156
 
157
            return false;
158
        }
159
 
160
        $name = $this->qualifyClass($this->getNameInput());
161
 
162
        $path = $this->getPath($name);
163
 
164
        // Next, We will check to see if the class already exists. If it does, we don't want
165
        // to create the class and overwrite the user's code. So, we will bail out so the
166
        // code is untouched. Otherwise, we will continue generating this class' files.
167
        if ((! $this->hasOption('force') ||
168
             ! $this->option('force')) &&
169
             $this->alreadyExists($this->getNameInput())) {
170
            $this->components->error($this->type.' already exists.');
171
 
172
            return false;
173
        }
174
 
175
        // Next, we will generate the path to the location where this class' file should get
176
        // written. Then, we will build the class and make the proper replacements on the
177
        // stub files so that it gets the correctly formatted namespace and class name.
178
        $this->makeDirectory($path);
179
 
180
        $this->files->put($path, $this->sortImports($this->buildClass($name)));
181
 
182
        $info = $this->type;
183
 
184
        if (in_array(CreatesMatchingTest::class, class_uses_recursive($this))) {
185
            if ($this->handleTestCreation($path)) {
186
                $info .= ' and test';
187
            }
188
        }
189
 
190
        $this->components->info(sprintf('%s [%s] created successfully.', $info, $path));
191
    }
192
 
193
    /**
194
     * Parse the class name and format according to the root namespace.
195
     *
196
     * @param  string  $name
197
     * @return string
198
     */
199
    protected function qualifyClass($name)
200
    {
201
        $name = ltrim($name, '\\/');
202
 
203
        $name = str_replace('/', '\\', $name);
204
 
205
        $rootNamespace = $this->rootNamespace();
206
 
207
        if (Str::startsWith($name, $rootNamespace)) {
208
            return $name;
209
        }
210
 
211
        return $this->qualifyClass(
212
            $this->getDefaultNamespace(trim($rootNamespace, '\\')).'\\'.$name
213
        );
214
    }
215
 
216
    /**
217
     * Qualify the given model class base name.
218
     *
219
     * @param  string  $model
220
     * @return string
221
     */
222
    protected function qualifyModel(string $model)
223
    {
224
        $model = ltrim($model, '\\/');
225
 
226
        $model = str_replace('/', '\\', $model);
227
 
228
        $rootNamespace = $this->rootNamespace();
229
 
230
        if (Str::startsWith($model, $rootNamespace)) {
231
            return $model;
232
        }
233
 
234
        return is_dir(app_path('Models'))
235
                    ? $rootNamespace.'Models\\'.$model
236
                    : $rootNamespace.$model;
237
    }
238
 
239
    /**
399 lars 240
     * Get a list of possible model names.
241
     *
242
     * @return array<int, string>
243
     */
244
    protected function possibleModels()
245
    {
246
        $modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path();
247
 
248
        return collect((new Finder)->files()->depth(0)->in($modelPath))
249
            ->map(fn ($file) => $file->getBasename('.php'))
250
            ->values()
251
            ->all();
252
    }
253
 
254
    /**
255
     * Get a list of possible event names.
256
     *
257
     * @return array<int, string>
258
     */
259
    protected function possibleEvents()
260
    {
261
        $eventPath = app_path('Events');
262
 
263
        if (! is_dir($eventPath)) {
264
            return [];
265
        }
266
 
267
        return collect((new Finder)->files()->depth(0)->in($eventPath))
268
            ->map(fn ($file) => $file->getBasename('.php'))
269
            ->values()
270
            ->all();
271
    }
272
 
273
    /**
148 lars 274
     * Get the default namespace for the class.
275
     *
276
     * @param  string  $rootNamespace
277
     * @return string
278
     */
279
    protected function getDefaultNamespace($rootNamespace)
280
    {
281
        return $rootNamespace;
282
    }
283
 
284
    /**
285
     * Determine if the class already exists.
286
     *
287
     * @param  string  $rawName
288
     * @return bool
289
     */
290
    protected function alreadyExists($rawName)
291
    {
292
        return $this->files->exists($this->getPath($this->qualifyClass($rawName)));
293
    }
294
 
295
    /**
296
     * Get the destination class path.
297
     *
298
     * @param  string  $name
299
     * @return string
300
     */
301
    protected function getPath($name)
302
    {
303
        $name = Str::replaceFirst($this->rootNamespace(), '', $name);
304
 
305
        return $this->laravel['path'].'/'.str_replace('\\', '/', $name).'.php';
306
    }
307
 
308
    /**
309
     * Build the directory for the class if necessary.
310
     *
311
     * @param  string  $path
312
     * @return string
313
     */
314
    protected function makeDirectory($path)
315
    {
316
        if (! $this->files->isDirectory(dirname($path))) {
317
            $this->files->makeDirectory(dirname($path), 0777, true, true);
318
        }
319
 
320
        return $path;
321
    }
322
 
323
    /**
324
     * Build the class with the given name.
325
     *
326
     * @param  string  $name
327
     * @return string
328
     *
329
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
330
     */
331
    protected function buildClass($name)
332
    {
333
        $stub = $this->files->get($this->getStub());
334
 
335
        return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
336
    }
337
 
338
    /**
339
     * Replace the namespace for the given stub.
340
     *
341
     * @param  string  $stub
342
     * @param  string  $name
343
     * @return $this
344
     */
345
    protected function replaceNamespace(&$stub, $name)
346
    {
347
        $searches = [
348
            ['DummyNamespace', 'DummyRootNamespace', 'NamespacedDummyUserModel'],
349
            ['{{ namespace }}', '{{ rootNamespace }}', '{{ namespacedUserModel }}'],
350
            ['{{namespace}}', '{{rootNamespace}}', '{{namespacedUserModel}}'],
351
        ];
352
 
353
        foreach ($searches as $search) {
354
            $stub = str_replace(
355
                $search,
356
                [$this->getNamespace($name), $this->rootNamespace(), $this->userProviderModel()],
357
                $stub
358
            );
359
        }
360
 
361
        return $this;
362
    }
363
 
364
    /**
365
     * Get the full namespace for a given class, without the class name.
366
     *
367
     * @param  string  $name
368
     * @return string
369
     */
370
    protected function getNamespace($name)
371
    {
372
        return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
373
    }
374
 
375
    /**
376
     * Replace the class name for the given stub.
377
     *
378
     * @param  string  $stub
379
     * @param  string  $name
380
     * @return string
381
     */
382
    protected function replaceClass($stub, $name)
383
    {
384
        $class = str_replace($this->getNamespace($name).'\\', '', $name);
385
 
386
        return str_replace(['DummyClass', '{{ class }}', '{{class}}'], $class, $stub);
387
    }
388
 
389
    /**
390
     * Alphabetically sorts the imports for the given stub.
391
     *
392
     * @param  string  $stub
393
     * @return string
394
     */
395
    protected function sortImports($stub)
396
    {
397
        if (preg_match('/(?P<imports>(?:^use [^;{]+;$\n?)+)/m', $stub, $match)) {
398
            $imports = explode("\n", trim($match['imports']));
399
 
400
            sort($imports);
401
 
402
            return str_replace(trim($match['imports']), implode("\n", $imports), $stub);
403
        }
404
 
405
        return $stub;
406
    }
407
 
408
    /**
409
     * Get the desired class name from the input.
410
     *
411
     * @return string
412
     */
413
    protected function getNameInput()
414
    {
415
        return trim($this->argument('name'));
416
    }
417
 
418
    /**
419
     * Get the root namespace for the class.
420
     *
421
     * @return string
422
     */
423
    protected function rootNamespace()
424
    {
425
        return $this->laravel->getNamespace();
426
    }
427
 
428
    /**
429
     * Get the model for the default guard's user provider.
430
     *
431
     * @return string|null
432
     */
433
    protected function userProviderModel()
434
    {
435
        $config = $this->laravel['config'];
436
 
437
        $provider = $config->get('auth.guards.'.$config->get('auth.defaults.guard').'.provider');
438
 
439
        return $config->get("auth.providers.{$provider}.model");
440
    }
441
 
442
    /**
443
     * Checks whether the given name is reserved.
444
     *
445
     * @param  string  $name
446
     * @return bool
447
     */
448
    protected function isReservedName($name)
449
    {
450
        $name = strtolower($name);
451
 
452
        return in_array($name, $this->reservedNames);
453
    }
454
 
455
    /**
456
     * Get the first view directory path from the application configuration.
457
     *
458
     * @param  string  $path
459
     * @return string
460
     */
461
    protected function viewPath($path = '')
462
    {
463
        $views = $this->laravel['config']['view.paths'][0] ?? resource_path('views');
464
 
465
        return $views.($path ? DIRECTORY_SEPARATOR.$path : $path);
466
    }
467
 
468
    /**
469
     * Get the console command arguments.
470
     *
471
     * @return array
472
     */
473
    protected function getArguments()
474
    {
475
        return [
399 lars 476
            ['name', InputArgument::REQUIRED, 'The name of the '.strtolower($this->type)],
148 lars 477
        ];
478
    }
399 lars 479
 
480
    /**
481
     * Prompt for missing input arguments using the returned questions.
482
     *
483
     * @return array
484
     */
485
    protected function promptForMissingArgumentsUsing()
486
    {
487
        return [
488
            'name' => 'What should the '.strtolower($this->type).' be named?',
489
        ];
490
    }
148 lars 491
}