Completed
Push — develop ( 71fd61...bbac44 )
by Jaap
06:03 queued 02:27
created

ParseCommand::getCacheMiddleware()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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\Middleware\CacheMiddleware;
30
use phpDocumentor\Parser\Parser;
31
use phpDocumentor\Parser\Util\ParserPopulator;
32
use phpDocumentor\Partials\Collection as PartialsCollection;
33
use phpDocumentor\Reflection\DocBlock\ExampleFinder;
34
use phpDocumentor\Reflection\File;
35
use Symfony\Component\Console\Helper\ProgressHelper;
36
use Symfony\Component\Console\Input\InputInterface;
37
use Symfony\Component\Console\Input\InputOption;
38
use Symfony\Component\Console\Output\OutputInterface;
39
use Symfony\Component\Filesystem\Filesystem;
40
use Zend\Cache\Storage\StorageInterface;
41
use Zend\I18n\Translator\Translator;
42
43
/**
44
 * Parses the given source code and creates a structure file.
45
 *
46
 * The parse task uses the source files defined either by -f or -d options and
47
 * generates a structure file (structure.xml) at the target location (which is
48
 * the folder 'output' unless the option -t is provided).
49
 */
50
class ParseCommand extends Command
51
{
52
    /** @var ProjectDescriptorBuilder $builder */
53
    protected $builder;
54
55
    /** @var Parser $parser */
56
    protected $parser;
57
58
    /** @var Translator */
59
    protected $translator;
60
61
    /** @var StorageInterface */
62
    private $cache;
63
64
    /**
65
     * @var ExampleFinder
66
     */
67
    private $exampleFinder;
68
    /**
69
     * @var PartialsCollection
70
     */
71
    private $partials;
72
    /**
73
     * @var FileCollector
74
     */
75
    private $fileCollector;
76
77
    /**
78
     * ParseCommand constructor.
79
     * @param ProjectDescriptorBuilder $builder
80
     * @param Parser $parser
81
     * @param FileCollector $fileCollector
82
     * @param Translator $translator
83
     * @param StorageInterface $cache
84
     * @param ExampleFinder $exampleFinder
85
     * @param PartialsCollection $partials
86
     */
87
    public function __construct(
88
        ProjectDescriptorBuilder $builder,
89
        Parser $parser,
90
        FileCollector $fileCollector,
91
        Translator $translator,
92
        StorageInterface $cache,
93
        ExampleFinder $exampleFinder,
94
        PartialsCollection $partials
95
    ) {
96
        $this->builder = $builder;
97
        $this->parser = $parser;
98
        $this->translator = $translator;
99
        $this->cache = $cache;
100
        $this->exampleFinder = $exampleFinder;
101
        $this->partials = $partials;
102
        $this->fileCollector = $fileCollector;
103
104
        parent::__construct('project:parse');
105
    }
106
107
    /**
108
     * @return ProjectDescriptorBuilder
109
     */
110
    public function getBuilder(): ProjectDescriptorBuilder
111
    {
112
        return $this->builder;
113
    }
114
115
    /**
116
     * @return \phpDocumentor\Parser\Parser
117
     */
118
    public function getParser(): Parser
119
    {
120
        return $this->parser;
121
    }
122
123
    /**
124
     * Returns the Cache.
125
     *
126
     * @return StorageInterface
127
     * @throws \InvalidArgumentException
128
     */
129 1
    protected function getCache(): StorageInterface
130
    {
131 1
        return $this->cache;
132
    }
133
134
    /**
135
     * Initializes this command and sets the name, description, options and
136
     * arguments.
137
     *
138
     * @return void
139
     */
140 1
    protected function configure()
141
    {
142
        // minimization of the following expression
143 1
        $VALUE_OPTIONAL_ARRAY = InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY;
144
145 1
        $this->setAliases(array('parse'))
146 1
            ->setDescription($this->__('PPCPP-DESCRIPTION'))
147 1
            ->setHelp($this->__('PPCPP-HELPTEXT'))
148 1
            ->addOption('filename', 'f', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-FILENAME'))
149 1
            ->addOption('directory', 'd', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-DIRECTORY'))
150 1
            ->addOption('target', 't', InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TARGET'))
151 1
            ->addOption('encoding', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-ENCODING'))
152 1
            ->addOption('extensions', 'e', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-EXTENSIONS'))
153 1
            ->addOption('ignore', 'i', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORE'))
154 1
            ->addOption('ignore-tags', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-IGNORETAGS'))
155 1
            ->addOption('hidden', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-HIDDEN'))
156 1
            ->addOption('ignore-symlinks', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-IGNORESYMLINKS'))
157 1
            ->addOption('markers', 'm', $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-MARKERS'), array('TODO', 'FIXME'))
158 1
            ->addOption('title', null, InputOption::VALUE_OPTIONAL, $this->__('PPCPP:OPT-TITLE'))
159 1
            ->addOption('force', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-FORCE'))
160 1
            ->addOption('validate', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-VALIDATE'))
161 1
            ->addOption('visibility', null, $VALUE_OPTIONAL_ARRAY, $this->__('PPCPP:OPT-VISIBILITY'))
162 1
            ->addOption('sourcecode', null, InputOption::VALUE_NONE, $this->__('PPCPP:OPT-SOURCECODE'))
163 1
            ->addOption('progressbar', 'p', InputOption::VALUE_NONE, $this->__('PPCPP:OPT-PROGRESSBAR'))
164 1
            ->addOption('parseprivate', null, InputOption::VALUE_NONE, 'PPCPP:OPT-PARSEPRIVATE')
165 1
            ->addOption(
166 1
                'defaultpackagename',
167 1
                null,
168 1
                InputOption::VALUE_OPTIONAL,
169 1
                $this->__('PPCPP:OPT-DEFAULTPACKAGENAME'),
170 1
                'Default'
171
            );
172
173 1
        parent::configure();
174 1
    }
175
176
    /**
177
     * Executes the business logic involved with this command.
178
     *
179
     * @param InputInterface $input
180
     * @param OutputInterface $output
181
     *
182
     * @throws \Exception if the target location is not a folder.
183
     *
184
     * @return integer
185
     */
186 1
    protected function execute(InputInterface $input, OutputInterface $output): int
187
    {
188
        /** @var ConfigurationHelper $configurationHelper */
189 1
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
190 1
        $target = $configurationHelper->getOption($input, 'target', 'parser/target');
191 1
        if (strpos($target, '/tmp/') === 0) {
192
            $target = str_replace('/tmp/', sys_get_temp_dir() . DIRECTORY_SEPARATOR, $target);
193
        }
194
195 1
        $fileSystem = new Filesystem();
196 1
        if (!$fileSystem->isAbsolutePath($target)) {
197 1
            $target = getcwd() . DIRECTORY_SEPARATOR . $target;
198
        }
199 1
        if (!file_exists($target)) {
200
            mkdir($target, 0777, true);
201
        }
202 1
        if (!is_dir($target)) {
203
            throw new \Exception($this->__('PPCPP:EXC-BADTARGET'));
204
        }
205 1
        $this->getCache()->getOptions()->setCacheDir($target);
206
207 1
        if ($input->getOption('force')) {
208
            $this->getCacheMiddleware()->disable();
209
        }
210
211 1
        $builder = $this->getBuilder();
212 1
        $builder->createProjectDescriptor();
213 1
        $projectDescriptor = $builder->getProjectDescriptor();
214
215 1
        $output->write($this->__('PPCPP:LOG-COLLECTING'));
216 1
        $files = $this->getFileCollection($input);
217
218
        //TODO: Should determine root based on filesystems. Could be an issue for multiple. Need some config update here.
219 1
        $this->exampleFinder->setSourceDirectory(getcwd());
220 1
        $this->exampleFinder->setExampleDirectories($configurationHelper->getConfigValueFromPath('files/examples'));
221 1
        $output->writeln($this->__('PPCPP:LOG-OK'));
222
223
        /** @var ProgressHelper $progress */
224 1
        $progress = $this->getProgressBar($input);
225 1
        if (!$progress) {
226 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...
227
        }
228
229 1
        $output->write($this->__('PPCPP:LOG-INITIALIZING'));
230 1
        $this->populateParser($input);
231
232 1
        if ($progress) {
233
            $progress->start($output, \count($files));
234
        }
235
236
        try {
237 1
            $output->writeln($this->__('PPCPP:LOG-OK'));
238 1
            $output->writeln($this->__('PPCPP:LOG-PARSING'));
239
240 1
            $mapper = new ProjectDescriptorMapper($this->getCache());
241
            //TODO: Re-enable garbage collection here.
242
            //$mapper->garbageCollect($files);
243 1
            $mapper->populate($projectDescriptor);
244
245 1
            $visibility = (array)$configurationHelper->getOption($input, 'visibility', 'parser/visibility');
246 1
            $visibilities = array();
247 1
            foreach ($visibility as $item) {
248 1
                $visibilities = $visibilities + explode(',', $item);
249
            }
250 1
            $visibility = null;
251 1
            foreach ($visibilities as $item) {
252
                switch ($item) {
253 1
                    case 'public':
254
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PUBLIC;
255
                        break;
256 1
                    case 'protected':
257
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PROTECTED;
258
                        break;
259 1
                    case 'private':
260
                        $visibility |= ProjectDescriptor\Settings::VISIBILITY_PRIVATE;
261 1
                        break;
262
                }
263
            }
264 1
            if ($visibility === null) {
265 1
                $visibility = ProjectDescriptor\Settings::VISIBILITY_DEFAULT;
266
            }
267 1
            if ($input->getOption('parseprivate')) {
268
                $visibility |= ProjectDescriptor\Settings::VISIBILITY_INTERNAL;
269
            }
270 1
            $projectDescriptor->getSettings()->setVisibility($visibility);
271 1
            $input->getOption('sourcecode')
272
                ? $projectDescriptor->getSettings()->includeSource()
273 1
                : $projectDescriptor->getSettings()->excludeSource();
274
275 1
            $this->getParser()->parse($builder, $files);
276
        } catch (FilesNotFoundException $e) {
277
            throw new \Exception($this->__('PPCPP:EXC-NOFILES'));
278
        } catch (\Exception $e) {
279
            throw new \Exception($e->getMessage(), 0, $e);
280
        }
281
282 1
        if ($progress) {
283
            $progress->finish();
284
        }
285
286 1
        $projectDescriptor->setPartials($this->partials);
287
288 1
        $output->write($this->__('PPCPP:LOG-STORECACHE', (array)$this->getCache()->getOptions()->getCacheDir()));
289 1
        $projectDescriptor->getSettings()->clearModifiedFlag();
290 1
        $mapper->save($projectDescriptor);
291
292 1
        $output->writeln($this->__('PPCPP:LOG-OK'));
293
294 1
        return 0;
295
    }
296
297
    /**
298
     * Returns the collection of files based on the input and configuration.
299
     *
300
     * @param InputInterface $input
301
     *
302
     * @return File[]
303
     * @throws \InvalidArgumentException
304
     */
305 1
    protected function getFileCollection($input): array
306
    {
307
        /** @var ConfigurationHelper $configurationHelper */
308 1
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
309
310 1
        $ignoreHidden = $configurationHelper->getOption($input, 'hidden', 'files/ignore-hidden', 'off');
311 1
        $file_options = (array)$configurationHelper->getOption($input, 'filename', 'files/files', array(), true);
312 1
        $directory_options = $configurationHelper->getOption($input, 'directory', 'files/directories', array(), true);
313
314
315 1
        $ignorePaths = array_map(
316 1
            function($value) {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after FUNCTION keyword; 0 found
Loading history...
317
                if (substr($value, -1) === '*') {
318
                    return substr($value, 0, -1);
319
                }
320
321
                return $value;
322 1
            },
323 1
            $configurationHelper->getOption($input, 'ignore', 'files/ignore', array(), true)
324
        );
325
326
        //TODO: Fix this, should we support symlinks? Or just print an error here.
327 1
        if ($configurationHelper->getOption($input, 'ignore-symlinks', 'files/ignore-symlinks', 'off') == 'on') {
328
            echo "Symlinks are not supported";
329
        }
330
331 1
        $files = [];
332
333 1
        foreach ($file_options as $file) {
334
            $files[] = new File\LocalFile($file);
335
        }
336
337 1
        foreach (array_merge($directory_options) as $option) {
338
            $files = array_merge(
339
                $files,
340
                $this->fileCollector->getFiles(
341
                    new Dsn('file://' . realpath($option)),
342
                    array_merge($directory_options),
343
                    [
344
                        'paths' => $ignorePaths,
345
                        'hidden' => $ignoreHidden !== 'off' && $ignoreHidden === false
346
                    ],
347
                    $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...
348
                        $input,
349
                        'extensions',
350
                        'parser/extensions',
351
                        array('php', 'php3', 'phtml'),
352
                        true
353
                    )
354
                )
355
            );
356
        }
357
358 1
        return $files;
359
    }
360
361
    /**
362
     * Adds the parser.file.pre event to the advance the progressbar.
363
     *
364
     * @param InputInterface $input
365
     *
366
     * @return \Symfony\Component\Console\Helper\HelperInterface|null
367
     */
368 1
    protected function getProgressBar(InputInterface $input)
369
    {
370 1
        $progress = parent::getProgressBar($input);
371 1
        if (!$progress) {
372 1
            return null;
373
        }
374
375
        $this->getService('event_dispatcher')->addListener(
376
            'parser.file.pre',
377
            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...
378
                $progress->advance();
379
            }
380
        );
381
382
        return $progress;
383
    }
384
385
    /**
386
     * Translates the provided text and replaces any contained parameters using printf notation.
387
     *
388
     * @param string $text
389
     * @param string[] $parameters
390
     *
391
     * @return string
392
     */
393
    // @codingStandardsIgnoreStart
394 1
    protected function __($text, $parameters = array())
395
        // @codingStandardsIgnoreEnd
396
    {
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...
397 1
        return vsprintf($this->translator->translate($text), $parameters);
398
    }
399
400
    /**
401
     * @param InputInterface $input
402
     */
403 1
    protected function populateParser(InputInterface $input)
404
    {
405
        /** @var ConfigurationHelper $configurationHelper */
406 1
        $configurationHelper = $this->getHelper('phpdocumentor_configuration');
407
408 1
        $title = (string)$configurationHelper->getOption($input, 'title', 'title');
409 1
        $this->getBuilder()->getProjectDescriptor()->setName($title ?: 'API Documentation');
410 1
        $parserPopulator = new ParserPopulator();
411 1
        $parserPopulator->populate(
412 1
            $this->getParser(),
413 1
            $input,
414 1
            $configurationHelper
415
        );
416 1
    }
417
418
    private function getCacheMiddleware(): CacheMiddleware
419
    {
420
        return $this->getContainer()->offsetGet('parser.middleware.cache');
421
    }
422
}
423