Completed
Push — develop ( 1aa11f...71fd61 )
by Jaap
03:29
created

ParseCommand   B

Complexity

Total Complexity 34

Size/Duplication

Total Lines 364
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 17

Test Coverage

Coverage 69.83%

Importance

Changes 0
Metric Value
dl 0
loc 364
c 0
b 0
f 0
rs 7.2285
ccs 125
cts 179
cp 0.6983
wmc 34
lcom 1
cbo 17

10 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A getBuilder() 0 4 1
A getParser() 0 4 1
A getCache() 0 4 1
B configure() 0 35 1
F execute() 0 106 18
B getFileCollection() 0 55 6
A getProgressBar() 0 16 2
A __() 0 5 1
A populateParser() 0 14 2
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\Parser\Command\Project;
13
14
use League\Flysystem\MountManager;
15
use phpDocumentor\Command\Command;
16
use phpDocumentor\Command\Helper\ConfigurationHelper;
17
use phpDocumentor\Descriptor\Cache\ProjectDescriptorMapper;
18
use phpDocumentor\Descriptor\Example\Finder;
19
use phpDocumentor\Descriptor\ProjectDescriptor;
20
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
21
use phpDocumentor\DomainModel\Dsn;
22
use phpDocumentor\DomainModel\Parser\FileCollector;
23
use phpDocumentor\Fileset\Collection;
24
use phpDocumentor\Infrastructure\FlySystemFactory;
25
use phpDocumentor\Infrastructure\Parser\FlySystemFile;
26
use phpDocumentor\Infrastructure\Parser\SpecificationFactory;
27
use phpDocumentor\Parser\Event\PreFileEvent;
28
use phpDocumentor\Parser\Exception\FilesNotFoundException;
29
use phpDocumentor\Parser\Parser;
30
use phpDocumentor\Parser\Util\ParserPopulator;
31
use phpDocumentor\Partials\Collection as PartialsCollection;
32
use phpDocumentor\Reflection\DocBlock\ExampleFinder;
33
use phpDocumentor\Reflection\File;
34
use Symfony\Component\Console\Helper\ProgressHelper;
35
use Symfony\Component\Console\Input\InputInterface;
36
use Symfony\Component\Console\Input\InputOption;
37
use Symfony\Component\Console\Output\OutputInterface;
38
use Symfony\Component\Filesystem\Filesystem;
39
use Zend\Cache\Storage\StorageInterface;
40
use Zend\I18n\Translator\Translator;
41
42
/**
43
 * Parses the given source code and creates a structure file.
44
 *
45
 * The parse task uses the source files defined either by -f or -d options and
46
 * generates a structure file (structure.xml) at the target location (which is
47
 * the folder 'output' unless the option -t is provided).
48
 */
49
class ParseCommand extends Command
50
{
51
    /** @var ProjectDescriptorBuilder $builder */
52
    protected $builder;
53
54
    /** @var Parser $parser */
55
    protected $parser;
56
57
    /** @var Translator */
58
    protected $translator;
59
60
    /** @var StorageInterface */
61
    private $cache;
62
63
    /**
64
     * @var ExampleFinder
65
     */
66
    private $exampleFinder;
67
    /**
68
     * @var PartialsCollection
69
     */
70
    private $partials;
71
    /**
72
     * @var FileCollector
73
     */
74
    private $fileCollector;
75
76
    /**
77
     * ParseCommand constructor.
78
     * @param ProjectDescriptorBuilder $builder
79
     * @param Parser $parser
80
     * @param FileCollector $fileCollector
81
     * @param Translator $translator
82
     * @param StorageInterface $cache
83
     * @param ExampleFinder $exampleFinder
84
     * @param PartialsCollection $partials
85
     */
86
    public function __construct(
87
        ProjectDescriptorBuilder $builder,
88
        Parser $parser,
89
        FileCollector $fileCollector,
90
        Translator $translator,
91
        StorageInterface $cache,
92
        ExampleFinder $exampleFinder,
93
        PartialsCollection $partials
94
    ) {
95
        $this->builder = $builder;
96
        $this->parser = $parser;
97
        $this->translator = $translator;
98
        $this->cache = $cache;
99
        $this->exampleFinder = $exampleFinder;
100
        $this->partials = $partials;
101
        $this->fileCollector = $fileCollector;
102
103
        parent::__construct('project:parse');
104
    }
105
106
    /**
107
     * @return ProjectDescriptorBuilder
108
     */
109
    public function getBuilder(): ProjectDescriptorBuilder
110
    {
111
        return $this->builder;
112
    }
113
114
    /**
115
     * @return \phpDocumentor\Parser\Parser
116
     */
117 1
    public function getParser(): Parser
118
    {
119 1
        return $this->parser;
120
    }
121
122
    /**
123
     * Returns the Cache.
124
     *
125
     * @return StorageInterface
126
     * @throws \InvalidArgumentException
127
     */
128 1
    protected function getCache(): StorageInterface
129
    {
130
        return $this->cache;
131 1
    }
132
133 1
    /**
134 1
     * Initializes this command and sets the name, description, options and
135 1
     * arguments.
136 1
     *
137 1
     * @return void
138 1
     */
139 1
    protected function configure()
140 1
    {
141 1
        // minimization of the following expression
142 1
        $VALUE_OPTIONAL_ARRAY = InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY;
143 1
144 1
        $this->setAliases(array('parse'))
145 1
            ->setDescription($this->__('PPCPP-DESCRIPTION'))
146 1
            ->setHelp($this->__('PPCPP-HELPTEXT'))
147 1
            ->addOption('filename', 'f', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-FILENAME'))
148 1
            ->addOption('directory', 'd', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-DIRECTORY'))
149 1
            ->addOption('target', 't', InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TARGET'))
150 1
            ->addOption('encoding', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-ENCODING'))
151 1
            ->addOption('extensions', 'e', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-EXTENSIONS'))
152 1
            ->addOption('ignore', 'i', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORE'))
153 1
            ->addOption('ignore-tags', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORETAGS'))
154 1
            ->addOption('hidden', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-HIDDEN'))
155 1
            ->addOption('ignore-symlinks', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-IGNORESYMLINKS'))
156 1
            ->addOption('markers', 'm', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-MARKERS'), array('TODO', 'FIXME'))
157 1
            ->addOption('title', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TITLE'))
158 1
            ->addOption('force', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-FORCE'))
159
            ->addOption('validate', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-VALIDATE'))
160
            ->addOption('visibility', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-VISIBILITY'))
161 1
            ->addOption('sourcecode', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-SOURCECODE'))
162 1
            ->addOption('progressbar', 'p', InputOption::VALUE_NONE, $this->__('PPCPP:OPT-PROGRESSBAR'))
163
            ->addOption('parseprivate', null, InputOption::VALUE_NONE, 'PPCPP:OPT-PARSEPRIVATE')
164
            ->addOption(
165
                'defaultpackagename',
166
                null,
167
                InputOption::VALUE_OPTIONAL,
168
                $this->__('PPCPP:OPT-DEFAULTPACKAGENAME'),
169
                'Default'
170
            );
171
172
        parent::configure();
173
    }
174 1
175
    /**
176
     * Executes the business logic involved with this command.
177 1
     *
178 1
     * @param InputInterface $input
179 1
     * @param OutputInterface $output
180
     *
181
     * @throws \Exception if the target location is not a folder.
182
     *
183 1
     * @return integer
184 1
     */
185 1
    protected function execute(InputInterface $input, OutputInterface $output): int
186
    {
187 1
        /** @var ConfigurationHelper $configurationHelper */
188
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
189
        $target = $configurationHelper->getOption($input, 'target', 'parser/target');
190 1
        if (strpos($target, '/tmp/') === 0) {
191
            $target = str_replace('/tmp/', sys_get_temp_dir() . DIRECTORY_SEPARATOR, $target);
192
        }
193 1
194
        $fileSystem = new Filesystem();
195 1
        if (!$fileSystem->isAbsolutePath($target)) {
196 1
            $target = getcwd() . DIRECTORY_SEPARATOR . $target;
197 1
        }
198
        if (!file_exists($target)) {
199 1
            mkdir($target, 0777, true);
200 1
        }
201
        if (!is_dir($target)) {
202 1
            throw new \Exception($this->__('PPCPP:EXC-BADTARGET'));
203 1
        }
204 1
        $this->getCache()->getOptions()->setCacheDir($target);
205
206
        $builder = $this->getBuilder();
207 1
        $builder->createProjectDescriptor();
208 1
        $projectDescriptor = $builder->getProjectDescriptor();
209 1
210
        $output->write($this->__('PPCPP:LOG-COLLECTING'));
211
        $files = $this->getFileCollection($input);
212 1
213 1
        //TODO: Should determine root based on filesystems. Could be an issue for multiple. Need some config update here.
214
        $this->exampleFinder->setSourceDirectory(getcwd());
215 1
        $this->exampleFinder->setExampleDirectories($configurationHelper->getConfigValueFromPath('files/examples'));
216
        $output->writeln($this->__('PPCPP:LOG-OK'));
217
218
        /** @var ProgressHelper $progress */
219
        $progress = $this->getProgressBar($input);
220 1
        if (!$progress) {
221 1
            $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...
222
        }
223 1
224 1
        $output->write($this->__('PPCPP:LOG-INITIALIZING'));
225 1
        $this->populateParser($input);
226
227 1
        if ($progress) {
228 1
            $progress->start($output, \count($files));
229 1
        }
230 1
231
        try {
232 1
            $output->writeln($this->__('PPCPP:LOG-OK'));
233 1
            $output->writeln($this->__('PPCPP:LOG-PARSING'));
234
235 1
            $mapper = new ProjectDescriptorMapper($this->getCache());
236
            //TODO: Re-enable garbage collection here.
237
            //$mapper->garbageCollect($files);
238 1
            $mapper->populate($projectDescriptor);
239
240
            $visibility = (array)$configurationHelper->getOption($input, 'visibility', 'parser/visibility');
241 1
            $visibilities = array();
242
            foreach ($visibility as $item) {
243 1
                $visibilities = $visibilities + explode(',', $item);
244
            }
245
            $visibility = null;
246 1
            foreach ($visibilities as $item) {
247 1
                switch ($item) {
248
                    case 'public':
249 1
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PUBLIC;
250
                        break;
251
                    case 'protected':
252 1
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PROTECTED;
253 1
                        break;
254
                    case 'private':
255 1
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PRIVATE;
256
                        break;
257 1
                }
258
            }
259
            if ($visibility === null) {
260
                $visibility = ProjectDescriptor\Settings::VISIBILITY_DEFAULT;
261
            }
262
            if ($input->getOption('parseprivate')) {
263
                $visibility |= ProjectDescriptor\Settings::VISIBILITY_INTERNAL;
264 1
            }
265
            $projectDescriptor->getSettings()->setVisibility($visibility);
266
            $input->getOption('sourcecode')
267
                ? $projectDescriptor->getSettings()->includeSource()
268 1
                : $projectDescriptor->getSettings()->excludeSource();
269
270 1
            $this->getParser()->parse($builder, $files);
271 1
        } catch (FilesNotFoundException $e) {
272 1
            throw new \Exception($this->__('PPCPP:EXC-NOFILES'));
273
        } catch (\Exception $e) {
274 1
            throw new \Exception($e->getMessage(), 0, $e);
275
        }
276 1
277
        if ($progress) {
278
            $progress->finish();
279
        }
280
281
        $projectDescriptor->setPartials($this->partials);
282
283 1
        $output->write($this->__('PPCPP:LOG-STORECACHE', (array)$this->getCache()->getOptions()->getCacheDir()));
284
        $projectDescriptor->getSettings()->clearModifiedFlag();
285
        $mapper->save($projectDescriptor);
286 1
287
        $output->writeln($this->__('PPCPP:LOG-OK'));
288 1
289 1
        return 0;
290 1
    }
291 1
292 1
    /**
293 1
     * Returns the collection of files based on the input and configuration.
294 1
     *
295 1
     * @param InputInterface $input
296
     *
297 1
     * @return File[]
298
     * @throws \InvalidArgumentException
299
     */
300
    protected function getFileCollection($input): array
301
    {
302
        /** @var ConfigurationHelper $configurationHelper */
303
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
304
305
        $ignoreHidden = $configurationHelper->getOption($input, 'hidden', 'files/ignore-hidden', 'off');
306 1
        $file_options = (array)$configurationHelper->getOption($input, 'filename', 'files/files', array(), true);
307
        $directory_options = $configurationHelper->getOption($input, 'directory', 'files/directories', array(), true);
308
309 1
310
        $ignorePaths = array_map(
311 1
            function($value) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
312 1
                if (substr($value, -1) === '*') {
313 1
                    return substr($value, 0, -1);
314 1
                }
315 1
316 1
                return $value;
317 1
            },
318
            $configurationHelper->getOption($input, 'ignore', 'files/ignore', array(), true)
319
        );
320 1
321 1
        //TODO: Fix this, should we support symlinks? Or just print an error here.
322 1
        if ($configurationHelper->getOption($input, 'ignore-symlinks', 'files/ignore-symlinks', 'off') == 'on') {
323 1
            echo "Symlinks are not supported";
324 1
        }
325
326
        $files = [];
327 1
328 1
        foreach ($file_options as $file) {
329 1
            $files[] = new File\LocalFile($file);
330
        }
331
332
        foreach (array_merge($directory_options) as $option) {
333
            $files = array_merge(
334
                $files,
335
                $this->fileCollector->getFiles(
336
                    new Dsn('file://' . realpath($option)),
337
                    array_merge($directory_options),
338
                    [
339
                        'paths' => $ignorePaths,
340
                        'hidden' => $ignoreHidden !== 'off' && $ignoreHidden === false
341
                    ],
342
                    $configurationHelper->getOption(
0 ignored issues
show
Documentation introduced by
$configurationHelper->ge...'php3', 'phtml'), true) is of type string|array, but the function expects a array<integer,string>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
343
                        $input,
344
                        'extensions',
345
                        'parser/extensions',
346 1
                        array('php', 'php3', 'phtml'),
347
                        true
348 1
                    )
349 1
                )
350 1
            );
351
        }
352
353
        return $files;
354
    }
355
356
    /**
357
     * Adds the parser.file.pre event to the advance the progressbar.
358
     *
359
     * @param InputInterface $input
360
     *
361
     * @return \Symfony\Component\Console\Helper\HelperInterface|null
362
     */
363
    protected function getProgressBar(InputInterface $input)
364
    {
365
        $progress = parent::getProgressBar($input);
366
        if (!$progress) {
367 1
            return null;
368
        }
369 1
370
        $this->getService('event_dispatcher')->addListener(
371
            'parser.file.pre',
372
            function (PreFileEvent $event) use ($progress) {
0 ignored issues
show
Unused Code introduced by
The parameter $event is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
373
                $progress->advance();
374
            }
375
        );
376
377
        return $progress;
378
    }
379 1
380
    /**
381 1
     * Translates the provided text and replaces any contained parameters using printf notation.
382 1
     *
383 1
     * @param string $text
384
     * @param string[] $parameters
385
     *
386
     * @return string
387
     */
388
    // @codingStandardsIgnoreStart
389
    protected function __($text, $parameters = array())
390
        // @codingStandardsIgnoreEnd
391
    {
0 ignored issues
show
Coding Style introduced by
Opening brace should be on the line after the declaration; found 1 blank line(s)
Loading history...
392
        return vsprintf($this->translator->translate($text), $parameters);
393
    }
394
395
    /**
396
     * @param InputInterface $input
397
     */
398
    protected function populateParser(InputInterface $input)
399
    {
400
        /** @var ConfigurationHelper $configurationHelper */
401
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
402
403
        $title = (string)$configurationHelper->getOption($input, 'title', 'title');
404
        $this->getBuilder()->getProjectDescriptor()->setName($title ?: 'API Documentation');
405 1
        $parserPopulator = new ParserPopulator();
406
        $parserPopulator->populate(
407
            $this->getParser(),
408 1
            $input,
409
            $configurationHelper
410
        );
411
    }
412
}
413