Completed
Push — master ( 09a7d5...a22bfe )
by Mike
02:34
created

FileCollector::doesFileRequireParsing()   B

Complexity

Conditions 9
Paths 18

Size

Total Lines 55

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 9
nc 18
nop 1
dl 0
loc 55
rs 7.4262
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace phpDocumentor\Guides;
6
7
use Flyfinder\Path;
8
use Flyfinder\Specification\AndSpecification;
9
use Flyfinder\Specification\HasExtension;
10
use Flyfinder\Specification\InPath;
11
use InvalidArgumentException;
12
use League\Flysystem\FilesystemInterface;
13
use function sprintf;
14
use function strlen;
15
use function substr;
16
use function trim;
17
18
class FileCollector
19
{
20
    /** @var Metas */
21
    private $metas;
22
23
    /** @var string[][] */
24
    private $fileInfos = [];
25
26
    public function __construct(Metas $metas)
27
    {
28
        $this->metas = $metas;
29
    }
30
31
    /**
32
     * Scans a directory recursively looking for all files to parse.
33
     *
34
     * This takes into account the presence of cached & fresh MetaEntry
35
     * objects, and avoids adding files to the parse queue that have
36
     * not changed and whose direct dependencies have not changed.
37
     */
38
    public function getFiles(FilesystemInterface $filesystem, string $directory, string $extension) : Files
39
    {
40
        $directory = trim($directory, '/');
41
        /** @var array<array<string>> $files */
42
        $files = $filesystem->find(
0 ignored issues
show
Bug introduced by
The method find() does not seem to exist on object<League\Flysystem\FilesystemInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
43
            new AndSpecification(new InPath(new Path($directory)), new HasExtension([$extension]))
44
        );
45
46
        // completely populate the splFileInfos property
47
        $this->fileInfos = [];
48
        foreach ($files as $fileInfo) {
49
            // Make paths relative to the provided source folder
50
            $fileInfo['path'] = substr($fileInfo['path'], strlen($directory) + 1);
51
            $fileInfo['dirname'] = substr($fileInfo['dirname'], strlen($directory) + 1) ?: '';
52
53
            $documentPath = $this->getFilenameFromFile($fileInfo);
54
55
            $this->fileInfos[$documentPath] = $fileInfo;
56
        }
57
58
        $parseQueue = new Files();
59
        foreach ($this->fileInfos as $filename => $_fileInfo) {
60
            if (!$this->doesFileRequireParsing($filename)) {
61
                continue;
62
            }
63
64
            $parseQueue->add($filename);
65
        }
66
67
        return $parseQueue;
68
    }
69
70
    private function doesFileRequireParsing(string $filename) : bool
71
    {
72
        if (!isset($this->fileInfos[$filename])) {
73
            throw new InvalidArgumentException(
74
                sprintf('No file info found for "%s" - file does not exist.', $filename)
75
            );
76
        }
77
78
        $file = $this->fileInfos[$filename];
79
80
        $documentFilename = $this->getFilenameFromFile($file);
81
        $entry = $this->metas->get($documentFilename);
82
83
        if ($this->hasFileBeenUpdated($filename)) {
84
            // File is new or changed and thus need to be parsed
85
            return true;
86
        }
87
88
        // Look to the file's dependencies to know if you need to parse it or not
89
        $dependencies = $entry !== null ? $entry->getDepends() : [];
90
91
        if ($entry !== null && $entry->getParent() !== null) {
92
            $dependencies[] = $entry->getParent();
93
        }
94
95
        foreach ($dependencies as $dependency) {
96
            /*
97
             * The dependency check is NOT recursive on purpose.
98
             * If fileA has a link to fileB that uses its "headline",
99
             * for example, then fileA is "dependent" on fileB. If
100
             * fileB changes, it means that its MetaEntry needs to
101
             * be updated. And because fileA gets the headline from
102
             * the MetaEntry, it means that fileA must also be re-parsed.
103
             * However, if fileB depends on fileC and file C only is
104
             * updated, fileB *does* need to be re-parsed, but fileA
105
             * does not, because the MetaEntry for fileB IS still
106
             * "fresh" - fileB did not actually change, so any metadata
107
             * about headlines, etc, is still fresh. Therefore, fileA
108
             * does not need to be parsed.
109
             */
110
111
            // dependency no longer exists? We should re-parse this file
112
            if (!isset($this->fileInfos[$dependency])) {
113
                return true;
114
            }
115
116
            // finally, we need to recursively ask if this file needs parsing
117
            if ($this->hasFileBeenUpdated($dependency)) {
118
                return true;
119
            }
120
        }
121
122
        // Meta is fresh and no dependencies need parsing
123
        return false;
124
    }
125
126
    private function hasFileBeenUpdated(string $filename) : bool
127
    {
128
        /** @var array<string> $file */
129
        $file = $this->fileInfos[$filename];
130
131
        $documentFilename = $this->getFilenameFromFile($file);
132
133
        /** @var array<string>|null $entry */
134
        $entry = $this->metas->get($documentFilename);
135
136
        // File is new or changed
137
        return $entry === null || $entry['timestamp'] < $file['timestamp'];
138
    }
139
140
    /**
141
     * Converts foo/bar.rst to foo/bar (the document filename)
142
     *
143
     * @param array<string> $fileInfo
144
     */
145
    private function getFilenameFromFile(array $fileInfo) : string
146
    {
147
        $directory = $fileInfo['dirname'] ? $fileInfo['dirname'] . '/' : '';
148
149
        return $directory . $fileInfo['filename'];
150
    }
151
}
152