Completed
Push — develop ( 114abf...08add9 )
by Jaap
03:44
created

Parser::parse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 7
nc 1
nop 2
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
ccs 0
cts 12
cp 0
crap 2
1
<?php
2
/**
3
 * phpDocumentor
4
 *
5
 * PHP Version 5.3
6
 *
7
 * @author    Mike van Riel <[email protected]>
8
 * @copyright 2010-2012 Mike van Riel / Naenius (http://www.naenius.com)
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace phpDocumentor\Parser;
14
15
use phpDocumentor\Descriptor\ProjectDescriptor;
16
use phpDocumentor\Descriptor\ProjectDescriptorBuilder;
17
use phpDocumentor\Event\Dispatcher;
18
use phpDocumentor\Event\LogEvent;
19
use phpDocumentor\Fileset\Collection;
20
use phpDocumentor\Parser\Exception\FilesNotFoundException;
21
use phpDocumentor\Reflection\File\LocalFile;
22
use phpDocumentor\Reflection\Php\Project;
23
use phpDocumentor\Reflection\ProjectFactory;
24
use Psr\Log\LoggerAwareInterface;
25
use Psr\Log\LoggerInterface;
26
use Psr\Log\LogLevel;
27
use Symfony\Component\Stopwatch\Stopwatch;
28
29
/**
30
 * Class responsible for parsing the given file or files to the intermediate
31
 * structure file.
32
 *
33
 * This class can be used to parse one or more files to the intermediate file
34
 * format for further processing.
35
 */
36
class Parser
37
{
38
    /** @var string the name of the default package */
39
    protected $defaultPackageName = 'Default';
40
41
    /** @var bool whether we force a full re-parse */
42
    protected $force = false;
43
44
    /** @var bool whether to execute a PHPLint on every file */
45
    protected $validate = false;
46
47
    /** @var string[] which markers (i.e. TODO or FIXME) to collect */
48
    protected $markers = array('TODO', 'FIXME');
49
50
    /** @var string[] which tags to ignore */
51
    protected $ignoredTags = array();
52
53
    /** @var string target location's root path */
54
    protected $path = '';
55
56
    /** @var LoggerInterface $logger */
57
    protected $logger;
58
59
    /** @var string The encoding in which the files are encoded */
60
    protected $encoding = 'utf-8';
61
62
    /** @var Stopwatch $stopwatch The profiling component that measures time and memory usage over time */
63
    protected $stopwatch = null;
64
65
    /**
66
     * @var ProjectFactory
67
     */
68
    private $projectFactory;
69
70
    /**
71
     * Initializes the parser.
72
     *
73
     * This constructor checks the user's PHP ini settings to detect which encoding is used by default. This encoding
74
     * is used as a default value for phpDocumentor to convert the source files that it receives.
75
     *
76
     * If no encoding is specified than 'utf-8' is assumed by default.
77
     *
78
     * @codeCoverageIgnore the ini_get call cannot be tested as setting it using ini_set has no effect.
79
     * @param ProjectFactory $projectFactory
80
     * @param Stopwatch $stopwatch
81
     */
82
    public function __construct(ProjectFactory $projectFactory, Stopwatch $stopwatch)
83
    {
84
        $defaultEncoding = ini_get('zend.script_encoding');
85
        if ($defaultEncoding) {
86
            $this->encoding = $defaultEncoding;
87
        }
88
        $this->projectFactory = $projectFactory;
89
        $this->stopwatch = $stopwatch;
90
    }
91
92
    /**
93
     * Sets whether to force a full parse run of all files.
94
     *
95
     * @param bool $forced Forces a full parse.
96
     *
97
     * @api
98
     *
99
     * @return void
100
     */
101 1
    public function setForced($forced)
102
    {
103 1
        $this->force = $forced;
104 1
    }
105
106
    /**
107
     * Returns whether a full rebuild is required.
108
     *
109
     * @api
110
     *
111
     * @return bool
112
     */
113 1
    public function isForced()
114
    {
115 1
        return $this->force;
116
    }
117
118
    /**
119
     * Sets whether to run PHPLint on every file.
120
     *
121
     * PHPLint has a huge performance impact on the execution of phpDocumentor and
122
     * is thus disabled by default.
123
     *
124
     * @param bool $validate when true this file will be checked.
125
     *
126
     * @api
127
     *
128
     * @return void
129
     */
130 1
    public function setValidate($validate)
131
    {
132 1
        $this->validate = $validate;
133 1
    }
134
135
    /**
136
     * Returns whether we want to run PHPLint on every file.
137
     *
138
     * @api
139
     *
140
     * @return bool
141
     */
142 1
    public function doValidation()
143
    {
144 1
        return $this->validate;
145
    }
146
147
    /**
148
     * Sets a list of markers to gather (i.e. TODO, FIXME).
149
     *
150
     * @param string[] $markers A list or markers to gather.
151
     *
152
     * @api
153
     *
154
     * @return void
155
     */
156 1
    public function setMarkers(array $markers)
157
    {
158 1
        $this->markers = $markers;
159 1
    }
160
161
    /**
162
     * Returns the list of markers.
163
     *
164
     * @api
165
     *
166
     * @return string[]
167
     */
168 1
    public function getMarkers()
169
    {
170 1
        return $this->markers;
171
    }
172
173
    /**
174
     * Sets a list of tags to ignore.
175
     *
176
     * @param string[] $ignoredTags A list of tags to ignore.
177
     *
178
     * @api
179
     *
180
     * @return void
181
     */
182 1
    public function setIgnoredTags(array $ignoredTags)
183
    {
184 1
        $this->ignoredTags = $ignoredTags;
185 1
    }
186
187
    /**
188
     * Returns the list of ignored tags.
189
     *
190
     * @api
191
     *
192
     * @return string[]
193
     */
194 1
    public function getIgnoredTags()
195
    {
196 1
        return $this->ignoredTags;
197
    }
198
199
    /**
200
     * Sets the base path of the files that will be parsed.
201
     *
202
     * @param string $path Must be an absolute path.
203
     *
204
     * @api
205
     *
206
     * @return void
207
     */
208 1
    public function setPath($path)
209
    {
210 1
        $this->path = $path;
211 1
    }
212
213
    /**
214
     * Returns the absolute base path for all files.
215
     *
216
     * @return string
217
     */
218 1
    public function getPath()
219
    {
220 1
        return $this->path;
221
    }
222
223
    /**
224
     * Sets the name of the default package.
225
     *
226
     * @param string $defaultPackageName Name used to categorize elements
227
     *  without an @package tag.
228
     *
229
     * @return void
230
     */
231 1
    public function setDefaultPackageName($defaultPackageName)
232
    {
233 1
        $this->defaultPackageName = $defaultPackageName;
234 1
    }
235
236
    /**
237
     * Returns the name of the default package.
238
     *
239
     * @return string
240
     */
241 1
    public function getDefaultPackageName()
242
    {
243 1
        return $this->defaultPackageName;
244
    }
245
246
    /**
247
     * Sets the encoding of the files.
248
     *
249
     * With this option it is possible to tell the parser to use a specific encoding to interpret the provided files.
250
     * By default this is set to UTF-8, in which case no action is taken. Any other encoding will result in the output
251
     * being converted to UTF-8 using `iconv`.
252
     *
253
     * Please note that it is recommended to provide files in UTF-8 format; this will ensure a faster performance since
254
     * no transformation is required.
255
     *
256
     * @param string $encoding
257
     *
258
     * @return void
259
     */
260 1
    public function setEncoding($encoding)
261
    {
262 1
        $this->encoding = $encoding;
263 1
    }
264
265
    /**
266
     * Returns the currently active encoding.
267
     *
268
     * @return string
269
     */
270 1
    public function getEncoding()
271
    {
272 1
        return $this->encoding;
273
    }
274
275
    /**
276
     * Iterates through the given files feeds them to the builder.
277
     *
278
     * @param ProjectDescriptorBuilder $builder
279
     * @param Collection               $files   A files container to parse.
280
     *
281
     * @api
282
     *
283
     * @throws FilesNotFoundException if no files were found.
284
     *
285
     * @return ProjectDescriptor
286
     */
287
    public function parse(ProjectDescriptorBuilder $builder, array $files)
288
    {
289
        $this->startTimingTheParsePhase();
290
291
        $this->forceRebuildIfSettingsHaveModified($builder);
292
293
//        $this->log('  Project root is:  ' . $files->getProjectRoot());
294
//        $this->log('  Ignore paths are: ' . implode(', ', $files->getIgnorePatterns()->getArrayCopy()));
295
296
        /** @var Project $project */
297
        $project = $this->projectFactory->create(ProjectDescriptorBuilder::DEFAULT_PROJECT_NAME, $files);
298
        $this->logAfterParsingAllFiles();
299
300
        /*
301
         *TODO: This should be moved to some view adapter layer when we are removing the descriptors from the application.
302
         * because we are now partly migrating to the new reflection component we are transforming back to the original structure.
303
         */
304
        $builder->build($project);
305
306
        return $builder->getProjectDescriptor();
307
    }
308
309
    /**
310
     * Extract all filenames from the given collection and output the amount of files.
311
     *
312
     * @param Collection $files
313
     *
314
     * @throws FilesNotFoundException if no files were found.
315
     *
316
     * @return string[]
317
     */
318
    protected function getFilenames(Collection $files)
319
    {
320
        $paths = $files->getFilenames();
321
        if (count($paths) < 1) {
322
            throw new FilesNotFoundException();
323
        }
324
        $this->log('Starting to process ' . count($paths) . ' files');
325
326
        return $paths;
327
    }
328
329
    /**
330
     * Checks if the settings of the project have changed and forces a complete rebuild if they have.
331
     *
332
     * @param ProjectDescriptorBuilder $builder
333
     *
334
     * @return void
335
     */
336
    private function forceRebuildIfSettingsHaveModified(ProjectDescriptorBuilder $builder)
337
    {
338
        if ($builder->getProjectDescriptor()->getSettings()->isModified()) {
339
            $this->setForced(true);
340
            $this->log('One of the project\'s settings have changed, forcing a complete rebuild');
341
        }
342
    }
343
344
    /**
345
     * Writes the complete parsing cycle to log.
346
     *
347
     * @return void
348
     */
349
    private function logAfterParsingAllFiles()
350
    {
351
        if (!$this->stopwatch) {
352
            return;
353
        }
354
355
        $event = $this->stopwatch->stop('parser.parse');
356
357
        $this->log('Elapsed time to parse all files: ' . round($event->getDuration(), 2) . 's');
358
        $this->log('Peak memory usage: ' . round($event->getMemory() / 1024 / 1024, 2) . 'M');
359
    }
360
361
    /**
362
     * Dispatches a logging request.
363
     *
364
     * @param string   $message  The message to log.
365
     * @param string   $priority The logging priority as declared in the LogLevel PSR-3 class.
366
     * @param string[] $parameters
367
     *
368
     * @return void
369
     */
370
    private function log($message, $priority = LogLevel::INFO, $parameters = array())
371
    {
372
        Dispatcher::getInstance()->dispatch(
373
            'system.log',
374
            LogEvent::createInstance($this)
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class phpDocumentor\Event\DebugEvent as the method setPriority() does only exist in the following sub-classes of phpDocumentor\Event\DebugEvent: phpDocumentor\Event\LogEvent. 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...
375
                ->setContext($parameters)
376
                ->setMessage($message)
377
                ->setPriority($priority)
378
        );
379
    }
380
381
    private function startTimingTheParsePhase()
382
    {
383
        if ($this->stopwatch) {
384
            $this->stopwatch->start('parser.parse');
385
        }
386
    }
387
}
388