alexskrypnyk / file
Provides file manipulations functionality.
Fund package maintenance!
alexskrypnyk
Patreon
Installs: 27 333
Dependents: 1
Suggesters: 0
Security: 0
Stars: 1
Watchers: 1
Forks: 0
pkg:composer/alexskrypnyk/file
Requires
- php: >=8.2
- symfony/filesystem: ^7.2
Requires (Dev)
- alexskrypnyk/phpunit-helpers: ^0.15.0
- dealerdirect/phpcodesniffer-composer-installer: ^1
- drevops/phpcs-standard: ^0.6
- drupal/coder: ^8.3
- ergebnis/composer-normalize: ^2.42
- phpbench/phpbench: ^1.4
- phpstan/phpstan: ^2
- phpunit/phpunit: ^11
- rector/rector: ^2
Suggests
- phpunit/phpunit: Required for using FileAssertionsTrait and DirectoryAssertionsTrait (^11)
This package is auto-updated.
Last update: 2025-12-23 00:38:31 UTC
README
Utilities to work with files and directories
Table of Contents
Installation
composer require alexskrypnyk/file
Usage
This 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(); }
Available Methods
| 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. |
Batch Operations
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. |
Performance Benefits
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
Usage Example
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)
Architecture
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.
Content Replacement
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.
Basic Usage
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);
Version Patterns Preset
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__" |
Exclusions
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'
Custom Replacements
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']);
Assertion Traits
The library includes PHPUnit traits for testing files and directories:
Directory Assertions Trait
| 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'); } }
Ignoring Paths in Directory Assertions
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
File Assertions Trait
| 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'); } }
Maintenance
composer install composer lint composer test # Run performance benchmarks (PHPBench) composer benchmark
This repository was created using the Scaffold project template
