Subversion-Projekte lars-tiefland.laravel_shop

Revision

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