Completed
Push — master ( 247b25...4111bd )
by Thomas
26:36 queued 10s
created
lib/private/Profiler/FileProfilerStorage.php 1 patch
Indentation   +252 added lines, -252 removed lines patch added patch discarded remove patch
@@ -14,256 +14,256 @@
 block discarded – undo
14 14
  * Storage for profiler using files.
15 15
  */
16 16
 class FileProfilerStorage {
17
-	// Folder where profiler data are stored.
18
-	private string $folder;
19
-
20
-	/**
21
-	 * Constructs the file storage using a "dsn-like" path.
22
-	 *
23
-	 * Example : "file:/path/to/the/storage/folder"
24
-	 *
25
-	 * @throws \RuntimeException
26
-	 */
27
-	public function __construct(string $folder) {
28
-		$this->folder = $folder;
29
-
30
-		if (!is_dir($this->folder) && @mkdir($this->folder, 0777, true) === false && !is_dir($this->folder)) {
31
-			throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
32
-		}
33
-	}
34
-
35
-	public function find(?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null): array {
36
-		$file = $this->getIndexFilename();
37
-
38
-		if (!file_exists($file)) {
39
-			return [];
40
-		}
41
-
42
-		$file = fopen($file, 'r');
43
-		fseek($file, 0, \SEEK_END);
44
-
45
-		$result = [];
46
-		while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
47
-			$values = str_getcsv($line);
48
-			[$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
49
-			$csvTime = (int)$csvTime;
50
-
51
-			if ($url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) {
52
-				continue;
53
-			}
54
-
55
-			if (!empty($start) && $csvTime < $start) {
56
-				continue;
57
-			}
58
-
59
-			if (!empty($end) && $csvTime > $end) {
60
-				continue;
61
-			}
62
-
63
-			$result[$csvToken] = [
64
-				'token' => $csvToken,
65
-				'method' => $csvMethod,
66
-				'url' => $csvUrl,
67
-				'time' => $csvTime,
68
-				'parent' => $csvParent,
69
-				'status_code' => $csvStatusCode,
70
-			];
71
-		}
72
-
73
-		fclose($file);
74
-
75
-		return array_values($result);
76
-	}
77
-
78
-	public function purge(): void {
79
-		$flags = \FilesystemIterator::SKIP_DOTS;
80
-		$iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
81
-		$iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
82
-
83
-		foreach ($iterator as $file) {
84
-			$path = $file->getPathname();
85
-			if (is_file($path)) {
86
-				unlink($path);
87
-			} else {
88
-				rmdir($path);
89
-			}
90
-		}
91
-	}
92
-
93
-	public function read(string $token): ?IProfile {
94
-		if (!$token || !file_exists($file = $this->getFilename($token))) {
95
-			return null;
96
-		}
97
-
98
-		if (\function_exists('gzcompress')) {
99
-			$file = 'compress.zlib://' . $file;
100
-		}
101
-
102
-		return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
103
-	}
104
-
105
-	/**
106
-	 * @throws \RuntimeException
107
-	 */
108
-	public function write(IProfile $profile): bool {
109
-		$file = $this->getFilename($profile->getToken());
110
-
111
-		$profileIndexed = is_file($file);
112
-		if (!$profileIndexed) {
113
-			// Create directory
114
-			$dir = \dirname($file);
115
-			if (!is_dir($dir) && @mkdir($dir, 0777, true) === false && !is_dir($dir)) {
116
-				throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
117
-			}
118
-		}
119
-
120
-		$profileToken = $profile->getToken();
121
-		// when there are errors in sub-requests, the parent and/or children tokens
122
-		// may equal the profile token, resulting in infinite loops
123
-		$parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
124
-		$childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
125
-			return $profileToken !== $p->getToken() ? $p->getToken() : null;
126
-		}, $profile->getChildren()));
127
-
128
-		// Store profile
129
-		$data = [
130
-			'token' => $profileToken,
131
-			'parent' => $parentToken,
132
-			'children' => $childrenToken,
133
-			'data' => $profile->getCollectors(),
134
-			'method' => $profile->getMethod(),
135
-			'url' => $profile->getUrl(),
136
-			'time' => $profile->getTime(),
137
-			'status_code' => $profile->getStatusCode(),
138
-		];
139
-
140
-		$context = stream_context_create();
141
-
142
-		if (\function_exists('gzcompress')) {
143
-			$file = 'compress.zlib://' . $file;
144
-			stream_context_set_option($context, 'zlib', 'level', 3);
145
-		}
146
-
147
-		if (file_put_contents($file, serialize($data), 0, $context) === false) {
148
-			return false;
149
-		}
150
-
151
-		if (!$profileIndexed) {
152
-			// Add to index
153
-			if (false === $file = fopen($this->getIndexFilename(), 'a')) {
154
-				return false;
155
-			}
156
-
157
-			fputcsv($file, [
158
-				$profile->getToken(),
159
-				$profile->getMethod(),
160
-				$profile->getUrl(),
161
-				$profile->getTime(),
162
-				$profile->getParentToken(),
163
-				$profile->getStatusCode(),
164
-			], escape: '');
165
-			fclose($file);
166
-		}
167
-
168
-		return true;
169
-	}
170
-
171
-	/**
172
-	 * Gets filename to store data, associated to the token.
173
-	 *
174
-	 * @return string The profile filename
175
-	 */
176
-	protected function getFilename(string $token): string {
177
-		// Uses 4 last characters, because first are mostly the same.
178
-		$folderA = substr($token, -2, 2);
179
-		$folderB = substr($token, -4, 2);
180
-
181
-		return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
182
-	}
183
-
184
-	/**
185
-	 * Gets the index filename.
186
-	 *
187
-	 * @return string The index filename
188
-	 */
189
-	protected function getIndexFilename(): string {
190
-		return $this->folder . '/index.csv';
191
-	}
192
-
193
-	/**
194
-	 * Reads a line in the file, backward.
195
-	 *
196
-	 * This function automatically skips the empty lines and do not include the line return in result value.
197
-	 *
198
-	 * @param resource $file The file resource, with the pointer placed at the end of the line to read
199
-	 *
200
-	 * @return ?string A string representing the line or null if beginning of file is reached
201
-	 */
202
-	protected function readLineFromFile($file): ?string {
203
-		$line = '';
204
-		$position = ftell($file);
205
-
206
-		if ($position === 0) {
207
-			return null;
208
-		}
209
-
210
-		while (true) {
211
-			$chunkSize = min($position, 1024);
212
-			$position -= $chunkSize;
213
-			fseek($file, $position);
214
-
215
-			if ($chunkSize === 0) {
216
-				// bof reached
217
-				break;
218
-			}
219
-
220
-			$buffer = fread($file, $chunkSize);
221
-
222
-			if (false === ($upTo = strrpos($buffer, "\n"))) {
223
-				$line = $buffer . $line;
224
-				continue;
225
-			}
226
-
227
-			$position += $upTo;
228
-			$line = substr($buffer, $upTo + 1) . $line;
229
-			fseek($file, max(0, $position), \SEEK_SET);
230
-
231
-			if ($line !== '') {
232
-				break;
233
-			}
234
-		}
235
-
236
-		return $line === '' ? null : $line;
237
-	}
238
-
239
-	protected function createProfileFromData(string $token, array $data, ?IProfile $parent = null): IProfile {
240
-		$profile = new Profile($token);
241
-		$profile->setMethod($data['method']);
242
-		$profile->setUrl($data['url']);
243
-		$profile->setTime($data['time']);
244
-		$profile->setStatusCode($data['status_code']);
245
-		$profile->setCollectors($data['data']);
246
-
247
-		if (!$parent && $data['parent']) {
248
-			$parent = $this->read($data['parent']);
249
-		}
250
-
251
-		if ($parent) {
252
-			$profile->setParent($parent);
253
-		}
254
-
255
-		foreach ($data['children'] as $token) {
256
-			if (!$token || !file_exists($file = $this->getFilename($token))) {
257
-				continue;
258
-			}
259
-
260
-			if (\function_exists('gzcompress')) {
261
-				$file = 'compress.zlib://' . $file;
262
-			}
263
-
264
-			$profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
265
-		}
266
-
267
-		return $profile;
268
-	}
17
+    // Folder where profiler data are stored.
18
+    private string $folder;
19
+
20
+    /**
21
+     * Constructs the file storage using a "dsn-like" path.
22
+     *
23
+     * Example : "file:/path/to/the/storage/folder"
24
+     *
25
+     * @throws \RuntimeException
26
+     */
27
+    public function __construct(string $folder) {
28
+        $this->folder = $folder;
29
+
30
+        if (!is_dir($this->folder) && @mkdir($this->folder, 0777, true) === false && !is_dir($this->folder)) {
31
+            throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $this->folder));
32
+        }
33
+    }
34
+
35
+    public function find(?string $url, ?int $limit, ?string $method, ?int $start = null, ?int $end = null, ?string $statusCode = null): array {
36
+        $file = $this->getIndexFilename();
37
+
38
+        if (!file_exists($file)) {
39
+            return [];
40
+        }
41
+
42
+        $file = fopen($file, 'r');
43
+        fseek($file, 0, \SEEK_END);
44
+
45
+        $result = [];
46
+        while (\count($result) < $limit && $line = $this->readLineFromFile($file)) {
47
+            $values = str_getcsv($line);
48
+            [$csvToken, $csvMethod, $csvUrl, $csvTime, $csvParent, $csvStatusCode] = $values;
49
+            $csvTime = (int)$csvTime;
50
+
51
+            if ($url && !str_contains($csvUrl, $url) || $method && !str_contains($csvMethod, $method) || $statusCode && !str_contains($csvStatusCode, $statusCode)) {
52
+                continue;
53
+            }
54
+
55
+            if (!empty($start) && $csvTime < $start) {
56
+                continue;
57
+            }
58
+
59
+            if (!empty($end) && $csvTime > $end) {
60
+                continue;
61
+            }
62
+
63
+            $result[$csvToken] = [
64
+                'token' => $csvToken,
65
+                'method' => $csvMethod,
66
+                'url' => $csvUrl,
67
+                'time' => $csvTime,
68
+                'parent' => $csvParent,
69
+                'status_code' => $csvStatusCode,
70
+            ];
71
+        }
72
+
73
+        fclose($file);
74
+
75
+        return array_values($result);
76
+    }
77
+
78
+    public function purge(): void {
79
+        $flags = \FilesystemIterator::SKIP_DOTS;
80
+        $iterator = new \RecursiveDirectoryIterator($this->folder, $flags);
81
+        $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
82
+
83
+        foreach ($iterator as $file) {
84
+            $path = $file->getPathname();
85
+            if (is_file($path)) {
86
+                unlink($path);
87
+            } else {
88
+                rmdir($path);
89
+            }
90
+        }
91
+    }
92
+
93
+    public function read(string $token): ?IProfile {
94
+        if (!$token || !file_exists($file = $this->getFilename($token))) {
95
+            return null;
96
+        }
97
+
98
+        if (\function_exists('gzcompress')) {
99
+            $file = 'compress.zlib://' . $file;
100
+        }
101
+
102
+        return $this->createProfileFromData($token, unserialize(file_get_contents($file)));
103
+    }
104
+
105
+    /**
106
+     * @throws \RuntimeException
107
+     */
108
+    public function write(IProfile $profile): bool {
109
+        $file = $this->getFilename($profile->getToken());
110
+
111
+        $profileIndexed = is_file($file);
112
+        if (!$profileIndexed) {
113
+            // Create directory
114
+            $dir = \dirname($file);
115
+            if (!is_dir($dir) && @mkdir($dir, 0777, true) === false && !is_dir($dir)) {
116
+                throw new \RuntimeException(sprintf('Unable to create the storage directory (%s).', $dir));
117
+            }
118
+        }
119
+
120
+        $profileToken = $profile->getToken();
121
+        // when there are errors in sub-requests, the parent and/or children tokens
122
+        // may equal the profile token, resulting in infinite loops
123
+        $parentToken = $profile->getParentToken() !== $profileToken ? $profile->getParentToken() : null;
124
+        $childrenToken = array_filter(array_map(function (IProfile $p) use ($profileToken) {
125
+            return $profileToken !== $p->getToken() ? $p->getToken() : null;
126
+        }, $profile->getChildren()));
127
+
128
+        // Store profile
129
+        $data = [
130
+            'token' => $profileToken,
131
+            'parent' => $parentToken,
132
+            'children' => $childrenToken,
133
+            'data' => $profile->getCollectors(),
134
+            'method' => $profile->getMethod(),
135
+            'url' => $profile->getUrl(),
136
+            'time' => $profile->getTime(),
137
+            'status_code' => $profile->getStatusCode(),
138
+        ];
139
+
140
+        $context = stream_context_create();
141
+
142
+        if (\function_exists('gzcompress')) {
143
+            $file = 'compress.zlib://' . $file;
144
+            stream_context_set_option($context, 'zlib', 'level', 3);
145
+        }
146
+
147
+        if (file_put_contents($file, serialize($data), 0, $context) === false) {
148
+            return false;
149
+        }
150
+
151
+        if (!$profileIndexed) {
152
+            // Add to index
153
+            if (false === $file = fopen($this->getIndexFilename(), 'a')) {
154
+                return false;
155
+            }
156
+
157
+            fputcsv($file, [
158
+                $profile->getToken(),
159
+                $profile->getMethod(),
160
+                $profile->getUrl(),
161
+                $profile->getTime(),
162
+                $profile->getParentToken(),
163
+                $profile->getStatusCode(),
164
+            ], escape: '');
165
+            fclose($file);
166
+        }
167
+
168
+        return true;
169
+    }
170
+
171
+    /**
172
+     * Gets filename to store data, associated to the token.
173
+     *
174
+     * @return string The profile filename
175
+     */
176
+    protected function getFilename(string $token): string {
177
+        // Uses 4 last characters, because first are mostly the same.
178
+        $folderA = substr($token, -2, 2);
179
+        $folderB = substr($token, -4, 2);
180
+
181
+        return $this->folder . '/' . $folderA . '/' . $folderB . '/' . $token;
182
+    }
183
+
184
+    /**
185
+     * Gets the index filename.
186
+     *
187
+     * @return string The index filename
188
+     */
189
+    protected function getIndexFilename(): string {
190
+        return $this->folder . '/index.csv';
191
+    }
192
+
193
+    /**
194
+     * Reads a line in the file, backward.
195
+     *
196
+     * This function automatically skips the empty lines and do not include the line return in result value.
197
+     *
198
+     * @param resource $file The file resource, with the pointer placed at the end of the line to read
199
+     *
200
+     * @return ?string A string representing the line or null if beginning of file is reached
201
+     */
202
+    protected function readLineFromFile($file): ?string {
203
+        $line = '';
204
+        $position = ftell($file);
205
+
206
+        if ($position === 0) {
207
+            return null;
208
+        }
209
+
210
+        while (true) {
211
+            $chunkSize = min($position, 1024);
212
+            $position -= $chunkSize;
213
+            fseek($file, $position);
214
+
215
+            if ($chunkSize === 0) {
216
+                // bof reached
217
+                break;
218
+            }
219
+
220
+            $buffer = fread($file, $chunkSize);
221
+
222
+            if (false === ($upTo = strrpos($buffer, "\n"))) {
223
+                $line = $buffer . $line;
224
+                continue;
225
+            }
226
+
227
+            $position += $upTo;
228
+            $line = substr($buffer, $upTo + 1) . $line;
229
+            fseek($file, max(0, $position), \SEEK_SET);
230
+
231
+            if ($line !== '') {
232
+                break;
233
+            }
234
+        }
235
+
236
+        return $line === '' ? null : $line;
237
+    }
238
+
239
+    protected function createProfileFromData(string $token, array $data, ?IProfile $parent = null): IProfile {
240
+        $profile = new Profile($token);
241
+        $profile->setMethod($data['method']);
242
+        $profile->setUrl($data['url']);
243
+        $profile->setTime($data['time']);
244
+        $profile->setStatusCode($data['status_code']);
245
+        $profile->setCollectors($data['data']);
246
+
247
+        if (!$parent && $data['parent']) {
248
+            $parent = $this->read($data['parent']);
249
+        }
250
+
251
+        if ($parent) {
252
+            $profile->setParent($parent);
253
+        }
254
+
255
+        foreach ($data['children'] as $token) {
256
+            if (!$token || !file_exists($file = $this->getFilename($token))) {
257
+                continue;
258
+            }
259
+
260
+            if (\function_exists('gzcompress')) {
261
+                $file = 'compress.zlib://' . $file;
262
+            }
263
+
264
+            $profile->addChild($this->createProfileFromData($token, unserialize(file_get_contents($file)), $profile));
265
+        }
266
+
267
+        return $profile;
268
+    }
269 269
 }
Please login to merge, or discard this patch.