Completed
Push — develop ( 722f70...af048b )
by Jaap
15:12 queued 05:04
created

TransformCommand   C

Complexity

Total Complexity 36

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 17

Importance

Changes 0
Metric Value
dl 0
loc 349
rs 6.9142
c 0
b 0
f 0
wmc 36
lcom 2
cbo 17

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 8 1
B configure() 0 46 1
A getBuilder() 0 4 1
A getTransformer() 0 4 1
C execute() 0 59 9
A getCache() 0 4 1
C getTemplates() 0 34 7
B loadTransformations() 0 21 5
A createTransformation() 0 9 4
A appendReceivedTransformations() 0 10 3
A getProgressBar() 0 18 2
B connectOutputToEvents() 0 26 1
1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.3
6
 *
7
 * @copyright 2010-2014 Mike van Riel / Naenius (http://www.naenius.com)
8
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
9
 * @link      http://phpdoc.org
10
 */
11
12
namespace phpDocumentor\Transformer\Command\Project;
13
14
use phpDocumentor\Command\Command;
15
use phpDocumentor\Command\Helper\ConfigurationHelper;
16
use phpDocumentor\Compiler\Compiler;
17
use phpDocumentor\Compiler\CompilerPassInterface;
18
use phpDocumentor\Descriptor\Cache\ProjectDescriptorMapper;
19
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
20
use phpDocumentor\Event\Dispatcher;
21
use phpDocumentor\Transformer\Event\PreTransformationEvent;
22
use phpDocumentor\Transformer\Event\PreTransformEvent;
23
use phpDocumentor\Transformer\Event\WriterInitializationEvent;
24
use phpDocumentor\Transformer\Template;
25
use phpDocumentor\Transformer\Transformation;
26
use phpDocumentor\Transformer\Transformer;
27
use Symfony\Component\Console\Helper\HelperInterface;
28
use Symfony\Component\Console\Input\InputInterface;
29
use Symfony\Component\Console\Input\InputOption;
30
use Symfony\Component\Console\Output\OutputInterface;
31
use Symfony\Component\Filesystem\Filesystem;
32
use Zend\Cache\Storage\StorageInterface;
33
34
/**
35
 * Transforms the structure file into the specified output format
36
 *
37
 * This task will execute the transformation rules described in the given
38
 * template (defaults to 'responsive') with the given source (defaults to
39
 * output/structure.xml) and writes these to the target location (defaults to
40
 * 'output').
41
 *
42
 * It is possible for the user to receive additional information using the
43
 * verbose option or stop additional information using the quiet option. Please
44
 * take note that the quiet option also disables logging to file.
45
 */
46
class TransformCommand extends Command
0 ignored issues
show
Complexity introduced by
The class TransformCommand has a coupling between objects value of 20. Consider to reduce the number of dependencies under 13.
Loading history...
47
{
48
    /** @var ProjectDescriptorBuilder $builder Object containing the project meta-data and AST */
49
    protected $builder;
50
51
    /** @var Transformer $transformer Principal object for guiding the transformation process */
52
    protected $transformer;
53
54
    /** @var Compiler $compiler Collection of pre-transformation actions (Compiler Passes) */
55
    protected $compiler;
56
57
    /**
58
     * Initializes the command with all necessary dependencies to construct human-suitable output from the AST.
59
     *
60
     * @param ProjectDescriptorBuilder $builder
61
     * @param Transformer              $transformer
62
     * @param Compiler                 $compiler
63
     */
64
    public function __construct(ProjectDescriptorBuilder $builder, Transformer $transformer, Compiler $compiler)
65
    {
66
        parent::__construct('project:transform');
67
68
        $this->builder     = $builder;
69
        $this->transformer = $transformer;
70
        $this->compiler    = $compiler;
71
    }
72
73
    /**
74
     * Initializes this command and sets the name, description, options and
75
     * arguments.
76
     *
77
     * @return void
78
     */
79
    protected function configure()
80
    {
81
        $this->setAliases(array('transform'))
82
            ->setDescription(
83
                'Converts the PHPDocumentor structure file to documentation'
84
            )
85
            ->setHelp(
86
<<<TEXT
87
This task will execute the transformation rules described in the given
88
template (defaults to 'responsive') with the given source (defaults to
89
output/structure.xml) and writes these to the target location (defaults to
90
'output').
91
92
It is possible for the user to receive additional information using the
93
verbose option or stop additional information using the quiet option. Please
94
take note that the quiet option also disables logging to file.
95
TEXT
96
            );
97
98
        $this->addOption(
99
            'source',
100
            's',
101
            InputOption::VALUE_OPTIONAL,
102
            'Path where the XML source file is located (optional)'
103
        );
104
        $this->addOption(
105
            'target',
106
            't',
107
            InputOption::VALUE_OPTIONAL,
108
            'Path where to store the generated output (optional)'
109
        );
110
        $this->addOption(
111
            'template',
112
            null,
113
            InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY,
114
            'Name of the template to use (optional)'
115
        );
116
        $this->addOption(
117
            'progressbar',
118
            'p',
119
            InputOption::VALUE_NONE,
120
            'Whether to show a progress bar; will automatically quiet logging to stdout'
121
        );
122
123
        parent::configure();
124
    }
125
126
    /**
127
     * Returns the builder object containing the AST and other meta-data.
128
     *
129
     * @return ProjectDescriptorBuilder
130
     */
131
    public function getBuilder()
132
    {
133
        return $this->builder;
134
    }
135
136
    /**
137
     * Returns the transformer used to guide the transformation process from AST to output.
138
     *
139
     * @return Transformer
140
     */
141
    public function getTransformer()
142
    {
143
        return $this->transformer;
144
    }
145
146
    /**
147
     * Executes the business logic involved with this command.
148
     *
149
     * @param InputInterface  $input
150
     * @param OutputInterface $output
151
     *
152
     * @throws \Exception if the provided source is not an existing file or a folder.
153
     *
154
     * @return int
155
     */
156
    protected function execute(InputInterface $input, OutputInterface $output)
157
    {
158
        /** @var ConfigurationHelper $configurationHelper */
159
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
160
161
        $progress = $this->getProgressBar($input);
162
        if (! $progress) {
163
            $this->connectOutputToEvents($output);
164
        }
165
166
        // initialize transformer
167
        $transformer = $this->getTransformer();
168
169
        $target = (string) $configurationHelper->getOption($input, 'target', 'transformer/target');
170
        $fileSystem = new Filesystem();
171
        if (! $fileSystem->isAbsolutePath($target)) {
172
            $target = getcwd() . DIRECTORY_SEPARATOR . $target;
173
        }
174
        $transformer->setTarget($target);
175
176
        $source = realpath($configurationHelper->getOption($input, 'source', 'parser/target'));
177
        if (!file_exists($source) || !is_dir($source)) {
178
            throw new \Exception('Invalid source location provided, a path to an existing folder was expected');
179
        }
180
181
        $this->getCache()->getOptions()->setCacheDir($source);
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Zend\Cache\Storage\Adapter\AdapterOptions as the method setCacheDir() does only exist in the following sub-classes of Zend\Cache\Storage\Adapter\AdapterOptions: Zend\Cache\Storage\Adapter\FilesystemOptions. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
182
183
        $projectDescriptor = $this->getBuilder()->getProjectDescriptor();
184
        $mapper = new ProjectDescriptorMapper($this->getCache());
185
        $output->writeTimedLog('Load cache', array($mapper, 'populate'), array($projectDescriptor));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method writeTimedLog() does only exist in the following implementations of said interface: phpDocumentor\Console\Output\Output.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
186
187
        foreach ($this->getTemplates($input) as $template) {
188
            $output->writeTimedLog(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method writeTimedLog() does only exist in the following implementations of said interface: phpDocumentor\Console\Output\Output.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
189
                'Preparing template "'. $template .'"',
190
                array($transformer->getTemplates(), 'load'),
191
                array($template, $transformer)
192
            );
193
        }
194
        $output->writeTimedLog(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method writeTimedLog() does only exist in the following implementations of said interface: phpDocumentor\Console\Output\Output.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
195
            'Preparing ' . count($transformer->getTemplates()->getTransformations()) . ' transformations',
196
            array($this, 'loadTransformations'),
197
            array($transformer)
198
        );
199
200
        if ($progress) {
201
            $progress->start($output, count($transformer->getTemplates()->getTransformations()));
202
        }
203
204
        /** @var CompilerPassInterface $pass */
205
        foreach ($this->compiler as $pass) {
206
            $output->writeTimedLog($pass->getDescription(), array($pass, 'execute'), array($projectDescriptor));
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Output\OutputInterface as the method writeTimedLog() does only exist in the following implementations of said interface: phpDocumentor\Console\Output\Output.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
207
        }
208
209
        if ($progress) {
210
            $progress->finish();
211
        }
212
213
        return 0;
214
    }
215
216
    /**
217
     * Returns the Cache.
218
     *
219
     * @return StorageInterface
220
     */
221
    protected function getCache()
222
    {
223
        return $this->getContainer()->offsetGet('descriptor.cache');
224
    }
225
226
    /**
227
     * Retrieves the templates to be used by analyzing the options and the configuration.
228
     *
229
     * @param InputInterface $input
230
     *
231
     * @return string[]
232
     */
233
    protected function getTemplates(InputInterface $input)
234
    {
235
        /** @var ConfigurationHelper $configurationHelper */
236
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
237
238
        $templates = $input->getOption('template');
239
        if (!$templates) {
240
            /** @var Template[] $templatesFromConfig */
241
            $templatesFromConfig = $configurationHelper->getConfigValueFromPath('transformations/templates');
242
            foreach ($templatesFromConfig as $template) {
243
                $templates[] = $template->getName();
244
            }
245
        }
246
247
        if (!$templates) {
248
            $templates = array('clean');
249
        }
250
251
        // Support template entries that contain multiple templates using a comma separated list
252
        // like checkstyle,clean
253
        foreach ($templates as $key => $template) {
254
            $commaSeparatedTemplates = explode(',', $template);
0 ignored issues
show
Comprehensibility Naming introduced by
The variable name $commaSeparatedTemplates exceeds the maximum configured length of 20.

Very long variable names usually make code harder to read. It is therefore recommended not to make variable names too verbose.

Loading history...
255
            if (count($commaSeparatedTemplates) > 1) {
256
                // replace the current item with the first in the list
257
                $templates[$key] = trim(array_shift($commaSeparatedTemplates));
258
                // append all additional templates to the list of templates
259
                foreach ($commaSeparatedTemplates as $subtemplate) {
260
                    $templates[] = $subtemplate;
261
                }
262
            }
263
        }
264
265
        return $templates;
266
    }
267
268
    /**
269
     * Load custom defined transformations.
270
     *
271
     * @param Transformer $transformer
272
     *
273
     * @todo this is an ugly implementation done for speed of development, should be refactored
274
     *
275
     * @return void
276
     */
277
    public function loadTransformations(Transformer $transformer)
278
    {
279
        /** @var ConfigurationHelper $configurationHelper */
280
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
281
282
        $received = array();
283
        $transformations = $configurationHelper->getConfigValueFromPath('transformations/transformations');
284
        if (is_array($transformations)) {
285
            if (isset($transformations['writer'])) {
286
                $received[] = $this->createTransformation($transformations);
287
            } else {
288
                foreach ($transformations as $transformation) {
289
                    if (is_array($transformation)) {
290
                        $received[] = $this->createTransformation($transformations);
291
                    }
292
                }
293
            }
294
        }
295
296
        $this->appendReceivedTransformations($transformer, $received);
297
    }
298
299
    /**
300
     * Create Transformation instance.
301
     *
302
     * @param array $transformations
303
     *
304
     * @return \phpDocumentor\Transformer\Transformation
305
     */
306
    protected function createTransformation(array $transformations)
307
    {
308
        return new Transformation(
309
            isset($transformations['query']) ? $transformations['query'] : '',
310
            $transformations['writer'],
311
            isset($transformations['source']) ? $transformations['source'] : '',
312
            isset($transformations['artifact']) ? $transformations['artifact'] : ''
313
        );
314
    }
315
316
    /**
317
     * Append received transformations.
318
     *
319
     * @param Transformer $transformer
320
     * @param array       $received
321
     *
322
     * @return void
323
     */
324
    protected function appendReceivedTransformations(Transformer $transformer, $received)
325
    {
326
        if (!empty($received)) {
327
            $template = new Template('__');
328
            foreach ($received as $transformation) {
329
                $template[] = $transformation;
330
            }
331
            $transformer->getTemplates()->append($template);
332
        }
333
    }
334
335
    /**
336
     * Adds the transformer.transformation.post event to advance the progressbar.
337
     *
338
     * @param InputInterface $input
339
     *
340
     * @return HelperInterface|null
341
     */
342
    protected function getProgressBar(InputInterface $input)
343
    {
344
        $progress = parent::getProgressBar($input);
345
        if (!$progress) {
346
            return null;
347
        }
348
349
        /** @var Dispatcher $eventDispatcher */
350
        $eventDispatcher = $this->getService('event_dispatcher');
351
        $eventDispatcher->addListener(
352
            'transformer.transformation.post',
353
            function () use ($progress) {
354
                $progress->advance();
355
            }
356
        );
357
358
        return $progress;
359
    }
360
361
    /**
362
     * Connect a series of output messages to various events to display progress.
363
     *
364
     * @param OutputInterface $output
365
     *
366
     * @return void
367
     */
368
    private function connectOutputToEvents(OutputInterface $output)
369
    {
370
        $this->getHelper('phpdocumentor_logger')->connectOutputToLogging($output, $this);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Symfony\Component\Console\Helper\HelperInterface as the method connectOutputToLogging() does only exist in the following implementations of said interface: phpDocumentor\Command\Helper\LoggerHelper.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
371
372
        Dispatcher::getInstance()->addListener(
373
            Transformer::EVENT_PRE_TRANSFORM,
374
            function (PreTransformEvent $event) use ($output) {
375
                $transformations = $event->getSubject()->getTemplates()->getTransformations();
376
                $output->writeln(sprintf("\nApplying %d transformations", count($transformations)));
377
            }
378
        );
379
        Dispatcher::getInstance()->addListener(
380
            Transformer::EVENT_PRE_INITIALIZATION,
381
            function (WriterInitializationEvent $event) use ($output) {
382
                $output->writeln('  Initialize writer "' . get_class($event->getWriter()) . '"');
383
            }
384
        );
385
        Dispatcher::getInstance()->addListener(
386
            Transformer::EVENT_PRE_TRANSFORMATION,
387
            function (PreTransformationEvent $event) use ($output) {
388
                $output->writeln(
389
                    '  Execute transformation using writer "' . $event->getTransformation()->getWriter() . '"'
390
                );
391
            }
392
        );
393
    }
394
}
395