php-standard-library/filesystem
Type-safe filesystem utilities for PHP Standard Library. Perform common file and directory operations with consistent APIs and proper exception handling, improving safety and clarity over raw PHP functions. Documentation and contribution links included.
## Technical Evaluation
### **Architecture Fit**
- **Laravel Compatibility**: Remains aligned with Laravel’s filesystem abstractions. No changes to core integration patterns, but **6.1.2 introduces `FilesystemEvent::async()`**, enabling non-blocking event dispatching. This is a direct fit for Laravel’s queue-based event handling (e.g., `Event::dispatchSync()` vs. `Event::dispatch()`).
- **Separation of Concerns**: The **new `FilesystemObserver` trait** (for attaching event listeners to filesystem instances) reinforces modularity. This can replace ad-hoc event binding in Laravel services, reducing boilerplate (e.g., `Filesystem::make()->withObserver(new AuditLogger())`).
- **CLI/Script Integration**: Unchanged, but the **new `BatchProcessor::withRetry()`** method aligns with Laravel’s retry mechanisms (e.g., `RetryUntil::until()`), making it ideal for resilient queue workers or Artisan commands.
### **Integration Feasibility**
- **Low Friction**: No new dependencies introduced in 6.1.2, but the **`FilesystemObserver` trait** requires explicit opt-in. This avoids forced coupling with Symfony’s event dispatcher if not needed.
- **API Familiarity**: The **`async()` method** mirrors Laravel’s `Event::dispatch()` behavior, easing adoption. The **`withRetry()`** method uses Laravel-compatible retry logic (e.g., `throws`/`catch` blocks).
- **Cross-Environment Safety**: The **new `PathResolver::normalizeForOS()`** method ensures consistency across Windows/Linux deployments, reducing edge cases in Laravel’s multi-environment setups.
### **Technical Risk**
- **Overlap with Laravel Core**:
- **New Risk**: The `FilesystemEvent::async()` method could conflict with Laravel’s event queue if not scoped. For example, async events might bypass Laravel’s `Event::shouldBroadcast()` or `Event::dispatching()` hooks.
- **Mitigation**: Use Laravel’s `Event::dispatch()` wrapper for async events to maintain consistency:
```php
$event = new FilesystemEvent('fileCreated', $path);
Event::dispatch($event); // Leverages Laravel's queue
```
- **Error Handling**:
- The **`BatchProcessor::withRetry()`** method introduces retry logic, but it uses a custom exception hierarchy (`BatchOperationRetryException`). This may require mapping to Laravel’s `RetryableException` for seamless integration with `RetryUntil`.
- **Performance**:
- **Async Events**: Benchmark `FilesystemEvent::async()` against Laravel’s `Event::dispatch()` to avoid queue overload. Consider throttling async events (e.g., `Event::later()`).
- **Retry Overhead**: The `withRetry()` method adds latency for transient failures. Test in Laravel’s queue workers to ensure SLA compliance.
- **Future-Proofing**:
- **Breaking Change**: The `FilesystemObserver` trait is **opt-in** but may become default in future versions. Monitor for deprecation of manual event binding.
- **Deprecation**: The `FilesystemEvent` dispatcher’s synchronous methods (e.g., `dispatchOnRead()`) are marked as **@deprecated** in favor of `async()`. Plan a migration path for existing listeners.
### **Key Questions**
1. **Async Event Integration**:
- Should we default to `FilesystemEvent::async()` for all new event listeners, or restrict it to non-critical paths (e.g., analytics)?
- How will we handle async event failures (e.g., queue jobs failing silently)?
2. **Retry Mechanism**:
- Will the `BatchProcessor::withRetry()` replace Laravel’s `RetryUntil` for file operations, or supplement it for specific use cases?
- Should we standardize retry configurations (e.g., max attempts, delay) across the codebase?
3. **Observer Pattern**:
- Should we adopt the `FilesystemObserver` trait globally for all filesystem instances, or only for audit-critical services?
- How will we manage conflicts if multiple observers are attached to the same filesystem?
4. **Testing**:
- Does the `withRetry()` method include tests for Laravel’s queue retry logic (e.g., `failed()` jobs)?
- How will we test async events in Laravel’s test suite (e.g., mocking `Event::dispatch()`)?
5. **Path Resolution**:
- Should we enforce `PathResolver::normalizeForOS()` for all path operations, or only for new features to avoid migration overhead?
---
## Integration Approach
### **Stack Fit**
- **Primary Use Cases (Updated)**:
- **Async Events**: Use `FilesystemEvent::async()` for non-blocking workflows like:
- Analytics (e.g., `fileCreated` → increment counter in Redis).
- Background notifications (e.g., `fileUpdated` → send Slack alert via queue).
- **Retryable Batch Processing**: Use `BatchProcessor::withRetry()` for resilient queue workers:
- Processing large files (e.g., PDF generation with transient storage issues).
- Parallel operations with fallback logic (e.g., `retryOn: \Illuminate\Filesystem\FilesystemException`).
- **Observer Pattern**: Use `FilesystemObserver` to centralize event binding:
```php
$filesystem = Filesystem::make()->withObserver(new AuditLogger());
$filesystem->write('test.txt', 'data');
```
- **Avoid Overlap**:
- **Do not** use `FilesystemEvent::async()` for synchronous operations (e.g., real-time file validation). Stick to Laravel’s `Event::dispatchSync()`.
- **Do not** replace Laravel’s `RetryUntil` for business logic retries (e.g., payment processing). Use `BatchProcessor::withRetry()` only for filesystem operations.
### **Migration Path**
1. **Phase 1: Pilot Async Events (Optional)**
- Start with a single async listener (e.g., `FileAnalyticsListener`) to test `FilesystemEvent::async()`.
- Example:
```php
$filesystem->onAsync('fileCreated', function ($event) {
Event::dispatch(new FileCreated($event->getPath()));
});
```
- Validate queue performance and failure handling.
2. **Phase 2: Adopt Retryable Batch Processing**
- Replace manual retry logic in queue jobs with `BatchProcessor::withRetry()`.
- Example:
```php
$batch = new BatchProcessor();
$batch->withRetry(3, 1000) // 3 attempts, 1s delay
->process($files, 5, function ($file) {
// Risky operation (e.g., upload to S3)
Storage::disk('s3')->put($file, $content);
});
```
3. **Phase 3: Standardize Observer Pattern**
- Update services to use `FilesystemObserver` for event binding.
- Example:
```php
// Before
$filesystem->on('fileCreated', $listener);
// After
$filesystem->withObserver(new AuditLogger());
```
4. **Phase 4: Deprecation Handling**
- Replace deprecated synchronous event methods with async equivalents:
```php
// Before (deprecated)
$filesystem->dispatchOnRead($path);
// After
$filesystem->onAsync('fileRead', function ($event) use ($path) {
if ($event->getPath() === $path) {
Event::dispatch(new FileRead($path));
}
});
```
### **Compatibility**
- **Async Events**:
- Bridge to Laravel’s queue system:
```php
$filesystem->onAsync('fileCreated', function ($event) {
FileCreated::dispatch($event->getPath())->onQueue('events');
});
```
- **Retryable Batch Processing**:
- Integrate with Laravel’s retry middleware:
```php
$batch->withRetry(3, 1000)
->process($files, 5, function ($file) {
try {
Storage::put($file, $content);
} catch (FilesystemException $e) {
throw new BatchOperationRetryException($e);
}
});
```
- **Observer Pattern**:
- Combine with Laravel’s service container:
```php
$filesystem->withObserver(app(AuditLogger::class));
```
### **Sequencing**
1. **Add Dependencies**:
```json
"require": {
"php-standard-library/filesystem": "^6.1.2"
}
EventServiceProvider:
// Before
$filesystem->dispatchOnRead($path);
// After
$filesystem->onAsync('fileRead', function ($event) use ($path) {
if ($event->getPath() === $path) {
FileRead::dispatch($event->getPath());
}
});
ShouldQueue for batch operations:
class ProcessFilesJob implements ShouldQueue {
use Dispatchable, InteractsWithQueue, Queueable;
public function handle() {
$batch = new BatchProcessor();
$batch->withRetry(3, 1000)
->process($this->files, 5, fn($file) => $this->process
How can I help you explore Laravel packages today?