FileWalker::__construct()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 12
cp 0
rs 9.8333
c 0
b 0
f 0
cc 2
nc 2
nop 4
crap 6
1
<?php
2
3
/**
4
 * This software package is licensed under AGPL, Commercial license.
5
 *
6
 * @package maslosoft/addendum
7
 * @licence AGPL, Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]> (Meta container, further improvements, bugfixes)
9
 * @copyright Copyright (c) Maslosoft (Meta container, further improvements, bugfixes)
10
 * @copyright Copyright (c) Jan Suchal (Original version, builder, parser)
11
 * @link https://maslosoft.com/addendum/ - maslosoft addendum
12
 * @link https://code.google.com/p/addendum/ - original addendum project
13
 */
14
15
namespace Maslosoft\Addendum\Utilities;
16
17
use DirectoryIterator;
18
19
/**
20
 * FileWalker
21
 *
22
 * This walks recursively on symlinks too, with loop detection.
23
 * Will process only files starting with Capital Letters.
24
 *
25
 * Will skip files with identical content.
26
 *
27
 * This class is meant to replace AnnotationUtility::fileWalker method.
28
 *
29
 * @author Piotr Maselkowski <pmaselkowski at gmail.com>
30
 */
31
class FileWalker
32
{
33
34
	/**
35
	 * Paths to scan
36
	 * @var string[]
37
	 */
38
	private $paths = [];
39
40
	/**
41
	 * Dirs to ignore
42
	 * @var string[]
43
	 */
44
	private $ignoreDirs = [];
45
46
	/**
47
	 * Patterns to match for annotations
48
	 * @var string[]
49
	 */
50
	private $patterns = [];
51
52
	/**
53
	 * Callback to execute on file
54
	 * @var callback
55
	 */
56
	private $callback;
57
58
	/**
59
	 * List of visited real paths
60
	 * @var bool[]
61
	 */
62
	private $visited = [];
63
64
	/**
65
	 * Whether sum was parsed. This is to avoid duplicated files parsing.
66
	 * @var bool[]
67
	 */
68
	private $sum = [];
69
70
	public function __construct($annotations, $callback, $paths, $ignoreDirs = [])
71
	{
72
		$this->paths = $paths;
73
		$this->ignoreDirs = $ignoreDirs;
74
		$this->callback = $callback;
75
		$this->patterns = [];
76
77
		foreach ($annotations as $annotation)
78
		{
79
			$annotation = preg_replace('~^@~', '', $annotation);
80
			$this->patterns[] = sprintf('~@%s~', $annotation);
81
		}
82
	}
83
84
	public function walk()
85
	{
86
		foreach ($this->paths as $path)
87
		{
88
			$this->scan($path);
89
		}
90
	}
91
92
	private function scan($path)
93
	{
94
		// Check if should be visited. Using realpath prevents symlink loops.
95
		$real = realpath($path);
96
		if (!empty($this->visited[$real]))
97
		{
98
			return;
99
		}
100
		// Mark real path as visited
101
		$this->visited[$real] = true;
102
103
		// Scan real path
104
		$iterator = new DirectoryIterator($real);
105
		foreach ($iterator as $info)
106
		{
107
			if ($info->isDot())
108
			{
109
				continue;
110
			}
111
112
			// Recurse, loop prevention check is on top of this function
113
			if ($info->isDir())
114
			{
115
				// Skip ignored dirs,
116
				// this is might be important when scanning nested projects
117
				// containing it's own vendors, cache paths etc.
118
				if (in_array($info->getBasename(), $this->ignoreDirs))
119
				{
120
					continue;
121
				}
122
				$this->scan($info->getPathname());
123
				continue;
124
			}
125
126
			$file = $info->getPathname();
127
128
			// Check if should be processed
129
			if (!empty($this->visited[$file]))
130
			{
131
				continue;
132
			}
133
134
			// Mark as processed
135
			$this->visited[$file] = true;
136
137
			// Only php
138
			if (!preg_match('~^.+\.php$~i', $file))
139
			{
140
				continue;
141
			}
142
143
			// Only starting with capital letter
144
			if (!preg_match('~^[A-Z]~', $info->getBasename()))
145
			{
146
				continue;
147
			}
148
149
			// If patterns are empty, parse every file
150
			$parse = empty($this->patterns);
151
152
			if (is_readable($file))
153
			{
154
				$contents = file_get_contents($file);
155
			}
156
			else
157
			{
158
				// TODO Log this
159
				continue;
160
			}
161
162
			// Check for file checksum
163
			$sum = md5($contents);
164
			// Check if should be processed
165
			if (!empty($this->sum[$sum]))
166
			{
167
				continue;
168
			}
169
			$this->sum[$sum] = true;
170
171
			foreach ($this->patterns as $pattern)
172
			{
173
				if ($parse)
174
				{
175
					continue;
176
				}
177
				if (preg_match($pattern, $contents))
178
				{
179
					$parse = true;
180
				}
181
			}
182
			if (!$parse)
183
			{
184
				continue;
185
			}
186
			call_user_func($this->callback, $file, $contents);
187
		}
188
	}
189
190
}
191