respect/stringifier
Respect\Stringifier converts any PHP value into a readable string for logging, debugging, and error messages. Use the stringify() function or the HandlerStringifier object. Secure-by-default handling of strings in newer versions. Requires PHP 8.3+.
Install via Composer:
composer require respect/stringifier
First Use Case: Replace ad-hoc string casting in Laravel logs or debugging:
use function Respect\Stringifier\stringify;
// Before: (string)$request->input('data')
// After:
Log::debug('Request data:', stringify($request->input('data')));
// For objects/arrays:
Log::debug('User:', stringify($user));
Key Starting Points:
stringify($value) for quick conversions.HandlerStringifier for customization (e.g., HandlerStringifier::create()).toString() in models or use a macro for Eloquent collections:
use Respect\Stringifier\stringify;
Collection::macro('toString', function () {
return stringify($this->all());
});
Logging and Debugging:
// Monolog handler
$handler = new \Monolog\Handler\StreamHandler(storage_path('logs/debug.log'));
$handler->pushProcessor(function ($record) {
$record['context']['data'] = stringify($record['context']['data'] ?? null);
return $record;
});
// Artisan commands
$this->info('Processing: ' . stringify($this->argument('data')));
API Responses:
// Custom JSON response formatter
Response::macro('stringify', function ($data) {
return response()->json(['data' => stringify($data)]);
});
// Usage:
return response()->stringify($complexObject);
Validation Error Messages:
// FormRequest validation
public function failedValidation(Validator $validator) {
$errors = stringify($validator->errors()->toArray());
return response()->json(['errors' => $errors], 422);
}
Caching Keys:
// Dynamic cache keys for complex objects
$cacheKey = 'user_' . stringify($user->id . $user->role);
Cache::put($cacheKey, $user);
Eloquent Model Stringification:
// Override toString() in a model
public function toString(): string {
return stringify([
'id' => $this->id,
'name' => $this->name,
'created_at' => $this->created_at,
]);
}
Service Container Binding:
// Bind a custom stringifier for specific use cases
$app->bind('custom.stringifier', function () {
$handler = HandlerStringifier::create();
$handler->addStringifier(new class implements Handler {
public function handle(mixed $raw, int $depth): ?string {
if ($raw instanceof YourCustomClass) {
return 'CustomClass{' . $raw->id . '}';
}
return null;
}
});
return $handler;
});
Exception Handling:
// Custom exception formatter
App::error(function (Throwable $e, $request) {
return response()->json([
'error' => stringify($e),
'trace' => stringify($e->getTrace()),
]);
});
Testing:
// Assert stringified output in tests
$this->assertEquals(
'User{id:1,name:"John"}',
stringify(new User(['id' => 1, 'name' => 'John']))
);
Version 2.0 Security Risks:
v1.x or disable sensitive detection:
$stringifier = HandlerStringifier::create();
$stringifier->setOptions(['detect_classes' => false]);
Performance with Deep Objects:
$stringifier = HandlerStringifier::create();
$stringifier->setMaxDepth(5); // Default is 10
Resource Leaks:
tmpfile()) may not close them properly.get_resource_type() or custom handlers.Closure/Callable Output:
$handler = CompositeHandler::create();
$handler->removeStringifier(CallableStringifier::class);
False Positives in is_callable:
['foo', 'bar'] may be misinterpreted as callables.v1.x or whitelist safe arrays.Inspect Handlers:
$stringifier = HandlerStringifier::create();
print_r($stringifier->getHandlers());
Custom Fallback:
DumpStringifier with a custom fallback for unsupported types:
$stringifier = new HandlerStringifier(
CompositeHandler::create(),
new class implements Stringify {
public function stringify(mixed $raw, int $depth): string {
return '[Unsupported: ' . gettype($raw) . ']';
}
}
);
Logging Stringifier Output:
$stringifier = HandlerStringifier::create();
$stringifier->addStringifier(new class implements Handler {
public function handle(mixed $raw, int $depth): ?string {
Log::debug('Stringifying:', ['value' => $raw, 'depth' => $depth]);
return null; // Let other handlers process it
}
});
Custom Handlers:
Carbon\Carbon):
$handler = new class implements Handler {
public function handle(mixed $raw, int $depth): ?string {
if ($raw instanceof Carbon\Carbon) {
return $raw->format('Y-m-d H:i:s');
}
return null;
}
};
$stringifier->addStringifier($handler);
Pre/Post Processing:
$stringifier = new class($delegate) implements Stringify {
public function __construct(private Stringify $delegate) {}
public function stringify(mixed $raw, int $depth): string {
$result = $this->delegate->stringify($raw, $depth);
return '[Sanitized] ' . str_replace(['{', '}'], '', $result);
}
};
Conditional Stringification:
$handler = new class implements Handler {
public function handle(mixed $raw, int $depth): ?string {
if ($raw instanceof SensitiveData) {
return '[REDACTED]';
}
return null;
}
};
Eloquent Relationships:
N+1 queries.$user = User::with('posts')->find(1);
stringify($user); // Safe
Collection Macros:
Collection::macro('toString'), ensure it doesn’t conflict with existing macros:
if (!Collection::hasMacro('toString')) {
Collection::macro('toString', function () {
return stringify($this->all());
});
}
Service Provider Boot:
public function boot() {
$app->singleton('stringifier', function () {
return HandlerStringifier::create();
});
}
Testing:
$this->app->instance('stringifier', Mockery::mock(Stringify::class));
How can I help you explore Laravel packages today?