twistor/stream-util
Lightweight PHP helper for working with streams. Copy/clone streams, inspect size and metadata, check readability/writability/seekability/appendability, and safely rewind/seek. Includes mode and URI utilities for fopen-compatible streams.
Installation:
composer require twistor/stream-util
Add to composer.json under require or via Laravel's require command:
php artisan vendor:publish --provider="Twistor\StreamUtil\StreamUtilServiceProvider"
First Use Case: Check stream capabilities in a Laravel controller or service:
use Twistor\StreamUtil;
$stream = fopen(storage_path('app/test.txt'), 'r+');
$isReadable = StreamUtil::isReadable($stream); // true
$size = StreamUtil::getSize($stream); // Returns file size in bytes
fclose($stream);
Key Classes:
StreamUtil (main facade)\Twistor\StreamUtil\StreamUtil class (for direct usage)// Validate stream capabilities before operations
if (StreamUtil::isReadable($stream) && StreamUtil::isWritable($stream)) {
$size = StreamUtil::getSize($stream);
$meta = StreamUtil::getMetaDataKey($stream, 'blocked');
}
// Clone a stream (useful for parallel processing)
$original = fopen('file.txt', 'r');
$cloned = StreamUtil::copy($original, false); // false = don't close original
// Check file modes before operations
if (StreamUtil::modeIsReadable('r+')) {
// Safe to read
}
// Use in Laravel Filesystem
use Illuminate\Support\Facades\Storage;
$stream = Storage::disk('local')->readStream('file.txt');
$isSeekable = StreamUtil::isSeekable($stream);
// Create and validate temp streams
$temp = fopen('php://temp', 'w+b');
if (StreamUtil::isWritable($temp)) {
fwrite($temp, 'data');
StreamUtil::tryRewind($temp);
}
// Check if a URI is usable (e.g., for fopen)
$uri = 's3://bucket/file.txt';
$usableUri = StreamUtil::getUsableUri($uri); // Returns false if invalid
// Process files in batches with stream validation
foreach ($files as $file) {
$stream = fopen($file, 'r');
if (StreamUtil::isReadable($stream) && StreamUtil::getSize($stream) > 0) {
// Process stream
}
fclose($stream);
}
Extend the package for Laravel-specific needs:
// app/Extensions/StreamUtil.php
namespace App\Extensions;
use Twistor\StreamUtil;
class LaravelStreamUtil extends StreamUtil
{
public static function isLaravelStorageStream($stream)
{
return is_resource($stream) && strpos(StreamUtil::getUri($stream), 'laravel-storage:') === 0;
}
}
Resource Leaks:
StreamUtil::copy($stream, true) closes the original stream. Use cautiously in loops or pipelines.fclose() or let Laravel's Storage handle cleanup.False Positives in Mode Checks:
modeIsReadOnly('r+') returns false (correct), but modeIsReadable('r+') returns true. Double-check logic.Non-Seekable Streams:
trySeek() may fail on non-seekable streams (e.g., php://stdin). Handle exceptions:
try {
StreamUtil::trySeek($stream, 0, SEEK_SET);
} catch (\RuntimeException $e) {
// Fallback logic
}
Metadata Limitations:
getMetaDataKey() may return null for unsupported stream types (e.g., php://memory).URI Handling:
getUsableUri() returns false for non-fopen-compatible URIs (e.g., php://temp). Validate before use.Stream State Inspection:
$stream = fopen('file.txt', 'r');
debug([
'readable' => StreamUtil::isReadable($stream),
'writable' => StreamUtil::isWritable($stream),
'seekable' => StreamUtil::isSeekable($stream),
'uri' => StreamUtil::getUri($stream),
]);
Common Errors:
StreamUtil::isSeekable() first.is_resource($stream).Performance:
getSize() calls on large streams. Cache results if needed.Custom Stream Types:
Override StreamUtil to support Laravel-specific streams:
class CustomStreamUtil extends \Twistor\StreamUtil\StreamUtil
{
public static function isLaravelStream($stream)
{
return strpos(self::getUri($stream), 'laravel-storage:') !== false;
}
}
Event-Based Validation: Hook into Laravel events to validate streams pre-operation:
// In EventServiceProvider
public function boot()
{
Storage::preventAccess(function ($path) {
$stream = fopen($path, 'r');
return !StreamUtil::isReadable($stream);
});
}
Testing:
Mock StreamUtil in PHPUnit:
$mockStream = $this->getMockBuilder(\stdClass::class)
->disableOriginalConstructor()
->getMock();
StreamUtil::shouldReceive('isReadable')
->with($mockStream)
->andReturn(true);
Storage Facade Integration:
Storage::readStream()/writeStream() with StreamUtil for metadata checks:
$stream = Storage::disk('s3')->readStream('file.txt');
if (StreamUtil::isReadable($stream)) {
// Process
}
Storage::disk('s3')->close($stream);
Filesystem Events:
Combine with Laravel's filesystem events for stream-aware logic:
Storage::disk('local')->addListener('reading', function ($event) {
if (!StreamUtil::isReadable($event->stream)) {
Log::warning('Unreadable stream accessed');
}
});
Queue Jobs: Validate streams in job payloads:
public function handle()
{
$stream = fopen($this->streamPath, 'r');
if (!StreamUtil::isReadable($stream)) {
throw new \RuntimeException('Invalid stream');
}
// Process
}
How can I help you explore Laravel packages today?