FileEditor::appendStubIfSectionNotFound()   B
last analyzed

Complexity

Conditions 7
Paths 10

Size

Total Lines 32
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 7.7656

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 12
c 1
b 0
f 0
dl 0
loc 32
ccs 9
cts 12
cp 0.75
rs 8.8333
cc 7
nc 10
nop 5
crap 7.7656
1
<?php
2
3
namespace ElegantMedia\PHPToolkit;
4
5
use ElegantMedia\PHPToolkit\Exceptions\FileSystem\FileNotFoundException;
6
use ElegantMedia\PHPToolkit\Exceptions\FileSystem\SectionAlreadyExistsException;
7
8
class FileEditor
9
{
10
	/**
11
	 * Check if a section exists, and if not, append contents.
12
	 *
13
	 * @param      $filePath
14
	 * @param      $stubPath
15
	 * @param      $sectionStartString
16
	 * @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...
17
	 * @param bool $throwEx            Returns the number of bytes written or FALSE on failure
18
	 *
19
	 * @return bool|int
20
	 *
21
	 * @throws SectionAlreadyExistsException
22
	 * @throws FileNotFoundException
23
	 */
24
	public static function appendStubIfSectionNotFound(
25
		$filePath,
26
		$stubPath,
27 9
		$sectionStartString = null,
28
		$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

28
		/** @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...
29
		$throwEx = false
30
	) {
31
		if (!file_exists($filePath)) {
32
			throw new FileNotFoundException("File {$filePath} not found");
33
		}
34 9
		if (!file_exists($stubPath)) {
35
			throw new FileNotFoundException("File {$stubPath} not found");
36
		}
37 9
38
		// by default, we use the first line of the stub as the section start line
39
		if (empty($sectionStartString)) {
40
			$sectionStartString = self::readFirstLine($stubPath);
41
		}
42 9
43 3
		if (empty($sectionStartString)) {
44
			throw new \InvalidArgumentException('A section start string is required.');
45
		}
46 9
47
		// check if the routes file mentions anything about the $sectionStartString
48
		// if so, it might already be there. Ask the user to confirm.
49
		if (self::isTextInFile($filePath, $sectionStartString, false)) {
50
			if ($throwEx) {
51
				throw new SectionAlreadyExistsException();
52 9
			}
53 6
		}
54 3
55
		return self::appendStub($filePath, $stubPath, false);
56
	}
57
58 6
	/**
59
	 * Append a stub to an existing file.
60
	 *
61
	 * @param      $filePath
62
	 * @param      $stubPath
63
	 * @param bool $verifyPathsExists
64
	 * @param bool $stripOpenTag
65
	 *
66
	 * @return bool|int
67
	 *
68
	 * @throws FileNotFoundException
69
	 */
70
	public static function appendStub($filePath, $stubPath, $verifyPathsExists = true, $stripOpenTag = true)
71
	{
72
		if ($verifyPathsExists) {
73 9
			if (!file_exists($filePath)) {
74
				throw new FileNotFoundException("File {$filePath} not found");
75 9
			}
76 3
			if (!file_exists($stubPath)) {
77
				throw new FileNotFoundException("File {$stubPath} not found");
78
			}
79 3
		}
80
81
		// get contents and update the file
82
		$contents = file_get_contents($stubPath);
83
84
		// strip open PHP tags
85 9
		if ($stripOpenTag) {
86
			$tagRegex = '/^\s?<\?(?:php|=)/';
87
			$contents = preg_replace($tagRegex, '', $contents);
88 9
		}
89 9
90 9
		// add a new line
91
		$contents = "\r\n" . trim($contents);
92
93
		return file_put_contents($filePath, $contents, FILE_APPEND);
94 9
	}
95
96 9
	/**
97
	 * Check if a string exists in a file. (Don't use to check on large files).
98
	 *
99
	 * @param      $filePath
100
	 * @param      $string
101
	 * @param bool $caseSensitive
102
	 *
103
	 * @return bool
104
	 *
105
	 * @throws FileNotFoundException
106
	 */
107
	public static function isTextInFile($filePath, $string, $caseSensitive = true): bool
108
	{
109
		if (!file_exists($filePath)) {
110
			throw new FileNotFoundException("File $filePath not found");
111 18
		}
112
113 18
		$command = ($caseSensitive) ? 'strpos' : 'stripos';
114 3
115
		return $command(file_get_contents($filePath), $string) !== false;
116
	}
117 15
118
	/**
119 15
	 * Find and replace text in a file.
120
	 *
121
	 * @param                 $filePath
122
	 * @param string|string[] $search   <p>
123
	 *                                  The value being searched for, otherwise known as the needle.
124
	 *                                  An array may be used to designate multiple needles.
125
	 *                                  </p>
126
	 * @param string|string[] $replace  <p>
127
	 *                                  The replacement value that replaces found search
128
	 *                                  values. An array may be used to designate multiple replacements.
129
	 *                                  </p>
130
	 * @param null|int        $count    [optional] If passed, this will hold the number of matched and replaced needles
131
	 *
132
	 * @return int|false the function returns the number of bytes that were written to the file, or
133 9
	 *                   false on failure
134
	 *
135 9
	 * @throws FileNotFoundException
136 6
	 */
137
	public static function findAndReplace($filePath, $search, $replace, &$count = null)
138
	{
139 3
		if (!file_exists($filePath)) {
140
			throw new FileNotFoundException("File $filePath not found");
141
		}
142
143
		$contents = str_replace($search, $replace, file_get_contents($filePath), $count);
144
145
		return file_put_contents($filePath, $contents);
146
	}
147
148
	/**
149
	 * Find and replace text in a file.
150
	 *
151
	 * @param                 $filePath
152 15
	 * @param string|string[] $search   <p>
153
	 *                                  The value being searched for, otherwise known as the needle.
154 15
	 *                                  An array may be used to designate multiple needles.
155
	 *                                  </p>
156 15
	 * @param string|string[] $replace  <p>
157 15
	 *                                  The replacement value that replaces found search
158
	 *                                  values. An array may be used to designate multiple replacements.
159 15
	 *                                  </p>
160 15
	 * @param int             $limit    [optional] <p>
161
	 *                                  The maximum possible replacements for each pattern in each
162
	 *                                  <i>subject</i> string. Defaults to
163 15
	 *                                  -1 (no limit).
164 12
	 *                                  </p>
165
	 * @param int             $count    [optional] <p>
166
	 *                                  If specified, this variable will be filled with the number of
167 15
	 *                                  replacements done.
168
	 *                                  </p>
169 15
	 *
170 12
	 * @return string|string[]|null <b>preg_replace</b> returns an array if the
171 12
	 * <i>subject</i> parameter is an array, or a string
172 3
	 * otherwise.
173 3
	 * </p>
174
	 * <p>
175
	 * If matches are found, the new <i>subject</i> will
176
	 * be returned, otherwise <i>subject</i> will be
177
	 * returned unchanged or <b>NULL</b> if an error occurred.
178 15
	 *
179 15
	 * @throws FileNotFoundException
180 15
	 */
181
	public static function findAndReplaceRegex($filePath, $search, $replace, $limit = -1, &$count = null)
182
	{
183
		if (!file_exists($filePath)) {
184 15
			throw new FileNotFoundException("File $filePath not found");
185
		}
186
187
		$contents = preg_replace($search, $replace, file_get_contents($filePath), $limit, $count);
188
189
		file_put_contents($filePath, $contents);
190 15
191
		return $contents;
192
	}
193
194
	/**
195
	 * Check if two files are identical in content.
196
	 *
197
	 * @param $path1
198
	 * @param $path2
199
	 *
200
	 * @return bool
201
	 *
202
	 * @throws FileNotFoundException
203
	 */
204
	public static function areFilesSimilar($path1, $path2): bool
205 3
	{
206
		if (!file_exists($path1) || !file_exists($path2)) {
207 3
			throw new FileNotFoundException("At least one of the requested files not found. {$path1}, {$path2}");
208
		}
209
210
		return (filesize($path1) == filesize($path2)) && (md5_file($path1) == md5_file($path2));
211 3
	}
212 3
213 3
	/**
214 3
	 * Returns the first line from an existing file.
215 3
	 *
216
	 * @param      $filePath
217
	 * @param bool $trim
218
	 * @param bool $skipOpenTag
219 3
	 *
220 3
	 * @return bool|string
221
	 */
222 3
	public static function readFirstLine($filePath, $trim = true, $skipOpenTag = true)
223
	{
224
		$handle = fopen($filePath, 'rb');
225
226 3
		$startingLine = null;
227 3
		$startingTagFound = false;
228 3
229 3
		while (!feof($handle)) {
230 3
			$line = fgets($handle);
231 3
232 3
			// trim the line
233
			if ($trim) {
234
				$line = trim($line);
235
			}
236
237 3
			if (!$startingTagFound) {
238 3
				// strip starting PHP tags
239 3
				if ($skipOpenTag) {
240 3
					$tagRegex = '/^\s?<\?(?:php|=)/';
241
					if (preg_match($tagRegex, $line)) {
242
						$startingTagFound = true;
243
						$line = preg_replace($tagRegex, '', $line);
244
					}
245
				}
246
			}
247 3
248
			if (strlen($line) > 0) {
249 3
				$startingLine = $line;
250 3
				break;
251
			}
252
		}
253 3
254
		fclose($handle);
255 3
256
		return $startingLine;
257 3
	}
258 3
259
	/**
260
	 * Get a Classname from a file. Only reads the file without parsing for syntax.
261 3
	 *
262
	 * @see https://stackoverflow.com/questions/7153000/get-class-name-from-file
263
	 *
264
	 * @param      $filePath
265
	 * @param bool $withNamespace
266
	 * @param bool $stripLeading
267
	 *
268
	 * @return string
269
	 *
270
	 * @throws FileNotFoundException
271
	 */
272
	public static function getPHPClassName($filePath, $withNamespace = true, $stripLeading = false): string
273
	{
274
		if (!file_exists($filePath)) {
275
			throw new FileNotFoundException("Filing {$filePath} not found.");
276
		}
277
278
		$fp = fopen($filePath, 'r');
279
		$class = $namespace = $buffer = '';
280
		$i = 0;
281
		while (!$class) {
282
			if (feof($fp)) {
283
				break;
284
			}
285
286
			$buffer .= fread($fp, 512);
287
			$tokens = @token_get_all($buffer);
288
289
			if (strpos($buffer, '{') === false) {
290
				continue;
291
			}
292
293
			for ($iMax = count($tokens); $i < $iMax; $i++) {
294
				if ($tokens[$i][0] === T_NAMESPACE) {
295
					for ($j = $i + 1, $jMax = count($tokens); $j < $jMax; $j++) {
296
						if (is_array($tokens[$j])) {
297
							// PHP 8.0+ has T_NAME_QUALIFIED for fully qualified names
298
							if (defined('T_NAME_QUALIFIED') && $tokens[$j][0] === T_NAME_QUALIFIED) {
299
								$namespace = $tokens[$j][1];
300
								break;
301
							} elseif ($tokens[$j][0] === T_STRING) {
302
								// PHP 7.4: collect T_STRING tokens
303
								if ($namespace && substr($namespace, -1) !== '\\') {
304
									$namespace .= '\\';
305
								}
306
								$namespace .= $tokens[$j][1];
307
							}
308
							// Skip T_NS_SEPARATOR and T_WHITESPACE tokens
309
						} else {
310
							// Non-array token
311
							if ($tokens[$j] === '\\') {
312
								// Namespace separator in PHP 7.4
313
								continue;
314
							} elseif ($tokens[$j] === '{' || $tokens[$j] === ';') {
315
								break;
316
							}
317
						}
318
					}
319
				}
320
321
				if ($tokens[$i][0] === T_CLASS) {
322
					for ($j = $i + 1, $jMax = count($tokens); $j < $jMax; $j++) {
323
						if ($tokens[$j] === '{') {
324
							$class = $tokens[$i + 2][1];
325
						}
326
					}
327
				}
328
			}
329
		}
330
331
		$response = [];
332
333
		if ($withNamespace && $namespace) {
334
			$response[] = $namespace;
335
		}
336
337
		if ($class) {
338
			$response[] = $class;
339
		}
340
341
		$response = implode('\\', $response);
342
343
		// Add leading backslash if namespace is present
344
		if ($withNamespace && $namespace && !$stripLeading) {
345
			$response = '\\' . $response;
346
		}
347
348
		if ($stripLeading) {
349
			$response = ltrim($response, '\\');
350
		}
351
352
		return $response;
353
	}
354
}
355