FileTrait   A
last analyzed

Complexity

Total Complexity 8

Size/Duplication

Total Lines 52
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
wmc 8
eloc 18
c 2
b 0
f 1
dl 0
loc 52
rs 10

1 Method

Rating   Name   Duplication   Size   Complexity  
B readFileStream() 0 40 8
1
<?php
2
declare(strict_types=1);
3
4
namespace BEdita\ImportTools\Utility;
5
6
use BEdita\Core\Filesystem\FilesystemRegistry;
7
use RuntimeException;
8
use Throwable;
9
10
/**
11
 * Utilities to help reading files from either the local filesystem or an adapter configured in BEdita.
12
 *
13
 * This provides `readFileStream` method to open "read-only" file stream (you can use local filesystem or adapter).
14
 *
15
 * Usage example:
16
 * ```php
17
 * use BEdita\ImportTools\Utility\FileTrait;
18
 *
19
 * class MyImporter
20
 * {
21
 *     use FileTrait;
22
 *
23
 *     public function read(string $file): void
24
 *     {
25
 *         [$fh, $close] = $this->readFileStream($path);
26
 *
27
 *         try {
28
 *             flock($fh, LOCK_SH);
29
 *             // do your stuff
30
 *         } finally {
31
 *             $close();
32
 *         }
33
 *     }
34
 * }
35
 * ```
36
 */
37
trait FileTrait
38
{
39
    /**
40
     * Open read-only file stream. Possible sources are:
41
     *  - `-` for STDIN
42
     *  - local paths
43
     *  - any URL supported by a registered PHP stream wrapper — notably, remote URLs via HTTP(S)
44
     *  - (if `bedita/core` is available) any mountpoint registered in `FilesystemRegistry`
45
     *
46
     * @param string $path Path to open file from.
47
     * @return array{resource, callable(): void}
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{resource, callable(): void} at position 2 could not be parsed: Expected ':' at position 2, but found 'resource'.
Loading history...
48
     */
49
    protected static function readFileStream(string $path): array
50
    {
51
        /**
52
         * Create a function to close the requested resource.
53
         *
54
         * @param resource|null $fh Resource to be closed.
55
         * @return callable(): void Function to be used for closing the resource.
56
         */
57
        $closerFactory = fn($resource): callable => function () use ($resource): void {
58
            if (is_resource($resource)) {
59
                fclose($resource);
60
            }
61
        };
62
63
        if ($path === '-') {
64
            return [STDIN, $closerFactory(null)]; // We don't really want to close STDIN.
65
        }
66
67
        if (!str_contains($path, '://') || in_array(explode('://', $path, 2)[0], stream_get_wrappers(), true)) {
68
            try {
69
                $fh = fopen($path, 'rb');
70
                if ($fh === false) {
71
                    throw new RuntimeException(sprintf('fopen(%s): failed to open stream', $path));
72
                }
73
            } catch (Throwable $previous) {
74
                throw new RuntimeException(sprintf('Cannot open file: %s', $path), 0, $previous);
75
            }
76
77
            return [$fh, $closerFactory($fh)];
78
        }
79
80
        // @codeCoverageIgnoreStart
81
        if (!class_exists(FilesystemRegistry::class)) {
82
            throw new RuntimeException(sprintf('Unsupported stream wrapper protocol: %s', $path));
83
        }
84
        // @codeCoverageIgnoreEnd
85
86
        $fh = FilesystemRegistry::getMountManager()->readStream($path);
87
88
        return [$fh, $closerFactory($fh)];
89
    }
90
}
91