1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* This file is part of the Abstract builder package, an RunOpenCode project. |
4
|
|
|
* |
5
|
|
|
* (c) 2017 RunOpenCode |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE |
8
|
|
|
* file that was distributed with this source code. |
9
|
|
|
*/ |
10
|
|
|
namespace RunOpenCode\AbstractBuilder\Command; |
11
|
|
|
|
12
|
|
|
use RunOpenCode\AbstractBuilder\Exception\RuntimeException; |
13
|
|
|
use RunOpenCode\AbstractBuilder\Helper\Style; |
14
|
|
|
use RunOpenCode\AbstractBuilder\Helper\Tokenizer; |
15
|
|
|
use Symfony\Component\Console\Command\Command; |
16
|
|
|
use Symfony\Component\Console\Input\InputArgument; |
17
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
18
|
|
|
use Symfony\Component\Console\Input\InputOption; |
19
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
20
|
|
|
use Symfony\Component\Console\Question\ChoiceQuestion; |
21
|
|
|
use Symfony\Component\Console\Question\Question; |
22
|
|
|
|
23
|
|
|
/** |
24
|
|
|
* Class GenerateBuilderCommand |
25
|
|
|
* |
26
|
|
|
* @package RunOpenCode\AbstractBuilder\Command |
27
|
|
|
*/ |
28
|
|
|
class GenerateBuilderCommand extends Command |
29
|
|
|
{ |
30
|
|
|
/** |
31
|
|
|
* @var Style |
32
|
|
|
*/ |
33
|
|
|
private $style; |
34
|
|
|
|
35
|
|
|
/** |
36
|
|
|
* @var InputInterface |
37
|
|
|
*/ |
38
|
|
|
private $input; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* @var OutputInterface |
42
|
|
|
*/ |
43
|
|
|
private $output; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* {@inheritdoc} |
47
|
|
|
*/ |
48
|
|
|
protected function configure() |
49
|
|
|
{ |
50
|
|
|
$this |
51
|
|
|
->setName('runopencode:generate:builder') |
52
|
|
|
->setDescription('Generates builder class skeleton for provided class.') |
53
|
|
|
->addArgument('class', InputArgument::OPTIONAL, 'Full qualified class name of building object that can be autoloaded, or path to file with class definition.') |
54
|
|
|
->addArgument('builder', InputArgument::OPTIONAL, 'Full qualified class name of builder class can be autoloaded, or it will be autoloaded, or path to file with class definition.') |
55
|
|
|
->addArgument('location', InputArgument::OPTIONAL, 'Path to location of file where builder class will be saved.') |
56
|
|
|
->addOption('all', '-a', InputOption::VALUE_NONE, 'Should all methods be generated by default.'); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* {@inheritdoc} |
61
|
|
|
*/ |
62
|
|
|
public function execute(InputInterface $input, OutputInterface $output) |
63
|
|
|
{ |
64
|
|
|
$this->input = $input; |
65
|
|
|
$this->output = $output; |
66
|
|
|
$this->style = new Style($input, $output); |
67
|
|
|
|
68
|
|
|
$this->style->displayLogo(); |
69
|
|
|
|
70
|
|
|
$this->style->title('Generate builder class'); |
71
|
|
|
|
72
|
|
|
try { |
73
|
|
|
$buildingClass = $this->getBuildingClass(); |
74
|
|
|
$this->style->info(sprintf('Builder class for class "%s" will be generated.', $buildingClass)); |
75
|
|
|
|
76
|
|
|
$builderClass = $this->getBuilderClass(sprintf('%sBuilder', $buildingClass)); |
77
|
|
|
$this->style->info(sprintf('Full qualified namespace for builder class is "%s".', $builderClass)); |
78
|
|
|
|
79
|
|
|
$filePath = $this->getBuilderLocation($builderClass); |
80
|
|
|
$this->style->info(sprintf('Path to file where builder class will be saved is "%s".', $filePath)); |
81
|
|
|
|
82
|
|
|
file_exists($filePath) ? $this->style->info('Existing builder class will be updated.') : $this->style->info('New builder class will be created.'); |
83
|
|
|
|
84
|
|
|
$methods = $this->getMethods($buildingClass, $builderClass); |
85
|
|
|
$this->style->info(sprintf('Methods to generate are: "%s".', implode('", "', $methods))); |
86
|
|
|
|
87
|
|
|
} catch (\Exception $e) { |
88
|
|
|
$this->style->error($e->getMessage()); |
89
|
|
|
return 0; |
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* Get class name for which skeleton should be built. |
96
|
|
|
* |
97
|
|
|
* @return string |
98
|
|
|
* |
99
|
|
|
* @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException |
100
|
|
|
*/ |
101
|
|
|
private function getBuildingClass() |
102
|
|
|
{ |
103
|
|
|
$class = $this->input->getArgument('class'); |
104
|
|
|
|
105
|
|
View Code Duplication |
if (null === $class) { |
|
|
|
|
106
|
|
|
$helper = $this->getHelper('question'); |
107
|
|
|
$question = new Question('Enter full qualified class name, or path to file with class, for which you want to generate builder class: ', null); |
108
|
|
|
|
109
|
|
|
$class = $helper->ask($this->input, $this->output, $question); |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
if (!class_exists($class, true)) { |
113
|
|
|
$class = Tokenizer::findClass($class); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
if (!class_exists($class, true)) { |
117
|
|
|
throw new RuntimeException(sprintf('Unable to autoload class "%s". Does this class exists? Can it be autoloaded?', $class)); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
return ltrim(str_replace('\\\\', '\\', $class), '\\'); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Get class name for builder class. |
125
|
|
|
* |
126
|
|
|
* @param string $suggest |
127
|
|
|
* |
128
|
|
|
* @return string |
129
|
|
|
* |
130
|
|
|
* @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException |
131
|
|
|
*/ |
132
|
|
|
private function getBuilderClass($suggest) |
133
|
|
|
{ |
134
|
|
|
$class = $this->input->getArgument('builder'); |
135
|
|
|
|
136
|
|
View Code Duplication |
if (null === $class) { |
|
|
|
|
137
|
|
|
$helper = $this->getHelper('question'); |
138
|
|
|
$question = new Question(sprintf('Enter full qualified class name of your builder class (default: "%s"): ', $suggest), $suggest); |
139
|
|
|
|
140
|
|
|
$class = $helper->ask($this->input, $this->output, $question); |
141
|
|
|
} |
142
|
|
|
|
143
|
|
|
if (file_exists($class) && !class_exists($class, true)) { |
144
|
|
|
$class = Tokenizer::findClass($class); |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
$class = ltrim(str_replace('\\\\', '\\', $class), '\\'); |
148
|
|
|
|
149
|
|
|
if ('' === $class) { |
150
|
|
|
throw new RuntimeException('Builder class name must be provided.'); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
foreach (explode('\\', $class) as $part) { |
154
|
|
|
|
155
|
|
|
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $part)) { |
156
|
|
|
throw new RuntimeException(sprintf('Provided builder class name "%s" is not valid PHP class name.', $class)); |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
return $class; |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
/** |
164
|
|
|
* Get builder class location. |
165
|
|
|
* |
166
|
|
|
* @param string $builderClass |
167
|
|
|
* |
168
|
|
|
* @return string |
169
|
|
|
* |
170
|
|
|
* @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException |
171
|
|
|
*/ |
172
|
|
|
private function getBuilderLocation($builderClass) |
173
|
|
|
{ |
174
|
|
|
$location = $this->input->getArgument('location'); |
175
|
|
|
|
176
|
|
|
if (null === $location && class_exists($builderClass)) { |
177
|
|
|
$location = (new \ReflectionClass($builderClass))->getFileName(); |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
if (null === $location) { |
181
|
|
|
$helper = $this->getHelper('question'); |
182
|
|
|
$question = new Question('Enter path to directory where you want to store builder class: ', null); |
183
|
|
|
|
184
|
|
|
$path = str_replace('\\', '/', ltrim($helper->ask($this->input, $this->output, $question), '/')); |
185
|
|
|
|
186
|
|
|
if (!is_dir($path)) { |
187
|
|
|
throw new RuntimeException(sprintf('Provided path "%s" is not path to directory.', $path)); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
if (!is_writable($path)) { |
191
|
|
|
throw new RuntimeException(sprintf('Directory on path "%s" is not writeable.', $path)); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
$path = $path.'/'.end(explode('/', $builderClass)).'.php'; |
|
|
|
|
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return $location; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Get methods which ought to be generated. |
202
|
|
|
* |
203
|
|
|
* @param string $buildingClass |
204
|
|
|
* @param string $builderClass |
205
|
|
|
* |
206
|
|
|
* @return array |
207
|
|
|
* |
208
|
|
|
* @throws \RunOpenCode\AbstractBuilder\Exception\RuntimeException |
209
|
|
|
*/ |
210
|
|
|
private function getMethods($buildingClass, $builderClass) |
211
|
|
|
{ |
212
|
|
|
$constructorParameters = (new \ReflectionClass($buildingClass))->getConstructor()->getParameters(); |
213
|
|
|
$builderMethods = class_exists($builderClass, true) ? get_class_methods($builderClass) : []; |
214
|
|
|
|
215
|
|
|
$methods = []; |
216
|
|
|
|
217
|
|
|
foreach ($constructorParameters as $parameter) { |
218
|
|
|
$getter = sprintf('get%s', ucfirst($parameter->getName())); |
219
|
|
|
$setter = sprintf('set%s', ucfirst($parameter->getName())); |
220
|
|
|
|
221
|
|
|
if (!in_array($getter, $builderMethods, true)) { |
222
|
|
|
$methods[] = sprintf('%s()', $getter); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
if (!in_array($setter, $builderMethods, true)) { |
226
|
|
|
$type = (null !== $parameter->getType()) ? '\\'.$parameter->getType().' ' : ''; |
227
|
|
|
$methods[] = sprintf('%s(%s$%s)', $setter, $type, $parameter->getName()); |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
if (0 === count($methods)) { |
232
|
|
|
return []; |
233
|
|
|
} |
234
|
|
|
|
235
|
|
|
if (true !== $this->input->getOption('all')) { |
236
|
|
|
|
237
|
|
|
$helper = $this->getHelper('question'); |
238
|
|
|
$question = new ChoiceQuestion( |
239
|
|
|
'Choose which methods you want to generate for your builder class (separate choices with coma, enter none for all choices):', |
240
|
|
|
$methods, |
241
|
|
|
implode(',', array_keys($methods)) |
242
|
|
|
); |
243
|
|
|
$question->setMultiselect(true); |
244
|
|
|
|
245
|
|
|
$methods = $helper->ask($this->input, $this->output, $question); |
246
|
|
|
} |
247
|
|
|
|
248
|
|
|
if (0 === count($methods)) { |
249
|
|
|
throw new RuntimeException('You have to choose at least one method to generate.'); |
250
|
|
|
} |
251
|
|
|
|
252
|
|
|
return $methods; |
253
|
|
|
} |
254
|
|
|
} |
255
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.