php-standard-library/io
Handle-based I/O abstractions for PHP: composable, testable streams and readers/writers designed to be async-ready. Part of PHP Standard Library, with docs and contribution links available via php-standard-library.dev.
Installation
composer require php-standard-library/io
For Laravel projects, ensure compatibility with your PHP version (8.1+) and Laravel version (10+ for async features).
First Use Case: Async File Processing
use PhpStandardLibrary\IO\FileHandle;
use PhpStandardLibrary\IO\FileReader;
use PhpStandardLibrary\IO\Async\AsyncReader;
// Sync read
$handle = new FileHandle(storage_path('app/example.txt'));
$reader = new FileReader($handle);
$content = $reader->read();
// Async read (requires async PHP environment like Swoole)
$asyncHandle = new AsyncHandle($handle);
$asyncReader = new AsyncReader($asyncHandle);
$content = yield $asyncReader->read();
Where to Look First
PhpStandardLibrary\IO\HandleInterface and PhpStandardLibrary\IO\ResourceInterface for Laravel service binding.tests/ for async workflows (e.g., AsyncReaderTest).Service Container Binding
Register handles as Laravel services in AppServiceProvider:
use PhpStandardLibrary\IO\FileHandle;
use Illuminate\Support\ServiceProvider;
public function register()
{
$this->app->bind(
\PhpStandardLibrary\IO\HandleInterface::class,
function () {
return new FileHandle(storage_path('app'));
}
);
}
Async Job Integration
Use IO handles in queued jobs for non-blocking file/network operations:
use PhpStandardLibrary\IO\Async\AsyncWriter;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
class ProcessLargeFile implements ShouldQueue
{
use Queueable, SerializesModels;
public function handle()
{
$handle = new AsyncHandle(new FileHandle(storage_path('large_file.csv')));
$writer = new AsyncWriter($handle);
yield $writer->write('data...'); // Async write
}
}
Composable Middleware for HTTP
Chain IO readers/writers with Laravel’s HTTP middleware:
use PhpStandardLibrary\IO\HttpClient;
use PhpStandardLibrary\IO\Reader;
$client = new HttpClient();
$response = $client->request('GET', 'https://api.example.com/data');
$reader = new Reader($response->getBody());
$data = $reader->read();
Testing with Mock Handles
Replace Laravel’s Storage mocks with IO handles in tests:
use PhpStandardLibrary\IO\HandleInterface;
use PhpStandardLibrary\IO\MockHandle;
public function testFileUpload()
{
$mockHandle = new MockHandle();
$mockHandle->expects('write')->with('test');
$this->app->instance(HandleInterface::class, $mockHandle);
// Test your upload logic here
}
Laravel Filesystem Adapter
Create a bridge between IO and Laravel’s Storage facade:
use PhpStandardLibrary\IO\HandleInterface;
use Illuminate\Contracts\Filesystem\Filesystem;
class LaravelIOAdapter implements HandleInterface
{
public function __construct(private Filesystem $filesystem) {}
public function write(string $path, string $contents): void
{
$this->filesystem->put($path, $contents);
}
// Implement other HandleInterface methods...
}
Event-Driven I/O
Use IO handles with Laravel events for reactive workflows:
use PhpStandardLibrary\IO\EventedHandle;
use Illuminate\Support\Facades\Event;
$handle = new EventedHandle(new FileHandle('log.txt'));
$handle->on('open', fn() => Event::dispatch('file.opened'));
$handle->open();
Dependency Injection
Prefer constructor injection for IO handles in Laravel services:
use PhpStandardLibrary\IO\HandleInterface;
class FileProcessor
{
public function __construct(private HandleInterface $handle) {}
public function process()
{
$this->handle->write('processed data');
}
}
Async Environment Requirements
AsyncReader, AsyncWriter) require PHP extensions like Swoole or RoadRunner.swoole in php.ini or RoadRunner as a process manager).Resource Leaks
illuminate/support traits:
use PhpStandardLibrary\IO\HandleInterface;
use Illuminate\Support\Traits\Macroable;
class FileService
{
public function __construct(private HandleInterface $handle) {}
public function __destruct()
{
$this->handle->close();
}
}
Blocking Calls in Async Code
IO operations can cause deadlocks.Laravel Facade Conflicts
IO handles alongside Laravel’s Storage facade may lead to confusion.IO for async-heavy services, Storage for simple file ops).Testing Async Code
IO operations require special test setups (e.g., Swoole test environments).phpunit/swoole-extension or mock async handles:
$mockAsyncHandle = new MockAsyncHandle();
$mockAsyncHandle->expects('read')->willReturn('mocked data');
Handle State Inspection
Use var_dump($handle->getMetadata()) to debug handle states (e.g., open/closed, position).
Async Debugging Enable Swoole’s debug mode for async operations:
swoole_set_process_name('laravel:async-io');
Laravel Log Integration
Wrap IO operations in Laravel’s logging:
use Illuminate\Support\Facades\Log;
try {
$handle->write($data);
} catch (\Exception $e) {
Log::error('IO Error', ['exception' => $e]);
throw $e;
}
Custom Handle Types
Extend HandleInterface for domain-specific handles (e.g., DatabaseHandle):
use PhpStandardLibrary\IO\HandleInterface;
class DatabaseHandle implements HandleInterface
{
public function write(string $query): void
{
DB::table('logs')->insert(['query' => $query]);
}
// Implement other methods...
}
Laravel-Specific Extensions
Add Laravel-specific methods to IO classes via traits:
use Illuminate\Support\Facades\Cache;
trait LaravelCacheHandle
{
public function cache(string $key, mixed $data): void
{
Cache::put($key, $data);
}
}
Async Event Dispatch
Integrate IO events with Laravel’s event system:
use PhpStandardLibrary\IO\EventedHandle;
use Illuminate\Support\Facades\Event;
$handle = new EventedHandle(new FileHandle('app.log'));
$handle->on('write', fn($data) => Event::dispatch('log.written', $data));
Handle Timeouts
Configure default timeouts for async handles in config/app.php:
'io' => [
'async_timeout' => 30, // seconds
],
Then apply in your handle:
$handle = new AsyncHandle($fileHandle, config('app.io.async_timeout'));
Laravel Cache Integration
Use Laravel’s cache for IO metadata (e.g., file hashes):
use Illuminate\Support\Facades\Cache;
$handle = new FileHandle('app.log');
$hash = Cache::remember("io.handle.{$handle->getPath()}.hash", 60, fn() => md5($handle->read()));
Service Provider Bootstrapping
Ensure IO handles are bound before Laravel’s bootstrapping:
How can I help you explore Laravel packages today?