|
0
|
1 |
<?php |
|
|
2 |
|
|
|
3 |
/* |
|
|
4 |
* This file is part of the Symfony package. |
|
|
5 |
* |
|
|
6 |
* (c) Fabien Potencier <fabien@symfony.com> |
|
|
7 |
* |
|
|
8 |
* For the full copyright and license information, please view the LICENSE |
|
|
9 |
* file that was distributed with this source code. |
|
|
10 |
*/ |
|
|
11 |
|
|
|
12 |
namespace Sensio\Bundle\GeneratorBundle\Command; |
|
|
13 |
|
|
|
14 |
use Sensio\Bundle\GeneratorBundle\Generator\DoctrineEntityGenerator; |
|
|
15 |
use Sensio\Bundle\GeneratorBundle\Command\Helper\DialogHelper; |
|
|
16 |
use Symfony\Component\Console\Input\InputOption; |
|
|
17 |
use Symfony\Component\Console\Input\InputInterface; |
|
|
18 |
use Symfony\Component\Console\Output\OutputInterface; |
|
|
19 |
use Doctrine\ORM\Mapping\ClassMetadataInfo; |
|
|
20 |
use Doctrine\DBAL\Types\Type; |
|
|
21 |
|
|
|
22 |
/** |
|
|
23 |
* Initializes a Doctrine entity inside a bundle. |
|
|
24 |
* |
|
|
25 |
* @author Fabien Potencier <fabien@symfony.com> |
|
|
26 |
*/ |
|
|
27 |
class GenerateDoctrineEntityCommand extends GenerateDoctrineCommand |
|
|
28 |
{ |
|
|
29 |
private $generator; |
|
|
30 |
|
|
|
31 |
protected function configure() |
|
|
32 |
{ |
|
|
33 |
$this |
|
|
34 |
->setName('doctrine:generate:entity') |
|
|
35 |
->setAliases(array('generate:doctrine:entity')) |
|
|
36 |
->setDescription('Generates a new Doctrine entity inside a bundle') |
|
|
37 |
->addOption('entity', null, InputOption::VALUE_REQUIRED, 'The entity class name to initialize (shortcut notation)') |
|
|
38 |
->addOption('fields', null, InputOption::VALUE_REQUIRED, 'The fields to create with the new entity') |
|
|
39 |
->addOption('format', null, InputOption::VALUE_REQUIRED, 'Use the format for configuration files (php, xml, yml, or annotation)', 'annotation') |
|
|
40 |
->addOption('with-repository', null, InputOption::VALUE_NONE, 'Whether to generate the entity repository or not') |
|
|
41 |
->setHelp(<<<EOT |
|
|
42 |
The <info>doctrine:generate:entity</info> task generates a new Doctrine |
|
|
43 |
entity inside a bundle: |
|
|
44 |
|
|
|
45 |
<info>php app/console doctrine:generate:entity --entity=AcmeBlogBundle:Blog/Post</info> |
|
|
46 |
|
|
|
47 |
The above command would initialize a new entity in the following entity |
|
|
48 |
namespace <info>Acme\BlogBundle\Entity\Blog\Post</info>. |
|
|
49 |
|
|
|
50 |
You can also optionally specify the fields you want to generate in the new |
|
|
51 |
entity: |
|
|
52 |
|
|
|
53 |
<info>php app/console doctrine:generate:entity --entity=AcmeBlogBundle:Blog/Post --fields="title:string(255) body:text"</info> |
|
|
54 |
|
|
|
55 |
The command can also generate the corresponding entity repository class with the |
|
|
56 |
<comment>--with-repository</comment> option: |
|
|
57 |
|
|
|
58 |
<info>php app/console doctrine:generate:entity --entity=AcmeBlogBundle:Blog/Post --with-repository</info> |
|
|
59 |
|
|
|
60 |
By default, the command uses annotations for the mapping information; change it |
|
|
61 |
with <comment>--format</comment>: |
|
|
62 |
|
|
|
63 |
<info>php app/console doctrine:generate:entity --entity=AcmeBlogBundle:Blog/Post --format=yml</info> |
|
|
64 |
|
|
|
65 |
To deactivate the interaction mode, simply use the `--no-interaction` option |
|
|
66 |
whitout forgetting to pass all needed options: |
|
|
67 |
|
|
|
68 |
<info>php app/console doctrine:generate:entity --entity=AcmeBlogBundle:Blog/Post --format=annotation --field="title:string(255) body:text" --with-repository --no-interaction</info> |
|
|
69 |
EOT |
|
|
70 |
); |
|
|
71 |
} |
|
|
72 |
|
|
|
73 |
/** |
|
|
74 |
* @throws \InvalidArgumentException When the bundle doesn't end with Bundle (Example: "Bundle/MySampleBundle") |
|
|
75 |
*/ |
|
|
76 |
protected function execute(InputInterface $input, OutputInterface $output) |
|
|
77 |
{ |
|
|
78 |
$dialog = $this->getDialogHelper(); |
|
|
79 |
|
|
|
80 |
if ($input->isInteractive()) { |
|
|
81 |
if (!$dialog->askConfirmation($output, $dialog->getQuestion('Do you confirm generation', 'yes', '?'), true)) { |
|
|
82 |
$output->writeln('<error>Command aborted</error>'); |
|
|
83 |
|
|
|
84 |
return 1; |
|
|
85 |
} |
|
|
86 |
} |
|
|
87 |
|
|
|
88 |
$entity = Validators::validateEntityName($input->getOption('entity')); |
|
|
89 |
list($bundle, $entity) = $this->parseShortcutNotation($entity); |
|
|
90 |
$format = Validators::validateFormat($input->getOption('format')); |
|
|
91 |
$fields = $this->parseFields($input->getOption('fields')); |
|
|
92 |
|
|
|
93 |
$dialog->writeSection($output, 'Entity generation'); |
|
|
94 |
|
|
|
95 |
$bundle = $this->getContainer()->get('kernel')->getBundle($bundle); |
|
|
96 |
|
|
|
97 |
$generator = $this->getGenerator(); |
|
|
98 |
$generator->generate($bundle, $entity, $format, array_values($fields), $input->getOption('with-repository')); |
|
|
99 |
|
|
|
100 |
$output->writeln('Generating the entity code: <info>OK</info>'); |
|
|
101 |
|
|
|
102 |
$dialog->writeGeneratorSummary($output, array()); |
|
|
103 |
} |
|
|
104 |
|
|
|
105 |
protected function interact(InputInterface $input, OutputInterface $output) |
|
|
106 |
{ |
|
|
107 |
$dialog = $this->getDialogHelper(); |
|
|
108 |
$dialog->writeSection($output, 'Welcome to the Doctrine2 entity generator'); |
|
|
109 |
|
|
|
110 |
// namespace |
|
|
111 |
$output->writeln(array( |
|
|
112 |
'', |
|
|
113 |
'This command helps you generate Doctrine2 entities.', |
|
|
114 |
'', |
|
|
115 |
'First, you need to give the entity name you want to generate.', |
|
|
116 |
'You must use the shortcut notation like <comment>AcmeBlogBundle:Post</comment>.', |
|
|
117 |
'' |
|
|
118 |
)); |
|
|
119 |
|
|
|
120 |
while (true) { |
|
|
121 |
$entity = $dialog->askAndValidate($output, $dialog->getQuestion('The Entity shortcut name', $input->getOption('entity')), array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateEntityName'), false, $input->getOption('entity')); |
|
|
122 |
|
|
|
123 |
list($bundle, $entity) = $this->parseShortcutNotation($entity); |
|
|
124 |
|
|
|
125 |
// check reserved words |
|
|
126 |
$keywordList = $this->getContainer()->get('doctrine')->getConnection()->getDatabasePlatform()->getReservedKeywordsList(); |
|
|
127 |
if($keywordList->isKeyword($entity)){ |
|
|
128 |
$output->writeln(sprintf('<bg=red> "%s" is a reserved word</>.', $entity)); |
|
|
129 |
continue; |
|
|
130 |
} |
|
|
131 |
|
|
|
132 |
try { |
|
|
133 |
$b = $this->getContainer()->get('kernel')->getBundle($bundle); |
|
|
134 |
|
|
|
135 |
if (!file_exists($b->getPath().'/Entity/'.str_replace('\\', '/', $entity).'.php')) { |
|
|
136 |
break; |
|
|
137 |
} |
|
|
138 |
|
|
|
139 |
$output->writeln(sprintf('<bg=red>Entity "%s:%s" already exists</>.', $bundle, $entity)); |
|
|
140 |
} catch (\Exception $e) { |
|
|
141 |
$output->writeln(sprintf('<bg=red>Bundle "%s" does not exist.</>', $bundle)); |
|
|
142 |
} |
|
|
143 |
} |
|
|
144 |
$input->setOption('entity', $bundle.':'.$entity); |
|
|
145 |
|
|
|
146 |
// format |
|
|
147 |
$output->writeln(array( |
|
|
148 |
'', |
|
|
149 |
'Determine the format to use for the mapping information.', |
|
|
150 |
'', |
|
|
151 |
)); |
|
|
152 |
$format = $dialog->askAndValidate($output, $dialog->getQuestion('Configuration format (yml, xml, php, or annotation)', $input->getOption('format')), array('Sensio\Bundle\GeneratorBundle\Command\Validators', 'validateFormat'), false, $input->getOption('format')); |
|
|
153 |
$input->setOption('format', $format); |
|
|
154 |
|
|
|
155 |
// fields |
|
|
156 |
$input->setOption('fields', $this->addFields($input, $output, $dialog)); |
|
|
157 |
|
|
|
158 |
// repository? |
|
|
159 |
$output->writeln(''); |
|
|
160 |
$withRepository = $dialog->askConfirmation($output, $dialog->getQuestion('Do you want to generate an empty repository class', $input->getOption('with-repository') ? 'yes' : 'no', '?'), $input->getOption('with-repository')); |
|
|
161 |
$input->setOption('with-repository', $withRepository); |
|
|
162 |
|
|
|
163 |
// summary |
|
|
164 |
$output->writeln(array( |
|
|
165 |
'', |
|
|
166 |
$this->getHelper('formatter')->formatBlock('Summary before generation', 'bg=blue;fg=white', true), |
|
|
167 |
'', |
|
|
168 |
sprintf("You are going to generate a \"<info>%s:%s</info>\" Doctrine2 entity", $bundle, $entity), |
|
|
169 |
sprintf("using the \"<info>%s</info>\" format.", $format), |
|
|
170 |
'', |
|
|
171 |
)); |
|
|
172 |
} |
|
|
173 |
|
|
|
174 |
private function parseFields($input) |
|
|
175 |
{ |
|
|
176 |
if (is_array($input)) { |
|
|
177 |
return $input; |
|
|
178 |
} |
|
|
179 |
|
|
|
180 |
$fields = array(); |
|
|
181 |
foreach (explode(' ', $input) as $value) { |
|
|
182 |
$elements = explode(':', $value); |
|
|
183 |
$name = $elements[0]; |
|
|
184 |
if (strlen($name)) { |
|
|
185 |
$type = isset($elements[1]) ? $elements[1] : 'string'; |
|
|
186 |
preg_match_all('/(.*)\((.*)\)/', $type, $matches); |
|
|
187 |
$type = isset($matches[1][0]) ? $matches[1][0] : $type; |
|
|
188 |
$length = isset($matches[2][0]) ? $matches[2][0] : null; |
|
|
189 |
|
|
|
190 |
$fields[$name] = array('fieldName' => $name, 'type' => $type, 'length' => $length); |
|
|
191 |
} |
|
|
192 |
} |
|
|
193 |
|
|
|
194 |
return $fields; |
|
|
195 |
} |
|
|
196 |
|
|
|
197 |
private function addFields(InputInterface $input, OutputInterface $output, DialogHelper $dialog) |
|
|
198 |
{ |
|
|
199 |
$fields = $this->parseFields($input->getOption('fields')); |
|
|
200 |
$output->writeln(array( |
|
|
201 |
'', |
|
|
202 |
'Instead of starting with a blank entity, you can add some fields now.', |
|
|
203 |
'Note that the primary key will be added automatically (named <comment>id</comment>).', |
|
|
204 |
'', |
|
|
205 |
)); |
|
|
206 |
$output->write('<info>Available types:</info> '); |
|
|
207 |
|
|
|
208 |
$types = array_keys(Type::getTypesMap()); |
|
|
209 |
$count = 20; |
|
|
210 |
foreach ($types as $i => $type) { |
|
|
211 |
if ($count > 50) { |
|
|
212 |
$count = 0; |
|
|
213 |
$output->writeln(''); |
|
|
214 |
} |
|
|
215 |
$count += strlen($type); |
|
|
216 |
$output->write(sprintf('<comment>%s</comment>', $type)); |
|
|
217 |
if (count($types) != $i + 1) { |
|
|
218 |
$output->write(', '); |
|
|
219 |
} else { |
|
|
220 |
$output->write('.'); |
|
|
221 |
} |
|
|
222 |
} |
|
|
223 |
$output->writeln(''); |
|
|
224 |
|
|
|
225 |
$fieldValidator = function ($type) use ($types) { |
|
|
226 |
// FIXME: take into account user-defined field types |
|
|
227 |
if (!in_array($type, $types)) { |
|
|
228 |
throw new \InvalidArgumentException(sprintf('Invalid type "%s".', $type)); |
|
|
229 |
} |
|
|
230 |
|
|
|
231 |
return $type; |
|
|
232 |
}; |
|
|
233 |
|
|
|
234 |
$lengthValidator = function ($length) { |
|
|
235 |
if (!$length) { |
|
|
236 |
return $length; |
|
|
237 |
} |
|
|
238 |
|
|
|
239 |
$result = filter_var($length, FILTER_VALIDATE_INT, array( |
|
|
240 |
'options' => array('min_range' => 1) |
|
|
241 |
)); |
|
|
242 |
|
|
|
243 |
if (false === $result) { |
|
|
244 |
throw new \InvalidArgumentException(sprintf('Invalid length "%s".', $length)); |
|
|
245 |
} |
|
|
246 |
|
|
|
247 |
return $length; |
|
|
248 |
}; |
|
|
249 |
|
|
|
250 |
while (true) { |
|
|
251 |
$output->writeln(''); |
|
|
252 |
$keywordList = $this->getContainer()->get('doctrine')->getConnection()->getDatabasePlatform()->getReservedKeywordsList(); |
|
|
253 |
$name = $dialog->askAndValidate($output, $dialog->getQuestion('New field name (press <return> to stop adding fields)', null), function ($name) use ($fields,$keywordList) { |
|
|
254 |
if (isset($fields[$name]) || 'id' == $name) { |
|
|
255 |
throw new \InvalidArgumentException(sprintf('Field "%s" is already defined.', $name)); |
|
|
256 |
} |
|
|
257 |
|
|
|
258 |
// check reserved words |
|
|
259 |
if($keywordList->isKeyword($name)){ |
|
|
260 |
throw new \InvalidArgumentException(sprintf('Name "%s" is a reserved word.', $name)); |
|
|
261 |
} |
|
|
262 |
|
|
|
263 |
return $name; |
|
|
264 |
}); |
|
|
265 |
if (!$name) { |
|
|
266 |
break; |
|
|
267 |
} |
|
|
268 |
|
|
|
269 |
$defaultType = 'string'; |
|
|
270 |
|
|
|
271 |
if (substr($name, -3) == '_at') { |
|
|
272 |
$defaultType = 'datetime'; |
|
|
273 |
} else if (substr($name, -3) == '_id') { |
|
|
274 |
$defaultType = 'integer'; |
|
|
275 |
} |
|
|
276 |
|
|
|
277 |
$type = $dialog->askAndValidate($output, $dialog->getQuestion('Field type', $defaultType), $fieldValidator, false, $defaultType); |
|
|
278 |
|
|
|
279 |
$data = array('fieldName' => $name, 'type' => $type); |
|
|
280 |
|
|
|
281 |
if ($type == 'string') { |
|
|
282 |
$data['length'] = $dialog->askAndValidate($output, $dialog->getQuestion('Field length', 255), $lengthValidator, false, 255); |
|
|
283 |
} |
|
|
284 |
|
|
|
285 |
$fields[$name] = $data; |
|
|
286 |
} |
|
|
287 |
|
|
|
288 |
return $fields; |
|
|
289 |
} |
|
|
290 |
|
|
|
291 |
protected function getGenerator() |
|
|
292 |
{ |
|
|
293 |
if (null === $this->generator) { |
|
|
294 |
$this->generator = new DoctrineEntityGenerator($this->getContainer()->get('filesystem'), $this->getContainer()->get('doctrine')); |
|
|
295 |
} |
|
|
296 |
|
|
|
297 |
return $this->generator; |
|
|
298 |
} |
|
|
299 |
|
|
|
300 |
public function setGenerator(DoctrineEntityGenerator $generator) |
|
|
301 |
{ |
|
|
302 |
$this->generator = $generator; |
|
|
303 |
} |
|
|
304 |
|
|
|
305 |
protected function getDialogHelper() |
|
|
306 |
{ |
|
|
307 |
$dialog = $this->getHelperSet()->get('dialog'); |
|
|
308 |
if (!$dialog || get_class($dialog) !== 'Sensio\Bundle\GeneratorBundle\Command\Helper\DialogHelper') { |
|
|
309 |
$this->getHelperSet()->set($dialog = new DialogHelper()); |
|
|
310 |
} |
|
|
311 |
|
|
|
312 |
return $dialog; |
|
|
313 |
} |
|
|
314 |
} |