Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
14 / 14
CRAP
100.00% covered (success)
100.00%
1 / 1
Filesystem
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
14 / 14
16
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 readFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 dumpFile
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 copy
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 chmod
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 remove
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 symlink
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 readlink
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAbsolutePath
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 mkdir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 makePathRelative
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getBasename
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDirectory
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * Fast Forward Development Tools for PHP projects.
7 *
8 * This file is part of fast-forward/dev-tools project.
9 *
10 * @author   Felipe SayĆ£o Lobato Abreu <github@mentordosnerds.com>
11 * @license  https://opensource.org/licenses/MIT MIT License
12 *
13 * @see      https://github.com/php-fast-forward/
14 * @see      https://github.com/php-fast-forward/dev-tools
15 * @see      https://github.com/php-fast-forward/dev-tools/issues
16 * @see      https://php-fast-forward.github.io/dev-tools/
17 * @see      https://datatracker.ietf.org/doc/html/rfc2119
18 */
19
20namespace FastForward\DevTools\Filesystem;
21
22use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
23use Symfony\Component\Filesystem\Path;
24
25use function Safe\getcwd;
26
27/**
28 * Concrete implementation of the standard filesystem interface.
29 *
30 * This class wraps over the Symfony Filesystem component, automatically
31 * converting provided paths to absolute representations when a base path is supplied or
32 * dynamically inferred from the generic working directory.
33 */
34  class Filesystem implements FilesystemInterface
35{
36    /**
37     * @param SymfonyFilesystem $filesystem
38     */
39    public function __construct(
40        private SymfonyFilesystem $filesystem = new SymfonyFilesystem(),
41    ) {}
42
43    /**
44     * Checks whether a file or directory exists.
45     *
46     * @param iterable<string>|string $files the file(s) or directory(ies) to check
47     * @param string|null $basePath the base path used to resolve relative paths
48     *
49     * @return bool true if the path exists, false otherwise
50     */
51    public function exists(string|iterable $files, ?string $basePath = null): bool
52    {
53        return $this->filesystem->exists($this->getAbsolutePath($files, $basePath));
54    }
55
56    /**
57     * Reads the entire content of a file.
58     *
59     * @param string $filename the target filename to read
60     * @param string|null $path the optional base path to resolve the filename against
61     *
62     * @return string the content of the file
63     */
64    public function readFile(string $filename, ?string $path = null): string
65    {
66        return $this->filesystem->readFile($this->getAbsolutePath($filename, $path));
67    }
68
69    /**
70     * Writes content to a file, overriding it if it already exists.
71     *
72     * @param string $filename the filename to write to
73     * @param mixed $content the content to write
74     * @param string|null $path the optional base path to resolve the filename against
75     */
76    public function dumpFile(string $filename, mixed $content, ?string $path = null): void
77    {
78        $this->filesystem->dumpFile($this->getAbsolutePath($filename, $path), $content);
79    }
80
81    /**
82     * Copies a file to a target path.
83     *
84     * @param string $originFile the source file path to copy
85     * @param string $targetFile the target file path to create
86     * @param bool $overwriteNewerFiles whether newer target files MAY be overwritten
87     */
88    public function copy(string $originFile, string $targetFile, bool $overwriteNewerFiles = false): void
89    {
90        $this->filesystem->copy(
91            $this->getAbsolutePath($originFile),
92            $this->getAbsolutePath($targetFile),
93            $overwriteNewerFiles
94        );
95    }
96
97    /**
98     * Changes the permission mode for one or more files.
99     *
100     * @param iterable<string>|string $files the target file paths
101     * @param int $mode the permission mode to apply
102     * @param int $umask the umask to apply
103     * @param bool $recursive whether permissions SHOULD be applied recursively
104     */
105    public function chmod(string|iterable $files, int $mode, int $umask = 0o000, bool $recursive = false): void
106    {
107        $this->filesystem->chmod($this->getAbsolutePath($files), $mode, $umask, $recursive);
108    }
109
110    /**
111     * Removes files, symbolic links, or directories.
112     *
113     * @param iterable<string>|string $files the file(s), link(s), or directory(ies) to remove
114     */
115    public function remove(string|iterable $files): void
116    {
117        $this->filesystem->remove($this->getAbsolutePath($files));
118    }
119
120    /**
121     * Creates a symbolic link.
122     *
123     * @param string $originDir the origin path the link MUST point to
124     * @param string $targetDir the link path to create
125     * @param bool $copyOnWindows whether directories SHOULD be copied on Windows instead of linked
126     */
127    public function symlink(string $originDir, string $targetDir, bool $copyOnWindows = false): void
128    {
129        $this->filesystem->symlink($originDir, $this->getAbsolutePath($targetDir), $copyOnWindows);
130    }
131
132    /**
133     * Reads a symbolic link target.
134     *
135     * @param string $path the symbolic link path
136     * @param bool $canonicalize whether the returned path SHOULD be canonicalized
137     *
138     * @return string|null the link target, or null when the path is not a symbolic link
139     */
140    public function readlink(string $path, bool $canonicalize = false): ?string
141    {
142        return $this->filesystem->readlink($this->getAbsolutePath($path), $canonicalize);
143    }
144
145    /**
146     * Resolves a path or iterable of paths into their absolute path representation.
147     *
148     * @param iterable<string>|string $files the path(s) to resolve
149     * @param string|null $basePath the base path for relative path resolution
150     *
151     * @return iterable<string>|string the resolved absolute path(s)
152     */
153    public function getAbsolutePath(string|iterable $files, ?string $basePath = null): string|iterable
154    {
155        $basePath ??= getcwd();
156
157        if (! Path::isAbsolute($basePath)) {
158            $basePath = Path::makeAbsolute($basePath, getcwd());
159        }
160
161        if (\is_string($files)) {
162            return Path::makeAbsolute($files, $basePath);
163        }
164
165        return array_map(static fn(string $file): string => Path::makeAbsolute($file, $basePath), $files);
166    }
167
168    /**
169     * Creates a directory recursively.
170     *
171     * @param iterable<string>|string $dirs the directory path(s) to create
172     * @param int $mode the permissions mode (defaults to 0777)
173     */
174    public function mkdir(string|iterable $dirs, int $mode = 0o777): void
175    {
176        $this->filesystem->mkdir($this->getAbsolutePath($dirs), $mode);
177    }
178
179    /**
180     * Computes the relative path from the base path to the target path.
181     *
182     * @param string $path the target absolute or relative path
183     * @param string|null $basePath the origin point; defaults to the current working directory
184     *
185     * @return string the computed relative path
186     */
187    public function makePathRelative(string $path, ?string $basePath = null): string
188    {
189        return $this->filesystem->makePathRelative($this->getAbsolutePath($path, $basePath), $basePath ?? getcwd());
190    }
191
192    /**
193     * Returns the trailing name component of a path.
194     *
195     * @param string $path the path to process
196     * @param string $suffix an optional suffix to strip from the returned basename
197     *
198     * @return string the base name of the given path
199     */
200    public function getBasename(string $path, string $suffix = ''): string
201    {
202        return basename($path, $suffix);
203    }
204
205    /**
206     * Returns a parent directory's path.
207     *
208     * @param string $path the path to evaluate
209     * @param int $levels the number of parent directories to go up
210     *
211     * @return string the parent path name
212     */
213    public function getDirectory(string $path, int $levels = 1): string
214    {
215        return \dirname($path, $levels);
216    }
217}