composer require alexskrypnyk/fileThis library provides a comprehensive set of utility methods for file and directory operations, including high-performance batch operations for processing multiple files efficiently.
All methods are available through the AlexSkrypnyk\File\File class.
use AlexSkrypnyk\File\ContentFile\ContentFile;
use AlexSkrypnyk\File\Exception\FileException;
use AlexSkrypnyk\File\File;
try {
// Get current working directory
$cwd = File::cwd();
// Copy a directory recursively
File::copy('/path/to/source', '/path/to/destination');
// Check if a file contains a string
if (File::contains('/path/to/file.txt', 'search term')) {
// Do something
}
// Process string content directly
$content = File::read('/path/to/file.txt');
$processed = File::replaceContent($content, 'old', 'new');
$processed = File::removeToken($processed, '# BEGIN', '# END');
File::dump('/path/to/file.txt', $processed);
// Append content to an existing file
File::append('/path/to/log.txt', "\nNew log entry: " . date('Y-m-d H:i:s'));
// Or use batch operations for better performance
File::addDirectoryTask(function(ContentFile $file_info): ContentFile {
$content = File::replaceContent($file_info->getContent(), 'old', 'new');
$file_info->setContent($content);
return $file_info;
});
File::runDirectoryTasks('/path/to/directory');
} catch (FileException $exception) {
// Handle any file operation errors
echo $exception->getMessage();
}| Method | Description |
|---|---|
absolute() |
Get absolute path for provided absolute or relative file. |
append() |
Append content to an existing file. |
collapseEmptyLines() |
Remove multiple consecutive empty lines from a string. |
collapseEmptyLinesInFile() |
Remove multiple consecutive empty lines from a file. |
collapseEmptyLinesInDir() |
Remove multiple consecutive empty lines from all files in a directory. |
contains() |
Check if file contains a specific string or matches a pattern. |
copy() |
Copy file or directory. |
copyIfExists() |
Copy file or directory if it exists. |
cwd() |
Get current working directory with absolute path. |
dir() |
Get absolute path for existing directory. |
dirIsEmpty() |
Check if directory is empty. |
dump() |
Write content to a file. |
exists() |
Check if file or directory exists. |
findContainingInDir() |
Find all files in directory containing a specific string. |
findMatchingPath() |
Find first path that matches a needle among provided paths. |
mkdir() |
Creates a directory if it doesn't exist. |
read() |
Read file contents. |
realpath() |
Replacement for PHP's realpath resolves non-existing paths. |
remove() |
Remove file or directory. |
removeLine() |
Remove lines containing a specific string from a string. |
removeLineInFile() |
Remove lines containing a specific string from a file. |
removeLineInDir() |
Remove lines containing a specific string from all files in a directory. |
removeToken() |
Remove tokens and optionally content between tokens from a string. |
removeTokenInFile() |
Remove tokens and optionally content between tokens from a file. |
removeTokenInDir() |
Remove tokens and optionally content between tokens from all files in a directory. |
renameInDir() |
Rename files in directory by replacing part of the filename. |
replaceContent() |
Replace content in a string. |
replaceContentInFile() |
Replace content in a file. |
replaceContentInDir() |
Replace content in all files in a directory. |
rmdir() |
Remove directory recursively. |
rmdirIfEmpty() |
Remove directory recursively if empty. |
scandir() |
Recursively scan directory for files. |
tmpdir() |
Create temporary directory. |
For improved performance when processing multiple files, the library provides batch operations that minimize directory scans and optimize I/O operations:
| Method | Description |
|---|---|
addDirectoryTask() |
Add a batch task to be executed on all files in a directory. |
runDirectoryTasks() |
Execute all queued tasks on files in a directory with optimized I/O. |
clearDirectoryTasks() |
Clear all queued batch tasks. |
The batch operations provide significant performance improvements over traditional file operations:
- Single directory scan: Instead of scanning the directory multiple times
- Single I/O per file: Each file is read once, processed by all tasks, then written once
- Memory efficient: Uses generators to handle large file sets without loading everything into memory
use AlexSkrypnyk\File\ContentFile\ContentFile;
use AlexSkrypnyk\File\File;
// Traditional approach (slow for multiple operations)
File::replaceContentInDir('/path/to/dir', 'old1', 'new1');
File::replaceContentInDir('/path/to/dir', 'old2', 'new2');
File::removeTokenInDir('/path/to/dir', '# token');
// Batch approach: significantly faster because while tasks are added first,
// the directory is scanned only once and each file is read/written only once.
File::addDirectoryTask(function(ContentFile $file_info): ContentFile {
$content = File::replaceContent($file_info->getContent(), 'old1', 'new1');
$content = File::replaceContent($content, 'old2', 'new2');
$content = File::removeToken($content, '# token');
$content = File::collapseEmptyLines($content);
$file_info->setContent($content);
return $file_info;
});
File::runDirectoryTasks('/path/to/dir');use AlexSkrypnyk\File\ContentFile\ContentFile;
use AlexSkrypnyk\File\File;
use AlexSkrypnyk\File\Replacer\Replacement;
use AlexSkrypnyk\File\Replacer\Replacer;
// Batch approach with a custom Replacer for complex replacements.
File::addDirectoryTask(function(ContentFile $file_info): ContentFile {
$content = $file_info->getContent();
Replacer::create()
->addReplacement(Replacement::create('version', '/v\d+\.\d+\.\d+/', '__VERSION__'))
->addReplacement(Replacement::create('year', '/20\d{2}/', '__YEAR__'))
->replace($content);
$file_info->setContent($content);
return $file_info;
});
File::runDirectoryTasks('/path/to/dir');Performance Results: In tests with 5,000 files across 100 directories performing 10 operations per file:
- Traditional approach: ~16s (multiple directory scans, multiple I/O per file)
- Batch approach: ~1.7s (~89% faster, single directory scan, single I/O per file)
The batch operations are powered by an internal Tasker queue management system
that:
- Uses PHP generators for memory-efficient processing of large file sets
- Implements a two-way communication pattern between the queue and file processors
- Leverages
ContentFileobjects for file content manipulation - Provides type-safe object validation to ensure data integrity
- Maintains complete separation between the generic queue system and file-specific operations
This architecture allows the library to scale efficiently from small single-file operations to large-scale batch processing scenarios.
The Replacer class provides pattern-based content replacement in files and
directories. It's particularly useful for normalizing volatile content like
version numbers, hashes, and timestamps.
use AlexSkrypnyk\File\Replacer\Replacement;
use AlexSkrypnyk\File\Replacer\Replacer;
// Use preset version patterns
$replacer = Replacer::create()->addVersionReplacements();
$replacer->replaceInDir($directory);
// Or create custom replacer
$replacer = Replacer::create()
->addReplacement(Replacement::create('version', '/v\d+\.\d+\.\d+/', '__VERSION__'))
->addReplacement(Replacement::create('date', '/\d{4}-\d{2}-\d{2}/', '__DATE__'));
// Apply to string content
$content = 'Version: v1.2.3';
$replacer->replace($content); // $content is now 'Version: __VERSION__'
// Apply to directory
$replacer->replaceInDir($directory);The addVersionReplacements() method adds common patterns for normalizing
volatile content:
| Pattern | Example Input | Output |
|---|---|---|
| Semver versions | 1.2.3, v1.2.3-beta.1 |
__VERSION__ |
| Git hashes | @abc123... (40 chars) |
@__HASH__ |
| SRI integrity hashes | sha512-... |
__INTEGRITY__ |
| Docker image tags | nginx:1.21.0 |
nginx:__VERSION__ |
| GitHub Actions versions | actions/checkout@v4 |
actions/checkout@__VERSION__ |
| Package versions in JSON | "^1.2.3" |
"__VERSION__" |
Add exclusions to prevent specific matches from being replaced:
$replacer = Replacer::create()
->addVersionReplacements()
->setMaxReplacements(0)
->addExclusions(['/^0\.0\./'], 'semver'); // Don't replace 0.0.x versions
// Or add exclusions to all rules
$replacer->addExclusions(['127.0.0.1']); // Don't replace IP addresses
// Exclusions can be:
// - Regex patterns: '/^0\./'
// - Exact strings: '127.0.0.1'
// - Callbacks: fn(string $match): bool => $match === '9.9.9'Create custom replacement patterns:
$replacer = Replacer::create()
->addReplacement(Replacement::create('build', '/BUILD-\d+/', '__BUILD__'))
->addReplacement(Replacement::create('timestamp', '/\d{10}/', '__TIMESTAMP__'))
->setMaxReplacements(0); // 0 = unlimited replacements
$replacer->replaceInDir($directory, ['/path/to/ignore']);The library includes PHPUnit traits for testing files and directories:
| Assertion Method | Description |
|---|---|
assertDirectoryContainsString() |
Assert that a directory contains files with a specific string. |
assertDirectoryNotContainsString() |
Assert that a directory does not contain files with a specific string. |
assertDirectoryContainsWord() |
Assert that a directory contains files with a specific word (bounded by word boundaries). |
assertDirectoryNotContainsWord() |
Assert that a directory does not contain files with a specific word. |
Usage example:
use PHPUnit\Framework\TestCase;
use AlexSkrypnyk\File\Testing\DirectoryAssertionsTrait;
class MyTest extends TestCase {
use DirectoryAssertionsTrait;
public function testDirectories(): void {
// Assert directory contains "example" string in at least one file
$this->assertDirectoryContainsString('/path/to/directory', 'example');
// Assert directory contains "example" string, ignoring specific files
$this->assertDirectoryContainsString('/path/to/directory', 'example', ['temp.log', 'cache']);
// Assert directory does not contain specific word
$this->assertDirectoryNotContainsWord('/path/to/directory', 'forbidden');
}
}The directory assertion methods support ignoring specific paths during searches. You can ignore paths in two ways:
- Per-method ignoring: Pass an
$ignoredarray parameter to individual assertion methods - Global ignoring: Override the
ignoredPaths()method in your test class
class MyTest extends TestCase {
use DirectoryAssertionsTrait;
// Global ignored paths for all directory assertions in this test class
public static function ignoredPaths(): array {
return ['.git', 'node_modules', 'vendor', 'temp/cache'];
}
public function testWithIgnoredPaths(): void {
// This will ignore both global ignored paths AND 'logs' directory
$this->assertDirectoryContainsString('/path/to/dir', 'search_term', ['logs']);
// Global ignored paths are automatically applied to all directory assertions
$this->assertDirectoryNotContainsWord('/path/to/dir', 'forbidden');
}
}Important Notes:
- Ignored paths are literal subpaths (not wildcard patterns)
- Global
ignoredPaths()and per-method$ignoredparameters are merged together - Both file names and directory paths can be ignored
- Ignored paths are relative to the directory being searched
| Assertion Method | Description |
|---|---|
assertFileContainsString() |
Assert that a file contains a specific string. |
assertFileNotContainsString() |
Assert that a file does not contain a specific string. |
assertFileContainsWord() |
Assert that a file contains a specific word (bounded by word boundaries). |
assertFileNotContainsWord() |
Assert that a file does not contain a specific word. |
assertFileEqualsFile() |
Assert that a file equals another file in contents. |
assertFileNotEqualsFile() |
Assert that a file does not equal another file in contents. |
assertFilesExist() |
Assert that multiple files exist in a directory. |
assertFilesDoNotExist() |
Assert that multiple files do not exist in a directory. |
assertFilesWildcardExists() |
Assert that files matching wildcard pattern(s) exist. |
assertFilesWildcardDoNotExist() |
Assert that files matching wildcard pattern(s) do not exist. |
Usage example:
use PHPUnit\Framework\TestCase;
use AlexSkrypnyk\File\Testing\FileAssertionsTrait;
class MyTest extends TestCase {
use FileAssertionsTrait;
public function testFiles(): void {
// Assert file contains "example" string
$this->assertFileContainsString('/path/to/file.txt', 'example');
// Assert file contains "test" as a complete word
$this->assertFileContainsWord('/path/to/file.txt', 'test');
// Assert file does not contain a partial word
$this->assertFileNotContainsWord('/path/to/file.txt', 'exampl');
// Assert two files have identical content
$this->assertFileEqualsFile('/path/to/expected.txt', '/path/to/actual.txt');
// Assert two files have different content
$this->assertFileNotEqualsFile('/path/to/expected.txt', '/path/to/actual.txt');
// Assert that multiple files exist in a directory
$this->assertFilesExist('/path/to/directory', ['file1.txt', 'file2.txt']);
// Assert that multiple files do not exist in a directory
$this->assertFilesDoNotExist('/path/to/directory', ['file1.txt', 'file2.txt']);
// Assert that files matching wildcard pattern(s) exist
$this->assertFilesWildcardExists('*.txt');
$this->assertFilesWildcardExists(['*.txt', '*.json']);
// Assert that files matching wildcard pattern(s) do not exist
$this->assertFilesWildcardDoNotExist('*.log');
$this->assertFilesWildcardDoNotExist(['*.tmp', '*.cache']);
// All assertion methods support optional custom failure messages
$this->assertFileContainsString('/path/to/file.txt', 'example', 'Custom failure message');
$this->assertFilesExist('/path/to/directory', ['file1.txt'], 'Files should exist');
$this->assertDirectoryContainsString('/path/to/dir', 'search_term', [], 'Custom message');
}
}composer install
composer lint
composer test
# Run performance benchmarks (PHPBench)
composer benchmarkThis repository was created using the Scaffold project template
