• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Utils / 8400644326

22 Mar 2024 08:15PM UTC coverage: 92.758%. Remained the same
8400644326

push

github

web-flow
Reorganize PHP extension requirements in composer.json (#49)

2 of 2 new or added lines in 1 file covered. (100.0%)

2 existing lines in 2 files now uncovered.

1665 of 1795 relevant lines covered (92.76%)

41.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.13
/src/FS.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Utils.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Utils
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Utils;
18

19
/**
20
 * @SuppressWarnings(PHPMD.TooManyPublicMethods)
21
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
22
 * @SuppressWarnings(PHPMD.ShortClassName)
23
 */
24
final class FS
25
{
26
    public const TYPE_SOCKET    = 0xC000;
27
    public const TYPE_SYMLINK   = 0xA000;
28
    public const TYPE_REGULAR   = 0x8000;
29
    public const TYPE_BLOCK     = 0x6000;
30
    public const TYPE_DIR       = 0x4000;
31
    public const TYPE_CHARACTER = 0x2000;
32
    public const TYPE_FIFO      = 0x1000;
33

34
    public const PERM_OWNER_READ        = 0x0100;
35
    public const PERM_OWNER_WRITE       = 0x0080;
36
    public const PERM_OWNER_EXEC        = 0x0040;
37
    public const PERM_OWNER_EXEC_STICKY = 0x0800;
38

39
    public const PERM_GROUP_READ        = 0x0020;
40
    public const PERM_GROUP_WRITE       = 0x0010;
41
    public const PERM_GROUP_EXEC        = 0x0008;
42
    public const PERM_GROUP_EXEC_STICKY = 0x0400;
43

44
    public const PERM_ALL_READ        = 0x0004;
45
    public const PERM_ALL_WRITE       = 0x0002;
46
    public const PERM_ALL_EXEC        = 0x0001;
47
    public const PERM_ALL_EXEC_STICKY = 0x0200;
48

49
    /**
50
     * Returns the file permissions as a nice string, like -rw-r--r-- or false if the file is not found.
51
     *
52
     * @param string   $file  The name of the file to get permissions form
53
     * @param null|int $perms numerical value of permissions to display as text
54
     *
55
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
56
     * @SuppressWarnings(PHPMD.NPathComplexity)
57
     */
58
    public static function perms(string $file, ?int $perms = null): string
59
    {
60
        if ($perms === null) {
12✔
61
            if (!\file_exists($file)) {
12✔
62
                return '';
6✔
63
            }
64

65
            /** @noinspection CallableParameterUseCaseInTypeContextInspection */
66
            $perms = \fileperms($file);
6✔
67
        }
68

69
        /** @codeCoverageIgnoreStart */
70
        $info = 'u'; // undefined
6✔
71
        if (($perms & self::TYPE_SOCKET) === self::TYPE_SOCKET) {
6✔
72
            $info = 's';
×
73
        } elseif (($perms & self::TYPE_SYMLINK) === self::TYPE_SYMLINK) {
6✔
74
            $info = 'l';
×
75
        } elseif (($perms & self::TYPE_REGULAR) === self::TYPE_REGULAR) {
6✔
76
            $info = '-';
6✔
77
        } elseif (($perms & self::TYPE_BLOCK) === self::TYPE_BLOCK) {
×
78
            $info = 'b';
×
79
        } elseif (($perms & self::TYPE_DIR) === self::TYPE_DIR) {
×
80
            $info = 'd';
×
81
        } elseif (($perms & self::TYPE_CHARACTER) === self::TYPE_CHARACTER) {
×
82
            $info = 'c';
×
83
        } elseif (($perms & self::TYPE_FIFO) === self::TYPE_FIFO) {
×
84
            $info = 'p';
×
85
        }
86
        // @codeCoverageIgnoreEnd
87

88
        // Owner
89
        $info .= (($perms & self::PERM_OWNER_READ) > 0 ? 'r' : '-');
6✔
90
        $info .= (($perms & self::PERM_OWNER_WRITE) > 0 ? 'w' : '-');
6✔
91

92
        /** @noinspection NestedTernaryOperatorInspection */
93
        $info .= (($perms & self::PERM_OWNER_EXEC) > 0
6✔
94
            ? (($perms & self::PERM_OWNER_EXEC_STICKY) > 0 ? 's' : 'x')
×
95
            : (($perms & self::PERM_OWNER_EXEC_STICKY) > 0 ? 'S' : '-'));
6✔
96

97
        // Group
98
        $info .= (($perms & self::PERM_GROUP_READ) > 0 ? 'r' : '-');
6✔
99
        $info .= (($perms & self::PERM_GROUP_WRITE) > 0 ? 'w' : '-');
6✔
100

101
        /** @noinspection NestedTernaryOperatorInspection */
102
        $info .= (($perms & self::PERM_GROUP_EXEC) > 0
6✔
103
            ? (($perms & self::PERM_GROUP_EXEC_STICKY) > 0 ? 's' : 'x')
×
104
            : (($perms & self::PERM_GROUP_EXEC_STICKY) > 0 ? 'S' : '-'));
6✔
105

106
        // All
107
        $info .= (($perms & self::PERM_ALL_READ) > 0 ? 'r' : '-');
6✔
108
        $info .= (($perms & self::PERM_ALL_WRITE) > 0 ? 'w' : '-');
6✔
109

110
        /** @noinspection NestedTernaryOperatorInspection */
111
        $info .= (($perms & self::PERM_ALL_EXEC) > 0
6✔
112
            ? (($perms & self::PERM_ALL_EXEC_STICKY) > 0 ? 't' : 'x')
×
113
            : (($perms & self::PERM_ALL_EXEC_STICKY) > 0 ? 'T' : '-'));
6✔
114

115
        return $info;
6✔
116
    }
117

118
    /**
119
     * Removes a directory (and its contents) recursively.
120
     * Contributed by Askar (ARACOOL) <https://github.com/ARACOOOL>.
121
     * @param string $dir              The directory to be deleted recursively
122
     * @param bool   $traverseSymlinks Delete contents of symlinks recursively
123
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
124
     * @SuppressWarnings(PHPMD.NPathComplexity)
125
     */
126
    public static function rmDir(string $dir, bool $traverseSymlinks = true): bool
127
    {
128
        if (!\file_exists($dir)) {
18✔
129
            return true;
6✔
130
        }
131

132
        if (!\is_dir($dir)) {
18✔
133
            throw new Exception('Given path is not a directory');
6✔
134
        }
135

136
        if ($traverseSymlinks || !\is_link($dir)) {
18✔
137
            $list = (array)\scandir($dir, \SCANDIR_SORT_NONE);
18✔
138

139
            foreach ($list as $file) {
18✔
140
                if ($file === '.' || $file === '..') {
18✔
141
                    continue;
18✔
142
                }
143

144
                $currentPath = "{$dir}/{$file}";
18✔
145

146
                if (\is_dir($currentPath)) {
18✔
147
                    self::rmDir($currentPath, $traverseSymlinks);
6✔
148
                } elseif (!\unlink($currentPath)) {
18✔
149
                    throw new Exception('Unable to delete ' . $currentPath);
×
150
                }
151
            }
152
        }
153

154
        // Windows treats removing directory symlinks identically to removing directories.
155
        if (!\defined('PHP_WINDOWS_VERSION_MAJOR') && \is_link($dir)) {
18✔
156
            if (!\unlink($dir)) {
6✔
UNCOV
157
                throw new Exception('Unable to delete ' . $dir);
2✔
158
            }
159
        } elseif (!\rmdir($dir)) {
18✔
160
            throw new Exception('Unable to delete ' . $dir);
×
161
        }
162

163
        return true;
18✔
164
    }
165

166
    /**
167
     * Binary safe to open file.
168
     * @deprecated Use \file_get_contents()
169
     */
170
    public static function openFile(string $filepath): ?string
171
    {
172
        $contents = null;
6✔
173

174
        $realPath = \realpath($filepath);
6✔
175
        if ($realPath !== false) {
6✔
176
            $handle = \fopen($realPath, 'r');
6✔
177
            if ($handle !== false) {
6✔
178
                $contents = (string)\fread($handle, (int)\filesize($realPath));
6✔
179
                \fclose($handle);
6✔
180
            }
181
        }
182

183
        return $contents;
6✔
184
    }
185

186
    /**
187
     * Quickest way for getting first file line.
188
     */
189
    public static function firstLine(string $filepath): ?string
190
    {
191
        if (\file_exists($filepath)) {
6✔
192
            $cacheRes = \fopen($filepath, 'r');
6✔
193
            if ($cacheRes !== false) {
6✔
194
                $firstLine = \fgets($cacheRes);
6✔
195
                \fclose($cacheRes);
6✔
196

197
                return $firstLine === false ? null : $firstLine;
6✔
198
            }
199
        }
200

201
        return null;
6✔
202
    }
203

204
    /**
205
     * Set the writable bit on a file to the minimum value that allows the user running PHP to write to it.
206
     * @param string $filename The filename to set the writable bit on
207
     * @param bool   $writable Whether to make the file writable or not
208
     */
209
    public static function writable(string $filename, bool $writable = true): bool
210
    {
211
        return self::setPerms($filename, $writable, 2);
6✔
212
    }
213

214
    /**
215
     * Set the readable bit on a file to the minimum value that allows the user running PHP to read to it.
216
     * @param string $filename The filename to set the readable bit on
217
     * @param bool   $readable Whether to make the file readable or not
218
     */
219
    public static function readable(string $filename, bool $readable = true): bool
220
    {
221
        return self::setPerms($filename, $readable, 4);
6✔
222
    }
223

224
    /**
225
     * Set the executable bit on a file to the minimum value that allows the user running PHP to read to it.
226
     * @param string $filename   The filename to set the executable bit on
227
     * @param bool   $executable Whether to make the file executable or not
228
     */
229
    public static function executable(string $filename, bool $executable = true): bool
230
    {
231
        return self::setPerms($filename, $executable, 1);
6✔
232
    }
233

234
    /**
235
     * Returns size of a given directory in bytes.
236
     */
237
    public static function dirSize(string $dir): int
238
    {
239
        $size = 0;
6✔
240

241
        $flags = \FilesystemIterator::CURRENT_AS_FILEINFO | \FilesystemIterator::SKIP_DOTS;
6✔
242

243
        $dirIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, $flags));
6✔
244

245
        /** @var \SplFileInfo $splFileInfo */
246
        foreach ($dirIterator as $splFileInfo) {
6✔
247
            if ($splFileInfo->isFile()) {
6✔
248
                $size += (int)$splFileInfo->getSize();
6✔
249
            }
250
        }
251

252
        return $size;
6✔
253
    }
254

255
    /**
256
     * Returns all paths inside a directory.
257
     * @SuppressWarnings(PHPMD.UnusedLocalVariable)
258
     * @SuppressWarnings(PHPMD.ShortMethodName)
259
     */
260
    public static function ls(string $dir): array
261
    {
262
        $contents = [];
6✔
263

264
        $flags = \FilesystemIterator::KEY_AS_PATHNAME
6✔
265
            | \FilesystemIterator::CURRENT_AS_FILEINFO
6✔
266
            | \FilesystemIterator::SKIP_DOTS;
6✔
267

268
        $dirIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir, $flags));
6✔
269

270
        /** @var \SplFileInfo $splFileInfo */
271
        foreach ($dirIterator as $splFileInfo) {
6✔
272
            $contents[] = $splFileInfo->getPathname();
6✔
273
        }
274

275
        \natsort($contents);
6✔
276

277
        return $contents;
6✔
278
    }
279

280
    /**
281
     * Nice formatting for computer sizes (Bytes).
282
     * @param int $bytes    The number in bytes to format
283
     * @param int $decimals The number of decimal points to include
284
     */
285
    public static function format(int $bytes, int $decimals = 2): string
286
    {
287
        $exp     = 0;
12✔
288
        $value   = 0;
12✔
289
        $symbols = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
12✔
290

291
        $bytes = (float)$bytes;
12✔
292

293
        if ($bytes > 0) {
12✔
294
            $exp   = (int)\floor(\log($bytes) / \log(1024));
12✔
295
            $value = ($bytes / (1024 ** \floor($exp)));
12✔
296
        }
297

298
        if ($symbols[$exp] === 'B') {
12✔
299
            $decimals = 0;
6✔
300
        }
301

302
        return \number_format($value, $decimals, '.', '') . ' ' . $symbols[$exp];
12✔
303
    }
304

305
    /**
306
     * Returns extension of file from FS pathname.
307
     */
308
    public static function ext(?string $path): string
309
    {
310
        if (isStrEmpty($path)) {
12✔
311
            return '';
6✔
312
        }
313

314
        if (\str_contains((string)$path, '?')) {
12✔
315
            $path = (string)\preg_replace('#\?(.*)#', '', (string)$path);
6✔
316
        }
317

318
        $ext = \pathinfo((string)$path, \PATHINFO_EXTENSION);
12✔
319

320
        return \strtolower($ext);
12✔
321
    }
322

323
    /**
324
     * Returns name of file with ext from FS pathname.
325
     */
326
    public static function base(?string $path): string
327
    {
328
        return \pathinfo((string)$path, \PATHINFO_BASENAME);
6✔
329
    }
330

331
    /**
332
     * Returns filename without ext from FS pathname.
333
     */
334
    public static function filename(?string $path): string
335
    {
336
        return \pathinfo((string)$path, \PATHINFO_FILENAME);
6✔
337
    }
338

339
    /**
340
     * Returns name for directory from FS pathname.
341
     */
342
    public static function dirName(?string $path): string
343
    {
344
        return \pathinfo((string)$path, \PATHINFO_DIRNAME);
6✔
345
    }
346

347
    /**
348
     * Returns realpath (smart analog of PHP \realpath()).
349
     */
350
    public static function real(?string $path): ?string
351
    {
352
        if (isStrEmpty($path)) {
24✔
353
            return null;
×
354
        }
355

356
        $result = \realpath((string)$path);
24✔
357

358
        return $result === false ? null : $result;
24✔
359
    }
360

361
    /**
362
     * Function to strip trailing / or \ in a pathname.
363
     * @param null|string $path   the path to clean
364
     * @param string      $dirSep directory separator (optional)
365
     * @SuppressWarnings(PHPMD.Superglobals)
366
     */
367
    public static function clean(?string $path, string $dirSep = \DIRECTORY_SEPARATOR): string
368
    {
369
        if (isStrEmpty($path)) {
42✔
370
            return '';
6✔
371
        }
372

373
        $path = \trim((string)$path);
42✔
374

375
        if (($dirSep === '\\') && ($path[0] === '\\') && ($path[1] === '\\')) {
42✔
376
            $path = '\\' . \preg_replace('#[/\\\\]+#', $dirSep, $path);
6✔
377
        } else {
378
            $path = (string)\preg_replace('#[/\\\\]+#', $dirSep, $path);
42✔
379
        }
380

381
        return $path;
42✔
382
    }
383

384
    /**
385
     * Strip off the extension if it exists.
386
     */
387
    public static function stripExt(string $path): string
388
    {
389
        $reg = '/\.' . \preg_quote(self::ext($path), '') . '$/';
6✔
390

391
        return (string)\preg_replace($reg, '', $path);
6✔
392
    }
393

394
    /**
395
     * Check is current path directory.
396
     */
397
    public static function isDir(?string $path): bool
398
    {
399
        if (isStrEmpty($path)) {
6✔
400
            return false;
×
401
        }
402

403
        $path = self::clean($path);
6✔
404

405
        return \is_dir($path);
6✔
406
    }
407

408
    /**
409
     * Check is current path regular file.
410
     */
411
    public static function isFile(string $path): bool
412
    {
413
        $path = self::clean($path);
6✔
414

415
        return \file_exists($path) && \is_file($path);
6✔
416
    }
417

418
    /**
419
     * Find relative path of file (remove root part).
420
     */
421
    public static function getRelative(
422
        string $path,
423
        ?string $rootPath = null,
424
        string $forceDS = \DIRECTORY_SEPARATOR,
425
    ): string {
426
        // Cleanup file path
427
        $cleanedPath = self::clean((string)self::real($path), $forceDS);
6✔
428

429
        // Cleanup root path
430
        $rootPath = isStrEmpty($rootPath) ? Sys::getDocRoot() : $rootPath;
6✔
431
        $rootPath = self::clean((string)self::real((string)$rootPath), $forceDS);
6✔
432

433
        // Remove root part
434
        $relPath = (string)\preg_replace('#^' . \preg_quote($rootPath, '\\') . '#', '', $cleanedPath);
6✔
435

436
        return \ltrim($relPath, $forceDS);
6✔
437
    }
438

439
    /**
440
     * Returns clean realpath if file or directory exists.
441
     */
442
    public static function isReal(?string $path): bool
443
    {
444
        if (isStrEmpty($path)) {
6✔
445
            return false;
×
446
        }
447

448
        $expected = self::clean((string)self::real($path));
6✔
449
        $actual   = self::clean($path);
6✔
450

451
        return !isStrEmpty($expected) && $expected === $actual;
6✔
452
    }
453

454
    /**
455
     * Returns true if file is writable.
456
     */
457
    private static function setPerms(string $filename, bool $isFlag, int $perm): bool
458
    {
459
        $stat = @\stat($filename);
18✔
460

461
        if ($stat === false) {
18✔
462
            return false;
18✔
463
        }
464

465
        // We're on Windows
466
        if (Sys::isWin()) {
18✔
467
            return true;
×
468
        }
469

470
        [$myuid, $mygid] = [\posix_geteuid(), \posix_getgid()];
18✔
471

472
        $isMyUid = $stat['uid'] === $myuid;
18✔
473
        $isMyGid = $stat['gid'] === $mygid;
18✔
474

475
        if ($isFlag) {
18✔
476
            // Set only the user writable bit (file is owned by us)
477
            if ($isMyUid) {
18✔
478
                return \chmod($filename, \fileperms($filename) | \intval('0' . $perm . '00', 8));
18✔
479
            }
480

481
            // Set only the group writable bit (file group is the same as us)
482
            if ($isMyGid) {
×
483
                return \chmod($filename, \fileperms($filename) | \intval('0' . $perm . $perm . '0', 8));
×
484
            }
485

486
            // Set the world writable bit (file isn't owned or grouped by us)
487
            return \chmod($filename, \fileperms($filename) | \intval('0' . $perm . $perm . $perm, 8));
×
488
        }
489

490
        // Set only the user writable bit (file is owned by us)
491
        if ($isMyUid) {
18✔
492
            $add = \intval("0{$perm}{$perm}{$perm}", 8);
18✔
493

494
            return self::chmod($filename, $perm, $add);
18✔
495
        }
496

497
        // Set only the group writable bit (file group is the same as us)
498
        if ($isMyGid) {
×
499
            $add = \intval("00{$perm}{$perm}", 8);
×
500

501
            return self::chmod($filename, $perm, $add);
×
502
        }
503

504
        // Set the world writable bit (file isn't owned or grouped by us)
505
        $add = \intval("000{$perm}", 8);
×
506

507
        return self::chmod($filename, $perm, $add);
×
508
    }
509

510
    /**
511
     * Chmod alias.
512
     */
513
    private static function chmod(string $filename, int $perm, int $add): bool
514
    {
515
        return \chmod($filename, (\fileperms($filename) | \intval('0' . $perm . $perm . $perm, 8)) ^ $add);
18✔
516
    }
517
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc