digital-craftsman/deserializing-connection
Installation:
composer require digital-craftsman/deserializing-connection:0.7.*
Pin to 0.7.* to avoid breaking changes.
Register the Bundle (Laravel-specific adaptation):
Add to config/app.php under providers:
DigitalCraftsman\DeserializingConnection\DeserializingConnectionServiceProvider::class,
First Use Case:
Define a DTO (e.g., UserDto):
namespace App\Dto;
use DigitalCraftsman\DeserializingConnection\Dto\Dto;
class UserDto implements Dto
{
public function __construct(
public string $name,
public int $age,
) {}
}
Query with DTO conversion:
use DigitalCraftsman\DeserializingConnection\QueryBuilder;
$users = QueryBuilder::for(UserDto::class)
->from('users')
->where('active', true)
->get();
DTO-First Queries:
Use QueryBuilder::for(DtoClass::class) to map database rows to DTOs directly. Avoid manual hydration:
$orders = QueryBuilder::for(OrderDto::class)
->select(['id', 'user_id', 'amount'])
->from('orders')
->where('status', 'completed')
->orderBy('created_at', 'desc')
->get();
Complex Joins:
Handle nested DTOs with with():
QueryBuilder::for(UserWithPostsDto::class)
->from('users')
->with('posts', PostDto::class)
->get();
Custom Mappings:
Override default column-to-property mapping via annotations or map():
QueryBuilder::for(UserDto::class)
->from('users')
->map(['full_name' => 'name']) // Maps 'full_name' column to 'name' property
->get();
Laravel Eloquent Bridge: Extend Eloquent queries:
$query = User::query()->where('active', true);
$dtos = QueryBuilder::for(UserDto::class)
->from($query->toSql())
->withBindings($query->getBindings())
->get();
API Responses: Use DTOs as API response objects:
return response()->json($dtos);
Caching: Cache DTO results with Laravel’s cache:
$cacheKey = 'user_dtos_' . $userId;
$dtos = Cache::remember($cacheKey, now()->addHours(1), function () use ($userId) {
return QueryBuilder::for(UserDto::class)
->from('users')
->where('id', $userId)
->get();
});
Breaking Changes:
Minor versions (0.7.x) may introduce breaking changes. Test thoroughly after updates.
Column Mismatches:
Ensure database columns match DTO properties. Use map() for custom mappings:
->map(['snake_case_column' => 'camelCaseProperty'])
Nested DTOs:
Nested DTOs (with()) require explicit column selection to avoid ambiguity:
->with('posts', PostDto::class, ['title', 'content'])
Performance:
Avoid SELECT *—explicitly define columns to reduce payload:
->select(['id', 'name']) // Instead of ->select('*')
Query Logging: Enable Laravel’s query logging to inspect generated SQL:
DB::enableQueryLog();
$dtos = QueryBuilder::for(UserDto::class)->from('users')->get();
dd(DB::getQueryLog());
DTO Validation: Validate DTOs early to catch mapping issues:
$dto = new UserDto(name: 'John', age: 'invalid'); // Throws TypeError
Custom Type Handling:
Extend type casting via DigitalCraftsman\DeserializingConnection\Type\TypeResolver:
// Example: Convert JSON column to array
TypeResolver::register('json', function ($value) {
return json_decode($value, true);
});
Query Modifiers:
Add custom query logic via modifyQuery():
QueryBuilder::for(UserDto::class)
->from('users')
->modifyQuery(function ($query) {
$query->where('created_at', '>', now()->subDays(30));
})
->get();
Testing: Mock the connection for unit tests:
$mockConnection = Mockery::mock(DigitalCraftsman\DeserializingConnection\Connection::class);
$mockConnection->shouldReceive('query')->andReturn([...]);
$this->app->instance(DigitalCraftsman\DeserializingConnection\Connection::class, $mockConnection);
How can I help you explore Laravel packages today?