Completed
Push — develop ( 391d43...2463ed )
by Bastian
10s
created

generateBundleBundleClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 16
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6
Metric Value
dl 0
loc 16
ccs 0
cts 0
cp 0
rs 9.4285
cc 2
eloc 9
nc 2
nop 0
crap 6
1
<?php
2
/**
3
 * generate dynamic bundles
4
 */
5
6
namespace Graviton\GeneratorBundle\Command;
7
8
use Graviton\GeneratorBundle\CommandRunner;
9
use Graviton\GeneratorBundle\Definition\JsonDefinition;
10
use Graviton\GeneratorBundle\Definition\JsonDefinitionArray;
11
use Graviton\GeneratorBundle\Definition\JsonDefinitionHash;
12
use Graviton\GeneratorBundle\Generator\DynamicBundleBundleGenerator;
13
use Graviton\GeneratorBundle\Manipulator\File\XmlManipulator;
14
use Graviton\GeneratorBundle\Definition\Loader\LoaderInterface;
15
use JMS\Serializer\SerializerInterface;
16
use Symfony\Component\Console\Command\Command;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
21
/**
22
 * Here, we generate all "dynamic" Graviton bundles..
23
 *
24
 * @todo     create a new Application in-situ
25
 * @todo     see if we can get rid of container dependency..
26
 *
27
 * @author   List of contributors <https://github.com/libgraviton/graviton/graphs/contributors>
28
 * @license  http://opensource.org/licenses/gpl-license.php GNU Public License
29
 * @link     http://swisscom.ch
30
 */
31
class GenerateDynamicBundleCommand extends Command
32
{
33
    const BUNDLE_NAME_MASK = 'GravitonDyn/%sBundle';
34
35
    /** @var  string */
36
    private $bundleBundleNamespace;
37
38
    /** @var  string */
39
    private $bundleBundleDir;
40
41
    /** @var  string */
42
    private $bundleBundleClassname;
43
44
    /** @var  string */
45
    private $bundleBundleClassfile;
46
47
    /** @var  array */
48
    private $bundleBundleList = [];
49
50
    /** @var array|null */
51
    private $bundleAdditions = null;
52
53
    /** @var array|null */
54
    private $serviceWhitelist = null;
55
56
    /**
57
     * @var CommandRunner
58
     */
59
    private $runner;
60
    /**
61
     * @var LoaderInterface
62
     */
63
    private $definitionLoader;
64
    /**
65
     * @var XmlManipulator
66
     */
67
    private $xmlManipulator;
68
    /**
69
     * @var SerializerInterface
70
     */
71
    private $serializer;
72
73
74
    /**
75
     * @param CommandRunner       $runner           Runs a console command.
76
     * @param XmlManipulator      $xmlManipulator   Helper to change the content of a xml file.
77
     * @param LoaderInterface     $definitionLoader JSON definition loader
78
     * @param SerializerInterface $serializer       Serializer
79
     * @param string|null         $bundleAdditions  Additional bundles list in JSON format
80
     * @param string|null         $serviceWhitelist Service whitelist in JSON format
81
     * @param string|null         $name             The name of the command; passing null means it must be set in
82
     *                                              configure()
83
     */
84 1
    public function __construct(
85
        CommandRunner       $runner,
86
        XmlManipulator      $xmlManipulator,
87
        LoaderInterface     $definitionLoader,
88
        SerializerInterface $serializer,
89
        $bundleAdditions = null,
90
        $serviceWhitelist = null,
91
        $name = null
92
    ) {
93 1
        parent::__construct($name);
94
95 1
        $this->runner = $runner;
96 1
        $this->xmlManipulator = $xmlManipulator;
97 1
        $this->definitionLoader = $definitionLoader;
98 1
        $this->serializer = $serializer;
99
100 1
        if ($bundleAdditions !== null && $bundleAdditions !== '') {
101
            $this->bundleAdditions = $bundleAdditions;
0 ignored issues
show
Documentation Bug introduced by
It seems like $bundleAdditions of type string is incompatible with the declared type array|null of property $bundleAdditions.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
102
        }
103 1
        if ($serviceWhitelist !== null && $serviceWhitelist !== '') {
104
            $this->serviceWhitelist = $serviceWhitelist;
0 ignored issues
show
Documentation Bug introduced by
It seems like $serviceWhitelist of type string is incompatible with the declared type array|null of property $serviceWhitelist.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
105
        }
106 1
    }
107
108
    /**
109
     * {@inheritDoc}
110
     *
111
     * @return void
112
     */
113 1
    protected function configure()
114
    {
115 1
        parent::configure();
116
117 1
        $this->addOption(
118 1
            'json',
119 1
            '',
120 1
            InputOption::VALUE_OPTIONAL,
121 1
            'Path to the json definition.'
122
        )
123 1
            ->addOption(
124 1
                'srcDir',
125 1
                '',
126 1
                InputOption::VALUE_OPTIONAL,
127 1
                'Src Dir',
128 1
                dirname(__FILE__) . '/../../../'
129
            )
130 1
            ->addOption(
131 1
                'bundleBundleName',
132 1
                '',
133 1
                InputOption::VALUE_OPTIONAL,
134 1
                'Which BundleBundle to manipulate to add our stuff',
135 1
                'GravitonDynBundleBundle'
136
            )
137 1
            ->addOption(
138 1
                'bundleFormat',
139 1
                '',
140 1
                InputOption::VALUE_OPTIONAL,
141 1
                'Which format',
142 1
                'xml'
143
            )
144 1
            ->setName('graviton:generate:dynamicbundles')
145 1
            ->setDescription(
146
                'Generates all dynamic bundles in the GravitonDyn namespace. Either give a path
147 1
                    to a single JSON file or a directory path containing multiple files.'
148
            );
149 1
    }
150
151
    /**
152
     * {@inheritDoc}
153
     *
154
     * @param InputInterface  $input  input
155
     * @param OutputInterface $output output
156
     *
157
     * @return void
158
     */
159 1
    protected function execute(InputInterface $input, OutputInterface $output)
160
    {
161
        /**
162
         * GENERATE THE BUNDLEBUNDLE
163
         */
164 1
        $namespace = sprintf(self::BUNDLE_NAME_MASK, 'Bundle');
165
166
        // GravitonDynBundleBundle
167 1
        $bundleName = str_replace('/', '', $namespace);
168
169
        // bundlebundle stuff..
170 1
        $this->bundleBundleNamespace = $namespace;
171 1
        $this->bundleBundleDir = $input->getOption('srcDir') . $namespace;
172 1
        $this->bundleBundleClassname = $bundleName;
173 1
        $this->bundleBundleClassfile = $this->bundleBundleDir . '/' . $this->bundleBundleClassname . '.php';
174
175 1
        $filesToWorkOn = $this->definitionLoader->load($input->getOption('json'));
176
177 1
        if (count($filesToWorkOn) < 1) {
178 1
            throw new \LogicException("Could not find any usable JSON files.");
179
        }
180
181
        /**
182
         * GENERATE THE BUNDLE(S)
183
         */
184
        foreach ($filesToWorkOn as $jsonDef) {
185
            $thisIdName = $jsonDef->getId();
186
            $namespace = sprintf(self::BUNDLE_NAME_MASK, $thisIdName);
187
188
            $jsonDef->setNamespace($namespace);
189
190
            $bundleName = str_replace('/', '', $namespace);
191
            $this->bundleBundleList[] = $namespace;
192
193
            try {
194
                $this->generateBundle($namespace, $bundleName, $input, $output);
195
                $this->generateBundleBundleClass();
196
                $this->generateSubResources($output, $jsonDef, $this->xmlManipulator, $bundleName, $namespace);
197
                $this->generateMainResource($output, $jsonDef, $bundleName);
198
                $this->generateValidationXml($this->xmlManipulator, $this->getGeneratedValidationXmlPath($namespace));
199
200
                $output->writeln('');
201
                $output->writeln(
202
                    sprintf('<info>Generated "%s" from definition %s</info>', $bundleName, $jsonDef->getId())
203
                );
204
                $output->writeln('');
205
            } catch (\Exception $e) {
206
                $output->writeln(
207
                    sprintf('<error>%s</error>', $e->getMessage())
208
                );
209
210
                // remove failed bundle from list
211
                array_pop($this->bundleBundleList);
212
            }
213
214
            $this->xmlManipulator->reset();
215
        }
216
    }
217
218
    /**
219
     * Generate Bundle entities
220
     *
221
     * @param OutputInterface $output         Instance to sent text to be displayed on stout.
222
     * @param JsonDefinition  $jsonDef        Configuration to be generated the entity from.
223
     * @param XmlManipulator  $xmlManipulator Helper to safe the validation xml file.
224
     * @param string          $bundleName     Name of the bundle the entity shall be generated for.
225
     * @param string          $namespace      Absolute path to the bundle root dir.
226
     *
227
     * @return void
228
     * @throws \Exception
229
     */
230 2
    protected function generateSubResources(
231
        OutputInterface $output,
232
        JsonDefinition $jsonDef,
233
        XmlManipulator $xmlManipulator,
234
        $bundleName,
235
        $namespace
236
    ) {
237 2
        foreach ($this->getSubResources($jsonDef) as $subRecource) {
238
            $arguments = [
239
                'graviton:generate:resource',
240
                '--entity' => $bundleName . ':' . $subRecource->getId(),
241
                '--format' => 'xml',
242
                '--json' => $this->serializer->serialize($subRecource->getDef(), 'json'),
243
                '--fields' => $this->getFieldString($subRecource),
244
                '--with-repository' => null,
245
                '--no-controller' => 'true',
246
            ];
247
            $this->generateResource($arguments, $output, $jsonDef);
248
249
            // look for validation.xml and save it from over-writing ;-)
250
            // we basically get the xml content that was generated in order to save them later..
251
            $validationXml = $this->getGeneratedValidationXmlPath($namespace);
252
            if (file_exists($validationXml)) {
253
                $xmlManipulator->addNodes(file_get_contents($validationXml));
254
            }
255
        }
256 2
    }
257
258
    /**
259
     * Generate the actual Bundle
260
     *
261
     * @param OutputInterface $output     Instance to sent text to be displayed on stout.
262
     * @param JsonDefinition  $jsonDef    Configuration to be generated the entity from.
263
     * @param string          $bundleName Name of the bundle the entity shall be generated for.
264
     *
265
     * @return void
266
     */
267
    protected function generateMainResource(OutputInterface $output, JsonDefinition $jsonDef, $bundleName)
268
    {
269
        $fields = $jsonDef->getFields();
270
        if (!empty($fields)) {
271
            $arguments = array(
272
                'graviton:generate:resource',
273
                '--entity' => $bundleName . ':' . $jsonDef->getId(),
274
                '--json' => $this->serializer->serialize($jsonDef->getDef(), 'json'),
275
                '--format' => 'xml',
276
                '--fields' => $this->getFieldString($jsonDef),
277
                '--with-repository' => null
278
            );
279
280
            $this->generateResource($arguments, $output, $jsonDef);
281
        }
282
    }
283
284
    /**
285
     * Get all sub hashes
286
     *
287
     * @param JsonDefinition $definition Main JSON definition
288
     * @return JsonDefinition[]
289
     */
290 3
    protected function getSubResources(JsonDefinition $definition)
291
    {
292 3
        $resources = [];
293 3
        foreach ($definition->getFields() as $field) {
294 2
            while ($field instanceof JsonDefinitionArray) {
295 2
                $field = $field->getElement();
296
            }
297 2
            if (!$field instanceof JsonDefinitionHash) {
298 2
                continue;
299
            }
300
301 1
            $subDefiniton = $field->getJsonDefinition();
302
303 1
            $resources = array_merge($this->getSubResources($subDefiniton), $resources);
304 1
            $resources[] = $subDefiniton;
305
        }
306
307 3
        return $resources;
308
    }
309
310
    /**
311
     * Gathers data for the command to run.
312
     *
313
     * @param array           $arguments Set of cli arguments passed to the command
314
     * @param OutputInterface $output    Output channel to send messages to.
315
     * @param JsonDefinition  $jsonDef   Configuration of the service
316
     *
317
     * @return void
318
     * @throws \LogicException
319
     */
320
    private function generateResource(array $arguments, OutputInterface $output, JsonDefinition $jsonDef)
321
    {
322
        // controller?
323
        $routerBase = $jsonDef->getRouterBase();
324
        if ($routerBase === false || $this->isNotWhitelistedController($routerBase)) {
325
            $arguments['--no-controller'] = 'true';
326
        }
327
328
        $this->runner->executeCommand($arguments, $output, 'Create resource call failed, see above. Exiting.');
329
    }
330
331
    /**
332
     * Generates a Bundle via command line (wrapping graviton:generate:bundle)
333
     *
334
     * @param string          $namespace  Namespace
335
     * @param string          $bundleName Name of bundle
336
     * @param InputInterface  $input      Input
337
     * @param OutputInterface $output     Output
338
     *
339
     * @return void
340
     *
341
     * @throws \LogicException
342
     */
343
    private function generateBundle(
344
        $namespace,
345
        $bundleName,
346
        InputInterface $input,
347
        OutputInterface $output
348
    ) {
349
        // first, create the bundle
350
        $arguments = array(
351
            'graviton:generate:bundle',
352
            '--namespace' => $namespace,
353
            '--bundle-name' => $bundleName,
354
            '--dir' => $input->getOption('srcDir'),
355
            '--format' => $input->getOption('bundleFormat'),
356
            '--doUpdateKernel' => 'false',
357
            '--loaderBundleName' => $input->getOption('bundleBundleName'),
358
            '--structure' => null
359
        );
360
361
        $this->runner->executeCommand(
362
            $arguments,
363
            $output,
364
            'Create bundle call failed, see above. Exiting.'
365
        );
366
    }
367
368
    /**
369
     * Generates our BundleBundle for dynamic bundles.
370
     * It basically replaces the Bundle main class that got generated
371
     * by the Sensio bundle task and it includes all of our bundles there.
372
     *
373
     * @return void
374
     */
375
    private function generateBundleBundleClass()
376
    {
377
        $dbbGenerator = new DynamicBundleBundleGenerator();
378
379
        // add optional bundles if defined by parameter.
380
        if ($this->bundleAdditions !== null) {
381
            $dbbGenerator->setAdditions($this->bundleAdditions);
382
        }
383
384
        $dbbGenerator->generate(
385
            $this->bundleBundleList,
386
            $this->bundleBundleNamespace,
387
            $this->bundleBundleClassname,
388
            $this->bundleBundleClassfile
389
        );
390
    }
391
392
    /**
393
     * Returns the path to the generated validation.xml
394
     *
395
     * @param string $namespace Namespace
396
     *
397
     * @return string path
398
     */
399
    private function getGeneratedValidationXmlPath($namespace)
400
    {
401
        return dirname(__FILE__) . '/../../../' . $namespace . '/Resources/config/validation.xml';
402
    }
403
404
    /**
405
     * Returns the field string as described in the json file
406
     *
407
     * @param JsonDefinition $jsonDef The json def
408
     *
409
     * @return string CommandLine string for the generator command
410
     */
411
    private function getFieldString(JsonDefinition $jsonDef)
412
    {
413
        $ret = array();
414
415
        foreach ($jsonDef->getFields() as $field) {
416
            // don't add 'id' field it seems..
417
            if ($field->getName() != 'id') {
418
                $ret[] = $field->getName() . ':' . $field->getTypeDoctrine();
419
            }
420
        }
421
422
        return implode(
423
            ' ',
424
            $ret
425
        );
426
    }
427
428
    /**
429
     * Checks an optional environment setting if this $routerBase is whitelisted there.
430
     * If something is 'not whitelisted' (return true) means that the controller should not be generated.
431
     * This serves as a lowlevel possibility to disable the generation of certain controllers.
432
     * If we have no whitelist defined, we consider that all services should be generated (default).
433
     *
434
     * @param string $routerBase router base
435
     *
436
     * @return bool true if yes, false if not
437
     */
438
    private function isNotWhitelistedController($routerBase)
439
    {
440
        if ($this->serviceWhitelist === null) {
441
            return false;
442
        }
443
444
        return !in_array($routerBase, $this->serviceWhitelist, true);
445
    }
446
447
    /**
448
     * renders and stores the validation.xml file of a bundle.
449
     *
450
     * what are we doing here?
451
     * well, when we started to generate our subclasses (hashes in our own service) as own
452
     * Document classes, i had the problem that the validation.xml always got overwritten by the
453
     * console task. sadly, validation.xml is one file for all classes in the bundle.
454
     * so here we merge the generated validation.xml we saved in the loop before back into the
455
     * final validation.xml again. the final result should be one validation.xml including all
456
     * the validation rules for all the documents in this bundle.
457
     *
458
     * @todo we might just make this an option to the resource generator, i need to grok why this was an issue
459
     *
460
     * @param XmlManipulator $xmlManipulator Helper to safe the validation xml file.
461
     * @param string         $location       Location where to store the file.
462
     *
463
     * @return void
464
     */
465
    private function generateValidationXml(XmlManipulator $xmlManipulator, $location)
466
    {
467
        if (file_exists($location)) {
468
            $xmlManipulator
469
                ->renderDocument(file_get_contents($location))
470
                ->saveDocument($location);
471
        }
472
    }
473
474
    /**
475
     * Returns an XMLElement from a generated validation.xml that was generated during Resources generation.
476
     *
477
     * @param string $namespace Namespace, ie GravitonDyn\ShowcaseBundle
478
     *
479
     * @return \SimpleXMLElement The element
480
     *
481
     * @deprecated is this really used?
482
     */
483
    public function getGeneratedValidationXml($namespace)
484
    {
485
        $validationXmlPath = $this->getGeneratedValidationXmlPath($namespace);
486
        if (file_exists($validationXmlPath)) {
487
            $validationXml = new \SimpleXMLElement(file_get_contents($validationXmlPath));
488
            $validationXml->registerXPathNamespace('sy', 'http://symfony.com/schema/dic/constraint-mapping');
489
        } else {
490
            throw new \LogicException('Could not find ' . $validationXmlPath . ' that should be generated.');
491
        }
492
493
        return $validationXml;
494
    }
495
}
496