1: <?php
2:
3: namespace Aurora\System\Console\Commands;
4:
5: use Aurora\System\Api;
6: use Illuminate\Filesystem\Filesystem;
7: use Illuminate\Support\Str;
8: use Symfony\Component\Console\Command\Command;
9: use Symfony\Component\Console\Input\InputInterface;
10: use Symfony\Component\Console\Output\OutputInterface;
11:
12: abstract class GeneratorCommand extends Command
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 array
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: 'eval',
62: 'exit',
63: 'extends',
64: 'final',
65: 'finally',
66: 'fn',
67: 'for',
68: 'foreach',
69: 'function',
70: 'global',
71: 'goto',
72: 'if',
73: 'implements',
74: 'include',
75: 'include_once',
76: 'instanceof',
77: 'insteadof',
78: 'interface',
79: 'isset',
80: 'list',
81: 'namespace',
82: 'new',
83: 'or',
84: 'print',
85: 'private',
86: 'protected',
87: 'public',
88: 'require',
89: 'require_once',
90: 'return',
91: 'static',
92: 'switch',
93: 'throw',
94: 'trait',
95: 'try',
96: 'unset',
97: 'use',
98: 'var',
99: 'while',
100: 'xor',
101: 'yield',
102: ];
103:
104: /**
105: * Create a new controller creator command instance.
106: *
107: * @param \Illuminate\Filesystem\Filesystem $files
108: * @return void
109: */
110: public function __construct(Filesystem $files)
111: {
112: parent::__construct();
113:
114: $this->files = $files;
115: }
116:
117: /**
118: * Get the stub file for the generator.
119: *
120: * @return string
121: */
122: abstract protected function getStub();
123:
124: /**
125: * Execute the console command.
126: *
127: * @return bool|null
128: *
129: * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
130: */
131: protected function execute(InputInterface $input, OutputInterface $output): int
132: {
133: // First we need to ensure that the given name is not a reserved word within the PHP
134: // language and that the class name will actually be valid. If it is not valid we
135: // can error now and prevent from polluting the filesystem using invalid files.
136: if ($this->isReservedName($this->getNameInput($input))) {
137: $output->writeln('The name "' . $this->getNameInput($input) . '" is reserved by PHP.');
138:
139: return false;
140: }
141:
142: $name = $this->qualifyClass($this->getNameInput($input));
143:
144: $path = $this->getPath($name);
145:
146: // Next, We will check to see if the class already exists. If it does, we don't want
147: // to create the class and overwrite the user's code. So, we will bail out so the
148: // code is untouched. Otherwise, we will continue generating this class' files.
149: if ((!$input->hasOption('force') ||
150: !$input->getOption('force')) &&
151: $this->alreadyExists($this->getNameInput($input))) {
152: $output->writeln('Seed already exists!');
153:
154: return false;
155: }
156:
157: // Next, we will generate the path to the location where this class' file should get
158: // written. Then, we will build the class and make the proper replacements on the
159: // stub files so that it gets the correctly formatted namespace and class name.
160: $this->makeDirectory($path);
161:
162: $this->files->put($path, $this->sortImports($this->buildClass($name)));
163:
164: $output->writeln('Seed created successfully.');
165:
166: return true;
167: }
168:
169: /**
170: * Parse the class name and format according to the root namespace.
171: *
172: * @param string $name
173: * @return string
174: */
175: protected function qualifyClass($name)
176: {
177: $name = ltrim($name, '\\/');
178:
179: $rootNamespace = Api::RootPath();
180:
181: if (Str::startsWith($name, $rootNamespace)) {
182: return $name;
183: }
184:
185: $name = str_replace('/', '\\', $name);
186:
187: return $this->qualifyClass(
188: $this->getDefaultNamespace(trim($rootNamespace, '\\')) . '\\' . $name
189: );
190: }
191:
192: /**
193: * Get the default namespace for the class.
194: *
195: * @param string $rootNamespace
196: * @return string
197: */
198: protected function getDefaultNamespace($rootNamespace)
199: {
200: return $rootNamespace;
201: }
202:
203: /**
204: * Determine if the class already exists.
205: *
206: * @param string $rawName
207: * @return bool
208: */
209: protected function alreadyExists($rawName)
210: {
211: return $this->files->exists($this->getPath($this->qualifyClass($rawName)));
212: }
213:
214: /**
215: * Get the destination class path.
216: *
217: * @param $name
218: * @param $moduleName
219: * @return string
220: */
221: protected function getPath($name)
222: {
223: return \Aurora\Api::RootPath() . 'seeds' . DIRECTORY_SEPARATOR . str_replace('\\', '/', $name) . '.php';
224: }
225:
226: /**
227: * Build the directory for the class if necessary.
228: *
229: * @param string $path
230: * @return string
231: */
232: protected function makeDirectory($path)
233: {
234: if (!$this->files->isDirectory(dirname($path))) {
235: $this->files->makeDirectory(dirname($path), 0777, true, true);
236: }
237:
238: return $path;
239: }
240:
241: /**
242: * Build the class with the given name.
243: *
244: * @param string $name
245: * @return string
246: *
247: * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
248: */
249: protected function buildClass($name)
250: {
251: $stub = $this->files->get($this->getStub());
252:
253: return $this->replaceNamespace($stub, $name)->replaceClass($stub, $name);
254: }
255:
256: /**
257: * Replace the namespace for the given stub.
258: *
259: * @param string $stub
260: * @param string $name
261: * @return $this
262: */
263: protected function replaceNamespace(&$stub, $name)
264: {
265: $searches = [
266: ['DummyNamespace', 'DummyRootNamespace', 'NamespacedDummyUserModel'],
267: ['{{ namespace }}', '{{ rootNamespace }}', '{{ namespacedUserModel }}'],
268: ['{{namespace}}', '{{rootNamespace}}', '{{namespacedUserModel}}'],
269: ];
270:
271: foreach ($searches as $search) {
272: $stub = str_replace(
273: $search,
274: [$this->getNamespace($name)],
275: $stub
276: );
277: }
278:
279: return $this;
280: }
281:
282: /**
283: * Get the full namespace for a given class, without the class name.
284: *
285: * @param string $name
286: * @return string
287: */
288: protected function getNamespace($name)
289: {
290: return trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\');
291: }
292:
293: /**
294: * Replace the class name for the given stub.
295: *
296: * @param string $stub
297: * @param string $name
298: * @return string
299: */
300: protected function replaceClass($stub, $name)
301: {
302: $class = str_replace($this->getNamespace($name) . '\\', '', $name);
303:
304: return str_replace(['DummyClass', '{{ class }}', '{{class}}'], $class, $stub);
305: }
306:
307: /**
308: * Alphabetically sorts the imports for the given stub.
309: *
310: * @param string $stub
311: * @return string
312: */
313: protected function sortImports($stub)
314: {
315: if (preg_match('/(?P<imports>(?:use [^;]+;$\n?)+)/m', $stub, $match)) {
316: $imports = explode("\n", trim($match['imports']));
317:
318: sort($imports);
319:
320: return str_replace(trim($match['imports']), implode("\n", $imports), $stub);
321: }
322:
323: return $stub;
324: }
325:
326: /**
327: * Get the desired class name from the input.
328: *
329: * @return string
330: */
331: protected function getNameInput(InputInterface $input)
332: {
333: return trim($input->getArgument('name'));
334: }
335:
336: /**
337: * Checks whether the given name is reserved.
338: *
339: * @param string $name
340: * @return bool
341: */
342: protected function isReservedName($name)
343: {
344: $name = strtolower($name);
345:
346: return in_array($name, $this->reservedNames);
347: }
348: }
349: