1: <?php
2: namespace TIC\MakeBundle\Maker;
3:
4: use Symfony\Bundle\MakerBundle\Maker\AbstractMaker;
5: use Symfony\Bundle\MakerBundle\DependencyBuilder;
6: use Symfony\Bundle\MakerBundle\ConsoleStyle;
7: use Symfony\Bundle\MakerBundle\Generator;
8: use Symfony\Bundle\MakerBundle\FileManager;
9: use Symfony\Bundle\MakerBundle\Str;
10: use Symfony\Bundle\MakerBundle\Util\ClassNameDetails;
11: use Symfony\Bundle\MakerBundle\Doctrine\EntityDetails;
12: use Symfony\Bundle\MakerBundle\InputConfiguration;
13: use Symfony\Component\Console\Input\InputInterface;
14: use Symfony\Component\Console\Input\InputOption;
15: use Symfony\Component\Console\Command\Command;
16:
17: use TIC\CoreBundle\Util\StringHelper;
18: use TIC\MakeBundle\Helper\GeneratorTwigHelper;
19: use TIC\MakeBundle\Renderer\FormTypeRenderer;
20:
21: /**
22: *
23: */
24: abstract class BaseMaker extends AbstractMaker
25: {
26:
27: protected $makerName;
28: protected static $description;
29: protected static $skelPath = __DIR__.'/../Resources/skeleton/';
30: protected $dependencies = [];
31: protected $twig = true;
32: protected $test = false;
33: protected $over = false;
34: protected $ask = true;
35: protected $io;
36: protected $generator;
37: protected $fileManager;
38:
39: public function __construct()
40: {
41: $this->makerName = self::getMakerName();
42: }
43:
44: /**
45: * Injection par call pour laisser le construct facilement surchargeable
46: */
47: public function setFileManager(FileManager $fileManager)
48: {
49: $this->fileManager = $fileManager;
50: }
51:
52: /**
53: *
54: */
55: protected static function getMakerName(): string
56: {
57: if (preg_match("/\\\\Maker\\\\Make(.*)\$/", static::class, $m)) return $m[1];
58: }
59:
60:
61: /**
62: * Return the command name for your maker (e.g. make:controller).
63: */
64: public static function getCommandName(): string
65: {
66: # return 'make:tic:' . strtolower(self::getMakerName());
67: return 'tic:make:' . strtolower(self::getMakerName());
68: }
69:
70: /**
71: *
72: */
73: public static function getCommandDescription(): string
74: {
75: return sprintf("Génération basée sur les TIC Bundles pour %s.", self::getMakerName());
76: }
77:
78: /**
79: * Configure the command: set description, input arguments, options, etc.
80: *
81: * By default, all arguments will be asked interactively. If you want
82: * to avoid that, use the $inputConfig->setArgumentAsNonInteractive() method.
83: */
84: public function configureCommand(Command $command, InputConfiguration $inputConfig)
85: {
86: $command
87: ->addOption('test', 't', InputOption::VALUE_NONE, "Mode test: affichage sans génération de fichier")
88: ->addOption('no-template', null, InputOption::VALUE_NONE, "Désactiver la génération de template twig")
89: ->addOption('overwrite', null, InputOption::VALUE_NONE, "ATTENTION: suppression des fichiers déjà existants !")
90: ->addOption('yes', null, InputOption::VALUE_NONE, "Désactiver les demandes de confirmation")
91: ;
92: $helpFile = __DIR__ . sprintf('/../Resources/help/Make%s.txt', $this->makerName);
93: if (file_exists($helpFile)) $command->setHelp(file_get_contents($helpFile));
94:
95: $this->ticConfigure($command);
96: }
97:
98: /**
99: * Configure any library dependencies that your maker requires.
100: */
101: public function configureDependencies(DependencyBuilder $dependencies)
102: {
103: foreach ($this->dependencies as $dependency) $dependencies->addClassDependency(
104: $dependency['class'], $dependency['value']
105: );
106: }
107:
108: /**
109: * Called after normal code generation: allows you to do anything.
110: */
111: public function generate(InputInterface $input, ConsoleStyle $io, Generator $generator)
112: {
113: $this->generator = $generator;
114: $this->io = $io;
115: $this->ask = ! $input->getOption('yes');
116: $this->test = $input->getOption('test');
117: $this->over = $input->getOption('overwrite');
118: $this->twig = ! $input->getOption('no-template');
119:
120: $rc = $this->ticGenerate($input, $io);
121: if (! $rc) return;
122:
123: $generator->writeChanges();
124: $this->writeSuccessMessage($io);
125: $io->text("Next: Open your new controller class and add some pages!");
126: }
127:
128:
129: /**
130: *
131: */
132: abstract protected function ticConfigure(Command $command);
133:
134: /**
135: *
136: */
137: abstract protected function ticGenerate(InputInterface $input);
138:
139:
140: /**
141: * Pré-traitement du nom de classe passé en argument aux commandes.
142: * - accepte les caractères '/' et '.' en séparateur de chemin (au lieu de '\')
143: * - force une majuscule sur tous éléments du chemin du nom de classe
144: */
145: protected function fixClassName(string $name, ?bool $skip_path = false): string
146: {
147: $name = StringHelper::asciify($name);
148: $name = preg_replace_callback('/_(.)/', function($m){ return strtoupper($m[1]); }, $name);
149:
150: if (strpos("\\", $name) === FALSE) $name = preg_replace('/[\/\.]+/', "\\", $name);
151:
152: $parts = explode("\\", $name);
153: return ($skip_path) ? ucfirst(array_pop($parts)) : implode("\\", array_map('ucfirst', $parts));
154: }
155:
156: /**
157: *
158: */
159: protected function getRootNamespace(ClassNameDetails $classNameDetails, ?string $classFolder = null): string
160: {
161: $ns = str_replace($classNameDetails->getRelativeName(), '', $classNameDetails->getFullName());
162: $ns = ($classFolder === null) ? preg_replace("/\\\\[^\\\\]*\\\\?\$/", "", $ns) : str_replace("\\".$classFolder."\\", "", $ns);
163: return $ns;
164: }
165:
166: /**
167: *
168: */
169: protected function getSnakeCaseName(ClassNameDetails $classNameDetails): string
170: {
171: $parts = explode("\\", $classNameDetails->getRelativeNameWithoutSuffix());
172: $last = array_pop($parts);
173: if (empty($parts)) $parts[] = 'app';
174: return strtolower(implode('', $parts) . '_' . $last);
175: }
176:
177: /**
178: *
179: */
180: protected function getVariablesFromName(string $inputName, ?string $type = "Controller"): array
181: {
182: $classPrefix = ucfirst($type) . "\\";
183: $classSuffix = ($classPrefix === "Entity\\") ? "" : ucfirst($type);
184: $classDetails = $this->generator->createClassNameDetails($inputName, $classPrefix, $classSuffix);
185: $nameSpace = $this->getRootNamespace($classDetails);
186: $snakeCase = $this->getSnakeCaseName($classDetails);
187:
188: $variables = array(
189: 'base_name' => $classDetails->getRelativeNameWithoutSuffix(),
190: 'item_name' => preg_replace("/${classSuffix}\$/", '', $classDetails->getShortName()),
191: 'trans_base' => str_replace('_', '.', $snakeCase) . '.',
192: );
193: if ($classPrefix == "Controller\\") $variables+= array(
194: 'route_name' => $snakeCase . '_',
195: 'route_path' => Str::asRoutePath($variables['base_name']),
196: 'views_path' => Str::asFilePath($variables['base_name']),
197: 'views_root' => preg_replace('/^(\/*)([^\/]+\/)?(.+)$/', '${2}layout.html.twig', Str::asFilePath($variables['base_name'])),
198: 'ctrl_class' => $classDetails->getFullName(),
199: 'form_class' => $nameSpace . "\\Form\\" . $variables['base_name'] . "Type",
200: );
201: if ($classPrefix == "Type\\") $variables+= array(
202: 'form_class' => $nameSpace . "\\Form\\" . $variables['base_name'] . "Type",
203: );
204: return $variables+= array(
205: 'item_class' => $nameSpace . "\\Entity\\" . $variables['item_name'],
206: 'repo_class' => $nameSpace . "\\Repository\\" . $variables['item_name'] . "Repository",
207: 'root_directory' => $this->generator->getRootDirectory(),
208: );
209: }
210:
211: // -----------------------------------------------------------------------------
212:
213: protected function ticGenerateTemplates(string $skelDir, array $templates, array $variables = []): array
214: {
215: if (isset($variables['views_root'])) $this->initTwigLayout($variables['views_root'], $variables);
216:
217: $res = array();
218: foreach ($templates as $template) $res[] = $this->ticGenerateTemplate(
219: $variables['views_path'] . '/' . $template . '.html.twig',
220: self::$skelPath . $skelDir . '/twig_' . $template . '.tpl.php',
221: $variables
222: );
223: return $res;
224: }
225:
226: /**
227: * Génération du template twig parent (si le fichier n'existe pas déjà).
228: */
229: protected function initTwigLayout(string $tpl_path, array $variables = []): void
230: {
231: $src_path = static::$skelPath . 'base/twig_layout.tpl.php';
232: # $tpl_name = dirname($tpl_path);
233: # if ($tpl_name !== '.') $variables['webpack_entry'] = 'app_' . $tpl_name;
234: try { $this->generator->generateTemplate($tpl_path, $src_path, $variables); }
235: catch (\Symfony\Bundle\MakerBundle\Exception\RuntimeCommandException $e) { }
236: }
237:
238: protected function ticGenerateTemplate(string $targetPath, string $templateName, array $variables = []): ?string
239: {
240: $templatePath = $this->fileManager->getPathForTemplate($targetPath);
241: if (! $this->checkTargetPath( $templatePath )) return null;
242:
243: $variables['ticHelper'] = new GeneratorTwigHelper();
244: $this->generator->generateTemplate($targetPath, $templateName, $variables);
245: return $templatePath;
246: }
247:
248: protected function ticGenerateClass(string $className, string $templateName, array $variables = []): ?string
249: {
250: if (! $this->checkTargetPath( $this->fileManager->getRelativePathForFutureClass($className) )) return null;
251:
252: return $this->generator->generateClass($className, $templateName, $variables);
253: }
254:
255: protected function ticGenerateController(string $controllerClassName, string $controllerTemplatePath, array $parameters = []): ?string
256: {
257: if (! $this->checkTargetPath( $this->fileManager->getRelativePathForFutureClass($controllerClassName) )) return null;
258:
259: return $this->generator->generateController($controllerClassName, $controllerTemplatePath, $parameters);
260: }
261:
262: protected function ticGenerateFormType(FormTypeRenderer $renderer, ClassNameDetails $formDetails, EntityDetails $doctrineDetails, ClassNameDetails $entityDetails, array $constraintClasses = [], array $extraUseClasses = []): ?string
263: {
264: if (! $this->checkTargetPath( $this->fileManager->getRelativePathForFutureClass($formDetails->getFullName()) )) return null;
265:
266: return $renderer->generate($formDetails, $doctrineDetails, $entityDetails);
267: }
268:
269: protected function checkTargetPath(string $targetPath): bool
270: {
271: $fullPath = $this->fileManager->absolutizePath($targetPath);
272:
273: if (! file_exists($fullPath)) return true;
274: $this->io->caution("Le fichier à générer existe déjà : " . $fullPath);
275:
276: if (! $this->over) return false;
277: if (! $this->confirm("Confirmer la suppression du fichier actuel")) return false;
278:
279: return unlink($fullPath);
280: }
281:
282: protected function confirm(string $question): bool
283: {
284: if (! $this->ask) return true;
285: return $this->io->confirm($question, true);
286: }
287:
288:
289: /**
290: *
291: */
292: protected static function hash2list(array $hash): array
293: {
294: $lines = array();
295: foreach ($hash as $k => $v) $lines[] = array($k, $v);
296: return $lines;
297: }
298:
299: /**
300: *
301: */
302: protected static function indentFile(string $file, ?bool $with_space = false): bool
303: {
304: if (! file_exists($file)) return false;
305: $old = ($with_space) ? "\t" : " ";
306: $new = ($with_space) ? " " : "\t";
307: $len = strlen($old);
308: $data = file_get_contents($file);
309: $data = preg_replace_callback("/\n(($old)+)/", function($m) use($new,$len) { return "\n" . str_repeat($new, ceil(strlen($m[1]) / $len)); }, $data);
310: $data = str_replace(";\n\n$new$new", ";\n$new$new", $data);
311: file_put_contents($file, $data);
312: return true;
313: }
314:
315: /**
316: * Récapitulatif des fichiers générés
317: */
318: protected function summary(array $results): bool
319: {
320: $this->io->section("Liste des fichiers générés :");
321: $this->io->table([ 'Name', 'Path' ], $results);
322: return true;
323: }
324:
325: }
326: