A lightweight, high-performance filesystem discovery component for the LiquidRazor ecosystem.
This library provides a deterministic, memory-efficient, and extensible way to locate PHP files across a project using convention-first defaults with optional project-level overrides.
It is the first step in the LiquidRazor pipeline:
filesystem → files → classes → descriptors → DI registry
- Fast, iterator-based filesystem traversal with low memory use
- Convention-first defaults for
include/,lib/, andsrc/ - YAML-based configuration with project overrides
- Clean separation of concerns with no DI, reflection, or parsing
- Safe traversal with configurable symlink, hidden file, and unreadable path handling
- No framework or third-party runtime dependencies
Out of the box, the discovery system scans:
include/lib/src/
With automatic exclusions:
vendor/node_modules/.git/.idea/.vscode/var/cache/
Only .php files are considered.
The library ships with an internal configuration:
resources/config/roots.yaml
This defines the baseline discovery rules.
You can override or extend discovery behavior by adding:
config/roots.yaml
or
config/roots.yml
discovery:
defaults:
follow_symlinks: false
include_hidden: false
on_unreadable: skip
extensions: [php]
exclude:
- vendor
- node_modules
- .git
roots:
include:
enabled: true
path: include
recursive: true
extensions: [php]
exclude: []
lib:
enabled: true
path: lib
recursive: true
extensions: [php]
exclude: []
src:
enabled: true
path: src
recursive: true
extensions: [php]
exclude: []discovery:
roots:
modules:
path: modules
recursive: trueGlobal exclusions:
discovery:
exclude:
- storage/tmpRoot-specific exclusions:
discovery:
roots:
src:
exclude:
- src/Legacy
- src/Experimentaldiscovery:
roots:
lib:
enabled: falseConfiguration is built as:
library defaults + project overrides → final discovery config
Rules:
- Scalars → overridden by project config
- Lists (e.g.
exclude) → merged and deduplicated - Roots → merged by key
- Root options → overridden per root
- Disabled roots → removed from traversal
The system is intentionally split into layers:
YamlDiscoveryConfigLoaderDiscoveryConfigMergerDiscoveryConfigValidatorDiscoveryConfig
FileLocator- streaming traversal using generators
- zero accumulation by default
- Class discovery (static parsing)
- DI registry loading
use LiquidRazor\FileLocator\Config\DiscoveryConfigFactory;
use LiquidRazor\FileLocator\FileLocator;
$factory = new DiscoveryConfigFactory();
$config = $factory->create(__DIR__);
$locator = new FileLocator($config);
foreach ($locator->locate() as $file) {
// process file
}The locator yields files lazily, ensuring minimal memory usage.
- Depth-first traversal
- Early directory pruning
- Generator-based streaming
- No reflection or file inclusion
- No unnecessary allocations
- Unreadable paths: configurable (
skiporfail) - Symlinks: disabled by default
- Hidden files: excluded by default
- Deterministic behavior (no implicit magic)
- Convention over configuration (but never forced)
- Explicit over implicit
- Small, composable components
- No framework lock-in
- Predictable behavior under load
This component is the foundation of the DI pipeline:
FileLocator
↓
ClassLocator (future)
↓
RegistryLoader (future)
↓
DIRegistry
- Discovery flow documentation
- Architecture overview documentation
- Configuration system documentation
- File locator API documentation
- YAML loading strategy documentation
- Development setup documentation
- Implementation guidelines documentation
- Testing strategy documentation
- Supported extension points documentation
MIT