bornfight/transfer-object-converter
Installation:
composer require bornfight/transfer-object-converter
Register the Service Provider (if not auto-discovered):
Add to config/app.php under providers:
Bornfight\TransferObjectConverter\TransferObjectConverterServiceProvider::class,
Define a Transfer Object (DTO): Create a class with public properties matching incoming request data:
namespace App\DTO;
class UserCreateRequest
{
public string $name;
public string $email;
public int $age;
}
First Use Case: In a controller, inject the converter and populate the DTO:
use Bornfight\TransferObjectConverter\TransferObjectConverter;
public function store(TransferObjectConverter $converter)
{
$dto = new UserCreateRequest();
$converter->convert($dto); // Populates $dto with request data
// Use $dto->name, $dto->email, etc.
}
src/TransferObjectConverter.php (Core logic)tests/ (Usage examples and edge cases)Define DTOs: Use classes with public properties to mirror request payloads.
class LoginRequest {
public string $username;
public string $password;
public bool $rememberMe = false;
}
Controller Integration:
public function login(TransferObjectConverter $converter)
{
$request = new LoginRequest();
$converter->convert($request);
// Validate or process $request
}
Nested Objects: Support nested DTOs by ensuring properties are objects:
class UserProfileRequest {
public string $bio;
public Address $address; // Another DTO
}
Combine with Laravel Validation:
$converter->convert($dto);
$validator = Validator::make($dto, [
'email' => 'required|email',
'age' => 'integer|min:18',
]);
Leverage PHP 7.4+ typed properties:
class OrderRequest {
public string $productId;
public float $quantity;
public array $options = [];
}
Skip properties by setting them to null or using convert() with a whitelist:
$converter->convert($dto, ['name', 'email']); // Only populate these fields
Convert API responses back to DTOs:
$responseDto = new ApiResponse();
$converter->convert($responseDto, $apiResponseData);
Extend FormRequest and use the converter:
class StoreUserRequest extends FormRequest {
public function rules() { ... }
public function prepareForConversion()
{
$this->merge([
'age' => (int) $this->age,
]);
}
}
Create middleware to auto-convert DTOs:
public function handle($request, Closure $next)
{
if ($request->has('dto_class')) {
$dto = new $request->dto_class();
$converter->convert($dto);
$request->merge((array) $dto);
}
return $next($request);
}
Property Naming Mismatches:
user_name vs. userName).convert() with a custom mapping:
$converter->convert($dto, [], [
'user_name' => 'userName',
]);
Non-Public Properties:
Circular References:
User has Address, Address has User) will cause infinite loops.Type Coercion Issues:
age=abc will set $dto->age = "abc".$dto->age = (int) $request->input('age');
Overwriting Existing Data:
convert() overwrites existing property values. Use with caution in partial updates.$dto->email = $existingEmail; // Preserve existing
$converter->convert($dto, ['name', 'age']);
Large Payloads:
xdebug.max_nesting_level or flatten the DTO structure.Enable Debug Mode:
Set TRANSFER_OBJECT_CONVERTER_DEBUG=true in .env to log conversion issues.
Check Request Data: Dump the raw request to verify payload structure:
dd($request->all());
Validate DTO Structure:
Use get_object_vars() to inspect populated DTOs:
dd(get_object_vars($dto));
Test Edge Cases:
$converter->convert($dto, [])null.json_decode($request->getContent(), true) first.Service Provider Auto-Discovery:
Binding Custom Converters:
Bind a custom converter in AppServiceProvider:
$this->app->bind(TransferObjectConverter::class, function ($app) {
return new CustomTransferObjectConverter();
});
Environment Variables:
config():
$converter->setStrictMode(config('transfer_object_converter.strict', false));
Custom Converters:
Extend TransferObjectConverter to add logic:
class CustomConverter extends TransferObjectConverter {
protected function convertProperty($dto, $property, $value) {
if ($property === 'age') {
return (int) $value;
}
return parent::convertProperty($dto, $property, $value);
}
}
Pre/Post Conversion Hooks: Use events or middleware to modify DTOs before/after conversion:
// Example: Sanitize input
$converter->convert($dto);
$dto->email = filter_var($dto->email, FILTER_SANITIZE_EMAIL);
Support for Arrays: Extend to handle array-shaped DTOs:
class ArrayDto {
public array $items = [];
}
Integration with API Platform: Use for serializing/deserializing API resources:
$converter->convert($resource, $apiData);
Avoid Deep Nesting: Flatten complex DTOs to reduce recursion overhead.
Reuse DTO Instances: Instantiate DTOs once and reuse them (e.g., in a service layer).
Cache Converters: For high-traffic APIs, cache converter instances:
$converter = app(TransferObjectConverter::class);
Cache::remember('converter', 60, fn() => $converter);
Use convert() Selectively:
Pass only the needed properties to avoid unnecessary processing:
$converter->convert($dto, ['email', 'name']); // Instead of all properties
How can I help you explore Laravel packages today?