Skip to content

Commit 324b2b8

Browse files
committed
refactor: implement Filesystem abstraction and integrate with console commands for improved path and file handling
1 parent 4f5a641 commit 324b2b8

4 files changed

Lines changed: 324 additions & 0 deletions

File tree

src/Filesystem/Filesystem.php

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
<?php
2+
3+
namespace FastForward\DevTools\Filesystem;
4+
5+
use Override;
6+
use function Safe\getcwd;
7+
use Symfony\Component\Filesystem\Filesystem as SymfonyFilesystem;
8+
use Symfony\Component\Filesystem\Path;
9+
10+
final class Filesystem extends SymfonyFilesystem implements FilesystemInterface
11+
{
12+
/**
13+
* @param string|iterable $files
14+
* @param string|null $basePath
15+
*
16+
* @return bool
17+
*/
18+
#[Override]
19+
public function exists(string|iterable $files, ?string $basePath = null): bool
20+
{
21+
return parent::exists($this->getAbsolutePath($files, $basePath));
22+
}
23+
24+
/**
25+
* @param string $filename
26+
* @param string|null $path
27+
*
28+
* @return string
29+
*/
30+
#[Override]
31+
public function readFile(string $filename, ?string $path = null): string
32+
{
33+
return parent::readFile($this->getAbsolutePath($filename, $path));
34+
}
35+
36+
/**
37+
* @param string $filename
38+
* @param mixed $content
39+
* @param string|null $path
40+
*
41+
* @return void
42+
*/
43+
#[Override]
44+
public function dumpFile(string $filename, $content, ?string $path = null): void
45+
{
46+
parent::dumpFile($this->getAbsolutePath($filename, $path), $content);
47+
}
48+
49+
/**
50+
* @param string|iterable $files
51+
* @param string|null $basePath
52+
*
53+
* @return string|iterable
54+
*/
55+
public function getAbsolutePath(string|iterable $files, ?string $basePath = null): string|iterable
56+
{
57+
$basePath ??= getcwd();
58+
59+
if (! $this->isAbsolutePath($basePath)) {
60+
$basePath = Path::makeAbsolute($basePath, getcwd());
61+
}
62+
63+
if (is_string($files)) {
64+
return Path::makeAbsolute($files, $basePath);
65+
}
66+
67+
return array_map(static fn (string $file): string => Path::makeAbsolute($file, $basePath), $files);
68+
}
69+
70+
/**
71+
* @param string|iterable $dirs
72+
* @param int $mode
73+
* @param string|null $basePath
74+
*
75+
* @return void
76+
*/
77+
#[Override]
78+
public function mkdir(string|iterable $dirs, int $mode = 0777, ?string $basePath = null): void
79+
{
80+
parent::mkdir($this->getAbsolutePath($dirs, $basePath), $mode);
81+
}
82+
83+
/**
84+
* @param string $path
85+
* @param string|null $basePath
86+
*
87+
* @return string
88+
*/
89+
#[Override]
90+
public function makePathRelative(string $path, ?string $basePath = null): string
91+
{
92+
return parent::makePathRelative(
93+
$this->getAbsolutePath($path, $basePath),
94+
$basePath ?? getcwd(),
95+
);
96+
}
97+
98+
/**
99+
* @param string $path
100+
* @param string $suffix
101+
*
102+
* @return string
103+
*/
104+
public function basename(string $path, string $suffix = ''): string
105+
{
106+
return \basename($path, $suffix);
107+
}
108+
109+
/**
110+
* @param string $path
111+
* @param int $levels
112+
*
113+
* @return string
114+
*/
115+
public function dirname(string $path, int $levels = 1): string
116+
{
117+
return \dirname($path, $levels);
118+
}
119+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace FastForward\DevTools\Filesystem;
6+
7+
interface FilesystemInterface
8+
{
9+
/**
10+
* @param string|iterable $files
11+
* @param string|null $basePath
12+
*
13+
* @return bool
14+
*/
15+
public function exists(string|iterable $files, ?string $basePath = null): bool;
16+
17+
/**
18+
* @param string $filename
19+
* @param string|null $path
20+
*
21+
* @return string
22+
*/
23+
public function readFile(string $filename, ?string $path = null): string;
24+
25+
/**
26+
* @param string $filename
27+
* @param mixed $content
28+
* @param string|null $path
29+
*
30+
* @return void
31+
*/
32+
public function dumpFile(string $filename, $content, ?string $path = null): void;
33+
34+
/**
35+
* @param string|iterable $files
36+
* @param string|null $basePath
37+
*
38+
* @return string|iterable
39+
*/
40+
public function getAbsolutePath(string|iterable $files, ?string $basePath = null): string|iterable;
41+
42+
/**
43+
* @param string|iterable $dirs
44+
* @param int $mode
45+
* @param string|null $basePath
46+
*
47+
* @return void
48+
*/
49+
public function mkdir(string|iterable $dirs, int $mode = 0777, ?string $basePath = null): void;
50+
51+
/**
52+
* @param string $path
53+
* @param string|null $basePath
54+
*
55+
* @return string
56+
*/
57+
public function makePathRelative(string $path, ?string $basePath = null): string;
58+
59+
/**
60+
* @param string $path
61+
* @param string $suffix
62+
*
63+
* @return string
64+
*/
65+
public function basename(string $path, string $suffix = ''): string;
66+
67+
/**
68+
* @param string $path
69+
* @param int $levels
70+
*
71+
* @return string
72+
*/
73+
public function dirname(string $path, int $levels = 1): string;
74+
}

src/ServiceProvider/DevToolsServiceProvider.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
namespace FastForward\DevTools\ServiceProvider;
2020

21+
use FastForward\DevTools\Filesystem\FilesystemInterface;
2122
use Interop\Container\ServiceProviderInterface;
2223
use FastForward\DevTools\Psr\Clock\SystemClock;
2324
use FastForward\DevTools\Composer\Capability\DevToolsCommandProvider;
@@ -45,6 +46,7 @@
4546
use FastForward\DevTools\License\TemplateLoader;
4647
use Composer\Plugin\Capability\CommandProvider;
4748
use FastForward\DevTools\Console\CommandLoader\DevToolsCommandLoader;
49+
use FastForward\DevTools\Filesystem\Filesystem;
4850
use FastForward\DevTools\GitAttributes\Merger as GitAttributesMerger;
4951
use FastForward\DevTools\GitAttributes\MergerInterface as GitAttributesMergerInterface;
5052
use FastForward\DevTools\GitAttributes\Reader as GitAttributesReader;
@@ -87,6 +89,9 @@ public function getFactories(): array
8789
ProcessBuilderInterface::class => get(ProcessBuilder::class),
8890
ProcessQueueInterface::class => get(ProcessQueue::class),
8991

92+
// Filesystem
93+
FilesystemInterface::class => get(Filesystem::class),
94+
9095
// Symfony Components
9196
Finder::class => create(Finder::class),
9297
FileLocatorInterface::class => create(FileLocator::class)->constructor([getcwd(), \dirname(__DIR__, 2)]),
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* This file is part of fast-forward/dev-tools.
7+
*
8+
* This source file is subject to the license bundled
9+
* with this source code in the file LICENSE.
10+
*
11+
* @copyright Copyright (c) 2026 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
12+
* @license https://opensource.org/licenses/MIT MIT License
13+
*
14+
* @see https://github.com/php-fast-forward/dev-tools
15+
* @see https://github.com/php-fast-forward
16+
* @see https://datatracker.ietf.org/doc/html/rfc2119
17+
*/
18+
19+
namespace FastForward\DevTools\Tests\Filesystem;
20+
21+
use FastForward\DevTools\Filesystem\Filesystem;
22+
use PHPUnit\Framework\Attributes\CoversClass;
23+
use PHPUnit\Framework\Attributes\Test;
24+
use PHPUnit\Framework\TestCase;
25+
use Symfony\Component\Filesystem\Path;
26+
use function Safe\getcwd;
27+
28+
#[CoversClass(Filesystem::class)]
29+
final class FilesystemTest extends TestCase
30+
{
31+
private Filesystem $filesystem;
32+
33+
private string $tempDir;
34+
35+
protected function setUp(): void
36+
{
37+
$this->filesystem = new Filesystem();
38+
$this->tempDir = sys_get_temp_dir() . '/' . uniqid('ff_dev_tools_', true);
39+
$this->filesystem->mkdir($this->tempDir);
40+
}
41+
42+
protected function tearDown(): void
43+
{
44+
$this->filesystem->remove($this->tempDir);
45+
}
46+
47+
#[Test]
48+
public function getAbsolutePathWillReturnAbsoluteForRelativePath(): void
49+
{
50+
$expected = Path::makeAbsolute('test/file.php', getcwd());
51+
52+
self::assertSame($expected, $this->filesystem->getAbsolutePath('test/file.php'));
53+
}
54+
55+
#[Test]
56+
public function getAbsolutePathWillReturnAbsoluteForMultipleRelativePaths(): void
57+
{
58+
$expected = [
59+
Path::makeAbsolute('test1.php', getcwd()),
60+
Path::makeAbsolute('test2.php', getcwd()),
61+
];
62+
63+
// Ensure returning array has matching elements
64+
$result = $this->filesystem->getAbsolutePath(['test1.php', 'test2.php']);
65+
66+
self::assertEquals($expected, $result);
67+
}
68+
69+
#[Test]
70+
public function getAbsolutePathWillUseProvidedBasePath(): void
71+
{
72+
$basePath = '/var/www';
73+
$expected = Path::makeAbsolute('test.php', $basePath);
74+
75+
self::assertSame($expected, $this->filesystem->getAbsolutePath('test.php', $basePath));
76+
}
77+
78+
#[Test]
79+
public function basenameWillReturnCorrectBasename(): void
80+
{
81+
self::assertSame('file', $this->filesystem->basename('/path/to/file.txt', '.txt'));
82+
self::assertSame('file.txt', $this->filesystem->basename('/path/to/file.txt'));
83+
}
84+
85+
#[Test]
86+
public function dirnameWillReturnCorrectDirname(): void
87+
{
88+
self::assertSame('/path/to', $this->filesystem->dirname('/path/to/file.txt'));
89+
self::assertSame('/path', $this->filesystem->dirname('/path/to/file.txt', 2));
90+
}
91+
92+
#[Test]
93+
public function makePathRelativeWillReturnRelativePathAgainstBase(): void
94+
{
95+
$path = '/var/www/project/src/file.php';
96+
$basePath = '/var/www/project';
97+
98+
$relative = $this->filesystem->makePathRelative($path, $basePath);
99+
100+
// Symfony makePathRelative usually returns trailing slash for directories, but not required for files
101+
self::assertStringStartsWith('src/file.php', $relative);
102+
}
103+
104+
#[Test]
105+
public function dumpFileAndReadFileWillWorkWithRelativePaths(): void
106+
{
107+
$filename = 'test_file.txt';
108+
$content = 'hello world';
109+
110+
$this->filesystem->dumpFile($filename, $content, $this->tempDir);
111+
112+
self::assertTrue($this->filesystem->exists($filename, $this->tempDir));
113+
self::assertSame($content, $this->filesystem->readFile($filename, $this->tempDir));
114+
}
115+
116+
#[Test]
117+
public function mkdirWillCreateDirectoryWithRelativePath(): void
118+
{
119+
$dirName = 'nested/dir';
120+
121+
$this->filesystem->mkdir($dirName, 0777, $this->tempDir);
122+
123+
self::assertTrue($this->filesystem->exists($dirName, $this->tempDir));
124+
self::assertTrue(is_dir($this->tempDir . '/' . $dirName));
125+
}
126+
}

0 commit comments

Comments
 (0)