symfony/finder
Symfony Finder offers an intuitive fluent interface for locating files and directories by name, size, modification time, and content. Chain filters, sort results, and iterate with ease using this battle-tested, standalone PHP component trusted across the Symfony ecosystem.
Installation:
composer require symfony/finder
No additional configuration is required—it’s a standalone component.
If you are already on 8.1.x, pull in the latest patch release to pick up two fixes that affect day-to-day usage (ignoreVCSIgnored() with .git-as-a-file repos, and glob patterns with consecutive *):
composer update symfony/finder # resolves to >= 8.1.8
Both fixes are non-breaking—no code changes required.
First Use Case: Locate all PHP files in a directory and its subdirectories:
use Symfony\Component\Finder\Finder;
$finder = Finder::create()
->files()
->in(__DIR__ . '/src')
->name('*.php');
foreach ($finder as $file) {
echo $file->getRealPath() . "\n";
}
Where to Look First:
// Find all files in a directory (non-recursive)
$finder = Finder::create()
->files()
->in('/path/to/dir');
// Recursive search with glob patterns
$finder = Finder::create()
->files()
->in('/path/to/dir')
->name('*.{php,js}');
// Files modified in the last 7 days
$finder = Finder::create()
->files()
->in('/logs')
->date('>1 week ago');
// Files larger than 1MB
$finder = Finder::create()
->files()
->in('/uploads')
->size('>1M');
// Exclude 'vendor' and 'node_modules'
$finder = Finder::create()
->files()
->in(__DIR__)
->exclude([
'vendor',
'node_modules',
'*.min.js'
]);
.gitignore (VCS-aware traversal)// Skip anything ignored by git — useful for "scan the project" commands
$finder = Finder::create()
->files()
->in(base_path())
->ignoreVCSIgnored(true)
->name('*.php');
// Keep git metadata itself out of results
$finder->ignoreVCS(true);
As of v8.1.8, repositories where .git is a file rather than a directory
(git worktrees, submodule checkouts, git submodule absorbgitdirs) are
correctly treated as repository roots, so ignoreVCSIgnored() returns the
expected file set instead of either ignoring nothing or over-ignoring.
// Sort by modification time (newest first)
$finder = Finder::create()
->files()
->in('/assets')
->sortByModifiedTime('DESC');
// Use with Laravel's Storage facade (for local files)
use Illuminate\Support\Facades\Storage;
$finder = Finder::create()
->files()
->in(storage_path('app/public'))
->name('*.jpg');
foreach ($finder as $file) {
$url = Storage::url($file->getRelativePathname());
// Process URL...
}
// Process files in chunks (memory-efficient for large directories)
$finder = Finder::create()->files()->in('/large/dir');
foreach ($finder as $file) {
// Process $file (e.g., resize images, parse logs)
}
// Get an array of paths (for batch operations)
$paths = iterator_to_array($finder, false);
use Symfony\Component\Finder\Finder;
use Illuminate\Console\Command;
class OptimizeImagesCommand extends Command
{
protected $signature = 'images:optimize';
protected $description = 'Optimize all images in the uploads directory';
public function handle()
{
$finder = Finder::create()
->files()
->in(public_path('uploads'))
->name('*.{jpg,png}');
foreach ($finder as $file) {
$this->info("Optimizing: {$file->getRelativePathname()}");
// Use Intervention Image or similar library
}
}
}
// Register a custom Finder service
public function register()
{
$this->app->singleton('finder', function () {
return Finder::create()
->ignoreDotFiles(false) // Include hidden files
->ignoreUnreadableDirs(true); // Skip permission-denied dirs
});
}
// Trigger actions when files match criteria
$finder = Finder::create()
->files()
->in(storage_path('logs'))
->date('>1 month ago');
foreach ($finder as $file) {
event(new LogFileFound($file));
}
// Mock Finder in unit tests
$finder = $this->createMock(Finder::class);
$finder->method('getIterator')->willReturn(new ArrayIterator([
new SplFileInfo('/fake/path/file1.txt'),
new SplFileInfo('/fake/path/file2.txt'),
]));
If your command relies on ignoreVCSIgnored() and CI uses shallow or
worktree-style checkouts, pin symfony/finder to ^8.1.8 and assert the
discovered file count in a test—that is exactly the scenario the .git-file
fix targets.
Path Separators:
base_path()/realpath() over manual concatenation:
$finder->in(realpath(__DIR__ . '/src'));
Hidden Files/Directories:
.). Explicitly include them if needed:
$finder->ignoreDotFiles(false);
.git Can Be a File:
.git is a plain file, not a directory.
Before v8.1.8, ignoreVCSIgnored() could mis-identify the repository
root in that layout. If your ignore-aware scans behave differently
depending on checkout style, upgrade to >= 8.1.8 rather than
special-casing it.Globs with Consecutive Stars:
**/*.php or src/**/**/*.php could previously degrade into
quadratic backtracking on pathological directory trees (v8.1.8 fixes
this). Practical guidance still applies: prefer one ** per pattern and
use ->path()/->notPath() for structural filters rather than stacking
stars.Performance with Large Directories:
foreach ($finder as $file) { ... } // Better than $finder->getIterator()->toArray()
Symlinks:
$finder->followLinks(); // opt-in
Case Sensitivity:
$finder->name('/\.php$/i'); // Case-insensitive regex
Empty Results:
Finder::append() can fail silently if iterators are empty. Validate inputs:
$finder = Finder::create()->files()->in('/nonexistent');
if ($finder->count() === 0) {
$this->error('No files found!');
}
Inspect the Iterator:
$files = iterator_to_array($finder, false);
dd($files);
Check Paths:
getRealPath() to verify absolute paths:
$file->getRealPath(); // Resolves symlinks and relative paths
Verify What ignoreVCSIgnored() Skipped:
.gitignore
rules removed—the fastest way to confirm a repository-root detection issue:
$all = Finder::create()->files()->in($path);
$kept = Finder::create()->files()->in($path)->ignoreVCSIgnored(true);
// compare counts / relative pathnames
Date Comparisons:
now() or DateTime for dynamic comparisons:
$finder->date('>' . now()->subDays(7)->format('Y-m-d'));
Size Units:
B, K, M, G for sizes (e.g., >1M for 1MB). Avoid raw bytes unless necessary.Custom Comparators:
Symfony\Component\Finder\Comparator\ComparatorInterface for custom logic (e.g., MIME-type filtering).Event Dispatching:
FileFound, SearchComplete):
class EventfulFinder
{
public function find()
{
$finder = Finder::create()->files()->in($this->path);
foreach ($finder as $file) {
event(new FileFound($file));
}
}
}
Laravel Facade:
// app/Facades/Finder.php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class Finder extends Facade { protected static function getFacadeAccessor() { return 'finder'; } }
Register in AppServiceProvider:
$this->app->bind('finder', function () {
return Finder::create()->ignoreDotFiles(false);
});
Caching Results:
$files = Cache::remember('finder_results', now()->addHours(1), function () {
return iterator_to_array(Finder::create()->files()->in('/data'));
});
vendor, .git) in recursive searches. ignoreVCS(true) is the default; ignoreVCSIgnored() is opt-in and now repo-root aware for .git-as-a-file checkouts.composer update.->limit($n) to cap results:
$finder->limit(1000); // Process only the first 1000 files
How can I help you explore Laravel packages today?