Passed
Push — master ( 259c4a...d70309 )
by Shane
13:37
created

FileEditor::findAndReplaceRegex()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
c 0
b 0
f 0
dl 0
loc 11
rs 10
ccs 2
cts 2
cp 1
cc 2
nc 2
nop 5
crap 2
1
<?php
2
3
4
namespace ElegantMedia\PHPToolkit;
5
6
use ElegantMedia\PHPToolkit\Exceptions\FileSystem\FileNotFoundException;
7
use ElegantMedia\PHPToolkit\Exceptions\FileSystem\SectionAlreadyExistsException;
8
9
class FileEditor
10
{
11
12
13
	/**
14
	 *
15
	 * Check if a section exists, and if not, append contents
16
	 *
17
	 * @param      $filePath
18
	 * @param      $stubPath
19
	 * @param      $sectionStartString
20
	 * @param null $sectionEndString
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $sectionEndString is correct as it would always require null to be passed?
Loading history...
21
	 * @param bool $throwEx				Returns the number of bytes written or FALSE on failure
22
	 *
23
	 * @return bool|int
24
	 * @throws SectionAlreadyExistsException
25
	 * @throws FileNotFoundException
26
	 */
27 9
	public static function appendStubIfSectionNotFound(
28
		$filePath,
29
		$stubPath,
30
		$sectionStartString = null,
31
		$sectionEndString = null,
0 ignored issues
show
Unused Code introduced by
The parameter $sectionEndString is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

31
		/** @scrutinizer ignore-unused */ $sectionEndString = null,

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

Loading history...
32
		$throwEx = false
33
	) {
34 9
		if (!file_exists($filePath)) {
35
			throw new FileNotFoundException("File {$filePath} not found");
36
		}
37 9
		if (!file_exists($stubPath)) {
38
			throw new FileNotFoundException("File {$stubPath} not found");
39
		}
40
41
		// by default, we use the first line of the stub as the section start line
42 9
		if (empty($sectionStartString)) {
43 3
			$sectionStartString = self::readFirstLine($stubPath);
44
		}
45
46 9
		if (empty($sectionStartString)) {
47
			throw new \InvalidArgumentException("A section start string is required.");
48
		}
49
50
		// check if the routes file mentions anything about the $sectionStartString
51
		// if so, it might already be there. Ask the user to confirm.
52 9
		if (self::isTextInFile($filePath, $sectionStartString, false)) {
53 6
			if ($throwEx) {
54 3
				throw new SectionAlreadyExistsException();
55
			}
56
		}
57
58 6
		return self::appendStub($filePath, $stubPath, false);
59
	}
60
61
	/**
62
	 *
63
	 * Append a stub to an existing file
64
	 *
65
	 * @param $filePath
66
	 * @param $stubPath
67
	 *
68
	 * @param bool $verifyPathsExists
69
	 * @param bool $stripOpenTag
70
	 * @return bool|int
71
	 * @throws FileNotFoundException
72
	 */
73 9
	public static function appendStub($filePath, $stubPath, $verifyPathsExists = true, $stripOpenTag = true)
74
	{
75 9
		if ($verifyPathsExists) {
76 3
			if (!file_exists($filePath)) {
77
				throw new FileNotFoundException("File {$filePath} not found");
78
			}
79 3
			if (!file_exists($stubPath)) {
80
				throw new FileNotFoundException("File {$stubPath} not found");
81
			}
82
		}
83
84
		// get contents and update the file
85 9
		$contents = file_get_contents($stubPath);
86
87
		// strip open PHP tags
88 9
		if ($stripOpenTag) {
89 9
			$tagRegex = '/^\s?<\?(?:php|=)/';
90 9
			$contents = preg_replace($tagRegex, '', $contents);
91
		}
92
93
		// add a new line
94 9
		$contents = "\r\n" . trim($contents);
95
96 9
		return file_put_contents($filePath, $contents, FILE_APPEND);
97
	}
98
99
100
	/**
101
	 *
102
	 * Check if a string exists in a file. (Don't use to check on large files)
103
	 *
104
	 * @param      $filePath
105
	 * @param      $string
106
	 * @param bool $caseSensitive
107
	 *
108
	 * @return bool
109
	 * @throws FileNotFoundException
110
	 */
111 18
	public static function isTextInFile($filePath, $string, $caseSensitive = true): bool
112
	{
113 18
		if (!file_exists($filePath)) {
114 3
			throw new FileNotFoundException("File $filePath not found");
115
		}
116
117 15
		$command = ($caseSensitive)? 'strpos': 'stripos';
118
119 15
		return $command(file_get_contents($filePath), $string) !== false;
120
	}
121
122
123
	/**
124
	 *
125
	 * Find and replace text in a file
126
	 *
127
	 * @param $filePath
128
	 * @param string|string[] $search <p>
129
	 * The value being searched for, otherwise known as the needle.
130
	 * An array may be used to designate multiple needles.
131
	 * </p>
132
	 * @param string|string[] $replace <p>
133 9
	 * The replacement value that replaces found search
134
	 * values. An array may be used to designate multiple replacements.
135 9
	 * </p>
136 6
	 * @param null|int $count [optional] If passed, this will hold the number of matched and replaced needles.
137
	 * @return int|false The function returns the number of bytes that were written to the file, or
138
	 * false on failure.
139 3
	 * @throws FileNotFoundException
140
	 */
141
	public static function findAndReplace($filePath, $search, $replace, &$count = null)
142
	{
143
		if (!file_exists($filePath)) {
144
			throw new FileNotFoundException("File $filePath not found");
145
		}
146
147
		$contents = str_replace($search, $replace, file_get_contents($filePath), $count);
148
149
		return file_put_contents($filePath, $contents);
150
	}
151
152 15
	/**
153
	 *
154 15
	 * Find and replace text in a file
155
	 *
156 15
	 * @param $filePath
157 15
	 * @param string|string[] $search <p>
158
	 * The value being searched for, otherwise known as the needle.
159 15
	 * An array may be used to designate multiple needles.
160 15
	 * </p>
161
	 * @param string|string[] $replace <p>
162
	 * The replacement value that replaces found search
163 15
	 * values. An array may be used to designate multiple replacements.
164 12
	 * </p>
165
	 * @param int $limit [optional] <p>
166
	 * The maximum possible replacements for each pattern in each
167 15
	 * <i>subject</i> string. Defaults to
168
	 * -1 (no limit).
169 15
	 * </p>
170 12
	 * @param int $count [optional] <p>
171 12
	 * If specified, this variable will be filled with the number of
172 3
	 * replacements done.
173 3
	 * </p>
174
	 * @return string|string[]|null <b>preg_replace</b> returns an array if the
175
	 * <i>subject</i> parameter is an array, or a string
176
	 * otherwise.
177
	 * </p>
178 15
	 * <p>
179 15
	 * If matches are found, the new <i>subject</i> will
180 15
	 * be returned, otherwise <i>subject</i> will be
181
	 * returned unchanged or <b>NULL</b> if an error occurred.
182
	 * @throws FileNotFoundException
183
	 */
184 15
	public static function findAndReplaceRegex($filePath, $search, $replace, $limit = -1, &$count = null)
185
	{
186
		if (!file_exists($filePath)) {
187
			throw new FileNotFoundException("File $filePath not found");
188
		}
189
190 15
		$contents = preg_replace($search, $replace, file_get_contents($filePath), $limit, $count);
191
192
		file_put_contents($filePath, $contents);
193
194
		return $contents;
195
	}
196
197
	/**
198
	 *
199
	 * Check if two files are identical in content
200
	 *
201
	 * @param $path1
202
	 * @param $path2
203
	 *
204
	 * @return bool
205 3
	 * @throws FileNotFoundException
206
	 */
207 3
	public static function areFilesSimilar($path1, $path2): bool
208
	{
209
		if (!file_exists($path1) || !file_exists($path2)) {
210
			throw new FileNotFoundException("At least one of the requested files not found. {$path1}, {$path2}");
211 3
		}
212 3
213 3
		return ((filesize($path1) == filesize($path2)) && (md5_file($path1) == md5_file($path2)));
214 3
	}
215 3
216
	/**
217
	 *
218
	 * Returns the first line from an existing file.
219 3
	 *
220 3
	 * @param $filePath
221
	 *
222 3
	 * @param bool $trim
223
	 * @param bool $skipOpenTag
224
	 * @return bool|string
225
	 */
226 3
	public static function readFirstLine($filePath, $trim = true, $skipOpenTag = true)
227 3
	{
228 3
		$handle = fopen($filePath, 'rb');
229 3
230 3
		$startingLine = null;
231 3
		$startingTagFound = false;
232 3
233
		while (!feof($handle)) {
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of feof() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

233
		while (!feof(/** @scrutinizer ignore-type */ $handle)) {
Loading history...
234
			$line = fgets($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

234
			$line = fgets(/** @scrutinizer ignore-type */ $handle);
Loading history...
235
236
			// trim the line
237 3
			if ($trim) {
238 3
				$line = trim($line);
239 3
			}
240 3
241
			if (!$startingTagFound) {
242
				// strip starting PHP tags
243
				if ($skipOpenTag) {
244
					$tagRegex = '/^\s?<\?(?:php|=)/';
245
					if (preg_match($tagRegex, $line)) {
246
						$startingTagFound = true;
247 3
						$line = preg_replace($tagRegex, '', $line);
248
					}
249 3
				}
250 3
			}
251
252
			if (strlen($line) > 0) {
253 3
				$startingLine = $line;
254
				break;
255 3
			}
256
		}
257 3
258 3
		fclose($handle);
0 ignored issues
show
Bug introduced by
It seems like $handle can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

258
		fclose(/** @scrutinizer ignore-type */ $handle);
Loading history...
259
260
		return $startingLine;
261 3
	}
262
263
264
	/**
265
	 *
266
	 * Get a Classname from a file. Only reads the file without parsing for syntax.
267
	 * @link https://stackoverflow.com/questions/7153000/get-class-name-from-file
268
	 *
269
	 * @param $filePath
270
	 * @param bool $withNamespace
271
	 * @param bool $stripLeading
272
	 * @return string
273
	 * @throws FileNotFoundException
274
	 */
275
	public static function getPHPClassName($filePath, $withNamespace = true, $stripLeading = false): string
276
	{
277
		if (!file_exists($filePath)) {
278
			throw new FileNotFoundException("Filing {$filePath} not found.");
279
		}
280
281
		$fp = fopen($filePath, 'r');
282
		$class = $namespace = $buffer = '';
283
		$i = 0;
284
		while (!$class) {
285
			if (feof($fp)) {
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of feof() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

285
			if (feof(/** @scrutinizer ignore-type */ $fp)) {
Loading history...
286
				break;
287
			}
288
289
			$buffer .= fread($fp, 512);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

289
			$buffer .= fread(/** @scrutinizer ignore-type */ $fp, 512);
Loading history...
290
			$tokens = token_get_all($buffer);
291
292
			if (strpos($buffer, '{') === false) {
293
				continue;
294
			}
295
296
			for ($iMax = count($tokens); $i< $iMax; $i++) {
297
				if ($tokens[$i][0] === T_NAMESPACE) {
298
					for ($j=$i+1, $jMax = count($tokens); $j< $jMax; $j++) {
299
						if ($tokens[$j][0] === T_STRING) {
300
							$namespace .= '\\'.$tokens[$j][1];
301
						} elseif ($tokens[$j] === '{' || $tokens[$j] === ';') {
302
							break;
303
						}
304
					}
305
				}
306
307
				if ($tokens[$i][0] === T_CLASS) {
308
					for ($j=$i+1, $jMax = count($tokens); $j< $jMax; $j++) {
309
						if ($tokens[$j] === '{') {
310
							$class = $tokens[$i+2][1];
311
						}
312
					}
313
				}
314
			}
315
		}
316
317
		$response = [];
318
319
		if ($withNamespace) {
320
			$response[] = $namespace;
321
		}
322
323
		$response[] = $class;
324
325
		$response = implode('\\', $response);
326
327
		if ($stripLeading) {
328
			$response = ltrim($response, '\\');
329
		}
330
331
		return $response;
332
	}
333
}
334